Skip to content

Commit 735ec24

Browse files
Implemented credential generation procedure
1 parent 125908f commit 735ec24

File tree

6 files changed

+88
-14
lines changed

6 files changed

+88
-14
lines changed

Sources/WebAuthn/Authenticators/KeyPairAuthenticator.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ extension KeyPairAuthenticator {
119119
}
120120
}
121121

122+
public var publicKey: PublicKey {
123+
switch key {
124+
case .es256(let privateKey): EC2PublicKey(privateKey.publicKey)
125+
case .es384(let privateKey): EC2PublicKey(privateKey.publicKey)
126+
case .es521(let privateKey): EC2PublicKey(privateKey.publicKey)
127+
}
128+
}
129+
122130
public init(
123131
id: ID,
124132
key: Key,

Sources/WebAuthn/Authenticators/Protocol/AuthenticatorCredentialSourceProtocol.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public protocol AuthenticatorCredentialSourceProtocol: Sendable, Identifiable wh
2323
var userHandle: PublicKeyCredentialUserEntity.ID { get }
2424
var counter: UInt32 { get }
2525

26+
var publicKey: PublicKey { get }
27+
2628
func signAssertion(
2729
authenticatorData: [UInt8],
2830
clientDataHash: SHA256Digest

Sources/WebAuthn/Authenticators/Protocol/AuthenticatorProtocol.swift

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -167,30 +167,50 @@ extension AuthenticatorProtocol {
167167
/// Let userVerification be false.
168168
/// → is set to discouraged
169169
/// Let userVerification be false.
170-
let shouldPerformUserVerification = false
170+
// let shouldPerformUserVerification = false
171171

172172
/// Step 16. Let enterpriseAttestationPossible be a Boolean value, as follows. If pkOptions.attestation
173173
/// → is set to enterprise
174174
/// Let enterpriseAttestationPossible be true if the user agent wishes to support enterprise attestation for pkOptions.rp.id (see Step 8, above). Otherwise false.
175175
/// → otherwise
176176
/// Let enterpriseAttestationPossible be false.
177-
let isEnterpriseAttestationPossible = false
177+
// let isEnterpriseAttestationPossible = false
178178

179179
/// Step 19. Let attestationFormats be a list of strings, initialized to the value of pkOptions.attestationFormats.
180+
// let attestationFormats: [AttestationFormat] = []
181+
180182
/// Step 20. If pkOptions.attestation
181183
/// → is set to none
182184
/// Set attestationFormats be the single-element list containing the string “none”
183185
guard case .none = registration.options.attestation else { throw WebAuthnError.attestationFormatNotSupported }
184186

185187
/// Step 22. Let excludeCredentialDescriptorList be a new list.
188+
// let excludeCredentialDescriptorList: [PublicKeyCredentialDescriptor] = []
186189
/// Step 23. For each credential descriptor C in pkOptions.excludeCredentials:
187190
/// 1. If C.transports is not empty, and authenticator is connected over a transport not mentioned in C.transports, the client MAY continue.
188191
/// 2. Otherwise, Append C to excludeCredentialDescriptorList.
192+
// Skip.
193+
189194
/// 3. Invoke the authenticatorMakeCredential operation on authenticator with clientDataHash, pkOptions.rp, pkOptions.user, requireResidentKey, userVerification, credTypesAndPubKeyAlgs, excludeCredentialDescriptorList, enterpriseAttestationPossible, attestationFormats, and authenticatorExtensions as parameters.
195+
/*
196+
registration.clientDataHash;
197+
registration.options.relyingParty
198+
registration.options.user
199+
requiresResidentKey
200+
shouldPerformUserVerification
201+
registration.publicKeyCredentialParameters
202+
excludeCredentialDescriptorList
203+
isEnterpriseAttestationPossible
204+
attestationFormats
205+
*/
206+
190207
/// Step 24. Append authenticator to issuedRequests.
208+
// Skip.
191209

192210
/// See [WebAuthn Level 3 Editor's Draft §6.3.2. The authenticatorMakeCredential Operation](https://w3c.github.io/webauthn/#sctn-op-make-cred)
193211
/// Step 1. Check if all the supplied parameters are syntactically well-formed and of the correct length. If not, return an error code equivalent to "UnknownError" and terminate the operation.
212+
// Skip.
213+
194214
/// Step 2. Check if at least one of the specified combinations of PublicKeyCredentialType and cryptographic parameters in credTypesAndPubKeyAlgs is supported. If not, return an error code equivalent to "NotSupportedError" and terminate the operation.
195215
guard let chosenCredentialParameters = registration.publicKeyCredentialParameters.first(where: supportedPublicKeyCredentialParameters.contains(_:))
196216
else { throw WebAuthnError.noSupportedCredentialParameters }
@@ -202,12 +222,20 @@ extension AuthenticatorProtocol {
202222
/// → does not consent to create a new credential
203223
/// return an error code equivalent to "NotAllowedError" and terminate the operation.
204224
/// NOTE: The purpose of this authorization gesture is not to proceed with creating a credential, but for privacy reasons to authorize disclosure of the fact that descriptor.id is bound to this authenticator. If the user consents, the client and Relying Party can detect this and guide the user to use a different authenticator. If the user does not consent, the authenticator does not reveal that descriptor.id is bound to it, and responds as if the user simply declined consent to create a credential.
225+
// Skip.
226+
205227
/// Step 4. If requireResidentKey is true and the authenticator cannot store a client-side discoverable public key credential source, return an error code equivalent to "ConstraintError" and terminate the operation.
228+
// Skip.
229+
206230
/// Step 5. If requireUserVerification is true and the authenticator cannot perform user verification, return an error code equivalent to "ConstraintError" and terminate the operation.
231+
// Skip.
232+
207233
/// Step 6. Collect an authorization gesture confirming user consent for creating a new credential. The prompt for the authorization gesture is shown by the authenticator if it has its own output capability, or by the user agent otherwise. The prompt SHOULD display rpEntity.id, rpEntity.name, userEntity.name and userEntity.displayName, if possible.
208234
/// → If requireUserVerification is true, the authorization gesture MUST include user verification.
209235
/// → If requireUserPresence is true, the authorization gesture MUST include a test of user presence.
210236
/// → If the user does not consent or if user verification fails, return an error code equivalent to "NotAllowedError" and terminate the operation.
237+
// Skip.
238+
211239
/// Step 7. Once the authorization gesture has been completed and user consent has been obtained, generate a new credential object:
212240
/// 1. Let (publicKey, privateKey) be a new pair of cryptographic keys using the combination of PublicKeyCredentialType and cryptographic parameters represented by the first item in credTypesAndPubKeyAlgs that is supported by this authenticator.
213241
/// 2. Let userHandle be userEntity.id.
@@ -235,7 +263,11 @@ extension AuthenticatorProtocol {
235263
)
236264

237265
/// Step 8. If any error occurred while creating the new credential object, return an error code equivalent to "UnknownError" and terminate the operation.
266+
// Skip.
267+
238268
/// Step 9. Let processedExtensions be the result of authenticator extension processing for each supported extension identifier → authenticator extension input in extensions.
269+
// Skip.
270+
239271
/// Step 10. If the authenticator:
240272
/// → is a U2F device
241273
/// let the signature counter value for the new credential be zero. (U2F devices may support signature counters but do not return a counter when making a credential. See [FIDO-U2F-Message-Formats].)
@@ -245,16 +277,45 @@ extension AuthenticatorProtocol {
245277
/// allocate the counter, associate it with the new credential, and initialize the counter value as zero.
246278
/// → does not support a signature counter
247279
/// let the signature counter value for the new credential be constant at zero.
280+
let counter: UInt32 = credentialSource.counter
281+
248282
/// Step 15. Let attestedCredentialData be the attested credential data byte array including the credentialId and publicKey.
283+
let attestedCredentialData = AttestedCredentialData(
284+
authenticatorAttestationGUID: attestationGloballyUniqueID,
285+
credentialID: credentialSource.id.bytes,
286+
publicKey: credentialSource.publicKey.bytes
287+
)
288+
249289
/// Step 16. Let attestationFormat be the first supported attestation statement format identifier from attestationFormats, taking into account enterpriseAttestationPossible. If attestationFormats contains no supported value, then let attestationFormat be the attestation statement format identifier most preferred by this authenticator.
290+
let attestationFormat = preferredAttestationFormat(from: [.none])
291+
250292
/// Step 17. Let authenticatorData be the byte array specified in § 6.1 Authenticator Data, including attestedCredentialData as the attestedCredentialData and processedExtensions, if any, as the extensions.
293+
let authenticatorData = AuthenticatorData(
294+
relyingPartyIDHash: SHA256.hash(data: Array(registration.options.relyingParty.id.utf8)),
295+
flags: AuthenticatorFlags(
296+
userPresent: true, // TODO: Make flags
297+
userVerified: true, // TODO: Make flags
298+
isBackupEligible: true,
299+
isCurrentlyBackedUp: true
300+
),
301+
counter: counter,
302+
attestedData: attestedCredentialData,
303+
extData: nil
304+
)
305+
251306
/// Step 18. Create an attestation object for the new credential using the procedure specified in § 6.5.4 Generating an Attestation Object, the attestation statement format attestationFormat, and the values authenticatorData and hash, as well as taking into account the value of enterpriseAttestationPossible. For more details on attestation, see § 6.5 Attestation.
307+
let attestationStatement = try await signAttestationStatement(
308+
attestationFormat: attestationFormat,
309+
authenticatorData: authenticatorData.bytes,
310+
clientDataHash: registration.clientDataHash
311+
)
312+
252313
/// On successful completion of this operation, the authenticator returns the attestation object to the client.
253-
// try await registration.attemptRegistration.submitAttestationObject(
254-
// attestationFormat: <#T##AttestationFormat#>,
255-
// authenticatorData: <#T##AuthenticatorData#>,
256-
// attestationStatement: <#T##CBOR#>
257-
// )
314+
try await registration.attemptRegistration.submitAttestationObject(
315+
attestationFormat: attestationFormat,
316+
authenticatorData: authenticatorData,
317+
attestationStatement: attestationStatement
318+
)
258319

259320
return credentialSource
260321
}

Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public struct AttestationObject {
5959
throw WebAuthnError.relyingPartyIDHashDoesNotMatch
6060
}
6161

62+
// TODO: Make flag
6263
guard authenticatorData.flags.userPresent else {
6364
throw WebAuthnError.userPresentFlagNotSet
6465
}

Sources/WebAuthn/Ceremonies/Shared/AuthenticatorFlags.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ public struct AuthenticatorFlags: Equatable, Sendable {
3333
case extensionDataIncluded = 7
3434
}
3535

36-
var userPresent: Bool
37-
var userVerified: Bool
38-
var isBackupEligible: Bool
39-
var isCurrentlyBackedUp: Bool
40-
var attestedCredentialData: Bool
41-
var extensionDataIncluded: Bool
36+
var userPresent: Bool = false
37+
var userVerified: Bool = false
38+
var isBackupEligible: Bool = false
39+
var isCurrentlyBackedUp: Bool = false
40+
var attestedCredentialData: Bool = false
41+
var extensionDataIncluded: Bool = false
4242

4343
var deviceType: VerifiedAuthentication.CredentialDeviceType {
4444
isBackupEligible ? .multiDevice : .singleDevice

Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ import _CryptoExtras
1717
import Foundation
1818
import SwiftCBOR
1919

20-
protocol PublicKey: Sendable {
20+
public protocol PublicKey: Sendable {
2121
var algorithm: COSEAlgorithmIdentifier { get }
2222
/// Verify a signature was signed with the private key corresponding to the public key.
2323
func verify(signature: some DataProtocol, data: some DataProtocol) throws
24+
25+
var bytes: [UInt8] { get }
2426
}
2527

2628
enum CredentialPublicKey: Sendable {

0 commit comments

Comments
 (0)