[WebAuthn] Formalize the Keychain schema
[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/CBORReader.h>
35 #import <WebCore/CBORWriter.h>
36 #import <WebCore/ExceptionData.h>
37 #import <WebCore/PublicKeyCredentialCreationOptions.h>
38 #import <WebCore/PublicKeyCredentialRequestOptions.h>
39 #import <WebCore/WebAuthenticationConstants.h>
40 #import <WebCore/WebAuthenticationUtils.h>
41 #import <pal/crypto/CryptoDigest.h>
42 #import <wtf/HashSet.h>
43 #import <wtf/RetainPtr.h>
44 #import <wtf/RunLoop.h>
45 #import <wtf/Vector.h>
46 #import <wtf/spi/cocoa/SecuritySPI.h>
47 #import <wtf/text/StringHash.h>
48
49 namespace WebKit {
50 using namespace WebCore;
51 using CBOR = cbor::CBORValue;
52
53 namespace LocalAuthenticatorInternal {
54
55 // See https://www.w3.org/TR/webauthn/#flags.
56 const uint8_t makeCredentialFlags = 0b01000101; // UP, UV and AT are set.
57 const uint8_t getAssertionFlags = 0b00000101; // UP and UV are set.
58 // Credential ID is currently SHA-1 of the corresponding public key.
59 const uint16_t credentialIdLength = 20;
60 const char* const userEntityIdKey = "id";
61 const char* const userEntityNameKey = "name";
62 const uint64_t counter = 0;
63
64 static inline bool emptyTransportsOrContain(const Vector<AuthenticatorTransport>& transports, AuthenticatorTransport target)
65 {
66     return transports.isEmpty() ? true : transports.contains(target);
67 }
68
69 // FIXME(183534): Find a better way of comparing credential id. Doing it with array seems fine given the list should be small.
70 static inline HashSet<String> produceHashSet(const Vector<PublicKeyCredentialDescriptor>& credentialDescriptors)
71 {
72     HashSet<String> result;
73     for (auto& credentialDescriptor : credentialDescriptors) {
74         if (emptyTransportsOrContain(credentialDescriptor.transports, AuthenticatorTransport::Internal)
75             && credentialDescriptor.type == PublicKeyCredentialType::PublicKey
76             && credentialDescriptor.idVector.size() == credentialIdLength)
77             result.add(String(reinterpret_cast<const char*>(credentialDescriptor.idVector.data()), credentialDescriptor.idVector.size()));
78     }
79     return result;
80 }
81
82 static inline Vector<uint8_t> toVector(NSData *data)
83 {
84     Vector<uint8_t> result;
85     result.append(reinterpret_cast<const uint8_t*>(data.bytes), data.length);
86     return result;
87 }
88
89 static inline RetainPtr<NSData> toNSData(const Vector<uint8_t>& data)
90 {
91     // FIXME(183534): Consider using initWithBytesNoCopy.
92     return adoptNS([[NSData alloc] initWithBytes:data.data() length:data.size()]);
93 }
94
95 static inline RetainPtr<NSData> toNSData(ArrayBuffer* buffer)
96 {
97     ASSERT(buffer);
98     // FIXME(183534): Consider using initWithBytesNoCopy.
99     return adoptNS([[NSData alloc] initWithBytes:buffer->data() length:buffer->byteLength()]);
100 }
101
102 static inline Ref<ArrayBuffer> toArrayBuffer(NSData *data)
103 {
104     return ArrayBuffer::create(reinterpret_cast<const uint8_t*>(data.bytes), data.length);
105 }
106
107 static inline Ref<ArrayBuffer> toArrayBuffer(const Vector<uint8_t>& data)
108 {
109     return ArrayBuffer::create(data.data(), data.size());
110 }
111
112 // FIXME(<rdar://problem/60108131>): Remove this whitelist once testing is complete.
113 static const HashSet<String>& whitelistedRpId()
114 {
115     static NeverDestroyed<HashSet<String>> whitelistedRpId = std::initializer_list<String> {
116         "",
117         "localhost",
118         "tlstestwebkit.org",
119     };
120     return whitelistedRpId;
121 }
122
123 static Optional<Vector<Ref<AuthenticatorAssertionResponse>>> getExistingCredentials(const String& rpId)
124 {
125     // Search Keychain for existing credential matched the RP ID.
126     NSDictionary *query = @{
127         (id)kSecClass: (id)kSecClassKey,
128         (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
129         (id)kSecAttrLabel: rpId,
130         (id)kSecReturnAttributes: @YES,
131         (id)kSecMatchLimit: (id)kSecMatchLimitAll,
132 #if HAVE(DATA_PROTECTION_KEYCHAIN)
133         (id)kSecUseDataProtectionKeychain: @YES
134 #else
135         (id)kSecAttrNoLegacy: @YES
136 #endif
137     };
138     CFTypeRef attributesArrayRef = nullptr;
139     OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef);
140     if (status && status != errSecItemNotFound)
141         return WTF::nullopt;
142     auto retainAttributesArray = adoptCF(attributesArrayRef);
143     NSArray *nsAttributesArray = (NSArray *)attributesArrayRef;
144
145     Vector<Ref<AuthenticatorAssertionResponse>> result;
146     result.reserveInitialCapacity(nsAttributesArray.count);
147     for (NSDictionary *attributes in nsAttributesArray) {
148         auto decodedResponse = cbor::CBORReader::read(toVector(attributes[(id)kSecAttrApplicationTag]));
149         if (!decodedResponse || !decodedResponse->isMap()) {
150             ASSERT_NOT_REACHED();
151             return WTF::nullopt;
152         }
153         auto& responseMap = decodedResponse->getMap();
154
155         auto it = responseMap.find(CBOR(userEntityIdKey));
156         if (it == responseMap.end() || !it->second.isByteString()) {
157             ASSERT_NOT_REACHED();
158             return WTF::nullopt;
159         }
160         auto& userHandle = it->second.getByteString();
161
162         it = responseMap.find(CBOR(userEntityNameKey));
163         if (it == responseMap.end() || !it->second.isString()) {
164             ASSERT_NOT_REACHED();
165             return WTF::nullopt;
166         }
167         auto& username = it->second.getString();
168
169         result.uncheckedAppend(AuthenticatorAssertionResponse::create(toArrayBuffer(attributes[(id)kSecAttrApplicationLabel]), toArrayBuffer(userHandle), String(username), (__bridge SecAccessControlRef)attributes[(id)kSecAttrAccessControl]));
170     }
171     return result;
172 }
173
174 } // LocalAuthenticatorInternal
175
176 LocalAuthenticator::LocalAuthenticator(UniqueRef<LocalConnection>&& connection)
177     : m_connection(WTFMove(connection))
178 {
179 }
180
181 void LocalAuthenticator::makeCredential()
182 {
183     using namespace LocalAuthenticatorInternal;
184     ASSERT(m_state == State::Init);
185     m_state = State::RequestReceived;
186     auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options);
187
188     // The following implements https://www.w3.org/TR/webauthn/#op-make-cred as of 5 December 2017.
189     // Skip Step 4-5 as requireResidentKey and requireUserVerification are enforced.
190     // Skip Step 9 as extensions are not supported yet.
191     // Step 8 is implicitly captured by all UnknownError exception receiveResponds.
192     // Skip Step 10 as counter is constantly 0.
193     // Step 2.
194     if (notFound == creationOptions.pubKeyCredParams.findMatching([] (auto& pubKeyCredParam) {
195         return pubKeyCredParam.type == PublicKeyCredentialType::PublicKey && pubKeyCredParam.alg == COSE::ES256;
196     })) {
197         receiveException({ NotSupportedError, "The platform attached authenticator doesn't support any provided PublicKeyCredentialParameters."_s });
198         return;
199     }
200
201     // Step 3.
202     auto existingCredentials = getExistingCredentials(creationOptions.rp.id);
203     if (!existingCredentials) {
204         receiveException({ UnknownError, makeString("Couldn't get existing credentials") });
205         return;
206     }
207     m_existingCredentials = WTFMove(*existingCredentials);
208
209     auto excludeCredentialIds = produceHashSet(creationOptions.excludeCredentials);
210     if (!excludeCredentialIds.isEmpty()) {
211         if (notFound != m_existingCredentials.findMatching([&excludeCredentialIds] (auto& credential) {
212             auto* rawId = credential->rawId();
213             ASSERT(rawId);
214             return excludeCredentialIds.contains(String(reinterpret_cast<const char*>(rawId->data()), rawId->byteLength()));
215         })) {
216             receiveException({ NotAllowedError, "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator."_s }, WebAuthenticationStatus::LAExcludeCredentialsMatched);
217             return;
218         }
219     }
220
221     // Step 6.
222     // Get user consent.
223     if (auto* observer = this->observer()) {
224         auto callback = [weakThis = makeWeakPtr(*this)] (LocalAuthenticatorPolicy policy) {
225             ASSERT(RunLoop::isMain());
226             if (!weakThis)
227                 return;
228
229             weakThis->continueMakeCredentialAfterDecidePolicy(policy);
230         };
231         observer->decidePolicyForLocalAuthenticator(WTFMove(callback));
232     }
233 }
234
235 void LocalAuthenticator::continueMakeCredentialAfterDecidePolicy(LocalAuthenticatorPolicy policy)
236 {
237     ASSERT(m_state == State::RequestReceived);
238     m_state = State::PolicyDecided;
239
240     if (policy == LocalAuthenticatorPolicy::Disallow) {
241         receiveRespond(ExceptionData { UnknownError, "Disallow local authenticator."_s });
242         return;
243     }
244
245     RetainPtr<SecAccessControlRef> accessControl;
246     {
247         CFErrorRef errorRef = nullptr;
248         accessControl = adoptCF(SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence, &errorRef));
249         auto retainError = adoptCF(errorRef);
250         if (errorRef) {
251             receiveException({ UnknownError, makeString("Couldn't create access control: ", String(((NSError*)errorRef).localizedDescription)) });
252             return;
253         }
254     }
255
256     SecAccessControlRef accessControlRef = accessControl.get();
257     auto callback = [accessControl = WTFMove(accessControl), weakThis = makeWeakPtr(*this)] (LocalConnection::UserVerification verification, LAContext *context) {
258         ASSERT(RunLoop::isMain());
259         if (!weakThis)
260             return;
261
262         weakThis->continueMakeCredentialAfterUserVerification(accessControl.get(), verification, context);
263     };
264     m_connection->verifyUser(accessControlRef, WTFMove(callback));
265 }
266
267 void LocalAuthenticator::continueMakeCredentialAfterUserVerification(SecAccessControlRef accessControlRef, LocalConnection::UserVerification verification, LAContext *context)
268 {
269     using namespace LocalAuthenticatorInternal;
270
271     ASSERT(m_state == State::PolicyDecided);
272     m_state = State::UserVerified;
273     auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options);
274
275     if (verification == LocalConnection::UserVerification::No) {
276         receiveException({ NotAllowedError, "Couldn't verify user."_s });
277         return;
278     }
279
280     // Here is the keychain schema.
281     // kSecAttrLabel: RP ID
282     // kSecAttrApplicationLabel: Credential ID (auto-gen by Keychain)
283     // kSecAttrApplicationTag: { "id": UserEntity.id, "name": UserEntity.name } (CBOR encoded)
284     // Noted, the vale of kSecAttrApplicationLabel is automatically generated by the Keychain, which is a SHA-1 hash of
285     // the public key.
286     const auto& secAttrLabel = creationOptions.rp.id;
287
288     cbor::CBORValue::MapValue userEntityMap;
289     userEntityMap[cbor::CBORValue(userEntityIdKey)] = cbor::CBORValue(creationOptions.user.idVector);
290     userEntityMap[cbor::CBORValue(userEntityNameKey)] = cbor::CBORValue(creationOptions.user.name);
291     auto userEntity = cbor::CBORWriter::write(cbor::CBORValue(WTFMove(userEntityMap)));
292     ASSERT(userEntity);
293     auto secAttrApplicationTag = toNSData(*userEntity);
294
295     // Step 7.
296     // The above-to-create private key will be inserted into keychain while using SEP.
297     auto privateKey = m_connection->createCredentialPrivateKey(context, accessControlRef, secAttrLabel, secAttrApplicationTag.get());
298     if (!privateKey) {
299         receiveException({ UnknownError, "Couldn't create private key."_s });
300         return;
301     }
302
303     RetainPtr<CFDataRef> publicKeyDataRef;
304     {
305         auto publicKey = adoptCF(SecKeyCopyPublicKey(privateKey.get()));
306         CFErrorRef errorRef = nullptr;
307         publicKeyDataRef = adoptCF(SecKeyCopyExternalRepresentation(publicKey.get(), &errorRef));
308         auto retainError = adoptCF(errorRef);
309         if (errorRef) {
310             receiveException({ UnknownError, makeString("Couldn't export the public key: ", String(((NSError*)errorRef).localizedDescription)) });
311             return;
312         }
313         ASSERT(((NSData *)publicKeyDataRef.get()).length == (1 + 2 * ES256FieldElementLength)); // 04 | X | Y
314     }
315     NSData *nsPublicKeyData = (NSData *)publicKeyDataRef.get();
316
317     // Query credentialId in the keychain could be racy as it is the only unique identifier
318     // of the key item. Instead we calculate that, and examine its equaity in DEBUG build.
319     Vector<uint8_t> credentialId;
320     {
321         auto digest = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_1);
322         digest->addBytes(nsPublicKeyData.bytes, nsPublicKeyData.length);
323         credentialId = digest->computeHash();
324
325 #ifndef NDEBUG
326         NSDictionary *credentialIdQuery = @{
327             (id)kSecClass: (id)kSecClassKey,
328             (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
329             (id)kSecAttrLabel: secAttrLabel,
330             (id)kSecAttrApplicationLabel: toNSData(credentialId).get(),
331 #if HAVE(DATA_PROTECTION_KEYCHAIN)
332             (id)kSecUseDataProtectionKeychain: @YES
333 #else
334             (id)kSecAttrNoLegacy: @YES
335 #endif
336         };
337         OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)credentialIdQuery, nullptr);
338         ASSERT(!status);
339 #endif // NDEBUG
340     }
341
342     // Step 11. https://www.w3.org/TR/webauthn/#attested-credential-data
343     // credentialPublicKey
344     Vector<uint8_t> cosePublicKey;
345     {
346         // COSE Encoding
347         Vector<uint8_t> x(ES256FieldElementLength);
348         [nsPublicKeyData getBytes: x.data() range:NSMakeRange(1, ES256FieldElementLength)];
349         Vector<uint8_t> y(ES256FieldElementLength);
350         [nsPublicKeyData getBytes: y.data() range:NSMakeRange(1 + ES256FieldElementLength, ES256FieldElementLength)];
351         cosePublicKey = encodeES256PublicKeyAsCBOR(WTFMove(x), WTFMove(y));
352     }
353     // FIXME(rdar://problem/38320512): Define Apple AAGUID.
354     auto attestedCredentialData = buildAttestedCredentialData(Vector<uint8_t>(aaguidLength, 0), credentialId, cosePublicKey);
355
356     // Step 12.
357     auto authData = buildAuthData(creationOptions.rp.id, makeCredentialFlags, counter, attestedCredentialData);
358
359     // Skip Apple Attestation for none attestation, and non whitelisted RP ID for now.
360     if (creationOptions.attestation == AttestationConveyancePreference::None || !whitelistedRpId().contains(creationOptions.rp.id)) {
361         deleteDuplicateCredential();
362
363         auto attestationObject = buildAttestationObject(WTFMove(authData), "", { }, AttestationConveyancePreference::None);
364         receiveRespond(AuthenticatorAttestationResponse::create(credentialId, attestationObject));
365         return;
366     }
367
368     // Step 13. Apple Attestation
369     auto nsAuthData = toNSData(authData);
370     auto callback = [credentialId = WTFMove(credentialId), authData = WTFMove(authData), weakThis = makeWeakPtr(*this)] (NSArray * _Nullable certificates, NSError * _Nullable error) mutable {
371         ASSERT(RunLoop::isMain());
372         if (!weakThis)
373             return;
374         weakThis->continueMakeCredentialAfterAttested(WTFMove(credentialId), WTFMove(authData), certificates, error);
375     };
376     m_connection->getAttestation(privateKey.get(), nsAuthData.get(), toNSData(requestData().hash).get(), WTFMove(callback));
377 }
378
379 void LocalAuthenticator::continueMakeCredentialAfterAttested(Vector<uint8_t>&& credentialId, Vector<uint8_t>&& authData, NSArray *certificates, NSError *error)
380 {
381     using namespace LocalAuthenticatorInternal;
382
383     ASSERT(m_state == State::UserVerified);
384     m_state = State::Attested;
385     auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options);
386
387     if (error) {
388         receiveException({ UnknownError, makeString("Couldn't attest: ", String(error.localizedDescription)) });
389         return;
390     }
391     // Attestation Certificate and Attestation Issuing CA
392     ASSERT(certificates && ([certificates count] == 2));
393
394     // Step 13. Apple Attestation Cont'
395     // Assemble the attestation object:
396     // https://www.w3.org/TR/webauthn/#attestation-object
397     cbor::CBORValue::MapValue attestationStatementMap;
398     {
399         attestationStatementMap[cbor::CBORValue("alg")] = cbor::CBORValue(COSE::ES256);
400         Vector<cbor::CBORValue> cborArray;
401         for (size_t i = 0; i < [certificates count]; i++)
402             cborArray.append(cbor::CBORValue(toVector((NSData *)adoptCF(SecCertificateCopyData((__bridge SecCertificateRef)certificates[i])).get())));
403         attestationStatementMap[cbor::CBORValue("x5c")] = cbor::CBORValue(WTFMove(cborArray));
404     }
405     auto attestationObject = buildAttestationObject(WTFMove(authData), "apple", WTFMove(attestationStatementMap), creationOptions.attestation);
406
407     deleteDuplicateCredential();
408     receiveRespond(AuthenticatorAttestationResponse::create(credentialId, attestationObject));
409 }
410
411 void LocalAuthenticator::getAssertion()
412 {
413     using namespace LocalAuthenticatorInternal;
414     ASSERT(m_state == State::Init);
415     m_state = State::RequestReceived;
416     auto& requestOptions = WTF::get<PublicKeyCredentialRequestOptions>(requestData().options);
417
418     // The following implements https://www.w3.org/TR/webauthn/#op-get-assertion as of 5 December 2017.
419     // Skip Step 2 as requireUserVerification is enforced.
420     // Skip Step 8 as extensions are not supported yet.
421     // Skip Step 9 as counter is constantly 0.
422     // Step 12 is implicitly captured by all UnknownError exception callbacks.
423     // 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.
424     auto allowCredentialIds = produceHashSet(requestOptions.allowCredentials);
425     if (!requestOptions.allowCredentials.isEmpty() && allowCredentialIds.isEmpty()) {
426         receiveException({ NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s }, WebAuthenticationStatus::LANoCredential);
427         return;
428     }
429
430     // Search Keychain for the RP ID.
431     auto existingCredentials = getExistingCredentials(requestOptions.rpId);
432     if (!existingCredentials) {
433         receiveException({ UnknownError, makeString("Couldn't get existing credentials") });
434         return;
435     }
436     m_existingCredentials = WTFMove(*existingCredentials);
437
438     for (auto& credential : m_existingCredentials) {
439         if (allowCredentialIds.isEmpty()) {
440             auto addResult = m_assertionResponses.add(credential.copyRef());
441             ASSERT_UNUSED(addResult, addResult.isNewEntry);
442             continue;
443         }
444
445         auto* rawId = credential->rawId();
446         if (allowCredentialIds.contains(String(reinterpret_cast<const char*>(rawId->data()), rawId->byteLength()))) {
447             auto addResult = m_assertionResponses.add(credential.copyRef());
448             ASSERT_UNUSED(addResult, addResult.isNewEntry);
449         }
450     }
451     if (m_assertionResponses.isEmpty()) {
452         receiveException({ NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s }, WebAuthenticationStatus::LANoCredential);
453         return;
454     }
455
456     // Step 6-7. User consent is implicitly acquired by selecting responses.
457     m_connection->filterResponses(m_assertionResponses);
458
459     if (auto* observer = this->observer()) {
460         auto callback = [this, weakThis = makeWeakPtr(*this)] (AuthenticatorAssertionResponse* response) {
461             ASSERT(RunLoop::isMain());
462             if (!weakThis)
463                 return;
464
465             auto returnResponse = m_assertionResponses.take(response);
466             if (!returnResponse)
467                 return;
468             continueGetAssertionAfterResponseSelected(WTFMove(*returnResponse));
469         };
470         observer->selectAssertionResponse(m_assertionResponses, WebAuthenticationSource::Local, WTFMove(callback));
471     }
472 }
473
474 void LocalAuthenticator::continueGetAssertionAfterResponseSelected(Ref<WebCore::AuthenticatorAssertionResponse>&& response)
475 {
476     ASSERT(m_state == State::RequestReceived);
477     m_state = State::ResponseSelected;
478
479     auto accessControlRef = response->accessControl();
480     auto callback = [
481         weakThis = makeWeakPtr(*this),
482         response = WTFMove(response)
483     ] (LocalConnection::UserVerification verification, LAContext *context) mutable {
484         ASSERT(RunLoop::isMain());
485         if (!weakThis)
486             return;
487
488         weakThis->continueGetAssertionAfterUserVerification(WTFMove(response), verification, context);
489     };
490     m_connection->verifyUser(accessControlRef, WTFMove(callback));
491 }
492
493 void LocalAuthenticator::continueGetAssertionAfterUserVerification(Ref<WebCore::AuthenticatorAssertionResponse>&& response, LocalConnection::UserVerification verification, LAContext *context)
494 {
495     using namespace LocalAuthenticatorInternal;
496     ASSERT(m_state == State::ResponseSelected);
497     m_state = State::UserVerified;
498
499     if (verification == LocalConnection::UserVerification::No) {
500         receiveException({ NotAllowedError, "Couldn't verify user."_s });
501         return;
502     }
503
504     // Step 10.
505     auto authData = buildAuthData(WTF::get<PublicKeyCredentialRequestOptions>(requestData().options).rpId, getAssertionFlags, counter, { });
506
507     // Step 11.
508     RetainPtr<CFDataRef> signature;
509     {
510         NSDictionary *query = @{
511             (id)kSecClass: (id)kSecClassKey,
512             (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
513             (id)kSecAttrApplicationLabel: toNSData(response->rawId()).get(),
514             (id)kSecUseAuthenticationContext: context,
515             (id)kSecReturnRef: @YES,
516 #if HAVE(DATA_PROTECTION_KEYCHAIN)
517             (id)kSecUseDataProtectionKeychain: @YES
518 #else
519             (id)kSecAttrNoLegacy: @YES
520 #endif
521         };
522         CFTypeRef privateKeyRef = nullptr;
523         OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &privateKeyRef);
524         if (status) {
525             receiveException({ UnknownError, makeString("Couldn't get the private key reference: ", status) });
526             return;
527         }
528         auto privateKey = adoptCF(privateKeyRef);
529
530         NSMutableData *dataToSign = [NSMutableData dataWithBytes:authData.data() length:authData.size()];
531         [dataToSign appendBytes:requestData().hash.data() length:requestData().hash.size()];
532
533         CFErrorRef errorRef = nullptr;
534         // FIXME: Converting CFTypeRef to SecKeyRef is quite subtle here.
535         signature = adoptCF(SecKeyCreateSignature((__bridge SecKeyRef)((id)privateKeyRef), kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)dataToSign, &errorRef));
536         auto retainError = adoptCF(errorRef);
537         if (errorRef) {
538             receiveException({ UnknownError, makeString("Couldn't generate the signature: ", String(((NSError*)errorRef).localizedDescription)) });
539             return;
540         }
541     }
542
543     // Step 13.
544     response->setAuthenticatorData(WTFMove(authData));
545     response->setSignature(toArrayBuffer((NSData *)signature.get()));
546     receiveRespond(WTFMove(response));
547 }
548
549 void LocalAuthenticator::receiveException(ExceptionData&& exception, WebAuthenticationStatus status) const
550 {
551     LOG_ERROR(exception.message.utf8().data());
552     if (auto* observer = this->observer())
553         observer->authenticatorStatusUpdated(status);
554     receiveRespond(WTFMove(exception));
555     return;
556 }
557
558 void LocalAuthenticator::deleteDuplicateCredential() const
559 {
560     using namespace LocalAuthenticatorInternal;
561
562     auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options);
563     m_existingCredentials.findMatching([creationOptions] (auto& credential) {
564         auto* userHandle = credential->userHandle();
565         ASSERT(userHandle);
566         if (memcmp(userHandle->data(), creationOptions.user.idVector.data(), userHandle->byteLength()))
567             return false;
568
569         NSDictionary* deleteQuery = @{
570             (id)kSecClass: (id)kSecClassKey,
571             (id)kSecAttrApplicationLabel: toNSData(credential->rawId()).get(),
572 #if HAVE(DATA_PROTECTION_KEYCHAIN)
573             (id)kSecUseDataProtectionKeychain: @YES
574 #else
575             (id)kSecAttrNoLegacy: @YES
576 #endif
577         };
578         OSStatus status = SecItemDelete((__bridge CFDictionaryRef)deleteQuery);
579         if (status && status != errSecItemNotFound)
580             LOG_ERROR(makeString("Couldn't delete older credential: "_s, status).utf8().data());
581         return true;
582     });
583 }
584
585 } // namespace WebKit
586
587 #endif // ENABLE(WEB_AUTHN)