From a93287367fde48ba16b9fd2f4e73641e00360226 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Sat, 22 Nov 2025 07:35:26 +0100 Subject: [PATCH] Concurrency adjustments, SPM: platforms, Demo: use LOCAL Package --- DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj | 31 +- .../xcshareddata/swiftpm/Package.resolved | 15 - DfnsDemo/DfnsDemo/DfnsDemoApp.swift | 2 +- DfnsDemo/DfnsDemo/MyBusinessLogic.swift | 2 +- DfnsDemo/DfnsDemo/MyServer.swift | 2 +- DfnsDemo/Package.swift | 13 + Package.swift | 8 + Sources/DfnsSdk/DfnsApi.swift | 351 ++++++++++++------ Sources/DfnsSdk/Imported/Passkey.swift | 12 +- .../DfnsSdk/Imported/PasskeyDelegate.swift | 2 +- Sources/DfnsSdk/PasskeysSigner.swift | 191 +++++----- 11 files changed, 370 insertions(+), 259 deletions(-) delete mode 100644 DfnsDemo/DfnsDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 DfnsDemo/Package.swift diff --git a/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj b/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj index dc8f1cc..b103d71 100644 --- a/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj +++ b/DfnsDemo/DfnsDemo.xcodeproj/project.pbxproj @@ -3,17 +3,17 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ + 48F0B3B02ED061F300500085 /* DfnsSdk in Frameworks */ = {isa = PBXBuildFile; productRef = 48F0B3AF2ED061F300500085 /* DfnsSdk */; }; 7A1A68702BBEA54B00167ED0 /* DfnsDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A686F2BBEA54B00167ED0 /* DfnsDemoApp.swift */; }; 7A1A68722BBEA54B00167ED0 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A68712BBEA54B00167ED0 /* ContentView.swift */; }; 7A1A68742BBEA54C00167ED0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A1A68732BBEA54C00167ED0 /* Assets.xcassets */; }; 7A1A68772BBEA54C00167ED0 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A1A68762BBEA54C00167ED0 /* Preview Assets.xcassets */; }; 7A1A68A92BC5943700167ED0 /* MyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A68A82BC5943700167ED0 /* MyServer.swift */; }; 7A439BB12BCD22710022D861 /* MyBusinessLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A439BB02BCD22710022D861 /* MyBusinessLogic.swift */; }; - 7A439BB72BCD67270022D861 /* DfnsSdk in Frameworks */ = {isa = PBXBuildFile; productRef = 7A439BB62BCD67270022D861 /* DfnsSdk */; }; 7A5207E72BCD8B22003EC637 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5207E62BCD8B22003EC637 /* Config.swift */; }; /* End PBXBuildFile section */ @@ -34,7 +34,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7A439BB72BCD67270022D861 /* DfnsSdk in Frameworks */, + 48F0B3B02ED061F300500085 /* DfnsSdk in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -97,7 +97,7 @@ ); name = DfnsDemo; packageProductDependencies = ( - 7A439BB62BCD67270022D861 /* DfnsSdk */, + 48F0B3AF2ED061F300500085 /* DfnsSdk */, ); productName = DfnsDemo; productReference = 7A1A686C2BBEA54B00167ED0 /* DfnsDemo.app */; @@ -128,7 +128,7 @@ ); mainGroup = 7A1A68632BBEA54B00167ED0; packageReferences = ( - 7A439BB52BCD67270022D861 /* XCRemoteSwiftPackageReference "dfns-sdk-swift" */, + 48F0B3AE2ED061F300500085 /* XCLocalSwiftPackageReference "../../dfns-sdk-swift" */, ); productRefGroup = 7A1A686D2BBEA54B00167ED0 /* Products */; projectDirPath = ""; @@ -227,6 +227,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -282,6 +283,7 @@ MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -313,7 +315,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -345,7 +346,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -373,21 +373,16 @@ }; /* End XCConfigurationList section */ -/* Begin XCRemoteSwiftPackageReference section */ - 7A439BB52BCD67270022D861 /* XCRemoteSwiftPackageReference "dfns-sdk-swift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/dfns/dfns-sdk-swift"; - requirement = { - branch = m; - kind = branch; - }; +/* Begin XCLocalSwiftPackageReference section */ + 48F0B3AE2ED061F300500085 /* XCLocalSwiftPackageReference "../../dfns-sdk-swift" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../dfns-sdk-swift"; }; -/* End XCRemoteSwiftPackageReference section */ +/* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 7A439BB62BCD67270022D861 /* DfnsSdk */ = { + 48F0B3AF2ED061F300500085 /* DfnsSdk */ = { isa = XCSwiftPackageProductDependency; - package = 7A439BB52BCD67270022D861 /* XCRemoteSwiftPackageReference "dfns-sdk-swift" */; productName = DfnsSdk; }; /* End XCSwiftPackageProductDependency section */ diff --git a/DfnsDemo/DfnsDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DfnsDemo/DfnsDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 163bf7e..0000000 --- a/DfnsDemo/DfnsDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,15 +0,0 @@ -{ - "originHash" : "4a21ff1bb0c8364c763bf1da609a1d60bf5c031cbce0665704b506df6d3af9a7", - "pins" : [ - { - "identity" : "dfns-sdk-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/dfns/dfns-sdk-swift", - "state" : { - "branch" : "m", - "revision" : "96b0ad2d93d967aa73893ea19f9b7f5b8eccaa98" - } - } - ], - "version" : 3 -} diff --git a/DfnsDemo/DfnsDemo/DfnsDemoApp.swift b/DfnsDemo/DfnsDemo/DfnsDemoApp.swift index 81baa45..57cad3a 100644 --- a/DfnsDemo/DfnsDemo/DfnsDemoApp.swift +++ b/DfnsDemo/DfnsDemo/DfnsDemoApp.swift @@ -1,4 +1,4 @@ -import DfnsSdk +@preconcurrency import DfnsSdk import SwiftUI class UserConfig: ObservableObject { diff --git a/DfnsDemo/DfnsDemo/MyBusinessLogic.swift b/DfnsDemo/DfnsDemo/MyBusinessLogic.swift index 5f6fa05..8aeecb3 100644 --- a/DfnsDemo/DfnsDemo/MyBusinessLogic.swift +++ b/DfnsDemo/DfnsDemo/MyBusinessLogic.swift @@ -1,4 +1,4 @@ -import DfnsSdk +@preconcurrency import DfnsSdk import Foundation /** diff --git a/DfnsDemo/DfnsDemo/MyServer.swift b/DfnsDemo/DfnsDemo/MyServer.swift index 6efdc8d..4aa7eae 100644 --- a/DfnsDemo/DfnsDemo/MyServer.swift +++ b/DfnsDemo/DfnsDemo/MyServer.swift @@ -1,4 +1,4 @@ -import DfnsSdk +@preconcurrency import DfnsSdk import Foundation /** diff --git a/DfnsDemo/Package.swift b/DfnsDemo/Package.swift new file mode 100644 index 0000000..88cb976 --- /dev/null +++ b/DfnsDemo/Package.swift @@ -0,0 +1,13 @@ +// swift-tools-version:6.2 + +// This is a HACKY workaround the fact that SPM does not allow for Package level exclusion +// of files/folders. SPM actually HAD support for it but it was removed in 2017, in PR +// https://github.com/apple/swift-package-manager/commit/cb69accf41da55386f9703308958aa49ca2a4c5f +// +// So instead we have to add an empty dummy Package.swift to each folder we wanna hide, as per: +// See: https://github.com/apple/swift-package-manager/issues/4460#issuecomment-1475025748 +// And: https://stackoverflow.com/questions/69382302/swift-package-how-to-exclude-files-in-root-git-directory-from-the-actual-swift/70990534#70990534 +// And: https://github.com/tuist/tuist/pull/2058 +import PackageDescription + +let package = Package(name: "HIDDEN") diff --git a/Package.swift b/Package.swift index 545668b..927d915 100644 --- a/Package.swift +++ b/Package.swift @@ -1,10 +1,18 @@ // swift-tools-version: 5.10 // The swift-tools-version declares the minimum version of Swift required to build this package. +// later change swift-tools-version to 6.2 + import PackageDescription let package = Package( name: "DfnsSdk", + platforms: [ + .macOS(.v10_15), + .iOS(.v15), + .watchOS(.v6), + .tvOS(.v13) + ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( diff --git a/Sources/DfnsSdk/DfnsApi.swift b/Sources/DfnsSdk/DfnsApi.swift index 092a5b1..22da994 100644 --- a/Sources/DfnsSdk/DfnsApi.swift +++ b/Sources/DfnsSdk/DfnsApi.swift @@ -1,9 +1,28 @@ /** - Types defined in the Dfns API that might be arguments or return values of the demo server - */ -public enum DfnsApi { - public struct UserActionChallenge: Codable { - public init(attestation: String, userVerification: String, externalAuthenticationUrl: String, challenge: String, challengeIdentifier: String, supportedCredentialKinds: [DfnsApi.SupportedCredentialKind], allowCredentials: DfnsApi.AllowCredentials) { + Types defined in the Dfns API that might be arguments or return values of the demo server +*/ +public enum DfnsApi {} // just a namespace + +// MARK: - UserActionChallenge +extension DfnsApi { + public struct UserActionChallenge: Sendable, Codable { + public let attestation: String + public let userVerification: String + public let externalAuthenticationUrl: String + public let challenge: String + public let challengeIdentifier: String + public let supportedCredentialKinds: [SupportedCredentialKind] + public let allowCredentials: AllowCredentials + + public init( + attestation: String, + userVerification: String, + externalAuthenticationUrl: String, + challenge: String, + challengeIdentifier: String, + supportedCredentialKinds: [SupportedCredentialKind], + allowCredentials: AllowCredentials + ) { self.attestation = attestation self.userVerification = userVerification self.externalAuthenticationUrl = externalAuthenticationUrl @@ -12,18 +31,33 @@ public enum DfnsApi { self.supportedCredentialKinds = supportedCredentialKinds self.allowCredentials = allowCredentials } + } +} - public let attestation: String - public let userVerification: String - public let externalAuthenticationUrl: String - public let challenge: String - public let challengeIdentifier: String - public let supportedCredentialKinds: [SupportedCredentialKind] - public let allowCredentials: AllowCredentials - } +// MARK: - UserRegistrationChallenge +extension DfnsApi { + public struct UserRegistrationChallenge: Sendable, Codable { + public let temporaryAuthenticationToken: String + public let user: UserInformation + public let supportedCredentialKinds: SupportedCredentialKinds + public let otpUrl: String + public let challenge: String + public let authenticatorSelection: AuthenticatorSelectionCriteria + public let attestation: String + public let pubKeyCredParams: [PublicKeyCredentialParameters] + public let excludeCredentials: [PublicKeyCredentialDescriptor] - public struct UserRegistrationChallenge: Codable { - public init(temporaryAuthenticationToken: String, user: DfnsApi.UserInformation, supportedCredentialKinds: DfnsApi.SupportedCredentialKinds, otpUrl: String, challenge: String, authenticatorSelection: DfnsApi.AuthenticatorSelectionCriteria, attestation: String, pubKeyCredParams: [DfnsApi.PublicKeyCredentialParameters], excludeCredentials: [DfnsApi.PublicKeyCredentialDescriptor]) { + public init( + temporaryAuthenticationToken: String, + user: UserInformation, + supportedCredentialKinds: SupportedCredentialKinds, + otpUrl: String, + challenge: String, + authenticatorSelection: AuthenticatorSelectionCriteria, + attestation: String, + pubKeyCredParams: [PublicKeyCredentialParameters], + excludeCredentials: [PublicKeyCredentialDescriptor] + ) { self.temporaryAuthenticationToken = temporaryAuthenticationToken self.user = user self.supportedCredentialKinds = supportedCredentialKinds @@ -34,174 +68,257 @@ public enum DfnsApi { self.pubKeyCredParams = pubKeyCredParams self.excludeCredentials = excludeCredentials } + } +} - public let temporaryAuthenticationToken: String - public let user: UserInformation - public let supportedCredentialKinds: SupportedCredentialKinds - public let otpUrl: String - public let challenge: String - public let authenticatorSelection: AuthenticatorSelectionCriteria - public let attestation: String - public let pubKeyCredParams: [PublicKeyCredentialParameters] - public let excludeCredentials: [PublicKeyCredentialDescriptor] - } +// MARK: - RelyingParty +extension DfnsApi { + public struct RelyingParty: Sendable, Codable { + public let id: String + public let name: String - public struct RelyingParty: Codable { - public init(id: String, name: String) { + public init( + id: String, + name: String + ) { self.id = id self.name = name } + } +} - public let id: String - public let name: String - } - public struct SupportedCredentialKind: Codable { - public init(kind: String, factor: String, requiresSecondFactor: Bool) { +// MARK: - SupportedCredentialKind +extension DfnsApi { + public struct SupportedCredentialKind: Sendable, Codable { + public let kind: String + public let factor: String + public let requiresSecondFactor: Bool + + public init( + kind: String, + factor: String, + requiresSecondFactor: Bool + ) { self.kind = kind self.factor = factor self.requiresSecondFactor = requiresSecondFactor } + } +} - public let kind: String - public let factor: String - public let requiresSecondFactor: Bool - } +// MARK: - AllowCredentials +extension DfnsApi { + public struct AllowCredentials: Sendable, Codable { + public let webauthn: [PublicKeyCredentialDescriptor] + public let key: [PublicKeyCredentialDescriptor] - public struct AllowCredentials: Codable { - public init(webauthn: [DfnsApi.PublicKeyCredentialDescriptor], key: [DfnsApi.PublicKeyCredentialDescriptor]) { + public init( + webauthn: [PublicKeyCredentialDescriptor], + key: [PublicKeyCredentialDescriptor] + ) { self.webauthn = webauthn self.key = key } + } +} - public let webauthn: [PublicKeyCredentialDescriptor] - public let key: [PublicKeyCredentialDescriptor] - } +// MARK: - PublicKeyCredentialDescriptor +extension DfnsApi { + public struct PublicKeyCredentialDescriptor: Sendable, Codable { + public let type: String + public let id: String - public struct PublicKeyCredentialDescriptor: Codable { - public init(type: String, id: String) { + public init( + type: String, + id: String + ) { self.type = type self.id = id } + } +} - public let type: String - public let id: String - } +// MARK: - Fido2Assertion +extension DfnsApi { + public struct Fido2Assertion: Sendable, Codable { + public let kind: String + public let credentialAssertion: Fido2AssertionData - public struct Fido2Assertion: Codable { - public init(kind: String, credentialAssertion: DfnsApi.Fido2AssertionData) { + public init( + kind: String, + credentialAssertion: Fido2AssertionData + ) { self.kind = kind self.credentialAssertion = credentialAssertion } + } +} - public let kind: String - public let credentialAssertion: Fido2AssertionData - } +// MARK: - UserActionAssertion +extension DfnsApi { + public struct UserActionAssertion: Sendable, Codable { + public let challengeIdentifier: String + public let firstFactor: Fido2Assertion - public struct UserActionAssertion: Codable { - public init(challengeIdentifier: String, firstFactor: DfnsApi.Fido2Assertion) { + public init( + challengeIdentifier: String, + firstFactor: Fido2Assertion + ) { self.challengeIdentifier = challengeIdentifier self.firstFactor = firstFactor } + } +} - public let challengeIdentifier: String - public let firstFactor: Fido2Assertion - } +// MARK: - ClientData +extension DfnsApi { + public struct ClientData: Sendable, Codable { + public let type: String + public let challenge: String + public let origin: String - public struct ClientData: Codable { - public init(type: String, challenge: String, origin: String) { + public init( + type: String, + challenge: String, + origin: String + ) { self.type = type self.challenge = challenge self.origin = origin } - - public let type: String - public let challenge: String - public let origin: String - // public let crossOrigin: Bool } +} - public struct Fido2AssertionData: Codable { - public init(clientData: String, credId: String, signature: String, authenticatorData: String, userHandle: String? = nil) { - self.clientData = clientData - self.credId = credId - self.signature = signature - self.authenticatorData = authenticatorData - self.userHandle = userHandle - } +// MARK: - Fido2AssertionData +extension DfnsApi { + public struct Fido2AssertionData: Sendable, Codable { + public let clientData: String + public let credId: String + public let signature: String + public var authenticatorData: String + public var userHandle: String? + + public init( + clientData: String, + credId: String, + signature: String, + authenticatorData: String, + userHandle: String? = nil + ) { + self.clientData = clientData + self.credId = credId + self.signature = signature + self.authenticatorData = authenticatorData + self.userHandle = userHandle + } + } +} - public let clientData: String - public let credId: String - public let signature: String - public var authenticatorData: String - public var userHandle: String? - } +// MARK: - PublicKeyCredentialParameters +extension DfnsApi { + public struct PublicKeyCredentialParameters: Sendable, Codable { + public let type: String + public let alg: Int - public struct PublicKeyCredentialParameters: Codable { - public init(type: String, alg: Int) { + public init( + type: String, + alg: Int + ) { self.type = type self.alg = alg } + } +} - public let type: String - public let alg: Int - } +// MARK: - SupportedCredentialKinds +extension DfnsApi { + public struct SupportedCredentialKinds: Sendable, Codable { + public let firstFactor: [String] + public let secondFactor: [String] - public struct SupportedCredentialKinds: Codable { - public init(firstFactor: [String], secondFactor: [String]) { + public init( + firstFactor: [String], + secondFactor: [String] + ) { self.firstFactor = firstFactor self.secondFactor = secondFactor } + } +} - public let firstFactor: [String] - public let secondFactor: [String] - } +// MARK: - UserInformation +extension DfnsApi { + public struct UserInformation: Sendable, Codable { + public let id: String + public let displayName: String + public let name: String - public struct UserInformation: Codable { - public init(id: String, displayName: String, name: String) { + public init( + id: String, + displayName: String, + name: String + ) { self.id = id self.displayName = displayName self.name = name } + } +} - public let id: String - public let displayName: String - public let name: String - } - - public struct AuthenticatorSelectionCriteria: Codable { - public init(authenticatorAttachment: String? = nil, residentKey: String, requireResidentKey: Bool, userVerification: String) { - self.authenticatorAttachment = authenticatorAttachment - self.residentKey = residentKey - self.requireResidentKey = requireResidentKey - self.userVerification = userVerification - } +// MARK: - AuthenticatorSelectionCriteria +extension DfnsApi { + public struct AuthenticatorSelectionCriteria: Sendable, Codable { + public let authenticatorAttachment: String? + public let residentKey: String + public let requireResidentKey: Bool + public let userVerification: String + + public init( + authenticatorAttachment: String? = nil, + residentKey: String, + requireResidentKey: Bool, + userVerification: String + ) { + self.authenticatorAttachment = authenticatorAttachment + self.residentKey = residentKey + self.requireResidentKey = requireResidentKey + self.userVerification = userVerification + } + } +} - public let authenticatorAttachment: String? - public let residentKey: String - public let requireResidentKey: Bool - public let userVerification: String - } +// MARK: - Fido2Attestation +extension DfnsApi { + public struct Fido2Attestation: Sendable, Codable { + public let credentialInfo: Fido2AttestationData + public let credentialKind: String - public struct Fido2Attestation: Codable { - public init(credentialInfo: DfnsApi.Fido2AttestationData, credentialKind: String) { + public init( + credentialInfo: Fido2AttestationData, + credentialKind: String + ) { self.credentialInfo = credentialInfo self.credentialKind = credentialKind } + } +} - public let credentialInfo: Fido2AttestationData - public let credentialKind: String - } +// MARK: - Fido2AttestationData +extension DfnsApi { + public struct Fido2AttestationData: Sendable, Codable { + public let attestationData: String + public let clientData: String + public let credId: String - public struct Fido2AttestationData: Codable { - public init(attestationData: String, clientData: String, credId: String) { + public init( + attestationData: String, + clientData: String, + credId: String + ) { self.attestationData = attestationData self.clientData = clientData self.credId = credId } - - public let attestationData: String - public let clientData: String - public let credId: String } } diff --git a/Sources/DfnsSdk/Imported/Passkey.swift b/Sources/DfnsSdk/Imported/Passkey.swift index 58b1a57..e5e8a5c 100644 --- a/Sources/DfnsSdk/Imported/Passkey.swift +++ b/Sources/DfnsSdk/Imported/Passkey.swift @@ -163,28 +163,28 @@ enum PassKeyError: String, Error { case unknown = "UnknownError" } -struct AuthRegistrationResult { +struct AuthRegistrationResult: Sendable { var passkey: PassKeyRegistrationResult var type: PasskeyOperation } -struct AuthAssertionResult { +struct AuthAssertionResult: Sendable { var passkey: PassKeyAssertionResult var type: PasskeyOperation } -struct PassKeyResult { +struct PassKeyResult: Sendable { var registrationResult: PassKeyRegistrationResult? var assertionResult: PassKeyAssertionResult? } -struct PassKeyRegistrationResult { +struct PassKeyRegistrationResult: Sendable { var credentialID: Data var rawAttestationObject: Data var rawClientDataJSON: Data } -struct PassKeyAssertionResult { +struct PassKeyAssertionResult: Sendable { var credentialID: Data var rawAuthenticatorData: Data var rawClientDataJSON: Data @@ -192,7 +192,7 @@ struct PassKeyAssertionResult { var userID: Data } -enum PasskeyOperation { +enum PasskeyOperation: Sendable { case Registration case Assertion } diff --git a/Sources/DfnsSdk/Imported/PasskeyDelegate.swift b/Sources/DfnsSdk/Imported/PasskeyDelegate.swift index ea044f2..037788d 100644 --- a/Sources/DfnsSdk/Imported/PasskeyDelegate.swift +++ b/Sources/DfnsSdk/Imported/PasskeyDelegate.swift @@ -10,7 +10,7 @@ class PasskeyDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizat private var _completion: (_ error: Error?, _ result: PassKeyResult?) -> Void; // Initializes delegate with a completion handler (callback function) - init(completionHandler: @escaping (_ error: Error?, _ result: PassKeyResult?) -> Void) { + init(completionHandler: @escaping @Sendable (_ error: Error?, _ result: PassKeyResult?) -> Void) { self._completion = completionHandler; } diff --git a/Sources/DfnsSdk/PasskeysSigner.swift b/Sources/DfnsSdk/PasskeysSigner.swift index 52dc874..93ad867 100644 --- a/Sources/DfnsSdk/PasskeysSigner.swift +++ b/Sources/DfnsSdk/PasskeysSigner.swift @@ -11,103 +11,96 @@ public enum PasskeysSignerError: Error { Converts completion handlers into async functions and make the necessary conversion to work with Dfns API */ public final class PasskeysSigner { - private let passkey = Passkey() - - /** - The relying party ID identifies your application to users, when users create/use passkeys. (Read more [here](https://www.w3.org/TR/webauthn-2/#relying-party)). - It is a valid domain string identifying the WebAuthn Relying Party. In other words, its the domain your application is running on, which will be tied to the passkeys that users create. - We advise to use the root domain, not the full domain (eg `acme.com`, not `app.acme.com` nor `foo.app.acme.com`), that way, passkeys created - by your users can be re-used on other subdomains (eg. on `foo.acme.com` and `bar.acme.com`) in the future. Read more [here](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialCreationOptions#rp). - */ - private let relyingPartyId: String - - public init(relyingPartyId: String) { - self.relyingPartyId = relyingPartyId - } - - public func register(challenge: DfnsApi.UserRegistrationChallenge) async throws -> DfnsApi.Fido2Attestation { - if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { - let result = await withCheckedContinuation { continuation in - register(challenge: challenge) { fido2Attestation, exception in - continuation.resume(returning: (fido2Attestation: fido2Attestation, exception: exception)) - } - } - - if result.exception != nil { - throw result.exception! - } - - return result.fido2Attestation! - } else { - throw PasskeysSignerError.unexpected(code: PassKeyError.notSupported.rawValue, message: PassKeyError.notSupported.rawValue, error: nil) - } - } - - private func register(challenge: DfnsApi.UserRegistrationChallenge, completion: @escaping (DfnsApi.Fido2Attestation?, Error?) -> Void) { - let userId = challenge.user.id - let displayName = challenge.user.displayName - let challengeBase64url = Utils.base64URLUnescaped(challenge.challenge) - - passkey.register(self.relyingPartyId, challenge: challengeBase64url, displayName: displayName, userId: userId, securityKey: false, - resolve: { authResult in - let credentialInfo = DfnsApi.Fido2AttestationData( - attestationData: self.extractFromAuthResultValue(authResult, path: ["response", "rawAttestationObject"]), - clientData: self.extractFromAuthResultValue(authResult, path: ["response", "rawClientDataJSON"]), - credId: self.extractFromAuthResultValue(authResult, path: ["credentialID"]) - ) - let fido2Attestation = DfnsApi.Fido2Attestation(credentialInfo: credentialInfo, credentialKind: "Fido2") - completion(fido2Attestation, nil) - }, reject: { code, message, error in - let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) - completion(nil, exception) - }) - } - - public func sign(challenge: DfnsApi.UserActionChallenge) async throws -> DfnsApi.Fido2Assertion { - if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { - let result = await withCheckedContinuation { continuation in - sign(challenge: challenge) { fido2Assertion, exception in - continuation.resume(returning: (fido2Assertion: fido2Assertion, exception: exception)) - } - } - - if result.exception != nil { - throw result.exception! - } - - return result.fido2Assertion! - } else { - throw PasskeysSignerError.unexpected(code: PassKeyError.notSupported.rawValue, message: PassKeyError.notSupported.rawValue, error: nil) - } - } - - private func sign(challenge: DfnsApi.UserActionChallenge, completion: @escaping (DfnsApi.Fido2Assertion?, Error?) -> Void) { - let challengeBase64url = Utils.base64URLUnescaped(challenge.challenge) - - passkey.authenticate(self.relyingPartyId, challenge: challengeBase64url, securityKey: false, resolve: { authResult in - let credentialAssertion = DfnsApi.Fido2AssertionData( - clientData: self.extractFromAuthResultValue(authResult, path: ["response", "rawClientDataJSON"]), - credId: self.extractFromAuthResultValue(authResult, path: ["credentialID"]), - signature: self.extractFromAuthResultValue(authResult, path: ["response", "signature"]), - authenticatorData: self.extractFromAuthResultValue(authResult, path: ["response", "rawAuthenticatorData"]), - userHandle: Utils.base64URLEscape((authResult["userID"] as! String).data(using: .utf8)!.base64EncodedString()) - ) - - let fido2Assertion = DfnsApi.Fido2Assertion(kind: "Fido2", credentialAssertion: credentialAssertion) - - completion(fido2Assertion, nil) - }, reject: { code, message, error in - let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) - completion(nil, exception) - }) - } - - private func extractFromAuthResultValue(_ authResult: NSDictionary, path: [String]) -> String { - var path = path - if path.count == 1 { - return Utils.base64URLEscape(authResult[path.removeFirst()] as! String) - } else { - return extractFromAuthResultValue(authResult[path.removeFirst()] as! NSDictionary, path: path) - } - } + private let passkey = Passkey() + + /** + The relying party ID identifies your application to users, when users create/use passkeys. (Read more [here](https://www.w3.org/TR/webauthn-2/#relying-party)). + It is a valid domain string identifying the WebAuthn Relying Party. In other words, its the domain your application is running on, which will be tied to the passkeys that users create. + We advise to use the root domain, not the full domain (eg `acme.com`, not `app.acme.com` nor `foo.app.acme.com`), that way, passkeys created + by your users can be re-used on other subdomains (eg. on `foo.acme.com` and `bar.acme.com`) in the future. Read more [here](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialCreationOptions#rp). + */ + private let relyingPartyId: String + + public init(relyingPartyId: String) { + self.relyingPartyId = relyingPartyId + } + + public func register(challenge: DfnsApi.UserRegistrationChallenge) async throws -> DfnsApi.Fido2Attestation { + let result = await withCheckedContinuation { continuation in + register(challenge: challenge) { fido2Attestation, exception in + continuation.resume(returning: (fido2Attestation: fido2Attestation, exception: exception)) + } + } + + if result.exception != nil { + throw result.exception! + } + + return result.fido2Attestation! + } + + private func register(challenge: DfnsApi.UserRegistrationChallenge, completion: @escaping (DfnsApi.Fido2Attestation?, Error?) -> Void) { + let userId = challenge.user.id + let displayName = challenge.user.displayName + let challengeBase64url = Utils.base64URLUnescaped(challenge.challenge) + + passkey.register(self.relyingPartyId, challenge: challengeBase64url, displayName: displayName, userId: userId, securityKey: false, + resolve: { authResult in + let credentialInfo = DfnsApi.Fido2AttestationData( + attestationData: self.extractFromAuthResultValue(authResult, path: ["response", "rawAttestationObject"]), + clientData: self.extractFromAuthResultValue(authResult, path: ["response", "rawClientDataJSON"]), + credId: self.extractFromAuthResultValue(authResult, path: ["credentialID"]) + ) + let fido2Attestation = DfnsApi.Fido2Attestation(credentialInfo: credentialInfo, credentialKind: "Fido2") + completion(fido2Attestation, nil) + }, reject: { code, message, error in + let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) + completion(nil, exception) + }) + } + + public func sign(challenge: DfnsApi.UserActionChallenge) async throws -> DfnsApi.Fido2Assertion { + let result = await withCheckedContinuation { continuation in + sign(challenge: challenge) { fido2Assertion, exception in + continuation.resume(returning: (fido2Assertion: fido2Assertion, exception: exception)) + } + } + + if result.exception != nil { + throw result.exception! + } + + return result.fido2Assertion! + + } + + private func sign(challenge: DfnsApi.UserActionChallenge, completion: @escaping (DfnsApi.Fido2Assertion?, Error?) -> Void) { + let challengeBase64url = Utils.base64URLUnescaped(challenge.challenge) + + passkey.authenticate(self.relyingPartyId, challenge: challengeBase64url, securityKey: false, resolve: { authResult in + let credentialAssertion = DfnsApi.Fido2AssertionData( + clientData: self.extractFromAuthResultValue(authResult, path: ["response", "rawClientDataJSON"]), + credId: self.extractFromAuthResultValue(authResult, path: ["credentialID"]), + signature: self.extractFromAuthResultValue(authResult, path: ["response", "signature"]), + authenticatorData: self.extractFromAuthResultValue(authResult, path: ["response", "rawAuthenticatorData"]), + userHandle: Utils.base64URLEscape((authResult["userID"] as! String).data(using: .utf8)!.base64EncodedString()) + ) + + let fido2Assertion = DfnsApi.Fido2Assertion(kind: "Fido2", credentialAssertion: credentialAssertion) + + completion(fido2Assertion, nil) + }, reject: { code, message, error in + let exception = PasskeysSignerError.unexpected(code: code, message: message, error: error) + completion(nil, exception) + }) + } + + private func extractFromAuthResultValue(_ authResult: NSDictionary, path: [String]) -> String { + var path = path + if path.count == 1 { + return Utils.base64URLEscape(authResult[path.removeFirst()] as! String) + } else { + return extractFromAuthResultValue(authResult[path.removeFirst()] as! NSDictionary, path: path) + } + } }