Title: [291624] trunk/Source
Revision
291624
Author
j_pas...@apple.com
Date
2022-03-22 09:14:25 -0700 (Tue, 22 Mar 2022)

Log Message

[WebAuthn] Support getAssertion for virtual HID authenticators
https://bugs.webkit.org/show_bug.cgi?id=238154
rdar://problem/90593150

Reviewed by Brent Fulgham.

Source/WebCore:

Virtual authenticators for WebAuthn support different transprots: nfc, usb, internal,
and ble. Currently, we only fully support the internal transport and makeCredential for
usb-transport. The default transport for web-platform-tests is usb. This patch implements
getAssertion for hid-based virtual authneticators.

* Modules/webauthn/WebAuthenticationUtils.cpp:
(WebCore::buildUserEntityMap):
(WebCore::buildCredentialDescriptor):
* Modules/webauthn/WebAuthenticationUtils.h:
* Modules/webauthn/fido/FidoConstants.h:

Source/WebKit:

Virtual authenticators for WebAuthn support different transports: nfc, usb, internal,
and ble. Currently, we only fully support the internal transport and makeCredential for
usb-transport. The default transport for web-platform-tests is usb. This patch implements
getAssertion for hid-based virtual authneticators.

Tested via manually creating virtual authenticator and performing create / get.

* UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorManager.cpp:
(WebKit::VirtualAuthenticatorManager::addCredential):
(WebKit::VirtualAuthenticatorManager::credentialsMatchingList):
* UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorManager.h:
* UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorUtils.h:
* UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorUtils.mm:
(WebKit::privateKeyFromBase64):
(WebKit::signatureForPrivateKey):
* UIProcess/WebAuthentication/Virtual/VirtualHidConnection.cpp:
(WebKit::VirtualHidConnection::parseRequest):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (291623 => 291624)


--- trunk/Source/WebCore/ChangeLog	2022-03-22 16:01:26 UTC (rev 291623)
+++ trunk/Source/WebCore/ChangeLog	2022-03-22 16:14:25 UTC (rev 291624)
@@ -1,3 +1,22 @@
+2022-03-22  J Pascoe  <j_pas...@apple.com>
+
+        [WebAuthn] Support getAssertion for virtual HID authenticators
+        https://bugs.webkit.org/show_bug.cgi?id=238154
+        rdar://problem/90593150
+
+        Reviewed by Brent Fulgham.
+
+        Virtual authenticators for WebAuthn support different transprots: nfc, usb, internal,
+        and ble. Currently, we only fully support the internal transport and makeCredential for
+        usb-transport. The default transport for web-platform-tests is usb. This patch implements
+        getAssertion for hid-based virtual authneticators.
+
+        * Modules/webauthn/WebAuthenticationUtils.cpp:
+        (WebCore::buildUserEntityMap):
+        (WebCore::buildCredentialDescriptor):
+        * Modules/webauthn/WebAuthenticationUtils.h:
+        * Modules/webauthn/fido/FidoConstants.h:
+
 2022-03-22  Ricky Mondello  <rmonde...@apple.com>
 
         It should be possible to copy text out of "AutoFilledAndViewable" password fields

Modified: trunk/Source/WebCore/Modules/webauthn/WebAuthenticationUtils.cpp (291623 => 291624)


--- trunk/Source/WebCore/Modules/webauthn/WebAuthenticationUtils.cpp	2022-03-22 16:01:26 UTC (rev 291623)
+++ trunk/Source/WebCore/Modules/webauthn/WebAuthenticationUtils.cpp	2022-03-22 16:14:25 UTC (rev 291624)
@@ -29,6 +29,7 @@
 #if ENABLE(WEB_AUTHN)
 
 #include "CBORWriter.h"
+#include "FidoConstants.h"
 #include "WebAuthenticationConstants.h"
 #include <pal/crypto/CryptoDigest.h>
 #include <wtf/JSONValues.h>
@@ -87,6 +88,22 @@
     return attestedCredentialData;
 }
 
