fix android module name

import fixes

fix some imports

use `@atproto/jwk` over local zod schema

preview oauth client

rename package

alignment

more alignment

database implementation

cleanup some types

alignment

switch implementation for es256

half of `verifyJwt` for android

half of `verifyJwt` for iOS

rm log

`createJwt` android

`createJwt` ios

rm print

cleanup native impl

silence some warnings

reorg, finish web impl

follow the `CryptoImplementation`

move some more things around

normalize some names

rename func

add generate on android

add android dep

rm bool

add alg

better implementation

ios jwk generation

fix index.ts

add `getRandomValues()` on native

web `getRandomValues`

silence ts error for missing crypto on web

add `digest` for web

add `digest`

add module
This commit is contained in:
Hailey
2024-04-10 12:58:41 -07:00
parent 310d865440
commit 13607edd43
27 changed files with 1502 additions and 0 deletions
@@ -0,0 +1,37 @@
import CryptoKit
import JOSESwift
class CryptoUtil {
// The equivalent of crypto.subtle.digest() with JS on web
public static func digest(data: Data) -> Data {
let hash = SHA256.hash(data: data)
return Data(hash)
}
public static func getRandomValues(byteLength: Int) -> Data {
let bytes = (0..<byteLength).map { _ in UInt8.random(in: UInt8.min...UInt8.max) }
return Data(bytes)
}
public static func generateKeyPair(kid: String?) throws -> (publicJWK: JWK, privateJWK: JWK)? {
let keyIdString = kid ?? UUID().uuidString
let privateKey = P256.Signing.PrivateKey()
let publicKey = privateKey.publicKey
let x = publicKey.x963Representation[1..<33].base64URLEncodedString()
let y = publicKey.x963Representation[33...].base64URLEncodedString()
let d = privateKey.rawRepresentation.base64URLEncodedString()
let publicJWK = JWK(kty: "EC", use: "sig", crv: "P-256", kid: keyIdString, x: x, y: y, alg: "ES256")
let privateJWK = JWK(kty: "EC", use: "sig", crv: "P-256", kid: keyIdString, x: x, y: y, d: d, alg: "ES256")
return (publicJWK, privateJWK)
}
}
extension Data {
func base64URLEncodedString() -> String {
return self.base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "")
}
}
@@ -0,0 +1,22 @@
Pod::Spec.new do |s|
s.name = 'ExpoBlueskyOAuthClient'
s.version = '0.0.1'
s.summary = 'A library of native functions to support Bluesky OAuth in React Native.'
s.description = 'A library of native functions to support Bluesky OAuth in React Native.'
s.author = ''
s.homepage = 'https://github.com/bluesky-social/social-app'
s.platforms = { :ios => '13.4', :tvos => '13.4' }
s.source = { git: '' }
s.static_framework = true
s.dependency 'ExpoModulesCore'
s.dependency 'JOSESwift', '~> 2.3'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
'SWIFT_COMPILATION_MODE' => 'wholemodule'
}
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
end
@@ -0,0 +1,43 @@
import ExpoModulesCore
import JOSESwift
public class ExpoBlueskyOAuthClientModule: Module {
public func definition() -> ModuleDefinition {
Name("ExpoBlueskyOAuthClient")
AsyncFunction("digest") { (data: Data, promise: Promise) in
promise.resolve(CryptoUtil.digest(data: data))
}
// We are going to leave this as sync to line up the APIs. It's fast, so not a big deal.
Function("getRandomValues") { (byteLength: Int) in
return CryptoUtil.getRandomValues(byteLength: byteLength)
}
AsyncFunction ("generateKeyPair") { (kid: String?, promise: Promise) in
let keypair = try? CryptoUtil.generateKeyPair(kid: kid)
guard let keypair = keypair else {
promise.reject("GenerateKeyError", "Error generating JWK.")
return
}
promise.resolve([
"publicKey": keypair.publicJWK.toJson(),
"privateKey": keypair.privateJWK.toJson()
])
}
AsyncFunction("createJwt") { (jwk: String, header: String, payload: String, promise: Promise) in
guard let jwt = JWTUtil.createJwt(jwk, header: header, payload: payload) else {
promise.reject("JWTError", "Error creating JWT.")
return
}
promise.resolve(jwt)
}
AsyncFunction("verifyJwt") { (jwk: String, token: String, options: String?, promise: Promise) in
promise.resolve(JWTUtil.verifyJwt(jwk, token: token, options: options))
}
}
}
@@ -0,0 +1,29 @@
struct JWK {
let kty: String
let use: String
let crv: String
let kid: String
let x: String
let y: String
var d: String?
let alg: String
func toJson() -> String {
var dict: [String: Any] = [
"kty": kty,
"use": use,
"crv": crv,
"kid": kid,
"x": x,
"y": y,
"alg": alg,
]
if let d = d {
dict["d"] = d
}
let jsonData = try! JSONSerialization.data(withJSONObject: dict, options: [])
return String(data: jsonData, encoding: .utf8)!
}
}
@@ -0,0 +1,81 @@
import JOSESwift
class JWTUtil {
static func jsonToPrivateKey(_ jwkString: String) throws -> SecKey? {
guard let jsonData = jwkString.data(using: .utf8),
let jwk = try? JSONDecoder().decode(ECPrivateKey.self, from: jsonData),
let key = try? jwk.converted(to: SecKey.self)
else {
let jsonData = jwkString.data(using: .utf8)!
let jwk = try! JSONDecoder().decode(ECPrivateKey.self, from: jsonData)
// let key = try! jwk.converted(to: SecKey.self)
print("Error creating JWK from JWK string \(jwkString).")
return nil
}
return key
}
static func jsonToPublicKey(_ jwkString: String) throws -> SecKey? {
guard let jsonData = jwkString.data(using: .utf8),
let jwk = try? JSONDecoder().decode(ECPublicKey.self, from: jsonData),
let key = try? jwk.converted(to: SecKey.self)
else {
print("Error creating JWK from JWK string.")
return nil
}
return key
}
static func payloadStringToPayload(_ payloadString: String) -> Payload? {
guard let payloadData = payloadString.data(using: .utf8) else {
print("Error converting payload to data.")
return nil
}
return Payload(payloadData)
}
static func headerStringToPayload(_ headerString: String) -> JWSHeader? {
guard let headerData = headerString.data(using: .utf8) else {
print("Error converting header to data.")
return nil
}
return JWSHeader(headerData)
}
public static func createJwt(_ jwkString: String, header headerString: String, payload payloadString: String) -> String? {
guard let key = try? jsonToPrivateKey(jwkString),
let payload = payloadStringToPayload(payloadString),
let header = headerStringToPayload(headerString)
else
{
return nil
}
let signer = Signer(signingAlgorithm: .ES256, key: key)
guard let signer = signer,
let jws = try? JWS(header: header, payload: payload, signer: signer)
else {
print("Error creating JWS.")
return nil
}
return jws.compactSerializedString
}
public static func verifyJwt(_ jwkString: String, token tokenString: String, options optionsString: String?) -> Bool {
guard let key = try? jsonToPublicKey(jwkString),
let jws = try? JWS(compactSerialization: tokenString),
let verifier = Verifier(verifyingAlgorithm: .ES256, key: key),
let isVerified = try? jws.validate(using: verifier).isValid(for: verifier)
else {
return false
}
return isVerified
}
}