| /* |
| * Copyright (C) 2018 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "LocalAuthenticator.h" |
| |
| #if ENABLE(WEB_AUTHN) |
| |
| #import <Security/SecItem.h> |
| #import <WebCore/AuthenticatorAssertionResponse.h> |
| #import <WebCore/AuthenticatorAttestationResponse.h> |
| #import <WebCore/CBORWriter.h> |
| #import <WebCore/ExceptionData.h> |
| #import <WebCore/PublicKeyCredentialCreationOptions.h> |
| #import <WebCore/PublicKeyCredentialRequestOptions.h> |
| #import <WebCore/WebAuthenticationConstants.h> |
| #import <WebCore/WebAuthenticationUtils.h> |
| #import <pal/crypto/CryptoDigest.h> |
| #import <wtf/HashSet.h> |
| #import <wtf/RetainPtr.h> |
| #import <wtf/RunLoop.h> |
| #import <wtf/Vector.h> |
| #import <wtf/spi/cocoa/SecuritySPI.h> |
| #import <wtf/text/StringHash.h> |
| |
| namespace WebKit { |
| using namespace WebCore; |
| |
| namespace LocalAuthenticatorInternal { |
| |
| // See https://www.w3.org/TR/webauthn/#flags. |
| const uint8_t makeCredentialFlags = 0b01000101; // UP, UV and AT are set. |
| const uint8_t getAssertionFlags = 0b00000101; // UP and UV are set. |
| // Credential ID is currently SHA-1 of the corresponding public key. |
| const uint16_t credentialIdLength = 20; |
| |
| static inline bool emptyTransportsOrContain(const Vector<AuthenticatorTransport>& transports, AuthenticatorTransport target) |
| { |
| return transports.isEmpty() ? true : transports.contains(target); |
| } |
| |
| static inline HashSet<String> produceHashSet(const Vector<PublicKeyCredentialDescriptor>& credentialDescriptors) |
| { |
| HashSet<String> result; |
| for (auto& credentialDescriptor : credentialDescriptors) { |
| if (emptyTransportsOrContain(credentialDescriptor.transports, AuthenticatorTransport::Internal) |
| && credentialDescriptor.type == PublicKeyCredentialType::PublicKey |
| && credentialDescriptor.idVector.size() == credentialIdLength) |
| result.add(String(reinterpret_cast<const char*>(credentialDescriptor.idVector.data()), credentialDescriptor.idVector.size())); |
| } |
| return result; |
| } |
| |
| static inline Vector<uint8_t> toVector(NSData *data) |
| { |
| Vector<uint8_t> result; |
| result.append(reinterpret_cast<const uint8_t*>(data.bytes), data.length); |
| return result; |
| } |
| |
| } // LocalAuthenticatorInternal |
| |
| LocalAuthenticator::LocalAuthenticator(UniqueRef<LocalConnection>&& connection) |
| : m_connection(WTFMove(connection)) |
| { |
| } |
| |
| void LocalAuthenticator::makeCredential() |
| { |
| using namespace LocalAuthenticatorInternal; |
| ASSERT(m_state == State::Init); |
| m_state = State::RequestReceived; |
| auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options); |
| |
| // The following implements https://www.w3.org/TR/webauthn/#op-make-cred as of 5 December 2017. |
| // Skip Step 4-5 as requireResidentKey and requireUserVerification are enforced. |
| // Skip Step 9 as extensions are not supported yet. |
| // Step 8 is implicitly captured by all UnknownError exception receiveResponds. |
| // Step 2. |
| bool canFullfillPubKeyCredParams = false; |
| for (auto& pubKeyCredParam : creationOptions.pubKeyCredParams) { |
| if (pubKeyCredParam.type == PublicKeyCredentialType::PublicKey && pubKeyCredParam.alg == COSE::ES256) { |
| canFullfillPubKeyCredParams = true; |
| break; |
| } |
| } |
| if (!canFullfillPubKeyCredParams) { |
| receiveRespond(ExceptionData { NotSupportedError, "The platform attached authenticator doesn't support any provided PublicKeyCredentialParameters."_s }); |
| return; |
| } |
| |
| // Step 3. |
| auto excludeCredentialIds = produceHashSet(creationOptions.excludeCredentials); |
| if (!excludeCredentialIds.isEmpty()) { |
| // Search Keychain for the RP ID. |
| NSDictionary *query = @{ |
| (id)kSecClass: (id)kSecClassKey, |
| (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, |
| (id)kSecAttrLabel: creationOptions.rp.id, |
| (id)kSecReturnAttributes: @YES, |
| (id)kSecMatchLimit: (id)kSecMatchLimitAll, |
| #if HAVE(DATA_PROTECTION_KEYCHAIN) |
| (id)kSecUseDataProtectionKeychain: @YES |
| #else |
| (id)kSecAttrNoLegacy: @YES |
| #endif |
| }; |
| CFTypeRef attributesArrayRef = nullptr; |
| OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef); |
| if (status && status != errSecItemNotFound) { |
| LOG_ERROR("Couldn't query Keychain: %d", status); |
| receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s }); |
| return; |
| } |
| auto retainAttributesArray = adoptCF(attributesArrayRef); |
| |
| for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) { |
| NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel]; |
| if (excludeCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length))) { |
| receiveRespond(ExceptionData { NotAllowedError, "At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator."_s }); |
| return; |
| } |
| } |
| } |
| |
| // Step 6. |
| // FIXME(rdar://problem/35900593): Update to a formal UI. |
| // Get user consent. |
| auto callback = [weakThis = makeWeakPtr(*this)](LocalConnection::UserConsent consent) { |
| ASSERT(RunLoop::isMain()); |
| if (!weakThis) |
| return; |
| |
| weakThis->continueMakeCredentialAfterUserConsented(consent); |
| }; |
| m_connection->getUserConsent( |
| "allow " + creationOptions.rp.id + " to create a public key credential for " + creationOptions.user.name, |
| WTFMove(callback)); |
| } |
| |
| void LocalAuthenticator::continueMakeCredentialAfterUserConsented(LocalConnection::UserConsent consent) |
| { |
| ASSERT(m_state == State::RequestReceived); |
| m_state = State::UserConsented; |
| auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options); |
| |
| if (consent == LocalConnection::UserConsent::No) { |
| receiveRespond(ExceptionData { NotAllowedError, "Couldn't get user consent."_s }); |
| return; |
| } |
| |
| // Step 7.5. |
| // Userhandle is stored in kSecAttrApplicationTag attribute. |
| // Failures after this point could block users' accounts forever. Should we follow the spec? |
| NSDictionary* deleteQuery = @{ |
| (id)kSecClass: (id)kSecClassKey, |
| (id)kSecAttrLabel: creationOptions.rp.id, |
| (id)kSecAttrApplicationTag: [NSData dataWithBytes:creationOptions.user.idVector.data() length:creationOptions.user.idVector.size()], |
| #if HAVE(DATA_PROTECTION_KEYCHAIN) |
| (id)kSecUseDataProtectionKeychain: @YES |
| #else |
| (id)kSecAttrNoLegacy: @YES |
| #endif |
| }; |
| OSStatus status = SecItemDelete((__bridge CFDictionaryRef)deleteQuery); |
| if (status && status != errSecItemNotFound) { |
| LOG_ERROR("Couldn't detele older credential: %d", status); |
| receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s }); |
| return; |
| } |
| |
| // Step 7.1, 13. Apple Attestation |
| auto callback = [weakThis = makeWeakPtr(*this)](SecKeyRef _Nullable privateKey, NSArray * _Nullable certificates, NSError * _Nullable error) { |
| ASSERT(RunLoop::isMain()); |
| if (!weakThis) |
| return; |
| weakThis->continueMakeCredentialAfterAttested(privateKey, certificates, error); |
| }; |
| m_connection->getAttestation(creationOptions.rp.id, creationOptions.user.name, requestData().hash, WTFMove(callback)); |
| } |
| |
| void LocalAuthenticator::continueMakeCredentialAfterAttested(SecKeyRef privateKey, NSArray *certificates, NSError *error) |
| { |
| using namespace LocalAuthenticatorInternal; |
| |
| ASSERT(m_state == State::UserConsented); |
| m_state = State::Attested; |
| auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options); |
| |
| if (error) { |
| LOG_ERROR("Couldn't attest: %@", error); |
| receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s }); |
| return; |
| } |
| // Attestation Certificate and Attestation Issuing CA |
| ASSERT(certificates && ([certificates count] == 2)); |
| |
| // Step 7.2-7.4. |
| // FIXME(183533): A single kSecClassKey item couldn't store all meta data. The following schema is a tentative solution |
| // to accommodate the most important meta data, i.e. RP ID, Credential ID, and userhandle. |
| // kSecAttrLabel: RP ID |
| // kSecAttrApplicationLabel: Credential ID (auto-gen by Keychain) |
| // kSecAttrApplicationTag: userhandle |
| // Noted, the current DeviceIdentity.Framework would only allow us to pass the kSecAttrLabel as the inital attribute |
| // for the Keychain item. Since that's the only clue we have to locate the unique item, we use the pattern username@rp.id |
| // as the initial value. |
| // Also noted, the vale of kSecAttrApplicationLabel is automatically generated by the Keychain, which is a SHA-1 hash of |
| // the public key. We borrow it directly for now to workaround the stated limitations. |
| // Update the Keychain item to the above schema. |
| // FIXME(183533): DeviceIdentity.Framework would insert certificates into Keychain as well. We should update those as well. |
| Vector<uint8_t> credentialId; |
| { |
| // -rk-ucrt is added by DeviceIdentity.Framework. |
| String label = makeString(creationOptions.user.name, "@", creationOptions.rp.id, "-rk-ucrt"); |
| NSDictionary *credentialIdQuery = @{ |
| (id)kSecClass: (id)kSecClassKey, |
| (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, |
| (id)kSecAttrLabel: label, |
| (id)kSecReturnAttributes: @YES, |
| #if HAVE(DATA_PROTECTION_KEYCHAIN) |
| (id)kSecUseDataProtectionKeychain: @YES |
| #else |
| (id)kSecAttrNoLegacy: @YES |
| #endif |
| }; |
| CFTypeRef attributesRef = nullptr; |
| OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)credentialIdQuery, &attributesRef); |
| if (status) { |
| LOG_ERROR("Couldn't get Credential ID: %d", status); |
| receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s }); |
| return; |
| } |
| auto retainAttributes = adoptCF(attributesRef); |
| |
| NSDictionary *nsAttributes = (NSDictionary *)attributesRef; |
| credentialId = toVector(nsAttributes[(id)kSecAttrApplicationLabel]); |
| |
| NSDictionary *updateQuery = @{ |
| (id)kSecClass: (id)kSecClassKey, |
| (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, |
| (id)kSecAttrApplicationLabel: nsAttributes[(id)kSecAttrApplicationLabel], |
| #if HAVE(DATA_PROTECTION_KEYCHAIN) |
| (id)kSecUseDataProtectionKeychain: @YES |
| #else |
| (id)kSecAttrNoLegacy: @YES |
| #endif |
| }; |
| NSDictionary *updateParams = @{ |
| (id)kSecAttrLabel: creationOptions.rp.id, |
| (id)kSecAttrApplicationTag: [NSData dataWithBytes:creationOptions.user.idVector.data() length:creationOptions.user.idVector.size()], |
| }; |
| status = SecItemUpdate((__bridge CFDictionaryRef)updateQuery, (__bridge CFDictionaryRef)updateParams); |
| if (status) { |
| LOG_ERROR("Couldn't update the Keychain item: %d", status); |
| receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s }); |
| return; |
| } |
| } |
| |
| // Step 10. |
| // FIXME(183533): store the counter. |
| uint32_t counter = 0; |
| |
| // Step 11. https://www.w3.org/TR/webauthn/#attested-credential-data |
| // credentialPublicKey |
| Vector<uint8_t> cosePublicKey; |
| { |
| RetainPtr<CFDataRef> publicKeyDataRef; |
| { |
| auto publicKey = adoptCF(SecKeyCopyPublicKey(privateKey)); |
| CFErrorRef errorRef = nullptr; |
| publicKeyDataRef = adoptCF(SecKeyCopyExternalRepresentation(publicKey.get(), &errorRef)); |
| auto retainError = adoptCF(errorRef); |
| if (errorRef) { |
| LOG_ERROR("Couldn't export the public key: %@", (NSError*)errorRef); |
| receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s }); |
| return; |
| } |
| ASSERT(((NSData *)publicKeyDataRef.get()).length == (1 + 2 * ES256FieldElementLength)); // 04 | X | Y |
| } |
| |
| // COSE Encoding |
| Vector<uint8_t> x(ES256FieldElementLength); |
| [(NSData *)publicKeyDataRef.get() getBytes: x.data() range:NSMakeRange(1, ES256FieldElementLength)]; |
| Vector<uint8_t> y(ES256FieldElementLength); |
| [(NSData *)publicKeyDataRef.get() getBytes: y.data() range:NSMakeRange(1 + ES256FieldElementLength, ES256FieldElementLength)]; |
| cosePublicKey = encodeES256PublicKeyAsCBOR(WTFMove(x), WTFMove(y)); |
| } |
| // FIXME(rdar://problem/38320512): Define Apple AAGUID. |
| auto attestedCredentialData = buildAttestedCredentialData(Vector<uint8_t>(aaguidLength, 0), credentialId, cosePublicKey); |
| |
| // Step 12. |
| auto authData = buildAuthData(creationOptions.rp.id, makeCredentialFlags, counter, attestedCredentialData); |
| |
| // Step 13. Apple Attestation Cont' |
| // Assemble the attestation object: |
| // https://www.w3.org/TR/webauthn/#attestation-object |
| cbor::CBORValue::MapValue attestationStatementMap; |
| { |
| Vector<uint8_t> signature; |
| { |
| CFErrorRef errorRef = nullptr; |
| // FIXME(183652): Reduce prompt for biometrics |
| auto signatureRef = adoptCF(SecKeyCreateSignature(privateKey, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)[NSData dataWithBytes:authData.data() length:authData.size()], &errorRef)); |
| auto retainError = adoptCF(errorRef); |
| if (errorRef) { |
| LOG_ERROR("Couldn't generate the signature: %@", (NSError*)errorRef); |
| receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s }); |
| return; |
| } |
| signature = toVector((NSData *)signatureRef.get()); |
| } |
| attestationStatementMap[cbor::CBORValue("alg")] = cbor::CBORValue(COSE::ES256); |
| attestationStatementMap[cbor::CBORValue("sig")] = cbor::CBORValue(signature); |
| Vector<cbor::CBORValue> cborArray; |
| for (size_t i = 0; i < [certificates count]; i++) |
| cborArray.append(cbor::CBORValue(toVector((NSData *)adoptCF(SecCertificateCopyData((__bridge SecCertificateRef)certificates[i])).get()))); |
| attestationStatementMap[cbor::CBORValue("x5c")] = cbor::CBORValue(WTFMove(cborArray)); |
| } |
| auto attestationObject = buildAttestationObject(WTFMove(authData), "Apple", WTFMove(attestationStatementMap), creationOptions.attestation); |
| |
| receiveRespond(AuthenticatorAttestationResponse::create(credentialId, attestationObject)); |
| } |
| |
| void LocalAuthenticator::getAssertion() |
| { |
| using namespace LocalAuthenticatorInternal; |
| ASSERT(m_state == State::Init); |
| m_state = State::RequestReceived; |
| auto& requestOptions = WTF::get<PublicKeyCredentialRequestOptions>(requestData().options); |
| |
| // The following implements https://www.w3.org/TR/webauthn/#op-get-assertion as of 5 December 2017. |
| // Skip Step 2 as requireUserVerification is enforced. |
| // Skip Step 8 as extensions are not supported yet. |
| // Step 12 is implicitly captured by all UnknownError exception callbacks. |
| // 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. |
| auto allowCredentialIds = produceHashSet(requestOptions.allowCredentials); |
| if (!requestOptions.allowCredentials.isEmpty() && allowCredentialIds.isEmpty()) { |
| receiveRespond(ExceptionData { NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s }); |
| return; |
| } |
| |
| // Search Keychain for the RP ID. |
| NSDictionary *query = @{ |
| (id)kSecClass: (id)kSecClassKey, |
| (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, |
| (id)kSecAttrLabel: requestOptions.rpId, |
| (id)kSecReturnAttributes: @YES, |
| (id)kSecMatchLimit: (id)kSecMatchLimitAll, |
| #if HAVE(DATA_PROTECTION_KEYCHAIN) |
| (id)kSecUseDataProtectionKeychain: @YES |
| #else |
| (id)kSecAttrNoLegacy: @YES |
| #endif |
| }; |
| CFTypeRef attributesArrayRef = nullptr; |
| OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef); |
| if (status && status != errSecItemNotFound) { |
| LOG_ERROR("Couldn't query Keychain: %d", status); |
| receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s }); |
| return; |
| } |
| auto retainAttributesArray = adoptCF(attributesArrayRef); |
| |
| NSArray *intersectedCredentialsAttributes = nil; |
| if (requestOptions.allowCredentials.isEmpty()) |
| intersectedCredentialsAttributes = (NSArray *)attributesArrayRef; |
| else { |
| NSMutableArray *result = [NSMutableArray arrayWithCapacity:allowCredentialIds.size()]; |
| for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) { |
| NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel]; |
| if (allowCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length))) |
| [result addObject:nsAttributes]; |
| } |
| intersectedCredentialsAttributes = result; |
| } |
| if (!intersectedCredentialsAttributes.count) { |
| receiveRespond(ExceptionData { NotAllowedError, "No matched credentials are found in the platform attached authenticator."_s }); |
| return; |
| } |
| |
| // Step 6. |
| auto *selectedCredentialAttributes = m_connection->selectCredential(intersectedCredentialsAttributes); |
| |
| // Step 7. Get user consent. |
| // FIXME(rdar://problem/35900593): Update to a formal UI. |
| auto callback = [ |
| weakThis = makeWeakPtr(*this), |
| credentialId = toVector(selectedCredentialAttributes[(id)kSecAttrApplicationLabel]), |
| userhandle = toVector(selectedCredentialAttributes[(id)kSecAttrApplicationTag]) |
| ](LocalConnection::UserConsent consent, LAContext *context) { |
| ASSERT(RunLoop::isMain()); |
| if (!weakThis) |
| return; |
| |
| weakThis->continueGetAssertionAfterUserConsented(consent, context, credentialId, userhandle); |
| }; |
| NSData *idData = selectedCredentialAttributes[(id)kSecAttrApplicationTag]; |
| StringView idStringView { static_cast<const UChar*>([idData bytes]), static_cast<unsigned>([idData length]) }; |
| m_connection->getUserConsent( |
| makeString("log into ", requestOptions.rpId, " with ", idStringView), |
| (__bridge SecAccessControlRef)selectedCredentialAttributes[(id)kSecAttrAccessControl], |
| WTFMove(callback)); |
| } |
| |
| void LocalAuthenticator::continueGetAssertionAfterUserConsented(LocalConnection::UserConsent consent, LAContext *context, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& userhandle) |
| { |
| using namespace LocalAuthenticatorInternal; |
| ASSERT(m_state == State::RequestReceived); |
| m_state = State::UserConsented; |
| |
| if (consent == LocalConnection::UserConsent::No) { |
| receiveRespond(ExceptionData { NotAllowedError, "Couldn't get user consent."_s }); |
| return; |
| } |
| |
| // Step 9-10. |
| // FIXME(183533): Due to the stated Keychain limitations, we can't save the counter value. |
| // Therefore, it is always zero. |
| uint32_t counter = 0; |
| auto authData = buildAuthData(WTF::get<PublicKeyCredentialRequestOptions>(requestData().options).rpId, getAssertionFlags, counter, { }); |
| |
| // Step 11. |
| Vector<uint8_t> signature; |
| { |
| NSDictionary *query = @{ |
| (id)kSecClass: (id)kSecClassKey, |
| (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, |
| (id)kSecAttrApplicationLabel: [NSData dataWithBytes:credentialId.data() length:credentialId.size()], |
| (id)kSecUseAuthenticationContext: context, |
| (id)kSecReturnRef: @YES, |
| #if HAVE(DATA_PROTECTION_KEYCHAIN) |
| (id)kSecUseDataProtectionKeychain: @YES |
| #else |
| (id)kSecAttrNoLegacy: @YES |
| #endif |
| }; |
| CFTypeRef privateKeyRef = nullptr; |
| OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &privateKeyRef); |
| if (status) { |
| LOG_ERROR("Couldn't get the private key reference: %d", status); |
| receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s }); |
| return; |
| } |
| auto privateKey = adoptCF(privateKeyRef); |
| |
| NSMutableData *dataToSign = [NSMutableData dataWithBytes:authData.data() length:authData.size()]; |
| [dataToSign appendBytes:requestData().hash.data() length:requestData().hash.size()]; |
| |
| CFErrorRef errorRef = nullptr; |
| // FIXME: Converting CFTypeRef to SecKeyRef is quite subtle here. |
| auto signatureRef = adoptCF(SecKeyCreateSignature((__bridge SecKeyRef)((id)privateKeyRef), kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)dataToSign, &errorRef)); |
| auto retainError = adoptCF(errorRef); |
| if (errorRef) { |
| LOG_ERROR("Couldn't generate the signature: %@", (NSError*)errorRef); |
| receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s }); |
| return; |
| } |
| signature = toVector((NSData *)signatureRef.get()); |
| } |
| |
| // Step 13. |
| receiveRespond(AuthenticatorAssertionResponse::create(credentialId, authData, signature, userhandle)); |
| } |
| |
| } // namespace WebKit |
| |
| #endif // ENABLE(WEB_AUTHN) |