+cbor::CBORValue::MapValue buildUserEntityMap(const Vector<uint8_t>& userId, const String& name, const String& displayName)
+{
+    cbor::CBORValue::MapValue userEntityMap;
+    userEntityMap[cbor::CBORValue(fido::kEntityIdMapKey)] = cbor::CBORValue(userId);
+    userEntityMap[cbor::CBORValue(fido::kEntityNameMapKey)] = cbor::CBORValue(name);
+    userEntityMap[cbor::CBORValue(fido::kDisplayNameMapKey)] = cbor::CBORValue(displayName);
+    return userEntityMap;
+}
+
+cbor::CBORValue::MapValue buildCredentialDescriptor(const Vector<uint8_t>& credentialId)
+{
+    cbor::CBORValue::MapValue credential;
+    credential[cbor::CBORValue("id")] = cbor::CBORValue(credentialId);
+    return credential;
+}
+
 Vector<uint8_t> buildAuthData(const String& rpId, const uint8_t flags, const uint32_t counter, const Vector<uint8_t>& optionalAttestedCredentialData)
 {
     Vector<uint8_t> authData;

Modified: trunk/Source/WebCore/Modules/webauthn/WebAuthenticationUtils.h (291623 => 291624)


--- trunk/Source/WebCore/Modules/webauthn/WebAuthenticationUtils.h	2022-03-22 16:01:26 UTC (rev 291623)
+++ trunk/Source/WebCore/Modules/webauthn/WebAuthenticationUtils.h	2022-03-22 16:14:25 UTC (rev 291624)
@@ -51,6 +51,8 @@
 
 WEBCORE_EXPORT cbor::CBORValue::MapValue buildAttestationMap(Vector<uint8_t>&&, String&&, cbor::CBORValue::MapValue&&, const AttestationConveyancePreference&);
 
+WEBCORE_EXPORT cbor::CBORValue::MapValue buildCredentialDescriptor(const Vector<uint8_t>& credentialId);
+
 // https://www.w3.org/TR/webauthn/#attestation-object
 WEBCORE_EXPORT Vector<uint8_t> buildAttestationObject(Vector<uint8_t>&& authData, String&& format, cbor::CBORValue::MapValue&& statementMap, const AttestationConveyancePreference&);
 
@@ -58,6 +60,7 @@
 
 WEBCORE_EXPORT Vector<uint8_t> buildClientDataJsonHash(const ArrayBuffer& clientDataJson);
 
+WEBCORE_EXPORT cbor::CBORValue::MapValue buildUserEntityMap(const Vector<uint8_t>& userId, const String& name, const String& displayName);
 } // namespace WebCore
 
 #endif // ENABLE(WEB_AUTHN)

Modified: trunk/Source/WebCore/Modules/webauthn/fido/FidoConstants.h (291623 => 291624)


--- trunk/Source/WebCore/Modules/webauthn/fido/FidoConstants.h	2022-03-22 16:01:26 UTC (rev 291623)
+++ trunk/Source/WebCore/Modules/webauthn/fido/FidoConstants.h	2022-03-22 16:14:25 UTC (rev 291624)
@@ -266,7 +266,14 @@
 const int64_t kCtapMakeCredentialExcludeListKey = 5;
 const int64_t kCtapMakeCredentialExtensionsKey = 6;
 const int64_t kCtapMakeCredentialRequestOptionsKey = 7;
+
+const int64_t kCtapGetAssertionRpIdKey = 1;
+const int64_t kCtapGetAssertionClientDataHashKey = 2;
+const int64_t kCtapGetAssertionAllowListKey = 3;
+const int64_t kCtapGetAssertionExtensionsKey = 4;
 const int64_t kCtapGetAssertionRequestOptionsKey = 5;
+const int64_t kCtapGetAssertionPinUvAuthParamKey = 6;
+const int64_t kCtapGetAssertionPinUvAuthProtocolKey = 7;
 
 } // namespace fido
 

Modified: trunk/Source/WebKit/ChangeLog (291623 => 291624)


--- trunk/Source/WebKit/ChangeLog	2022-03-22 16:01:26 UTC (rev 291623)
+++ trunk/Source/WebKit/ChangeLog	2022-03-22 16:14:25 UTC (rev 291624)
@@ -1,3 +1,29 @@
+2022-03-22  J Pascoe  <j_pas...@apple.com>
+
+        [WebAuthn] Support getAssertion for virtual HID authenticators
+        https://bugs.webkit.org/show_bug.cgi?id=238154
+        rdar://problem/90593150
+
+        Reviewed by Brent Fulgham.
+
+        Virtual authenticators for WebAuthn support different transports: nfc, usb, internal,
+        and ble. Currently, we only fully support the internal transport and makeCredential for
+        usb-transport. The default transport for web-platform-tests is usb. This patch implements
+        getAssertion for hid-based virtual authneticators.
+
+        Tested via manually creating virtual authenticator and performing create / get.
+
+        * UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorManager.cpp:
+        (WebKit::VirtualAuthenticatorManager::addCredential):
+        (WebKit::VirtualAuthenticatorManager::credentialsMatchingList):
+        * UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorManager.h:
+        * UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorUtils.h:
+        * UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorUtils.mm:
+        (WebKit::privateKeyFromBase64):
+        (WebKit::signatureForPrivateKey):
+        * UIProcess/WebAuthentication/Virtual/VirtualHidConnection.cpp:
+        (WebKit::VirtualHidConnection::parseRequest):
+
 2022-03-22  Jer Noble  <jer.no...@apple.com>
 
         Fetching a Blob URL with an unbounded Range header do not generate a Content-Range response header

