android impl

This commit is contained in:
Hailey
2024-04-15 02:03:10 -07:00
parent 37f5840214
commit caddbeb227
8 changed files with 206 additions and 54 deletions
@@ -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
@@ -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
),
@@ -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)
}
}
}
@@ -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
@@ -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
@@ -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
+3
View File
@@ -68,6 +68,9 @@ function HomeScreenReady({
},
)
console.log(jwt)
console.log(key.publicJwk)
const verified = await key.verifyJwt(jwt)
console.log(verified)