Skip to content

Commit 7bc6bda

Browse files
Added KeyPairAuthenticator for authenticating with unattested private keys as data
1 parent ea34e76 commit 7bc6bda

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the WebAuthn Swift open source project
4+
//
5+
// Copyright (c) 2024 the WebAuthn Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
import Crypto
17+
18+
public struct KeyPairAuthenticator: AuthenticatorProtocol, Sendable {
19+
public let attestationGloballyUniqueID: AAGUID
20+
public let attachmentModality: AuthenticatorAttachment
21+
public let supportedPublicKeyCredentialParameters: Set<PublicKeyCredentialParameters>
22+
public let canPerformUserVerification: Bool = true
23+
public let canStoreCredentialSourceClientSide: Bool = true
24+
25+
/// Initialize a key-pair based authenticator with a globally unique ID representing your application.
26+
/// - Note: To generate an AAGUID, run `% uuidgen` in your terminal. This value should generally not change across installations or versions of your app, and should be the same for every user.
27+
/// - Parameter attestationGloballyUniqueID: The AAGUID associated with the authenticator.
28+
/// - Parameter attachmentModality: The connected-nature of the authenticator to the device the client is running on. If credential keys can roam between devices, specify ``AuthenticatorModality/crossPlatform``. Set to ``AuthenticatorModality/platform`` by default.
29+
/// - Parameter supportedPublicKeyCredentialParameters: A customized set of key credentials the authenticator will limit support to.
30+
public init(
31+
attestationGloballyUniqueID: AAGUID,
32+
attachmentModality: AuthenticatorAttachment = .platform,
33+
supportedPublicKeyCredentialParameters: Set<PublicKeyCredentialParameters> = .supported
34+
) {
35+
self.attestationGloballyUniqueID = attestationGloballyUniqueID
36+
self.attachmentModality = attachmentModality
37+
self.supportedPublicKeyCredentialParameters = supportedPublicKeyCredentialParameters
38+
}
39+
40+
public func generateCredentialSource(
41+
requiresClientSideKeyStorage: Bool,
42+
credentialParameters: PublicKeyCredentialParameters,
43+
relyingPartyID: PublicKeyCredentialRelyingPartyEntity.ID,
44+
userHandle: PublicKeyCredentialUserEntity.ID
45+
) async throws -> CredentialSource {
46+
throw WebAuthnError.unsupported
47+
}
48+
49+
public func filteredCredentialDescriptors(
50+
credentialDescriptors: [PublicKeyCredentialDescriptor],
51+
relyingPartyID: PublicKeyCredentialRelyingPartyEntity.ID
52+
) -> [PublicKeyCredentialDescriptor] {
53+
return credentialDescriptors
54+
}
55+
56+
public func collectAuthorizationGesture(
57+
requiresUserVerification: Bool,
58+
requiresUserPresence: Bool,
59+
credentialOptions: [CredentialSource]
60+
) async throws -> CredentialSource {
61+
guard let credentialSource = credentialOptions.first
62+
else { throw WebAuthnError.authorizationGestureNotAllowed }
63+
64+
return credentialSource
65+
}
66+
}
67+
68+
extension KeyPairAuthenticator {
69+
public struct CredentialSource: AuthenticatorCredentialSourceProtocol, Sendable {
70+
public var id: UUID
71+
public var relyingPartyID: PublicKeyCredentialRelyingPartyEntity.ID
72+
public var userHandle: PublicKeyCredentialUserEntity.ID
73+
public var counter: UInt32
74+
75+
public var credentialParameters: PublicKeyCredentialParameters {
76+
PublicKeyCredentialParameters(alg: .algES256)
77+
}
78+
79+
public var rawKeyData: Data {
80+
Data()
81+
}
82+
83+
public init(
84+
id: ID,
85+
credentialParameters: PublicKeyCredentialParameters,
86+
rawKeyData: some ContiguousBytes,
87+
relyingPartyID: PublicKeyCredentialRelyingPartyEntity.ID,
88+
userHandle: PublicKeyCredentialUserEntity.ID,
89+
counter: UInt32
90+
) throws {
91+
guard credentialParameters.type == .publicKey
92+
else { throw WebAuthnError.unsupportedCredentialPublicKeyType }
93+
94+
self.id = id
95+
self.relyingPartyID = relyingPartyID
96+
self.userHandle = userHandle
97+
self.counter = counter
98+
}
99+
100+
public func signAssertion(
101+
authenticatorData: [UInt8],
102+
clientDataHash: SHA256Digest
103+
) throws -> [UInt8] {
104+
throw WebAuthnError.unsupported
105+
}
106+
}
107+
}
108+
109+
extension KeyPairAuthenticator.CredentialSource: Codable {
110+
public init(from decoder: Decoder) throws {
111+
let container = try decoder.container(keyedBy: CodingKeys.self)
112+
113+
try self.init(
114+
id: try container.decode(UUID.self, forKey: .id),
115+
credentialParameters: try container.decode(PublicKeyCredentialParameters.self, forKey: .credentialParameters),
116+
rawKeyData: try container.decode(Data.self, forKey: .key),
117+
relyingPartyID: try container.decode(String.self, forKey: .relyingPartyID),
118+
userHandle: PublicKeyCredentialUserEntity.ID(try container.decode(Data.self, forKey: .userHandle)),
119+
counter: try container.decode(UInt32.self, forKey: .counter)
120+
)
121+
}
122+
123+
public func encode(to encoder: Encoder) throws {
124+
var container = encoder.container(keyedBy: CodingKeys.self)
125+
126+
try container.encode(id, forKey: .id)
127+
try container.encode(credentialParameters, forKey: .credentialParameters)
128+
try container.encode(rawKeyData, forKey: .key)
129+
try container.encode(relyingPartyID, forKey: .relyingPartyID)
130+
try container.encode(Data(userHandle), forKey: .userHandle)
131+
try container.encode(counter, forKey: .counter)
132+
}
133+
134+
enum CodingKeys: CodingKey {
135+
case id
136+
case credentialParameters
137+
case key
138+
case relyingPartyID
139+
case userHandle
140+
case counter
141+
}
142+
}