Modified: trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorManager.cpp (291623 => 291624)


--- trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorManager.cpp	2022-03-22 16:01:26 UTC (rev 291623)
+++ trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorManager.cpp	2022-03-22 16:14:25 UTC (rev 291624)
@@ -57,11 +57,22 @@
     return m_virtualAuthenticators.remove(id);
 }
 
-void VirtualAuthenticatorManager::addCredential(const String& authenticatorId, const VirtualCredential& credential)
+void VirtualAuthenticatorManager::addCredential(const String& authenticatorId, VirtualCredential& credential)
 {
-    m_credentialsByAuthenticator.get(authenticatorId).append(credential);
+    m_credentialsByAuthenticator.find(authenticatorId)->value.append(WTFMove(credential));
 }
 
+Vector<VirtualCredential> VirtualAuthenticatorManager::credentialsMatchingList(const String& authenticatorId, const String& rpId, const Vector<Vector<uint8_t>>& credentialIds)
+{
+    Vector<VirtualCredential> matching;
+    auto it = m_credentialsByAuthenticator.find(authenticatorId);
+    for (auto& credential : it->value) {
+        if (credential.rpId == rpId && ((credentialIds.isEmpty() && credential.isResidentCredential) || credentialIds.contains(credential.credentialId)))
+            matching.append(credential);
+    }
+    return matching;
+}
+
 UniqueRef<AuthenticatorTransportService> VirtualAuthenticatorManager::createService(WebCore::AuthenticatorTransport transport, AuthenticatorTransportService::Observer& observer) const
 {
     Vector<std::pair<String, VirtualAuthenticatorConfiguration>> configs;

Modified: trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorManager.h (291623 => 291624)


--- trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorManager.h	2022-03-22 16:01:26 UTC (rev 291623)
+++ trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorManager.h	2022-03-22 16:14:25 UTC (rev 291624)
@@ -44,7 +44,8 @@
 
     bool isVirtual() const final { return true; }
 
-    void addCredential(const String&, const VirtualCredential&);
+    void addCredential(const String&, VirtualCredential&);
+    Vector<VirtualCredential> credentialsMatchingList(const String& authenticatorId, const String& rpId, const Vector<Vector<uint8_t>>& credentialIds);
 
 protected:
     void decidePolicyForLocalAuthenticator(CompletionHandler<void(LocalAuthenticatorPolicy)>&&) override;

Modified: trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorUtils.h (291623 => 291624)


--- trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorUtils.h	2022-03-22 16:01:26 UTC (rev 291623)
+++ trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorUtils.h	2022-03-22 16:14:25 UTC (rev 291624)
@@ -35,6 +35,8 @@
 RetainPtr<SecKeyRef> createPrivateKey();
 std::pair<Vector<uint8_t>, Vector<uint8_t>> credentialIdAndCosePubKeyForPrivateKey(RetainPtr<SecKeyRef> privateKey);
 String base64PrivateKey(RetainPtr<SecKeyRef> privateKey);
+RetainPtr<SecKeyRef> privateKeyFromBase64(const String& base64PrivateKey);
+Vector<uint8_t> signatureForPrivateKey(RetainPtr<SecKeyRef> privateKey, const Vector<uint8_t>& authData, const Vector<uint8_t>& clientDataHash);
 
 } // namespace WebKit
 

Modified: trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorUtils.mm (291623 => 291624)


--- trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorUtils.mm	2022-03-22 16:01:26 UTC (rev 291623)
+++ trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualAuthenticatorUtils.mm	2022-03-22 16:14:25 UTC (rev 291624)
@@ -32,6 +32,8 @@
 #include <WebCore/WebAuthenticationConstants.h>
 #include <WebCore/WebAuthenticationUtils.h>
 #include <pal/crypto/CryptoDigest.h>
