swift impl

This commit is contained in:
Hailey
2024-04-15 00:37:12 -07:00
parent 2ada0cbd0d
commit 2c1b3709af
7 changed files with 62 additions and 28 deletions
@@ -38,8 +38,8 @@ public class ExpoBlueskyOAuthClientModule: Module {
promise.resolve(jwt) promise.resolve(jwt)
} }
AsyncFunction("verifyJwt") { (jwk: String, token: String, options: String?, promise: Promise) in AsyncFunction("verifyJwt") { (token: String, jwk: JWK, promise: Promise) in
promise.resolve(JWTUtil.verifyJwt(jwk, token: token, options: options)) promise.resolve(JWTUtil.verifyJwt(token: token, jwk: jwk))
} }
} }
} }
@@ -27,7 +27,7 @@ struct JWK : Record {
return Field(wrappedValue: self) return Field(wrappedValue: self)
} }
func toSecKey() throws -> SecKey? { func toPrivateSecKey() throws -> SecKey? {
let jsonData = try JSONSerialization.data(withJSONObject: self.toDictionary()) let jsonData = try JSONSerialization.data(withJSONObject: self.toDictionary())
guard let jwk = try? JSONDecoder().decode(ECPrivateKey.self, from: jsonData), guard let jwk = try? JSONDecoder().decode(ECPrivateKey.self, from: jsonData),
let key = try? jwk.converted(to: SecKey.self) let key = try? jwk.converted(to: SecKey.self)
@@ -37,6 +37,17 @@ struct JWK : Record {
} }
return key return key
} }
func toPublicSecKey() throws -> SecKey? {
let jsonData = try JSONSerialization.data(withJSONObject: self.toDictionary())
guard let jwk = try? JSONDecoder().decode(ECPublicKey.self, from: jsonData),
let key = try? jwk.converted(to: SecKey.self)
else {
print("Error creating SecKey.")
return nil
}
return key
}
} }
struct JWKPair : Record { struct JWKPair : Record {
@@ -7,14 +7,10 @@ struct JWTHeader : Record {
@Field @Field
var jku: String? var jku: String?
@Field @Field
var jwk: JWK
@Field
var kid: String? var kid: String?
@Field @Field
var x5u: String?
@Field
var x5c: String?
@Field
var x5t: String?
@Field
var typ: String? var typ: String?
@Field @Field
var cty: String? var cty: String?
@@ -179,3 +175,10 @@ struct JWTPayloadAuthorizationDetails : Record {
return Field(wrappedValue: self) return Field(wrappedValue: self)
} }
} }
struct JWTVerifyResponse : Record {
@Field
var protectedHeader: JWTHeader
@Field
var payload: String
}
@@ -1,3 +1,4 @@
import ExpoModulesCore
import JOSESwift import JOSESwift
class JWTUtil { class JWTUtil {
@@ -34,7 +35,7 @@ class JWTUtil {
public static func createJwt(header: JWTHeader, payload: JWTPayload, jwk: JWK) -> String? { public static func createJwt(header: JWTHeader, payload: JWTPayload, jwk: JWK) -> String? {
guard let header = try? header.toJWSHeader(), guard let header = try? header.toJWSHeader(),
let payload = try? payload.toPayload(), let payload = try? payload.toPayload(),
let key = try? jwk.toSecKey() let key = try? jwk.toPrivateSecKey()
else { else {
print("didn't have one") print("didn't have one")
return nil return nil
@@ -52,15 +53,34 @@ class JWTUtil {
return jws.compactSerializedString return jws.compactSerializedString
} }
public static func verifyJwt(_ jwkString: String, token tokenString: String, options optionsString: String?) -> Bool { public static func verifyJwt(token: String, jwk: JWK) -> JWTVerifyResponse? {
guard let key = try? jsonToPublicKey(jwkString), guard let key = try? jwk.toPublicSecKey(),
let jws = try? JWS(compactSerialization: tokenString), let jws = try? JWS(compactSerialization: token),
let verifier = Verifier(verifyingAlgorithm: .ES256, key: key), let verifier = Verifier(verifyingAlgorithm: .ES256, key: key),
let isVerified = try? jws.validate(using: verifier).isValid(for: verifier) let validation = try? jws.validate(using: verifier)
else { else {
return false return nil
} }
return isVerified let header = validation.header
let serializedHeader = JWTHeader(
alg: "ES256",
jku: Field(wrappedValue: header.jku?.absoluteString),
kid: Field(wrappedValue:header.kid),
typ: Field(wrappedValue: header.typ),
cty: Field(wrappedValue: header.cty),
crit: Field(wrappedValue: header.cty)
)
let payload = String(data: validation.payload.data(), encoding: .utf8)
guard let payload = payload else {
return nil
}
return JWTVerifyResponse(
protectedHeader: serializedHeader.toField(),
payload: payload.toField()
)
} }
} }
@@ -40,7 +40,7 @@ export const OauthClientReactNative = (NativeModule as null) || {
_token: Jwt, _token: Jwt,
_jwk: Jwk, _jwk: Jwk,
): Promise<{ ): Promise<{
payload: Record<string, unknown> payload: string // this is a JSON response to make Swift a bit easier to work with
protectedHeader: Record<string, unknown> protectedHeader: Record<string, unknown>
}> { }> {
throw new Error(LINKING_ERROR) throw new Error(LINKING_ERROR)
@@ -2,7 +2,6 @@ import {
jwkValidator, jwkValidator,
Jwt, Jwt,
JwtHeader, JwtHeader,
jwtHeaderSchema,
JwtPayload, JwtPayload,
jwtPayloadSchema, jwtPayloadSchema,
Key, Key,
@@ -45,8 +44,13 @@ export class ReactNativeKey extends Key {
>(token: Jwt, options?: VerifyOptions<C>): Promise<VerifyResult<P, C>> { >(token: Jwt, options?: VerifyOptions<C>): Promise<VerifyResult<P, C>> {
const result = await OauthClientReactNative.verifyJwt(token, this.jwk) const result = await OauthClientReactNative.verifyJwt(token, this.jwk)
const payload = jwtPayloadSchema.parse(result.payload) let payloadParsed = JSON.parse(result.payload)
const protectedHeader = jwtHeaderSchema.parse(result.protectedHeader) payloadParsed = Object.fromEntries(
Object.entries(payloadParsed as object).filter(([_, v]) => v !== null),
)
const payload = jwtPayloadSchema.parse(payloadParsed)
const protectedHeader = result.protectedHeader
if (options?.audience != null) { if (options?.audience != null) {
const audience = Array.isArray(options.audience) const audience = Array.isArray(options.audience)
@@ -85,6 +89,8 @@ export class ReactNativeKey extends Key {
} }
} }
console.log(payload)
if (payload.iat == null) { if (payload.iat == null) {
throw new Error('Missing issued at') throw new Error('Missing issued at')
} }
+2 -8
View File
@@ -64,17 +64,11 @@ function HomeScreenReady({
}, },
{ {
sub: 'test', sub: 'test',
iat: Math.floor(Date.now() / 1000),
}, },
) )
console.log(jwt) const verified = await key.verifyJwt(jwt)
// console.log('public', key.publicJwk)
// const jwt = await key.createJwt(
// {alg: 'ES256', kid: key.kid},
// {sub: 'test'},
// )
// console.log(jwt)
})() })()
}, []) }, [])