android impl
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
package expo.modules.blueskyoauthclient
|
||||
|
||||
import expo.modules.kotlin.records.Record
|
||||
import expo.modules.kotlin.records.Field
|
||||
|
||||
class JWK(
|
||||
@Field var alg: String = "",
|
||||
@Field var kty: String = "",
|
||||
@Field var crv: String? = null,
|
||||
@Field var x: String? = null,
|
||||
@Field var y: String? = null,
|
||||
@Field var e: String? = null,
|
||||
@Field var n: String? = null,
|
||||
@Field var d: String? = null,
|
||||
@Field var use: String? = null,
|
||||
@Field var kid: String? = null
|
||||
) : Record
|
||||
|
||||
class JWKPair(@Field val privateKey: JWK, @Field val publicKey: JWK) : Record
|
||||
+1
@@ -52,6 +52,7 @@ class CryptoUtil {
|
||||
crv = privateJwk.curve.toString(),
|
||||
x = privateJwk.x.toString(),
|
||||
y = privateJwk.y.toString(),
|
||||
d = privateJwk.d.toString(),
|
||||
use = privateJwk.keyUse.toString(),
|
||||
kid = privateJwk.keyID
|
||||
),
|
||||
|
||||
+10
-11
@@ -1,5 +1,6 @@
|
||||
package expo.modules.blueskyoauthclient
|
||||
|
||||
import android.util.Log
|
||||
import expo.modules.kotlin.modules.Module
|
||||
import expo.modules.kotlin.modules.ModuleDefinition
|
||||
|
||||
@@ -15,21 +16,19 @@ class ExpoBlueskyOAuthClientModule : Module() {
|
||||
return@Function CryptoUtil().getRandomValues(byteLength)
|
||||
}
|
||||
|
||||
AsyncFunction("generateKeyPair") { keyId: String? ->
|
||||
val res = CryptoUtil().generateKeyPair(keyId)
|
||||
|
||||
return@AsyncFunction mapOf(
|
||||
"publicKey" to res.first,
|
||||
"privateKey" to res.second
|
||||
)
|
||||
AsyncFunction("generateJwk") { algorithim: String ->
|
||||
if (algorithim != "ES256") {
|
||||
throw Exception("Unsupported algorithm")
|
||||
}
|
||||
return@AsyncFunction CryptoUtil().generateKeyPair()
|
||||
}
|
||||
|
||||
AsyncFunction("createJwt") { jwkString: String, headerString: String, payloadString: String ->
|
||||
return@AsyncFunction JWTUtil().createJwt(jwkString, headerString, payloadString)
|
||||
AsyncFunction("createJwt") { header: JWTHeader, payload: JWTPayload, jwk: JWK ->
|
||||
return@AsyncFunction JWTUtil().createJwt(header, payload, jwk)
|
||||
}
|
||||
|
||||
AsyncFunction("verifyJwt") { jwkString: String, tokenString: String, options: String? ->
|
||||
return@AsyncFunction JWTUtil().verifyJwt(jwkString, tokenString, options)
|
||||
AsyncFunction("verifyJwt") { token: String, jwk: JWK ->
|
||||
return@AsyncFunction JWTUtil().verifyJwt(token, jwk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package expo.modules.blueskyoauthclient
|
||||
|
||||
import expo.modules.kotlin.records.Record
|
||||
import expo.modules.kotlin.records.Field
|
||||
|
||||
class JWK(
|
||||
@Field var alg: String = "",
|
||||
@Field var kty: String = "",
|
||||
@Field var crv: String? = null,
|
||||
@Field var x: String? = null,
|
||||
@Field var y: String? = null,
|
||||
@Field var e: String? = null,
|
||||
@Field var n: String? = null,
|
||||
@Field var d: String? = null,
|
||||
@Field var use: String? = null,
|
||||
@Field var kid: String? = null
|
||||
) : Record {
|
||||
fun toJson(): String {
|
||||
val parts = mutableListOf<String>()
|
||||
if (alg.isNotEmpty()) parts.add("\"alg\": \"$alg\"")
|
||||
if (kty.isNotEmpty()) parts.add("\"kty\": \"$kty\"")
|
||||
if (crv != null) parts.add("\"crv\": \"$crv\"")
|
||||
if (x != null) parts.add("\"x\": \"$x\"")
|
||||
if (y != null) parts.add("\"y\": \"$y\"")
|
||||
if (e != null) parts.add("\"e\": \"$e\"")
|
||||
if (n != null) parts.add("\"n\": \"$n\"")
|
||||
if (d != null) parts.add("\"d\": \"$d\"")
|
||||
if (use != null) parts.add("\"use\": \"$use\"")
|
||||
if (kid != null) parts.add("\"kid\": \"$kid\"")
|
||||
return "{ ${parts.joinToString()} }"
|
||||
}
|
||||
}
|
||||
|
||||
class JWKPair(@Field val privateKey: JWK, @Field val publicKey: JWK) : Record
|
||||
+108
-9
@@ -6,12 +6,24 @@ import expo.modules.kotlin.records.Field
|
||||
class JWTHeader(
|
||||
@Field var alg: String = "",
|
||||
@Field var jku: String? = null,
|
||||
@Field var jwk: JWK = JWK(),
|
||||
@Field var jwk: JWK? = null,
|
||||
@Field var kid: String? = null,
|
||||
@Field var typ: String? = null,
|
||||
@Field var cty: String? = null,
|
||||
@Field var crit: String? = null
|
||||
) : Record
|
||||
) : Record {
|
||||
fun toJson(): String {
|
||||
val parts = mutableListOf<String>()
|
||||
if (alg.isNotEmpty()) parts.add("\"alg\": \"$alg\"")
|
||||
if (jku != null) parts.add("\"jku\": \"$jku\"")
|
||||
if (jwk != null) parts.add("\"jwk\": ${jwk?.toJson()}")
|
||||
if (kid != null) parts.add("\"kid\": \"$kid\"")
|
||||
if (typ != null) parts.add("\"typ\": \"$typ\"")
|
||||
if (cty != null) parts.add("\"cty\": \"$cty\"")
|
||||
if (crit != null) parts.add("\"crit\": \"$crit\"")
|
||||
return "{ ${parts.joinToString()} }"
|
||||
}
|
||||
}
|
||||
|
||||
class JWTPayload(
|
||||
@Field var iss: String? = null,
|
||||
@@ -41,7 +53,63 @@ class JWTPayload(
|
||||
@Field var middle_name: String? = null,
|
||||
@Field var nickname: String? = null,
|
||||
@Field var preferred_username: String? = null,
|
||||
) : Record
|
||||
@Field var gender: String? = null,
|
||||
@Field var picture: String? = null,
|
||||
@Field var profile: String? = null,
|
||||
@Field var birthdate: String? = null,
|
||||
@Field var zoneinfo: String? = null,
|
||||
@Field var updated_at: Int? = null,
|
||||
@Field var email: String? = null,
|
||||
@Field var email_verified: Boolean? = null,
|
||||
@Field var phone_number: String? = null,
|
||||
@Field var phone_number_verified: Boolean? = null,
|
||||
@Field var address: JWTPayloadAddress? = null,
|
||||
@Field var authorization_details: JWTPayloadAuthorizationDetails? = null
|
||||
) : Record {
|
||||
fun toJson(): String {
|
||||
val parts = mutableListOf<String>()
|
||||
if (iss != null) parts.add("\"iss\": \"$iss\"")
|
||||
if (aud != null) parts.add("\"aud\": \"$aud\"")
|
||||
if (sub != null) parts.add("\"sub\": \"$sub\"")
|
||||
if (exp != null) parts.add("\"exp\": $exp")
|
||||
if (nbr != null) parts.add("\"nbr\": $nbr")
|
||||
if (iat != null) parts.add("\"iat\": $iat")
|
||||
if (jti != null) parts.add("\"jti\": \"$jti\"")
|
||||
if (htm != null) parts.add("\"htm\": \"$htm\"")
|
||||
if (htu != null) parts.add("\"htu\": \"$htu\"")
|
||||
if (ath != null) parts.add("\"ath\": \"$ath\"")
|
||||
if (acr != null) parts.add("\"acr\": \"$acr\"")
|
||||
if (azp != null) parts.add("\"azp\": \"$azp\"")
|
||||
if (amr != null) parts.add("\"amr\": \"$amr\"")
|
||||
if (cnf != null) parts.add("\"cnf\": ${cnf?.toJson()}")
|
||||
if (client_id != null) parts.add("\"client_id\": \"$client_id\"")
|
||||
if (scope != null) parts.add("\"scope\": \"$scope\"")
|
||||
if (nonce != null) parts.add("\"nonce\": \"$nonce\"")
|
||||
if (at_hash != null) parts.add("\"at_hash\": \"$at_hash\"")
|
||||
if (c_hash != null) parts.add("\"c_hash\": \"$c_hash\"")
|
||||
if (s_hash != null) parts.add("\"s_hash\": \"$s_hash\"")
|
||||
if (auth_time != null) parts.add("\"auth_time\": $auth_time")
|
||||
if (name != null) parts.add("\"name\": \"$name\"")
|
||||
if (family_name != null) parts.add("\"family_name\": \"$family_name\"")
|
||||
if (given_name != null) parts.add("\"given_name\": \"$given_name\"")
|
||||
if (middle_name != null) parts.add("\"middle_name\": \"$middle_name\"")
|
||||
if (nickname != null) parts.add("\"nickname\": \"$nickname\"")
|
||||
if (preferred_username != null) parts.add("\"preferred_username\": \"$preferred_username\"")
|
||||
if (gender != null) parts.add("\"gender\": \"$gender\"")
|
||||
if (picture != null) parts.add("\"picture\": \"$picture\"")
|
||||
if (profile != null) parts.add("\"profile\": \"$profile\"")
|
||||
if (birthdate != null) parts.add("\"birthdate\": \"$birthdate\"")
|
||||
if (zoneinfo != null) parts.add("\"zoneinfo\": \"$zoneinfo\"")
|
||||
if (updated_at != null) parts.add("\"updated_at\": $updated_at")
|
||||
if (email != null) parts.add("\"email\": \"$email\"")
|
||||
if (email_verified != null) parts.add("\"email_verified\": $email_verified")
|
||||
if (phone_number != null) parts.add("\"phone_number\": \"$phone_number\"")
|
||||
if (phone_number_verified != null) parts.add("\"phone_number_verified\": $phone_number_verified")
|
||||
if (address != null) parts.add("\"address\": ${address?.toJson()}")
|
||||
if (authorization_details != null) parts.add("\"authorization_details\": ${authorization_details?.toJson()}")
|
||||
return "{ ${parts.joinToString()} }"
|
||||
}
|
||||
}
|
||||
|
||||
class JWTPayloadCNF(
|
||||
@Field var jwk: JWK? = null,
|
||||
@@ -49,7 +117,17 @@ class JWTPayloadCNF(
|
||||
@Field var jku: String? = null,
|
||||
@Field var jkt: String? = null,
|
||||
@Field var osc: String? = null
|
||||
) : Record
|
||||
) : Record {
|
||||
fun toJson(): String {
|
||||
val parts = mutableListOf<String>()
|
||||
if (jwk != null) parts.add("\"jwk\": ${jwk?.toJson()}")
|
||||
if (jwe != null) parts.add("\"jwe\": \"$jwe\"")
|
||||
if (jku != null) parts.add("\"jku\": \"$jku\"")
|
||||
if (jkt != null) parts.add("\"jkt\": \"$jkt\"")
|
||||
if (osc != null) parts.add("\"osc\": \"$osc\"")
|
||||
return "{ ${parts.joinToString()} }"
|
||||
}
|
||||
}
|
||||
|
||||
class JWTPayloadAddress(
|
||||
@Field var formatted: String? = null,
|
||||
@@ -58,7 +136,18 @@ class JWTPayloadAddress(
|
||||
@Field var region: String? = null,
|
||||
@Field var postal_code: String? = null,
|
||||
@Field var country: String? = null
|
||||
) : Record
|
||||
) : Record {
|
||||
fun toJson(): String {
|
||||
val parts = mutableListOf<String>()
|
||||
if (formatted != null) parts.add("\"formatted\": \"$formatted\"")
|
||||
if (street_address != null) parts.add("\"street_address\": \"$street_address\"")
|
||||
if (locality != null) parts.add("\"locality\": \"$locality\"")
|
||||
if (region != null) parts.add("\"region\": \"$region\"")
|
||||
if (postal_code != null) parts.add("\"postal_code\": \"$postal_code\"")
|
||||
if (country != null) parts.add("\"country\": \"$country\"")
|
||||
return "{ ${parts.joinToString()} }"
|
||||
}
|
||||
}
|
||||
|
||||
class JWTPayloadAuthorizationDetails(
|
||||
@Field var type: String? = null,
|
||||
@@ -67,10 +156,20 @@ class JWTPayloadAuthorizationDetails(
|
||||
@Field var datatypes: Array<String>? = null,
|
||||
@Field var identifier: String? = null,
|
||||
@Field var privileges: Array<String>? = null
|
||||
) : Record
|
||||
) : Record {
|
||||
fun toJson(): String {
|
||||
val parts = mutableListOf<String>()
|
||||
if (type != null) parts.add("\"type\": \"$type\"")
|
||||
if (locations != null) parts.add("\"locations\": [${locations?.joinToString()}]")
|
||||
if (actions != null) parts.add("\"actions\": [${actions?.joinToString()}]")
|
||||
if (datatypes != null) parts.add("\"datatypes\": [${datatypes?.joinToString()}]")
|
||||
if (identifier != null) parts.add("\"identifier\": \"$identifier\"")
|
||||
if (privileges != null) parts.add("\"privileges\": [${privileges?.joinToString()}]")
|
||||
return "{ ${parts.joinToString()} }"
|
||||
}
|
||||
}
|
||||
|
||||
class JWTVerifyResponse(
|
||||
@Field var header: JWTHeader = JWTHeader(),
|
||||
@Field var payload: JWTPayload = JWTPayload(),
|
||||
@Field var signature: String = ""
|
||||
@Field var protectedHeader: JWTHeader = JWTHeader(),
|
||||
@Field var payload: String = "",
|
||||
) : Record
|
||||
+49
-14
@@ -7,29 +7,64 @@ import com.nimbusds.jose.jwk.ECKey
|
||||
import com.nimbusds.jwt.JWTClaimsSet
|
||||
import com.nimbusds.jwt.SignedJWT
|
||||
|
||||
|
||||
class JWTUtil {
|
||||
fun createJwt(jwkString: String, headerString: String, payloadString: String): String {
|
||||
val key = ECKey.parse(jwkString)
|
||||
val header = JWSHeader.parse(headerString)
|
||||
val payload = JWTClaimsSet.parse(payloadString)
|
||||
fun createJwt(header: JWTHeader, payload: JWTPayload, jwk: JWK): String {
|
||||
val parsedKey = ECKey.parse(jwk.toJson())
|
||||
val parsedHeader = JWSHeader.parse(header.toJson())
|
||||
val parsedPayload = JWTClaimsSet.parse(payload.toJson())
|
||||
|
||||
val signer = ECDSASigner(key)
|
||||
val jwt = SignedJWT(header, payload)
|
||||
val signer = ECDSASigner(parsedKey)
|
||||
val jwt = SignedJWT(parsedHeader, parsedPayload)
|
||||
jwt.sign(signer)
|
||||
|
||||
return jwt.serialize()
|
||||
}
|
||||
|
||||
fun verifyJwt(jwkString: String, tokenString: String, options: String?): Boolean {
|
||||
return try {
|
||||
val key = ECKey.parse(jwkString)
|
||||
val jwt = SignedJWT.parse(tokenString)
|
||||
val verifier = ECDSAVerifier(key)
|
||||
fun verifyJwt(token: String, jwk: JWK): JWTVerifyResponse {
|
||||
try {
|
||||
val parsedKey = ECKey.parse(jwk.toJson())
|
||||
val jwt = SignedJWT.parse(token)
|
||||
val verifier = ECDSAVerifier(parsedKey)
|
||||
|
||||
jwt.verify(verifier)
|
||||
if (!jwt.verify(verifier)) {
|
||||
throw Exception("Invalid signature")
|
||||
}
|
||||
|
||||
val header = jwt.header
|
||||
val payload = jwt.payload
|
||||
val ecKey = header.jwk?.toECKey()
|
||||
val serializedJwk = if (ecKey != null) {
|
||||
JWK(
|
||||
alg = ecKey.algorithm.toString(),
|
||||
kty = ecKey.keyType.toString(),
|
||||
crv = ecKey.curve.toString(),
|
||||
x = ecKey.x.toString(),
|
||||
y = ecKey.y.toString(),
|
||||
d = ecKey.d.toString(),
|
||||
use = ecKey.keyUse.toString(),
|
||||
kid = ecKey.keyID
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val serializedHeader = JWTHeader(
|
||||
alg = header.algorithm.toString(),
|
||||
jku = header.jwkurl?.toString(),
|
||||
jwk = serializedJwk,
|
||||
kid = header.keyID,
|
||||
typ = header.type?.toString(),
|
||||
cty = header.contentType,
|
||||
crit = header.criticalParams?.joinToString()
|
||||
)
|
||||
val serializedPayload = payload.toString()
|
||||
|
||||
return JWTVerifyResponse(
|
||||
protectedHeader = serializedHeader,
|
||||
payload = serializedPayload,
|
||||
)
|
||||
} catch(e: Exception) {
|
||||
false
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ struct JWTPayload : Record {
|
||||
@Field
|
||||
var email: String?
|
||||
@Field
|
||||
var email_verified: String?
|
||||
var email_verified: Bool?
|
||||
@Field
|
||||
var phone_number: String?
|
||||
@Field
|
||||
|
||||
@@ -68,6 +68,9 @@ function HomeScreenReady({
|
||||
},
|
||||
)
|
||||
|
||||
console.log(jwt)
|
||||
console.log(key.publicJwk)
|
||||
|
||||
const verified = await key.verifyJwt(jwt)
|
||||
|
||||
console.log(verified)
|
||||
|
||||
Reference in New Issue
Block a user