+#include <wtf/cocoa/TypeCastsCocoa.h>
+#include <wtf/cocoa/VectorCocoa.h>
 
 namespace WebKit {
 using namespace WebCore;
@@ -109,6 +111,39 @@
     return String([nsPrivateKeyRep base64EncodedStringWithOptions:0]);
 }
 
+RetainPtr<SecKeyRef> privateKeyFromBase64(const String& base64PrivateKey)
+{
+    NSDictionary* options = @{
+        (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
+        (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
+        (id)kSecAttrKeySizeInBits: @256,
+    };
+    RetainPtr<NSData> privateKey = adoptNS([[NSData alloc] initWithBase64EncodedString:base64PrivateKey options:0]);
+    CFErrorRef errorRef = nullptr;
+    auto key = adoptCF(SecKeyCreateWithData(
+        bridge_cast(privateKey.get()),
+        bridge_cast(options),
+        &errorRef
+    ));
+    ASSERT(!errorRef);
+    return key;
+}
+
+Vector<uint8_t> signatureForPrivateKey(RetainPtr<SecKeyRef> privateKey, const Vector<uint8_t>& authData, const Vector<uint8_t>& clientDataHash)
+{
+    NSMutableData *dataToSign = [NSMutableData dataWithBytes:authData.data() length:authData.size()];
+    [dataToSign appendBytes:clientDataHash.data() length:clientDataHash.size()];
+    RetainPtr<CFDataRef> signature;
+    {
+        CFErrorRef errorRef = nullptr;
+        signature = adoptCF(SecKeyCreateSignature((__bridge SecKeyRef)((id)privateKey.get()), kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)dataToSign, &errorRef));
+        auto retainError = adoptCF(errorRef);
+        ASSERT(!errorRef);
+    }
+
+    return vectorFromNSData((NSData *)signature.get());
+}
+
 } // namespace WebKit
 
 #endif // ENABLE(WEB_AUTHN)

Modified: trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualHidConnection.cpp (291623 => 291624)


--- trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualHidConnection.cpp	2022-03-22 16:01:26 UTC (rev 291623)
+++ trunk/Source/WebKit/UIProcess/WebAuthentication/Virtual/VirtualHidConnection.cpp	2022-03-22 16:14:25 UTC (rev 291624)
@@ -212,19 +212,17 @@
                 return;
             }
             credential.userHandle = it->second.getByteString();
-
+            auto credentialIdAndCosePubKey = credentialIdAndCosePubKeyForPrivateKey(privateKey);
+            credential.credentialId = credentialIdAndCosePubKey.first;
             manager->addCredential(m_authenticatorId, credential);
-            auto credentialIdAndCosePubKey = credentialIdAndCosePubKeyForPrivateKey(privateKey);
 
             auto attestedCredentialData = buildAttestedCredentialData(Vector<uint8_t>(aaguidLength, 0), credentialIdAndCosePubKey.first, credentialIdAndCosePubKey.second);
 
             auto authenticatorData = buildAuthData(credential.rpId, flagsForConfig(m_configuration), credential.signCount, attestedCredentialData);
             CBORValue::MapValue response;
+            response[CBORValue(1)] = CBORValue("none");
             response[CBORValue(2)] = CBORValue(authenticatorData);
-
             auto attObj = buildAttestationMap(WTFMove(authenticatorData), "", { }, AttestationConveyancePreference::None);
-
-            response[CBORValue(1)] = CBORValue("none");
             response[CBORValue(3)] = CBORValue(attObj);
             auto payload = CBORWriter::write(CBORValue(response));
             Vector<uint8_t> buffer;
@@ -234,6 +232,93 @@
 
             auto message = FidoHidMessage::create(m_requestMessage->channelId(), FidoHidDeviceCommand::kCbor, WTFMove(buffer));
             receiveHidMessage(WTFMove(*message));
