477d3d3455c5b776c5e7f090703672222c75d410
[WebKit-https.git] / Source / WebKit / UIProcess / WebAuthentication / Cocoa / LocalAuthenticator.mm
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "LocalAuthenticator.h"
28
29 #if ENABLE(WEB_AUTHN)
30
31 #import <Security/SecItem.h>
32 #import <WebCore/AuthenticatorAssertionResponse.h>
33 #import <WebCore/AuthenticatorAttestationResponse.h>
34 #import <WebCore/CBORWriter.h>
35 #import <WebCore/ExceptionData.h>
36 #import <WebCore/PublicKeyCredentialCreationOptions.h>
37 #import <WebCore/PublicKeyCredentialRequestOptions.h>
38 #import <WebCore/WebAuthenticationConstants.h>
39 #import <WebCore/WebAuthenticationUtils.h>
40 #import <pal/crypto/CryptoDigest.h>
41 #import <wtf/HashSet.h>
42 #import <wtf/RetainPtr.h>
43 #import <wtf/RunLoop.h>
44 #import <wtf/Vector.h>
45 #import <wtf/spi/cocoa/SecuritySPI.h>
46 #import <wtf/text/StringHash.h>
47
48 namespace WebKit {
49 using namespace WebCore;
50
51 namespace LocalAuthenticatorInternal {
52
53 // See https://www.w3.org/TR/webauthn/#flags.
54 const uint8_t makeCredentialFlags = 0b01000101; // UP, UV and AT are set.
55 const uint8_t getAssertionFlags = 0b00000101; // UP and UV are set.
56 // Credential ID is currently SHA-1 of the corresponding public key.
57 const uint16_t credentialIdLength = 20;
58
59 static inline bool emptyTransportsOrContain(const Vector<AuthenticatorTransport>& transports, AuthenticatorTransport target)
60 {
61     return transports.isEmpty() ? true : transports.contains(target);
62 }
63
64 static inline HashSet<String> produceHashSet(const Vector<PublicKeyCredentialDescriptor>& credentialDescriptors)
65 {
66     HashSet<String> result;
67     for (auto& credentialDescriptor : credentialDescriptors) {
68         if (emptyTransportsOrContain(credentialDescriptor.transports, AuthenticatorTransport::Internal)
69             && credentialDescriptor.type == PublicKeyCredentialType::PublicKey
70             && credentialDescriptor.idVector.size() == credentialIdLength)
71             result.add(String(reinterpret_cast<const char*>(credentialDescriptor.idVector.data()), credentialDescriptor.idVector.size()));
72     }
73     return result;
74 }
75
76 static inline Vector<uint8_t> toVector(NSData *data)
77 {
78     Vector<uint8_t> result;
79     result.append(reinterpret_cast<const uint8_t*>(data.bytes), data.length);
80     return result;
81 }
82
83 static inline RetainPtr<NSData> toNSData(const Vector<uint8_t>& data)
84 {
85     // FIXME(183534): Consider using initWithBytesNoCopy.
86     return adoptNS([[NSData alloc] initWithBytes:data.data() length:data.size()]);
87 }
88
89 static inline RetainPtr<NSData> toNSData(ArrayBuffer* buffer)
90 {
91     ASSERT(buffer);
92     // FIXME(183534): Consider using initWithBytesNoCopy.
93     return adoptNS([[NSData alloc] initWithBytes:buffer->data() length:buffer->byteLength()]);
94 }
95
96 static inline Ref<ArrayBuffer> toArrayBuffer(NSData *data)
97 {
98     return ArrayBuffer::create(reinterpret_cast<const uint8_t*>(data.bytes), data.length);
99 }
100
101 // FIXME(<rdar://problem/60108131>): Remove this whitelist once testing is complete.
102 static const HashSet<String>& whitelistedRpId()
103 {
104     static NeverDestroyed<HashSet<String>> whitelistedRpId = std::initializer_list<String> {
105         "",
106         "localhost",
107         "tlstestwebkit.org",
108     };
109     return whitelistedRpId;
110 }
111
112 } // LocalAuthenticatorInternal
113
114 LocalAuthenticator::LocalAuthenticator(UniqueRef<LocalConnection>&& connection)
115     : m_connection(WTFMove(connection))
116 {
117 }
118
119 void LocalAuthenticator::makeCredential()
120 {
121     using namespace LocalAuthenticatorInternal;
122     ASSERT(m_state == State::Init);
123     m_state = State::RequestReceived;
124     auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options);
125
126     // The following implements https://www.w3.org/TR/webauthn/#op-make-cred as of 5 December 2017.
127     // Skip Step 4-5 as requireResidentKey and requireUserVerification are enforced.
128     // Skip Step 9 as extensions are not supported yet.
129     // Step 8 is implicitly captured by all UnknownError exception receiveResponds.
130     // Step 2.
131     if (notFound == creationOptions.pubKeyCredParams.findMatching([] (auto& pubKeyCredParam) {
132         return pubKeyCredParam.type == PublicKeyCredentialType::PublicKey && pubKeyCredParam.alg == COSE::ES256;
133     })) {
134         receiveException({ NotSupportedError, "The platform attached authenticator doesn't support any provided PublicKeyCredentialParameters."_s });
135         return;
136     }
137
138     // Step 3.
139     auto excludeCredentialIds = produceHashSet(creationOptions.excludeCredentials);
140     if (!excludeCredentialIds.isEmpty()) {
141         // Search Keychain for the RP ID.
142         NSDictionary *query = @{
143             (id)kSecClass: (id)kSecClassKey,
144             (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
145             (id)kSecAttrLabel: creationOptions.rp.id,
146             (id)kSecReturnAttributes: @YES,
147             (id)kSecMatchLimit: (id)kSecMatchLimitAll,
148 #if HAVE(DATA_PROTECTION_KEYCHAIN)
149             (id)kSecUseDataProtectionKeychain: @YES
150 #else
151             (id)kSecAttrNoLegacy: @YES
152 #endif
153         };
154         CFTypeRef attributesArrayRef = nullptr;
155         OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef);
156         if (status && status != errSecItemNotFound) {
157             receiveException({ UnknownError, makeString("Couldn't query Keychain: ", status) });
158             return;
159         }
160         auto retainAttributesArray = adoptCF(attributesArrayRef);
161
162         // FIXME: Need to obtain user consent and then return different error according to the result.
163         for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) {
164             NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel];
165             if (excludeCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length))) {
166                 receiveException({ NotAllowedError, "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator."_s }, WebAuthenticationStatus::LAExcludeCredentialsMatched);
167                 return;
168             }
169         }
170     }
171
172     // Step 6.
173     // Get user consent.
174     if (auto* observer = this->observer()) {
175         auto callback = [weakThis = makeWeakPtr(*this)] (LocalAuthenticatorPolicy policy) {
176             ASSERT(RunLoop::isMain());
177             if (!weakThis)
178                 return;
179
180             weakThis->continueMakeCredentialAfterDecidePolicy(policy);
181         };
182         observer->decidePolicyForLocalAuthenticator(WTFMove(callback));
183     }
184 }
185
186 void LocalAuthenticator::continueMakeCredentialAfterDecidePolicy(LocalAuthenticatorPolicy policy)
187 {
188     ASSERT(m_state == State::RequestReceived);
189     m_state = State::PolicyDecided;
190
191     if (policy == LocalAuthenticatorPolicy::Disallow) {
192         receiveRespond(ExceptionData { UnknownError, "Disallow local authenticator."_s });
193         return;
194     }
195
196     RetainPtr<SecAccessControlRef> accessControl;
197     {
198         CFErrorRef errorRef = nullptr;
199         accessControl = adoptCF(SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence, &errorRef));
200         auto retainError = adoptCF(errorRef);
201         if (errorRef) {
202             receiveException({ UnknownError, makeString("Couldn't create access control: ", String(((NSError*)errorRef).localizedDescription)) });
203             return;
204         }
205     }
206
207     SecAccessControlRef accessControlRef = accessControl.get();
208     auto callback = [accessControl = WTFMove(accessControl), weakThis = makeWeakPtr(*this)] (LocalConnection::UserVerification verification, LAContext *context) {
209         ASSERT(RunLoop::isMain());
210         if (!weakThis)
211             return;
212
213         weakThis->continueMakeCredentialAfterUserVerification(accessControl.get(), verification, context);
214     };
215     m_connection->verifyUser(accessControlRef, WTFMove(callback));
216 }
217
218 void LocalAuthenticator::continueMakeCredentialAfterUserVerification(SecAccessControlRef accessControlRef, LocalConnection::UserVerification verification, LAContext *context)
219 {
220     using namespace LocalAuthenticatorInternal;
221
222     ASSERT(m_state == State::PolicyDecided);
223     m_state = State::UserVerified;
224     auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options);
225
226     if (verification == LocalConnection::UserVerification::No) {
227         receiveException({ NotAllowedError, "Couldn't verify user."_s });
228         return;
229     }
230
231     // FIXME(183533): A single kSecClassKey item couldn't store all meta data. The following schema is a tentative solution
232     // to accommodate the most important meta data, i.e. RP ID, Credential ID, and userhandle.
233     // kSecAttrLabel: RP ID
234     // kSecAttrApplicationLabel: Credential ID (auto-gen by Keychain)
235     // kSecAttrApplicationTag: userhandle
236     // Noted, the vale of kSecAttrApplicationLabel is automatically generated by the Keychain, which is a SHA-1 hash of
237     // the public key. We borrow it directly for now to workaround the stated limitations.
238     const auto& secAttrLabel = creationOptions.rp.id;
239     auto secAttrApplicationTag = toNSData(creationOptions.user.idVector);
240
241     // Step 7.5.
242     // Failures after this point could block users' accounts forever. Should we follow the spec?
243     NSDictionary* deleteQuery = @{
244         (id)kSecClass: (id)kSecClassKey,
245         (id)kSecAttrLabel: secAttrLabel,
246         (id)kSecAttrApplicationTag: secAttrApplicationTag.get(),
247 #if HAVE(DATA_PROTECTION_KEYCHAIN)
248         (id)kSecUseDataProtectionKeychain: @YES
249 #else
250         (id)kSecAttrNoLegacy: @YES
251 #endif
252     };
253     OSStatus status = SecItemDelete((__bridge CFDictionaryRef)deleteQuery);
254     if (status && status != errSecItemNotFound) {
255         receiveException({ UnknownError, makeString("Couldn't delete older credential: ", status) });
256         return;
257     }
258
259     // Step 7.1-7.4.
260     // The above-to-create private key will be inserted into keychain while using SEP.
261     auto privateKey = m_connection->createCredentialPrivateKey(context, accessControlRef, secAttrLabel, secAttrApplicationTag.get());
262     if (!privateKey) {
263         receiveException({ UnknownError, "Couldn't create private key."_s });
264         return;
265     }
266
267     Vector<uint8_t> credentialId;
268     {
269         NSDictionary *credentialIdQuery = @{
270             (id)kSecClass: (id)kSecClassKey,
271             (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
272             (id)kSecAttrLabel: secAttrLabel,
273             (id)kSecAttrApplicationTag: secAttrApplicationTag.get(),
274             (id)kSecReturnAttributes: @YES,
275 #if HAVE(DATA_PROTECTION_KEYCHAIN)
276             (id)kSecUseDataProtectionKeychain: @YES
277 #else
278             (id)kSecAttrNoLegacy: @YES
279 #endif
280         };
281         CFTypeRef attributesRef = nullptr;
282         OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)credentialIdQuery, &attributesRef);
283         if (status) {
284             receiveException({ UnknownError, makeString("Couldn't get Credential ID: ", status) });
285             return;
286         }
287         auto retainAttributes = adoptCF(attributesRef);
288
289         NSDictionary *nsAttributes = (NSDictionary *)attributesRef;
290         credentialId = toVector(nsAttributes[(id)kSecAttrApplicationLabel]);
291     }
292
293     // Step 10.
294     // FIXME(183533): store the counter.
295     uint32_t counter = 0;
296
297     // Step 11. https://www.w3.org/TR/webauthn/#attested-credential-data
298     // credentialPublicKey
299     Vector<uint8_t> cosePublicKey;
300     {
301         RetainPtr<CFDataRef> publicKeyDataRef;
302         {
303             auto publicKey = adoptCF(SecKeyCopyPublicKey(privateKey.get()));
304             CFErrorRef errorRef = nullptr;
305             publicKeyDataRef = adoptCF(SecKeyCopyExternalRepresentation(publicKey.get(), &errorRef));
306             auto retainError = adoptCF(errorRef);
307             if (errorRef) {
308                 receiveException({ UnknownError, makeString("Couldn't export the public key: ", String(((NSError*)errorRef).localizedDescription)) });
309                 return;
310             }
311             ASSERT(((NSData *)publicKeyDataRef.get()).length == (1 + 2 * ES256FieldElementLength)); // 04 | X | Y
312         }
313
314         // COSE Encoding
315         Vector<uint8_t> x(ES256FieldElementLength);
316         [(NSData *)publicKeyDataRef.get() getBytes: x.data() range:NSMakeRange(1, ES256FieldElementLength)];
317         Vector<uint8_t> y(ES256FieldElementLength);
318         [(NSData *)publicKeyDataRef.get() getBytes: y.data() range:NSMakeRange(1 + ES256FieldElementLength, ES256FieldElementLength)];
319         cosePublicKey = encodeES256PublicKeyAsCBOR(WTFMove(x), WTFMove(y));
320     }
321     // FIXME(rdar://problem/38320512): Define Apple AAGUID.
322     auto attestedCredentialData = buildAttestedCredentialData(Vector<uint8_t>(aaguidLength, 0), credentialId, cosePublicKey);
323
324     // Step 12.
325     auto authData = buildAuthData(creationOptions.rp.id, makeCredentialFlags, counter, attestedCredentialData);
326
327     // Skip Apple Attestation for none attestation, and non whitelisted RP ID for now.
328     if (creationOptions.attestation == AttestationConveyancePreference::None || !whitelistedRpId().contains(creationOptions.rp.id)) {
329         auto attestationObject = buildAttestationObject(WTFMove(authData), "", { }, AttestationConveyancePreference::None);
330         receiveRespond(AuthenticatorAttestationResponse::create(credentialId, attestationObject));
331         return;
332     }
333
334     // Step 13. Apple Attestation
335     auto nsAuthData = toNSData(authData);
336     auto callback = [credentialId = WTFMove(credentialId), authData = WTFMove(authData), weakThis = makeWeakPtr(*this)] (NSArray * _Nullable certificates, NSError * _Nullable error) mutable {
337         ASSERT(RunLoop::isMain());
338         if (!weakThis)
339             return;
340         weakThis->continueMakeCredentialAfterAttested(WTFMove(credentialId), WTFMove(authData), certificates, error);
341     };
342     m_connection->getAttestation(privateKey.get(), nsAuthData.get(), toNSData(requestData().hash).get(), WTFMove(callback));
343 }
344
345 void LocalAuthenticator::continueMakeCredentialAfterAttested(Vector<uint8_t>&& credentialId, Vector<uint8_t>&& authData, NSArray *certificates, NSError *error)
346 {
347     using namespace LocalAuthenticatorInternal;
348
349     ASSERT(m_state == State::UserVerified);
350     m_state = State::Attested;
351     auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options);
352
353     if (error) {
354         receiveException({ UnknownError, makeString("Couldn't attest: ", String(error.localizedDescription)) });
355         return;
356     }
357     // Attestation Certificate and Attestation Issuing CA
358     ASSERT(certificates && ([certificates count] == 2));
359
360     // Step 13. Apple Attestation Cont'
361     // Assemble the attestation object:
362     // https://www.w3.org/TR/webauthn/#attestation-object
363     cbor::CBORValue::MapValue attestationStatementMap;
364     {
365         attestationStatementMap[cbor::CBORValue("alg")] = cbor::CBORValue(COSE::ES256);
366         Vector<cbor::CBORValue> cborArray;
367         for (size_t i = 0; i < [certificates count]; i++)
368             cborArray.append(cbor::CBORValue(toVector((NSData *)adoptCF(SecCertificateCopyData((__bridge SecCertificateRef)certificates[i])).get())));
369         attestationStatementMap[cbor::CBORValue("x5c")] = cbor::CBORValue(WTFMove(cborArray));
370     }
371     auto attestationObject = buildAttestationObject(WTFMove(authData), "apple", WTFMove(attestationStatementMap), creationOptions.attestation);
372
373     receiveRespond(AuthenticatorAttestationResponse::create(credentialId, attestationObject));
374 }
375
376 void LocalAuthenticator::getAssertion()
377 {
378     using namespace LocalAuthenticatorInternal;
379     ASSERT(m_state == State::Init);
380     m_state = State::RequestReceived;
381     auto& requestOptions = WTF::get<PublicKeyCredentialRequestOptions>(requestData().options);
382
383     // The following implements https://www.w3.org/TR/webauthn/#op-get-assertion as of 5 December 2017.
384     // Skip Step 2 as requireUserVerification is enforced.
385     // Skip Step 8 as extensions are not supported yet.
386     // Step 12 is implicitly captured by all UnknownError exception callbacks.
387     // Step 3-5. Unlike the spec, if an allow list is provided and there is no intersection between existing ones and the allow list, we always return NotAllowedError.
388     auto allowCredentialIds = produceHashSet(requestOptions.allowCredentials);
389     if (!requestOptions.allowCredentials.isEmpty() && allowCredentialIds.isEmpty()) {
390         receiveException({ NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s }, WebAuthenticationStatus::LANoCredential);
391         return;
392     }
393
394     // Search Keychain for the RP ID.
395     NSDictionary *query = @{
396         (id)kSecClass: (id)kSecClassKey,
397         (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
398         (id)kSecAttrLabel: requestOptions.rpId,
399         (id)kSecReturnAttributes: @YES,
400         (id)kSecMatchLimit: (id)kSecMatchLimitAll,
401 #if HAVE(DATA_PROTECTION_KEYCHAIN)
402         (id)kSecUseDataProtectionKeychain: @YES
403 #else
404         (id)kSecAttrNoLegacy: @YES
405 #endif
406     };
407     CFTypeRef attributesArrayRef = nullptr;
408     OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef);
409     if (status && status != errSecItemNotFound) {
410         receiveException({ UnknownError, makeString("Couldn't query Keychain: ", status) });
411         return;
412     }
413     auto retainAttributesArray = adoptCF(attributesArrayRef);
414
415     NSArray *intersectedCredentialsAttributes = nil;
416     if (requestOptions.allowCredentials.isEmpty())
417         intersectedCredentialsAttributes = (NSArray *)attributesArrayRef;
418     else {
419         NSMutableArray *result = [NSMutableArray arrayWithCapacity:allowCredentialIds.size()];
420         for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) {
421             NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel];
422             if (allowCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length)))
423                 [result addObject:nsAttributes];
424         }
425         intersectedCredentialsAttributes = result;
426     }
427     if (!intersectedCredentialsAttributes.count) {
428         receiveException({ NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s }, WebAuthenticationStatus::LANoCredential);
429         return;
430     }
431
432     // Step 6-7. User consent is implicitly acquired by selecting responses.
433     for (NSDictionary *attribute : intersectedCredentialsAttributes) {
434         auto addResult = m_assertionResponses.add(AuthenticatorAssertionResponse::create(
435             toArrayBuffer(attribute[(id)kSecAttrApplicationLabel]),
436             toArrayBuffer(attribute[(id)kSecAttrApplicationTag]),
437             (__bridge SecAccessControlRef)attribute[(id)kSecAttrAccessControl]));
438         ASSERT_UNUSED(addResult, addResult.isNewEntry);
439     }
440     m_connection->filterResponses(m_assertionResponses);
441
442     if (auto* observer = this->observer()) {
443         auto callback = [this, weakThis = makeWeakPtr(*this)] (AuthenticatorAssertionResponse* response) {
444             ASSERT(RunLoop::isMain());
445             if (!weakThis)
446                 return;
447
448             auto returnResponse = m_assertionResponses.take(response);
449             if (!returnResponse)
450                 return;
451             continueGetAssertionAfterResponseSelected(WTFMove(*returnResponse));
452         };
453         observer->selectAssertionResponse(m_assertionResponses, WebAuthenticationSource::Local, WTFMove(callback));
454     }
455 }
456
457 void LocalAuthenticator::continueGetAssertionAfterResponseSelected(Ref<WebCore::AuthenticatorAssertionResponse>&& response)
458 {
459     ASSERT(m_state == State::RequestReceived);
460     m_state = State::ResponseSelected;
461
462     auto accessControlRef = response->accessControl();
463     auto callback = [
464         weakThis = makeWeakPtr(*this),
465         response = WTFMove(response)
466     ] (LocalConnection::UserVerification verification, LAContext *context) mutable {
467         ASSERT(RunLoop::isMain());
468         if (!weakThis)
469             return;
470
471         weakThis->continueGetAssertionAfterUserVerification(WTFMove(response), verification, context);
472     };
473     m_connection->verifyUser(accessControlRef, WTFMove(callback));
474 }
475
476 void LocalAuthenticator::continueGetAssertionAfterUserVerification(Ref<WebCore::AuthenticatorAssertionResponse>&& response, LocalConnection::UserVerification verification, LAContext *context)
477 {
478     using namespace LocalAuthenticatorInternal;
479     ASSERT(m_state == State::ResponseSelected);
480     m_state = State::UserVerified;
481
482     if (verification == LocalConnection::UserVerification::No) {
483         receiveException({ NotAllowedError, "Couldn't verify user."_s });
484         return;
485     }
486
487     // Step 9-10.
488     // FIXME(183533): Due to the stated Keychain limitations, we can't save the counter value.
489     // Therefore, it is always zero.
490     uint32_t counter = 0;
491     auto authData = buildAuthData(WTF::get<PublicKeyCredentialRequestOptions>(requestData().options).rpId, getAssertionFlags, counter, { });
492
493     // Step 11.
494     RetainPtr<CFDataRef> signature;
495     {
496         NSDictionary *query = @{
497             (id)kSecClass: (id)kSecClassKey,
498             (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
499             (id)kSecAttrApplicationLabel: toNSData(response->rawId()).get(),
500             (id)kSecUseAuthenticationContext: context,
501             (id)kSecReturnRef: @YES,
502 #if HAVE(DATA_PROTECTION_KEYCHAIN)
503             (id)kSecUseDataProtectionKeychain: @YES
504 #else
505             (id)kSecAttrNoLegacy: @YES
506 #endif
507         };
508         CFTypeRef privateKeyRef = nullptr;
509         OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &privateKeyRef);
510         if (status) {
511             receiveException({ UnknownError, makeString("Couldn't get the private key reference: ", status) });
512             return;
513         }
514         auto privateKey = adoptCF(privateKeyRef);
515
516         NSMutableData *dataToSign = [NSMutableData dataWithBytes:authData.data() length:authData.size()];
517         [dataToSign appendBytes:requestData().hash.data() length:requestData().hash.size()];
518
519         CFErrorRef errorRef = nullptr;
520         // FIXME: Converting CFTypeRef to SecKeyRef is quite subtle here.
521         signature = adoptCF(SecKeyCreateSignature((__bridge SecKeyRef)((id)privateKeyRef), kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)dataToSign, &errorRef));
522         auto retainError = adoptCF(errorRef);
523         if (errorRef) {
524             receiveException({ UnknownError, makeString("Couldn't generate the signature: ", String(((NSError*)errorRef).localizedDescription)) });
525             return;
526         }
527     }
528
529     // Step 13.
530     response->setAuthenticatorData(WTFMove(authData));
531     response->setSignature(toArrayBuffer((NSData *)signature.get()));
532     receiveRespond(WTFMove(response));
533 }
534
535 void LocalAuthenticator::receiveException(ExceptionData&& exception, WebAuthenticationStatus status) const
536 {
537     LOG_ERROR(exception.message.utf8().data());
538     if (auto* observer = this->observer())
539         observer->authenticatorStatusUpdated(status);
540     receiveRespond(WTFMove(exception));
541     return;
542 }
543
544 } // namespace WebKit
545
546 #endif // ENABLE(WEB_AUTHN)