Sources/WebAuthn/Authenticators/Protocol/AuthenticatorCredentialSourceIdentifier.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,28 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
import Foundation
16+
1517
public protocol AuthenticatorCredentialSourceIdentifier: Hashable {
1618
init?(bytes: some BidirectionalCollection<UInt8>)
1719
var bytes: [UInt8] { get }
1820
}
21+
22+
extension UUID: AuthenticatorCredentialSourceIdentifier {
23+
public init?(bytes: some BidirectionalCollection<UInt8>) {
24+
let uuidSize = MemoryLayout<uuid_t>.size
25+
guard bytes.count == uuidSize else { return nil }
26+
27+
/// Either load it directly, or copy it to a new array to load the uuid from there.
28+
let uuid = bytes.withContiguousStorageIfAvailable {
29+
$0.withUnsafeBytes {
30+
$0.loadUnaligned(as: uuid_t.self)
31+
}
32+
} ?? Array(bytes).withUnsafeBytes {
33+
$0.loadUnaligned(as: uuid_t.self)
34+
}
35+
self = UUID(uuid: uuid)
36+
}
37+
38+
public var bytes: [UInt8] { withUnsafeBytes(of: self) { Array($0) } }
39+
}

Sources/WebAuthn/WebAuthnError.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ public struct WebAuthnError: Error, Hashable, Sendable {
6666
case invalidExponent
6767
case unsupportedCOSEAlgorithmForRSAPublicKey
6868
case unsupported
69+
70+
// MARK: Authenticator
71+
case unsupportedCredentialPublicKeyType
72+
case authorizationGestureNotAllowed
6973
}
7074

7175
let reason: Reason
@@ -125,4 +129,8 @@ public struct WebAuthnError: Error, Hashable, Sendable {
125129
public static let invalidExponent = Self(reason: .invalidExponent)
126130
public static let unsupportedCOSEAlgorithmForRSAPublicKey = Self(reason: .unsupportedCOSEAlgorithmForRSAPublicKey)
127131
public static let unsupported = Self(reason: .unsupported)
132+
133+
// MARK: Authenticator
134+
public static let unsupportedCredentialPublicKeyType = Self(reason: .unsupportedCredentialPublicKeyType)
135+
public static let authorizationGestureNotAllowed = Self(reason: .authorizationGestureNotAllowed)
128136
}

0 commit comments

Comments
 (0)