+        } else if (cmd == CtapRequestCommand::kAuthenticatorGetAssertion) {
+            auto it = requestMap->getMap().find(CBORValue(kCtapGetAssertionRpIdKey));
+            if (it == requestMap->getMap().end()) {
+                recieveResponseCode(CtapDeviceResponseCode::kCtap2ErrMissingParameter);
+                return;
+            }
+            auto rpId = it->second.getString();
+            (void)rpId;
+            it = requestMap->getMap().find(CBORValue(kCtapGetAssertionClientDataHashKey));
+            if (it == requestMap->getMap().end()) {
+                recieveResponseCode(CtapDeviceResponseCode::kCtap2ErrMissingParameter);
+                return;
+            }
+            auto clientDataHash = it->second.getByteString();
+            (void)clientDataHash;
+            Vector<Vector<uint8_t>> allowList;
+            it = requestMap->getMap().find(CBORValue(kCtapGetAssertionAllowListKey));
+            if (it != requestMap->getMap().end()) {
+                const auto& cborAllowList = it->second.getArray();
+                if (cborAllowList.isEmpty()) {
+                    recieveResponseCode(CtapDeviceResponseCode::kCtap2ErrInvalidOption);
+                    return;
+                }
+
+                for (const auto& cborCredential : cborAllowList) {
+                    auto& credMap = cborCredential.getMap();
+                    auto itr = credMap.find(CBORValue(fido::kEntityIdMapKey));
+                    if (itr == credMap.end() || !itr->second.isByteString()) {
+                        recieveResponseCode(CtapDeviceResponseCode::kCtap2ErrInvalidOption);
+                        return;
+                    }
+                    allowList.append(itr->second.getByteString());
+                }
+            }
+
+            it = requestMap->getMap().find(CBORValue(kCtapGetAssertionRequestOptionsKey));
+            if (it != requestMap->getMap().end()) {
+                auto& optionMap = it->second.getMap();
+
+                auto itr = optionMap.find(CBORValue(kUserVerificationMapKey));
+                if (itr != optionMap.end()) {
+                    auto requireUserVerification = itr->second.getBool();
+                    if (requireUserVerification && !m_configuration.hasUserVerification) {
+                        recieveResponseCode(CtapDeviceResponseCode::kCtap2ErrInvalidOption);
+                        return;
+                    }
+                    if (requireUserVerification && !m_configuration.isUserVerified) {
+                        recieveResponseCode(CtapDeviceResponseCode::kCtap2ErrOperationDenied);
+                        return;
+                    }
+                }
+
+                itr = optionMap.find(CBORValue(kUserPresenceMapKey));
+                if (itr != optionMap.end()) {
+                    auto requireUserPresence = itr->second.getBool();
+                    if (requireUserPresence && !m_configuration.isUserConsenting) {
+                        recieveResponseCode(CtapDeviceResponseCode::kCtap2ErrOperationDenied);
+                        return;
+                    }
+                }
+            }
+            auto matchingCredentials = m_manager->credentialsMatchingList(m_authenticatorId, rpId, allowList);
+            if (matchingCredentials.isEmpty()) {
+                recieveResponseCode(CtapDeviceResponseCode::kCtap2ErrNoCredentials);
+                return;
+            }
+            auto& credential = matchingCredentials[0];
+            CBORValue::MapValue response;
+
+            response[CBORValue(1)] = CBORValue(buildCredentialDescriptor(credential.credentialId));
+            auto key = privateKeyFromBase64(credential.privateKey);
+            auto credentialIdAndCosePubKey = credentialIdAndCosePubKeyForPrivateKey(key);
+            auto attestedCredentialData = buildAttestedCredentialData(Vector<uint8_t>(aaguidLength, 0), credentialIdAndCosePubKey.first, credentialIdAndCosePubKey.second);
+            auto authData = buildAuthData(rpId, flagsForConfig(m_configuration), credential.signCount, attestedCredentialData);
+            response[CBORValue(2)] = CBORValue(authData);
+            response[CBORValue(3)] = CBORValue(signatureForPrivateKey(key, authData, clientDataHash));
+            if (credential.userHandle)
+                response[CBORValue(4)] = CBORValue(buildUserEntityMap(*credential.userHandle, "", ""));
+            response[CBORValue(5)] = CBORValue((int64_t)matchingCredentials.size());
+            auto payload = CBORWriter::write(CBORValue(response));
+            Vector<uint8_t> buffer;
+            buffer.reserveCapacity(payload->size() + 1);
+            buffer.append(static_cast<uint8_t>(fido::CtapDeviceResponseCode::kSuccess));
+            buffer.appendVector(*payload);
+
+            auto message = FidoHidMessage::create(m_requestMessage->channelId(), FidoHidDeviceCommand::kCbor, WTFMove(buffer));
+            receiveHidMessage(WTFMove(*message));
         }
         break;
     }
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to