Skip to content

Commit

Permalink
Adding extensions for base64 coding
Browse files Browse the repository at this point in the history
  • Loading branch information
briancorbin committed Mar 29, 2024
1 parent 17184a1 commit 0193b7f
Showing 1 changed file with 74 additions and 16 deletions.
90 changes: 74 additions & 16 deletions ios/Passkey.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import AuthenticationServices
import Foundation

@objc(Passkey)
class Passkey: NSObject {
var passKeyDelegate: PasskeyDelegate?;

@objc(register:withChallenge:withDisplayName:withUserId:withSecurityKey:withResolver:withRejecter:)
func register(_ identifier: String, challenge: String, displayName: String, userId: String, securityKey: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
// Convert challenge and userId to correct type
guard let challengeData: Data = Data(base64Encoded: challenge) else {
reject(PassKeyError.invalidChallenge.rawValue, PassKeyError.invalidChallenge.rawValue, nil);
return;
guard let challengeData = Data.fromBase64Url(challenge) else {
reject(PassKeyError.invalidChallenge.rawValue, PassKeyError.invalidChallenge.rawValue, nil);
return
}

guard let userIdData = Data.fromBase64Url(userId) else {
reject(PassKeyError.invalidUserId.rawValue, PassKeyError.invalidUserId.rawValue, nil);
return
}
let userIdData: Data = RCTConvert.nsData(userId);

// Check if Passkeys are supported on this OS version
if #available(iOS 15.0, *) {
Expand Down Expand Up @@ -44,12 +48,12 @@ class Passkey: NSObject {
if let registrationResult = result?.registrationResult {
// Return a NSDictionary instance with the received authorization data
let authResponse: NSDictionary = [
"rawAttestationObject": registrationResult.rawAttestationObject.base64EncodedString(),
"rawClientDataJSON": registrationResult.rawClientDataJSON.base64EncodedString()
"rawAttestationObject": registrationResult.rawAttestationObject.toBase64URL(),
"rawClientDataJSON": registrationResult.rawClientDataJSON.toBase64URL()
];

let authResult: NSDictionary = [
"credentialID": registrationResult.credentialID.base64EncodedString(),
"credentialID": registrationResult.credentialID.toBase64URL(),
"response": authResponse
]
resolve(authResult);
Expand All @@ -73,9 +77,9 @@ class Passkey: NSObject {
func authenticate(_ identifier: String, challenge: String, securityKey: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {

// Convert challenge to correct type
guard let challengeData: Data = Data(base64Encoded: challenge) else {
reject(PassKeyError.invalidChallenge.rawValue, PassKeyError.invalidChallenge.rawValue, nil);
return;
guard let challengeData = Data.fromBase64Url(challenge) else {
reject(PassKeyError.invalidChallenge.rawValue, PassKeyError.invalidChallenge.rawValue, nil);
return
}

// Check if Passkeys are supported on this OS version
Expand Down Expand Up @@ -107,14 +111,14 @@ class Passkey: NSObject {
if let assertionResult = result?.assertionResult {
// Return a NSDictionary instance with the received authorization data
let authResponse: NSDictionary = [
"rawAuthenticatorData": assertionResult.rawAuthenticatorData.base64EncodedString(),
"rawClientDataJSON": assertionResult.rawClientDataJSON.base64EncodedString(),
"signature": assertionResult.signature.base64EncodedString(),
"rawAuthenticatorData": assertionResult.rawAuthenticatorData.toBase64URL(),
"rawClientDataJSON": assertionResult.rawClientDataJSON.toBase64URL(),
"signature": assertionResult.signature.toBase64URL(),
];

let authResult: NSDictionary = [
"credentialID": assertionResult.credentialID.base64EncodedString(),
"userID": String(decoding: assertionResult.userID, as: UTF8.self),
"credentialID": assertionResult.credentialID.toBase64URL(),
"userID": assertionResult.userID.toBase64URL(),
"response": authResponse
]
resolve(authResult);
Expand Down Expand Up @@ -155,6 +159,7 @@ enum PassKeyError: String, Error {
case requestFailed = "RequestFailed"
case cancelled = "UserCancelled"
case invalidChallenge = "InvalidChallenge"
case invalidUserId = "InvalidUserId"
case notConfigured = "NotConfigured"
case unknown = "UnknownError"
}
Expand Down Expand Up @@ -192,3 +197,56 @@ enum PasskeyOperation {
case Registration
case Assertion
}

public extension Data {
/// Same as ``Data(base64Encoded:)``, but adds padding automatically
/// (if missing, instead of returning `nil`).
static func fromBase64(_ encoded: String) -> Data? {
// Prefixes padding-character(s) (if needed).
var encoded = encoded
let remainder = encoded.count % 4
if remainder > 0 {
encoded = encoded.padding(
toLength: encoded.count + 4 - remainder,
withPad: "=", startingAt: 0
)
}

// Finally, decode.
return Data(base64Encoded: encoded)
}

static func fromBase64Url(_ encoded: String) -> Data? {
let base64String = base64UrlToBase64(base64Url: encoded)
return fromBase64(base64String)
}

private static func base64UrlToBase64(base64Url: String) -> String {
let base64 = base64Url
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")

return base64
}
}

public extension String {
static func fromBase64(_ encoded: String) -> String? {
if let data = Data.fromBase64(encoded) {
return String(data: data, encoding: .utf8)
}
return nil
}
}

extension Data {
func toBase64URL() -> String {
let current = self

var result = current.base64EncodedString()
result = result.replacingOccurrences(of: "+", with: "-")
result = result.replacingOccurrences(of: "/", with: "_")
result = result.replacingOccurrences(of: "=", with: "")
return result
}
}

0 comments on commit 0193b7f

Please sign in to comment.