squash
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:
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user