[WebAuthN] Import U2F command/response converters from Chromium
authorjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 Jan 2019 02:08:20 +0000 (02:08 +0000)
committerjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 Jan 2019 02:08:20 +0000 (02:08 +0000)
https://bugs.webkit.org/show_bug.cgi?id=193150
<rdar://problem/47054028>

Reviewed by Brent Fulgham.

Source/WebCore:

This patch imports Chromium's U2F command/response converters:
https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#u2f-interoperability
1. It directly imports the following files and suit them to WebKit's coding style:
https://cs.chromium.org/chromium/src/device/fido/u2f_command_constructor.cc?l=1&rcl=db624110317d01efa78cd32e7be1524190e1beb0
https://cs.chromium.org/chromium/src/device/fido/u2f_command_constructor.h?rcl=db624110317d01efa78cd32e7be1524190e1beb0
https://cs.chromium.org/chromium/src/device/fido/u2f_command_constructor_unittest.cc?rcl=db624110317d01efa78cd32e7be1524190e1beb0
2. It gathers the following methods into U2fResponseConverter:
AuthenticatorMakeCredentialResponse::CreateFromU2fRegisterResponse()
AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse()
3. It also updates FidoConstants.h, FidoTestData.h and CtapResponseTest.cpp accordingly.

Besides importing stuff from Chroimum, it also gathers a bunch of constants and helper functions into WebAuthenticationConstants.h
and WebAuthenticationUtils.h. It also fixes Bug 183534: 2) and 7).

Covered by API tests.

* Modules/webauthn/AuthenticatorCoordinator.cpp:
(WebCore::AuthenticatorCoordinatorInternal::produceClientDataJsonHash):
* Modules/webauthn/WebAuthenticationConstants.h: Copied from Source/WebCore/Modules/webauthn/COSEConstants.h.
* Modules/webauthn/WebAuthenticationUtils.cpp: Added.
(WebCore::convertBytesToVector):
(WebCore::produceRpIdHash):
(WebCore::encodeES256PublicKeyAsCBOR):
(WebCore::buildAttestedCredentialData):
(WebCore::buildAuthData):
(WebCore::buildAttestationObject):
* Modules/webauthn/WebAuthenticationUtils.h: Renamed from Source/WebCore/Modules/webauthn/COSEConstants.h.
* Modules/webauthn/fido/DeviceResponseConverter.cpp:
(fido::getCredentialId):
(fido::readCTAPGetInfoResponse):
* Modules/webauthn/fido/FidoConstants.h:
* Modules/webauthn/fido/U2fCommandConstructor.cpp: Added.
(fido::WebCore::constructU2fRegisterCommand):
(fido::WebCore::constructU2fSignCommand):
(fido::isConvertibleToU2fRegisterCommand):
(fido::isConvertibleToU2fSignCommand):
(fido::convertToU2fRegisterCommand):
(fido::convertToU2fCheckOnlySignCommand):
(fido::convertToU2fSignCommand):
(fido::constructBogusU2fRegistrationCommand):
* Modules/webauthn/fido/U2fCommandConstructor.h: Added.
* Modules/webauthn/fido/U2fResponseConverter.cpp: Added.
(fido::WebCore::extractECPublicKeyFromU2fRegistrationResponse):
(fido::WebCore::extractCredentialIdFromU2fRegistrationResponse):
(fido::WebCore::createAttestedCredentialDataFromU2fRegisterResponse):
(fido::WebCore::parseX509Length):
(fido::WebCore::createFidoAttestationStatementFromU2fRegisterResponse):
(fido::readU2fRegisterResponse):
(fido::readFromU2fSignResponse):
* Modules/webauthn/fido/U2fResponseConverter.h: Added.
* Modules/webgpu/WebGPUCommandBuffer.cpp:
* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:

Source/WebKit:

Moves helper functions to WebAuthenticationUtils.

* UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm:
(WebKit::LocalAuthenticatorInternal::produceHashSet):
(WebKit::LocalAuthenticator::continueMakeCredentialAfterAttested):
(): Deleted.
(WebKit::LocalAuthenticatorInternal::buildAuthData): Deleted.
* UIProcess/WebAuthentication/Mock/MockHidConnection.cpp:
(WebKit::MockHidConnection::feedReports):

Tools:

Adds API tests.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebCore/CtapResponseTest.cpp:
(TestWebKitAPI::getTestAttestedCredentialDataBytes):
(TestWebKitAPI::getTestAuthenticatorDataBytes):
(TestWebKitAPI::getTestAttestationObjectBytes):
(TestWebKitAPI::getTestSignResponse):
(TestWebKitAPI::getTestU2fRegisterResponse):
(TestWebKitAPI::TEST):
(TestWebKitAPI::convertToVector): Deleted.
* TestWebKitAPI/Tests/WebCore/FidoTestData.h:
* TestWebKitAPI/Tests/WebCore/U2fCommandConstructorTest.cpp: Added.
(TestWebKitAPI::constructMakeCredentialRequest):
(TestWebKitAPI::constructGetAssertionRequest):
(TestWebKitAPI::TEST):

LayoutTests:

Fixes Bug 183534:
7) Change little endian bytes (ARM and x86 default) to big endian as requested by the spec.

* http/wpt/webauthn/public-key-credential-create-success-hid.https.html:
* http/wpt/webauthn/public-key-credential-get-success-hid.https.html:
* http/wpt/webauthn/resources/util.js:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@239665 268f45cc-cd09-0410-ab3c-d52691b4dbfc

26 files changed:
LayoutTests/ChangeLog
LayoutTests/http/wpt/webauthn/public-key-credential-create-success-hid.https.html
LayoutTests/http/wpt/webauthn/public-key-credential-get-success-hid.https.html
LayoutTests/http/wpt/webauthn/resources/util.js
Source/WebCore/ChangeLog
Source/WebCore/Modules/webauthn/AuthenticatorCoordinator.cpp
Source/WebCore/Modules/webauthn/WebAuthenticationConstants.h [moved from Source/WebCore/Modules/webauthn/COSEConstants.h with 64% similarity]
Source/WebCore/Modules/webauthn/WebAuthenticationUtils.cpp [new file with mode: 0644]
Source/WebCore/Modules/webauthn/WebAuthenticationUtils.h [new file with mode: 0644]
Source/WebCore/Modules/webauthn/fido/DeviceResponseConverter.cpp
Source/WebCore/Modules/webauthn/fido/FidoConstants.h
Source/WebCore/Modules/webauthn/fido/U2fCommandConstructor.cpp [new file with mode: 0644]
Source/WebCore/Modules/webauthn/fido/U2fCommandConstructor.h [new file with mode: 0644]
Source/WebCore/Modules/webauthn/fido/U2fResponseConverter.cpp [new file with mode: 0644]
Source/WebCore/Modules/webauthn/fido/U2fResponseConverter.h [new file with mode: 0644]
Source/WebCore/Modules/webgpu/WebGPUCommandBuffer.cpp
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm
Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidConnection.cpp
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebCore/CtapResponseTest.cpp
Tools/TestWebKitAPI/Tests/WebCore/FidoTestData.h
Tools/TestWebKitAPI/Tests/WebCore/U2fCommandConstructorTest.cpp [new file with mode: 0644]

index d6da6ca..f6d6282 100644 (file)
@@ -1,3 +1,18 @@
+2019-01-06  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Import U2F command/response converters from Chromium
+        https://bugs.webkit.org/show_bug.cgi?id=193150
+        <rdar://problem/47054028>
+
+        Reviewed by Brent Fulgham.
+
+        Fixes Bug 183534:
+        7) Change little endian bytes (ARM and x86 default) to big endian as requested by the spec.
+
+        * http/wpt/webauthn/public-key-credential-create-success-hid.https.html:
+        * http/wpt/webauthn/public-key-credential-get-success-hid.https.html:
+        * http/wpt/webauthn/resources/util.js:
+
 2019-01-05  Zalan Bujtas  <zalan@apple.com>
 
         Incorrect clipping across compositing boundary.
index 957c274..5d93b80 100644 (file)
@@ -22,7 +22,7 @@
         const attestationObject = CBOR.decode(credential.response.attestationObject);
         assert_equals(attestationObject.fmt, "packed");
         // Check authData
-        const authData = decodeAuthData(attestationObject.authData, false);
+        const authData = decodeAuthData(attestationObject.authData);
         assert_equals(bytesToHexString(authData.rpIdHash), "46cc7fb9679d55b2db9092e1c8d9e5e1d02b7580f0b4812c770962e1e48f5ad8");
         assert_equals(authData.flags, 65);
         assert_equals(authData.counter, 78);
index 1f13ded..67db831 100644 (file)
@@ -18,7 +18,7 @@
         assert_equals(credential.response.userHandle, null);
 
         // Check authData
-        const authData = decodeAuthData(new Uint8Array(credential.response.authenticatorData), false);
+        const authData = decodeAuthData(new Uint8Array(credential.response.authenticatorData));
         assert_equals(bytesToHexString(authData.rpIdHash), "46cc7fb9679d55b2db9092e1c8d9e5e1d02b7580f0b4812c770962e1e48f5ad8");
         assert_equals(authData.flags, 1);
         assert_equals(authData.counter, 80);
index cd5980d..83f4b48 100644 (file)
@@ -134,7 +134,7 @@ function hexStringToUint8Array(hexString)
     return arrayBuffer;
 }
 
-function decodeAuthData(authDataUint8Array, littleEndian = true)
+function decodeAuthData(authDataUint8Array)
 {
     let authDataObject = { };
     let pos = 0;
@@ -158,10 +158,7 @@ function decodeAuthData(authDataUint8Array, littleEndian = true)
     size = 4;
     if (pos + size > authDataUint8Array.byteLength)
         return { };
-    if (littleEndian)
-        authDataObject.counter = new Uint32Array(authDataUint8Array.slice(pos, pos + size))[0];
-    else
-        authDataObject.counter = (authDataUint8Array[pos] << 24) + (authDataUint8Array[pos + 1] << 16) + (authDataUint8Array[pos + 2] << 8) + authDataUint8Array[pos + 3];
+    authDataObject.counter = (authDataUint8Array[pos] << 24) + (authDataUint8Array[pos + 1] << 16) + (authDataUint8Array[pos + 2] << 8) + authDataUint8Array[pos + 3];
     pos = pos + size;
 
     if (pos == authDataUint8Array.byteLength)
@@ -178,10 +175,7 @@ function decodeAuthData(authDataUint8Array, littleEndian = true)
     size = 2;
     if (pos + size > authDataUint8Array.byteLength)
         return { };
-    if (littleEndian)
-        authDataObject.l = new Uint16Array(authDataUint8Array.slice(pos, pos + size))[0];
-    else
-        authDataObject.l = (authDataUint8Array[pos] << 8) + authDataUint8Array[pos + 1];
+    authDataObject.l = (authDataUint8Array[pos] << 8) + authDataUint8Array[pos + 1];
     pos = pos + size;
 
     // Credential ID
index 4f0f106..062c5b7 100644 (file)
@@ -1,3 +1,65 @@
+2019-01-06  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Import U2F command/response converters from Chromium
+        https://bugs.webkit.org/show_bug.cgi?id=193150
+        <rdar://problem/47054028>
+
+        Reviewed by Brent Fulgham.
+
+        This patch imports Chromium's U2F command/response converters:
+        https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#u2f-interoperability
+        1. It directly imports the following files and suit them to WebKit's coding style:
+        https://cs.chromium.org/chromium/src/device/fido/u2f_command_constructor.cc?l=1&rcl=db624110317d01efa78cd32e7be1524190e1beb0
+        https://cs.chromium.org/chromium/src/device/fido/u2f_command_constructor.h?rcl=db624110317d01efa78cd32e7be1524190e1beb0
+        https://cs.chromium.org/chromium/src/device/fido/u2f_command_constructor_unittest.cc?rcl=db624110317d01efa78cd32e7be1524190e1beb0
+        2. It gathers the following methods into U2fResponseConverter:
+        AuthenticatorMakeCredentialResponse::CreateFromU2fRegisterResponse()
+        AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse()
+        3. It also updates FidoConstants.h, FidoTestData.h and CtapResponseTest.cpp accordingly.
+
+        Besides importing stuff from Chroimum, it also gathers a bunch of constants and helper functions into WebAuthenticationConstants.h
+        and WebAuthenticationUtils.h. It also fixes Bug 183534: 2) and 7).
+
+        Covered by API tests.
+
+        * Modules/webauthn/AuthenticatorCoordinator.cpp:
+        (WebCore::AuthenticatorCoordinatorInternal::produceClientDataJsonHash):
+        * Modules/webauthn/WebAuthenticationConstants.h: Copied from Source/WebCore/Modules/webauthn/COSEConstants.h.
+        * Modules/webauthn/WebAuthenticationUtils.cpp: Added.
+        (WebCore::convertBytesToVector):
+        (WebCore::produceRpIdHash):
+        (WebCore::encodeES256PublicKeyAsCBOR):
+        (WebCore::buildAttestedCredentialData):
+        (WebCore::buildAuthData):
+        (WebCore::buildAttestationObject):
+        * Modules/webauthn/WebAuthenticationUtils.h: Renamed from Source/WebCore/Modules/webauthn/COSEConstants.h.
+        * Modules/webauthn/fido/DeviceResponseConverter.cpp:
+        (fido::getCredentialId):
+        (fido::readCTAPGetInfoResponse):
+        * Modules/webauthn/fido/FidoConstants.h:
+        * Modules/webauthn/fido/U2fCommandConstructor.cpp: Added.
+        (fido::WebCore::constructU2fRegisterCommand):
+        (fido::WebCore::constructU2fSignCommand):
+        (fido::isConvertibleToU2fRegisterCommand):
+        (fido::isConvertibleToU2fSignCommand):
+        (fido::convertToU2fRegisterCommand):
+        (fido::convertToU2fCheckOnlySignCommand):
+        (fido::convertToU2fSignCommand):
+        (fido::constructBogusU2fRegistrationCommand):
+        * Modules/webauthn/fido/U2fCommandConstructor.h: Added.
+        * Modules/webauthn/fido/U2fResponseConverter.cpp: Added.
+        (fido::WebCore::extractECPublicKeyFromU2fRegistrationResponse):
+        (fido::WebCore::extractCredentialIdFromU2fRegistrationResponse):
+        (fido::WebCore::createAttestedCredentialDataFromU2fRegisterResponse):
+        (fido::WebCore::parseX509Length):
+        (fido::WebCore::createFidoAttestationStatementFromU2fRegisterResponse):
+        (fido::readU2fRegisterResponse):
+        (fido::readFromU2fSignResponse):
+        * Modules/webauthn/fido/U2fResponseConverter.h: Added.
+        * Modules/webgpu/WebGPUCommandBuffer.cpp:
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+
 2019-01-06  David Kilzer  <ddkilzer@apple.com>
 
         Leak of WTF::Function objects in WebCore::CryptoKeyRSA::generatePair() (64-80 bytes each) in com.apple.WebKit.WebContent running WebKit layout tests
index 43f16b0..7830799 100644 (file)
@@ -73,7 +73,6 @@ static Ref<ArrayBuffer> produceClientDataJson(ClientDataType type, const BufferS
 
 static Vector<uint8_t> produceClientDataJsonHash(const ArrayBuffer& clientDataJson)
 {
-    // FIXME: This might be platform dependent.
     auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
     crypto->addBytes(clientDataJson.data(), clientDataJson.byteLength());
     return crypto->computeHash();
@@ -41,3 +41,30 @@ const int64_t ES256 = -7;
 const int64_t P_256 = 1;
 
 } // namespace COSE
+
+namespace WebCore {
+
+// Length of the SHA-256 hash of the RP ID asssociated with the credential:
+// https://www.w3.org/TR/webauthn/#sec-authenticator-data
+const size_t rpIdHashLength = 32;
+
+// Length of the flags:
+// https://www.w3.org/TR/webauthn/#sec-authenticator-data
+const size_t flagsLength = 1;
+
+// Length of the signature counter, 32-bit unsigned big-endian integer:
+// https://www.w3.org/TR/webauthn/#sec-authenticator-data
+const size_t signCounterLength = 4;
+
+// Length of the AAGUID of the authenticator:
+// https://www.w3.org/TR/webauthn/#sec-attested-credential-data
+const size_t aaguidLength = 16;
+
+// Length of the byte length L of Credential ID, 16-bit unsigned big-endian
+// integer: https://www.w3.org/TR/webauthn/#sec-attested-credential-data
+const size_t credentialIdLengthLength = 2;
+
+// Per Section 2.3.5 of http://www.secg.org/sec1-v2.pdf
+const size_t ES256FieldElementLength = 32;
+
+} // namespace WebCore
diff --git a/Source/WebCore/Modules/webauthn/WebAuthenticationUtils.cpp b/Source/WebCore/Modules/webauthn/WebAuthenticationUtils.cpp
new file mode 100644 (file)
index 0000000..1332619
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 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 "WebAuthenticationUtils.h"
+
+#if ENABLE(WEB_AUTHN)
+
+#include "CBORWriter.h"
+#include "WebAuthenticationConstants.h"
+#include <pal/crypto/CryptoDigest.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+Vector<uint8_t> convertBytesToVector(const uint8_t byteArray[], const size_t length)
+{
+    Vector<uint8_t> result;
+    result.append(byteArray, length);
+    return result;
+}
+
+Vector<uint8_t> produceRpIdHash(const String& rpId)
+{
+    auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
+    auto rpIdUtf8 = rpId.utf8();
+    crypto->addBytes(rpIdUtf8.data(), rpIdUtf8.length());
+    return crypto->computeHash();
+}
+
+Vector<uint8_t> encodeES256PublicKeyAsCBOR(Vector<uint8_t>&& x, Vector<uint8_t>&& y)
+{
+    cbor::CBORValue::MapValue publicKeyMap;
+    publicKeyMap[cbor::CBORValue(COSE::kty)] = cbor::CBORValue(COSE::EC2);
+    publicKeyMap[cbor::CBORValue(COSE::alg)] = cbor::CBORValue(COSE::ES256);
+    publicKeyMap[cbor::CBORValue(COSE::crv)] = cbor::CBORValue(COSE::P_256);
+    publicKeyMap[cbor::CBORValue(COSE::x)] = cbor::CBORValue(WTFMove(x));
+    publicKeyMap[cbor::CBORValue(COSE::y)] = cbor::CBORValue(WTFMove(y));
+
+    auto cosePublicKey = cbor::CBORWriter::write(cbor::CBORValue(WTFMove(publicKeyMap)));
+    ASSERT(cosePublicKey);
+    return *cosePublicKey;
+}
+
+Vector<uint8_t> buildAttestedCredentialData(const Vector<uint8_t>& aaguid, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& coseKey)
+{
+    Vector<uint8_t> attestedCredentialData;
+    attestedCredentialData.reserveInitialCapacity(aaguidLength + credentialIdLengthLength + credentialId.size() + coseKey.size());
+
+    // aaguid
+    ASSERT(aaguid.size() == aaguidLength);
+    attestedCredentialData.appendVector(aaguid);
+
+    // credentialIdLength
+    ASSERT(credentialId.size() <= std::numeric_limits<uint16_t>::max());
+    attestedCredentialData.append(credentialId.size() >> 8 & 0xff);
+    attestedCredentialData.append(credentialId.size() & 0xff);
+
+    // credentialId
+    attestedCredentialData.appendVector(credentialId);
+
+    // credentialPublicKey
+    attestedCredentialData.appendVector(coseKey);
+
+    return attestedCredentialData;
+}
+
+Vector<uint8_t> buildAuthData(const String& rpId, const uint8_t flags, const uint32_t counter, const Vector<uint8_t>& optionalAttestedCredentialData)
+{
+    Vector<uint8_t> authData;
+    authData.reserveInitialCapacity(rpIdHashLength + flagsLength + signCounterLength + optionalAttestedCredentialData.size());
+
+    // RP ID hash
+    authData.appendVector(produceRpIdHash(rpId));
+
+    // FLAGS
+    authData.append(flags);
+
+    // COUNTER
+    authData.append(counter >> 24 & 0xff);
+    authData.append(counter >> 16 & 0xff);
+    authData.append(counter >> 8 & 0xff);
+    authData.append(counter & 0xff);
+
+    // ATTESTED CRED. DATA
+    authData.appendVector(optionalAttestedCredentialData);
+
+    return authData;
+}
+
+Vector<uint8_t> buildAttestationObject(Vector<uint8_t>&& authData, String&& format, cbor::CBORValue::MapValue&& statementMap)
+{
+    cbor::CBORValue::MapValue attestationObjectMap;
+    attestationObjectMap[cbor::CBORValue("authData")] = cbor::CBORValue(WTFMove(authData));
+    attestationObjectMap[cbor::CBORValue("fmt")] = cbor::CBORValue(WTFMove(format));
+    attestationObjectMap[cbor::CBORValue("attStmt")] = cbor::CBORValue(WTFMove(statementMap));
+
+    auto attestationObject = cbor::CBORWriter::write(cbor::CBORValue(WTFMove(attestationObjectMap)));
+    ASSERT(attestationObject);
+    return *attestationObject;
+}
+
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_AUTHN)
diff --git a/Source/WebCore/Modules/webauthn/WebAuthenticationUtils.h b/Source/WebCore/Modules/webauthn/WebAuthenticationUtils.h
new file mode 100644 (file)
index 0000000..17f022e
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#if ENABLE(WEB_AUTHN)
+
+#include "CBORValue.h"
+#include <wtf/Forward.h>
+
+namespace WebCore {
+
+WEBCORE_EXPORT Vector<uint8_t> convertBytesToVector(const uint8_t byteArray[], const size_t length);
+
+// Produce a SHA-256 hash of the given RP ID.
+WEBCORE_EXPORT Vector<uint8_t> produceRpIdHash(const String& rpId);
+
+WEBCORE_EXPORT Vector<uint8_t> encodeES256PublicKeyAsCBOR(Vector<uint8_t>&& x, Vector<uint8_t>&& y);
+
+// https://www.w3.org/TR/webauthn/#attested-credential-data
+WEBCORE_EXPORT Vector<uint8_t> buildAttestedCredentialData(const Vector<uint8_t>& aaguid, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& coseKey);
+
+// https://www.w3.org/TR/webauthn/#sec-authenticator-data
+WEBCORE_EXPORT Vector<uint8_t> buildAuthData(const String& rpId, const uint8_t flags, const uint32_t counter, const Vector<uint8_t>& optionalAttestedCredentialData);
+
+// https://www.w3.org/TR/webauthn/#attestation-object
+WEBCORE_EXPORT Vector<uint8_t> buildAttestationObject(Vector<uint8_t>&& authData, String&& format, cbor::CBORValue::MapValue&& statementMap);
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_AUTHN)
index a404a12..d036717 100644 (file)
@@ -35,6 +35,7 @@
 #include "AuthenticatorSupportedOptions.h"
 #include "CBORReader.h"
 #include "CBORWriter.h"
+#include "WebAuthenticationConstants.h"
 #include <wtf/StdSet.h>
 #include <wtf/Vector.h>
 
@@ -65,17 +66,17 @@ CtapDeviceResponseCode getResponseCode(const Vector<uint8_t>& buffer)
 
 static Vector<uint8_t> getCredentialId(const Vector<uint8_t>& authenticatorData)
 {
-    const size_t credentialIdLengthOffset = kRpIdHashLength + kFlagsLength + kSignCounterLength + kAaguidLength;
+    const size_t credentialIdLengthOffset = rpIdHashLength + flagsLength + signCounterLength + aaguidLength;
 
-    if (authenticatorData.size() < credentialIdLengthOffset + kCredentialIdLengthLength)
+    if (authenticatorData.size() < credentialIdLengthOffset + credentialIdLengthLength)
         return { };
     size_t credentialIdLength = (static_cast<size_t>(authenticatorData[credentialIdLengthOffset]) << 8) | static_cast<size_t>(authenticatorData[credentialIdLengthOffset + 1]);
 
-    if (authenticatorData.size() < credentialIdLengthOffset + kCredentialIdLengthLength + credentialIdLength)
+    if (authenticatorData.size() < credentialIdLengthOffset + credentialIdLengthLength + credentialIdLength)
         return { };
     Vector<uint8_t> credentialId;
     credentialId.reserveInitialCapacity(credentialIdLength);
-    auto beginIt = authenticatorData.begin() + credentialIdLengthOffset + kCredentialIdLengthLength;
+    auto beginIt = authenticatorData.begin() + credentialIdLengthOffset + credentialIdLengthLength;
     credentialId.appendRange(beginIt, beginIt + credentialIdLength);
     return credentialId;
 }
@@ -205,7 +206,7 @@ Optional<AuthenticatorGetInfoResponse> readCTAPGetInfoResponse(const Vector<uint
         return WTF::nullopt;
 
     it = responseMap.find(CBOR(3));
-    if (it == responseMap.end() || !it->second.isByteString() || it->second.getByteString().size() != kAaguidLength)
+    if (it == responseMap.end() || !it->second.isByteString() || it->second.getByteString().size() != aaguidLength)
         return WTF::nullopt;
 
     AuthenticatorGetInfoResponse response(WTFMove(protocolVersions), Vector<uint8_t>(it->second.getByteString()));
index 69b9199..530a174 100644 (file)
@@ -41,25 +41,14 @@ enum class ProtocolVersion {
     kUnknown,
 };
 
-// Length of the SHA-256 hash of the RP ID asssociated with the credential:
-// https://www.w3.org/TR/webauthn/#sec-authenticator-data
-constexpr size_t kRpIdHashLength = 32;
-
-// Length of the flags:
-// https://www.w3.org/TR/webauthn/#sec-authenticator-data
-constexpr size_t kFlagsLength = 1;
-
-// Length of the signature counter, 32-bit unsigned big-endian integer:
-// https://www.w3.org/TR/webauthn/#sec-authenticator-data
-constexpr size_t kSignCounterLength = 4;
-
-// Length of the AAGUID of the authenticator:
-// https://www.w3.org/TR/webauthn/#sec-attested-credential-data
-constexpr size_t kAaguidLength = 16;
-
-// Length of the byte length L of Credential ID, 16-bit unsigned big-endian
-// integer: https://www.w3.org/TR/webauthn/#sec-attested-credential-data
-constexpr size_t kCredentialIdLengthLength = 2;
+// Length of the U2F challenge/application parameter:
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#registration-request-message---u2f_register
+constexpr size_t kU2fChallengeParamLength = 32;
+constexpr size_t kU2fApplicationParamLength = 32;
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#registration-response-message-success
+constexpr size_t kReservedLength = 1;
+constexpr size_t kU2fKeyHandleLengthOffset = 66;
+constexpr size_t kU2fKeyHandleOffset = 67;
 
 // CTAP protocol device response code, as specified in
 // https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#error-responses
@@ -134,6 +123,19 @@ enum class FidoHidDeviceCommand : uint8_t {
 
 bool isFidoHidDeviceCommand(FidoHidDeviceCommand);
 
+// Parameters for fake U2F registration used to check for user presence.
+const uint8_t kBogusAppParam[] = {
+    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41
+};
+
+const uint8_t kBogusChallenge[] = {
+    0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+    0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+    0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42
+};
+
 // String key values for CTAP request optional parameters and
 // AuthenticatorGetInfo response.
 const char kResidentKeyMapKey[] = "rk";
@@ -170,6 +172,15 @@ const size_t kHidMaxMessageSize = 7609;
 // CTAP/U2F devices only provide a single report so specify a report ID of 0 here.
 const uint8_t kHidReportId = 0x00;
 
+// U2F APDU encoding constants, as specified in
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#authentication-messages
+
+// P1 instructions.
+constexpr uint8_t kP1EnforceUserPresenceAndSign = 0x03;
+constexpr uint8_t kP1CheckOnly = 0x07;
+
+constexpr size_t kMaxKeyHandleLength = 255;
+
 // Authenticator API commands supported by CTAP devices, as specified in
 // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#authenticator-api
 enum class CtapRequestCommand : uint8_t {
@@ -181,6 +192,16 @@ enum class CtapRequestCommand : uint8_t {
     kAuthenticatorReset = 0x07,
 };
 
+// APDU instruction code for U2F request encoding.
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#command-and-parameter-values
+enum class U2fApduInstruction : uint8_t {
+    kRegister = 0x01,
+    kSign = 0x02,
+    kVersion = 0x03,
+    kVendorFirst = 0x40,
+    kVenderLast = 0xBF,
+};
+
 // String key values for attestation object as a response to MakeCredential
 // request.
 const char kFormatKey[] = "fmt";
diff --git a/Source/WebCore/Modules/webauthn/fido/U2fCommandConstructor.cpp b/Source/WebCore/Modules/webauthn/fido/U2fCommandConstructor.cpp
new file mode 100644 (file)
index 0000000..1606ab0
--- /dev/null
@@ -0,0 +1,129 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright (C) 2019 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:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * 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.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+// OWNER OR 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 "U2fCommandConstructor.h"
+
+#if ENABLE(WEB_AUTHN)
+
+#include "ApduCommand.h"
+#include "FidoConstants.h"
+#include "PublicKeyCredentialCreationOptions.h"
+#include "PublicKeyCredentialRequestOptions.h"
+#include "UserVerificationRequirement.h"
+#include "WebAuthenticationConstants.h"
+#include "WebAuthenticationUtils.h"
+
+namespace fido {
+using namespace WebCore;
+
+namespace {
+
+static Vector<uint8_t> constructU2fRegisterCommand(const Vector<uint8_t>& applicationParameter, const Vector<uint8_t>& challengeParameter)
+{
+    Vector<uint8_t> data;
+    data.reserveInitialCapacity(kU2fChallengeParamLength + kU2fApplicationParamLength);
+    data.appendVector(challengeParameter);
+    data.appendVector(applicationParameter);
+
+    apdu::ApduCommand command;
+    command.setIns(static_cast<uint8_t>(U2fApduInstruction::kRegister));
+    command.setData(WTFMove(data));
+    command.setResponseLength(apdu::ApduCommand::kApduMaxResponseLength);
+    return command.getEncodedCommand();
+}
+
+static Optional<Vector<uint8_t>> constructU2fSignCommand(const Vector<uint8_t>& applicationParameter, const Vector<uint8_t>& challengeParameter, const Vector<uint8_t>& keyHandle, bool checkOnly)
+{
+    if (keyHandle.size() > kMaxKeyHandleLength)
+        return WTF::nullopt;
+
+    Vector<uint8_t> data;
+    data.reserveInitialCapacity(kU2fChallengeParamLength + kU2fApplicationParamLength + 1 + keyHandle.size());
+    data.appendVector(challengeParameter);
+    data.appendVector(applicationParameter);
+    data.append(static_cast<uint8_t>(keyHandle.size()));
+    data.appendVector(keyHandle);
+
+    apdu::ApduCommand command;
+    command.setIns(static_cast<uint8_t>(U2fApduInstruction::kSign));
+    command.setP1(checkOnly ? kP1CheckOnly : kP1EnforceUserPresenceAndSign);
+    command.setData(WTFMove(data));
+    command.setResponseLength(apdu::ApduCommand::kApduMaxResponseLength);
+    return command.getEncodedCommand();
+}
+
+} // namespace
+
+bool isConvertibleToU2fRegisterCommand(const PublicKeyCredentialCreationOptions& request)
+{
+    if (request.authenticatorSelection && (request.authenticatorSelection->userVerification == UserVerificationRequirement::Required || request.authenticatorSelection->requireResidentKey))
+        return false;
+    if (request.pubKeyCredParams.findMatching([](auto& item) { return item.alg == COSE::ES256; }) == notFound)
+        return false;
+    return true;
+}
+
+bool isConvertibleToU2fSignCommand(const PublicKeyCredentialRequestOptions& request)
+{
+    return (request.userVerification != UserVerificationRequirement::Required) && !request.allowCredentials.isEmpty();
+}
+
+Optional<Vector<uint8_t>> convertToU2fRegisterCommand(const Vector<uint8_t>& clientDataHash, const PublicKeyCredentialCreationOptions& request)
+{
+    if (!isConvertibleToU2fRegisterCommand(request))
+        return WTF::nullopt;
+
+    return constructU2fRegisterCommand(produceRpIdHash(request.rp.id), clientDataHash);
+}
+
+Optional<Vector<uint8_t>> convertToU2fCheckOnlySignCommand(const Vector<uint8_t>& clientDataHash, const PublicKeyCredentialCreationOptions& request, const PublicKeyCredentialDescriptor& keyHandle)
+{
+    if (keyHandle.type != PublicKeyCredentialType::PublicKey)
+        return WTF::nullopt;
+
+    return constructU2fSignCommand(produceRpIdHash(request.rp.id), clientDataHash, keyHandle.idVector, true /* checkOnly */);
+}
+
+Optional<Vector<uint8_t>> convertToU2fSignCommand(const Vector<uint8_t>& clientDataHash, const PublicKeyCredentialRequestOptions& request, const Vector<uint8_t>& keyHandle, bool checkOnly)
+{
+    if (!isConvertibleToU2fSignCommand(request))
+        return WTF::nullopt;
+
+    return constructU2fSignCommand(produceRpIdHash(request.rpId), clientDataHash, keyHandle, checkOnly);
+}
+
+Vector<uint8_t> constructBogusU2fRegistrationCommand()
+{
+    return constructU2fRegisterCommand(convertBytesToVector(kBogusAppParam, sizeof(kBogusAppParam)), convertBytesToVector(kBogusChallenge, sizeof(kBogusChallenge)));
+}
+
+} // namespace fido
+
+#endif // ENABLE(WEB_AUTHN)
diff --git a/Source/WebCore/Modules/webauthn/fido/U2fCommandConstructor.h b/Source/WebCore/Modules/webauthn/fido/U2fCommandConstructor.h
new file mode 100644 (file)
index 0000000..45ab365
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright (C) 2019 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:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * 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.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+// OWNER OR 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.
+
+#pragma once
+
+#if ENABLE(WEB_AUTHN)
+
+#include <wtf/Forward.h>
+#include <wtf/Optional.h>
+
+namespace WebCore {
+struct PublicKeyCredentialCreationOptions;
+struct PublicKeyCredentialDescriptor;
+struct PublicKeyCredentialRequestOptions;
+}
+
+namespace fido {
+
+// Checks whether the request can be translated to valid U2F request
+// parameter. Namely, U2F request does not support resident key and
+// user verification, and ES256 algorithm must be used for public key
+// credential.
+// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#u2f-authenticatorMakeCredential-interoperability
+WEBCORE_EXPORT bool isConvertibleToU2fRegisterCommand(const WebCore::PublicKeyCredentialCreationOptions&);
+
+// Checks whether user verification is not required and that allow list is
+// not empty.
+// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#u2f-authenticatorGetAssertion-interoperability
+WEBCORE_EXPORT bool isConvertibleToU2fSignCommand(const WebCore::PublicKeyCredentialRequestOptions&);
+
+// Extracts APDU encoded U2F register command from PublicKeyCredentialCreationOptions.
+WEBCORE_EXPORT Optional<Vector<uint8_t>> convertToU2fRegisterCommand(const Vector<uint8_t>& clientDataHash, const WebCore::PublicKeyCredentialCreationOptions&);
+
+// Extracts APDU encoded U2F check only sign command from
+// PublicKeyCredentialCreationOptions. Invoked when U2F register operation includes key
+// handles in exclude list.
+WEBCORE_EXPORT Optional<Vector<uint8_t>> convertToU2fCheckOnlySignCommand(const Vector<uint8_t>& clientDataHash, const WebCore::PublicKeyCredentialCreationOptions&, const WebCore::PublicKeyCredentialDescriptor&);
+
+// Extracts APDU encoded U2F sign command from PublicKeyCredentialRequestOptions.
+WEBCORE_EXPORT Optional<Vector<uint8_t>> convertToU2fSignCommand(const Vector<uint8_t>& clientDataHash, const WebCore::PublicKeyCredentialRequestOptions&, const Vector<uint8_t>& keyHandle, bool checkOnly = false);
+
+WEBCORE_EXPORT Vector<uint8_t> constructBogusU2fRegistrationCommand();
+
+} // namespace fido
+
+#endif // ENABLE(WEB_AUTHN)
diff --git a/Source/WebCore/Modules/webauthn/fido/U2fResponseConverter.cpp b/Source/WebCore/Modules/webauthn/fido/U2fResponseConverter.cpp
new file mode 100644 (file)
index 0000000..c45a171
--- /dev/null
@@ -0,0 +1,194 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright (C) 2019 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:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * 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.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+// OWNER OR 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 "U2fResponseConverter.h"
+
+#if ENABLE(WEB_AUTHN)
+
+#include "CommonCryptoDERUtilities.h"
+#include "FidoConstants.h"
+#include "WebAuthenticationConstants.h"
+#include "WebAuthenticationUtils.h"
+
+namespace fido {
+using namespace WebCore;
+
+namespace {
+
+// In a U2F registration response, the key is in X9.62 format:
+// - a constant 0x04 prefix to indicate an uncompressed key
+// - the 32-byte x coordinate
+// - the 32-byte y coordinate.
+const uint8_t uncompressedKey = 0x04;
+// https://www.w3.org/TR/webauthn/#flags
+const uint8_t makeCredentialFlags = 0b01000001; // UP and AT are set.
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#registration-response-message-success
+const uint8_t minSignatureLength = 71;
+const uint8_t maxSignatureLength = 73;
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#authentication-response-message-success
+const size_t flagIndex = 0;
+const size_t counterIndex = 1;
+const size_t signatureIndex = 5;
+
+static Vector<uint8_t> extractECPublicKeyFromU2fRegistrationResponse(const Vector<uint8_t>& u2fData)
+{
+    size_t pos = kReservedLength;
+    if (u2fData.size() <= pos || u2fData[pos] != uncompressedKey)
+        return { };
+    pos++;
+
+    if (u2fData.size() < pos + 2 * ES256FieldElementLength)
+        return { };
+
+    Vector<uint8_t> x;
+    x.append(u2fData.data() + pos, ES256FieldElementLength);
+    pos += ES256FieldElementLength;
+
+    Vector<uint8_t> y;
+    y.append(u2fData.data() + pos, ES256FieldElementLength);
+
+    return encodeES256PublicKeyAsCBOR(WTFMove(x), WTFMove(y));
+}
+
+static Vector<uint8_t> extractCredentialIdFromU2fRegistrationResponse(const Vector<uint8_t>& u2fData)
+{
+    size_t pos = kU2fKeyHandleLengthOffset;
+    if (u2fData.size() <= pos)
+        return { };
+    size_t credentialIdLength = u2fData[pos];
+    pos++;
+
+    if (u2fData.size() < pos + credentialIdLength)
+        return { };
+    Vector<uint8_t> credentialId;
+    credentialId.append(u2fData.data() + pos, credentialIdLength);
+    return credentialId;
+}
+
+static Vector<uint8_t> createAttestedCredentialDataFromU2fRegisterResponse(const Vector<uint8_t>& u2fData, const Vector<uint8_t>& publicKey)
+{
+    auto credentialId = extractCredentialIdFromU2fRegistrationResponse(u2fData);
+    if (credentialId.isEmpty())
+        return { };
+
+    return buildAttestedCredentialData(Vector<uint8_t>(aaguidLength, 0), credentialId, publicKey);
+}
+
+static size_t parseX509Length(const Vector<uint8_t>& u2fData, size_t offset)
+{
+    if (u2fData.size() <= offset || u2fData[offset] != SequenceMark)
+        return 0;
+    offset++;
+
+    if (u2fData.size() <= offset)
+        return 0;
+    const auto sequenceLengthLength = bytesUsedToEncodedLength(u2fData[offset]);
+
+    if (sequenceLengthLength > sizeof(size_t) || (u2fData.size() < offset + sequenceLengthLength))
+        return 0;
+    size_t sequenceLength = sequenceLengthLength == 1 ? u2fData[offset] : 0;
+    offset++;
+    for (auto i = sequenceLengthLength - 1; i; i--, offset++)
+        sequenceLength += u2fData[offset] << (i - 1) * 8;
+
+    return sequenceLength + sequenceLengthLength + sizeof(SequenceMark);
+}
+
+static cbor::CBORValue::MapValue createFidoAttestationStatementFromU2fRegisterResponse(const Vector<uint8_t>& u2fData, size_t offset)
+{
+    auto x509Length = parseX509Length(u2fData, offset);
+    if (!x509Length || u2fData.size() < offset + x509Length)
+        return { };
+
+    Vector<uint8_t> x509;
+    x509.append(u2fData.data() + offset, x509Length);
+    offset += x509Length;
+
+    Vector<uint8_t> signature;
+    signature.append(u2fData.data() + offset, u2fData.size() - offset);
+    if (signature.size() < minSignatureLength || signature.size() > maxSignatureLength)
+        return { };
+
+    cbor::CBORValue::MapValue attestationStatementMap;
+    attestationStatementMap[cbor::CBORValue("sig")] = cbor::CBORValue(WTFMove(signature));
+    Vector<cbor::CBORValue> cborArray;
+    cborArray.append(cbor::CBORValue(WTFMove(x509)));
+    attestationStatementMap[cbor::CBORValue("x5c")] = cbor::CBORValue(WTFMove(cborArray));
+
+    return attestationStatementMap;
+}
+
+} // namespace
+
+Optional<PublicKeyCredentialData> readU2fRegisterResponse(const String& rpId, const Vector<uint8_t>& u2fData)
+{
+    auto publicKey = extractECPublicKeyFromU2fRegistrationResponse(u2fData);
+    if (publicKey.isEmpty())
+        return WTF::nullopt;
+
+    auto attestedCredentialData = createAttestedCredentialDataFromU2fRegisterResponse(u2fData, publicKey);
+    if (attestedCredentialData.isEmpty())
+        return WTF::nullopt;
+
+    // Extract the credentialId for packing into the response data.
+    auto credentialId = extractCredentialIdFromU2fRegistrationResponse(u2fData);
+    ASSERT(!credentialId.isEmpty());
+
+    // The counter is zeroed out for Register requests.
+    auto authData = buildAuthData(rpId, makeCredentialFlags, 0, attestedCredentialData);
+
+    auto fidoAttestationStatement = createFidoAttestationStatementFromU2fRegisterResponse(u2fData, kU2fKeyHandleOffset + credentialId.size());
+    if (fidoAttestationStatement.empty())
+        return WTF::nullopt;
+
+    auto attestationObject = buildAttestationObject(WTFMove(authData), "fido-u2f", WTFMove(fidoAttestationStatement));
+
+    return PublicKeyCredentialData { ArrayBuffer::create(credentialId.data(), credentialId.size()), true, nullptr, ArrayBuffer::create(attestationObject.data(), attestationObject.size()), nullptr, nullptr, nullptr };
+}
+
+Optional<PublicKeyCredentialData> readFromU2fSignResponse(const String& rpId, const Vector<uint8_t>& keyHandle, const Vector<uint8_t>& u2fData)
+{
+    if (keyHandle.isEmpty() || u2fData.size() <= signatureIndex)
+        return WTF::nullopt;
+
+    // 1 byte flags, 4 bytes counter
+    auto flags = u2fData[flagIndex];
+    uint32_t counter = u2fData[counterIndex] << 24;
+    counter += u2fData[counterIndex + 1] << 16;
+    counter += u2fData[counterIndex + 2] << 8;
+    counter += u2fData[counterIndex + 3];
+    auto authData = buildAuthData(rpId, flags, counter, { });
+
+    return PublicKeyCredentialData { ArrayBuffer::create(keyHandle.data(), keyHandle.size()), false, nullptr, nullptr, ArrayBuffer::create(authData.data(), authData.size()), ArrayBuffer::create(u2fData.data() + signatureIndex, u2fData.size() - signatureIndex), nullptr };
+}
+
+} // namespace fido
+
+#endif // ENABLE(WEB_AUTHN)
diff --git a/Source/WebCore/Modules/webauthn/fido/U2fResponseConverter.h b/Source/WebCore/Modules/webauthn/fido/U2fResponseConverter.h
new file mode 100644 (file)
index 0000000..d6a41f8
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright (C) 2019 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:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * 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.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+// OWNER OR 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.
+
+#pragma once
+
+#if ENABLE(WEB_AUTHN)
+
+#include "PublicKeyCredentialData.h"
+#include <wtf/Forward.h>
+
+namespace fido {
+
+// Converts a U2F register response to WebAuthN makeCredential response.
+// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#u2f-authenticatorMakeCredential-interoperability
+WEBCORE_EXPORT Optional<WebCore::PublicKeyCredentialData> readU2fRegisterResponse(const String& rpId, const Vector<uint8_t>& u2fData);
+
+// Converts a U2F authentication response to WebAuthN getAssertion response.
+// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#u2f-authenticatorGetAssertion-interoperability
+WEBCORE_EXPORT Optional<WebCore::PublicKeyCredentialData> readFromU2fSignResponse(const String& rpId, const Vector<uint8_t>& keyHandle, const Vector<uint8_t>& u2fData);
+
+} // namespace fido
+
+#endif // ENABLE(WEB_AUTHN)
index b6ecaaa..1a41053 100644 (file)
@@ -31,6 +31,7 @@
 #include "GPUCommandBuffer.h"
 #include "GPURenderPassDescriptor.h"
 #include "GPURenderPassEncoder.h"
+#include "GPURenderPipeline.h"
 #include "Logging.h"
 #include "WebGPURenderPassDescriptor.h"
 #include "WebGPURenderPassEncoder.h"
index 69edce3..27b0b20 100644 (file)
@@ -256,6 +256,7 @@ Modules/webaudio/WaveShaperProcessor.cpp
 Modules/webauthn/AuthenticatorCoordinator.cpp
 Modules/webauthn/AuthenticatorCoordinatorClient.cpp
 Modules/webauthn/PublicKeyCredential.cpp
+Modules/webauthn/WebAuthenticationUtils.cpp
 Modules/webauthn/apdu/ApduCommand.cpp
 Modules/webauthn/apdu/ApduResponse.cpp
 Modules/webauthn/cbor/CBORReader.cpp
@@ -269,6 +270,8 @@ Modules/webauthn/fido/FidoConstants.cpp
 Modules/webauthn/fido/FidoHidMessage.cpp
 Modules/webauthn/fido/FidoHidPacket.cpp
 Modules/webauthn/fido/FidoParsingUtils.cpp
+Modules/webauthn/fido/U2fCommandConstructor.cpp
+Modules/webauthn/fido/U2fResponseConverter.cpp
 
 Modules/webdatabase/ChangeVersionWrapper.cpp
 Modules/webdatabase/DOMWindowWebDatabase.cpp
index f57164b..c1748b6 100644 (file)
                571252691E524EB1008FF369 /* CryptoAlgorithmAES_CFB.h in Headers */ = {isa = PBXBuildFile; fileRef = 571252681E524EB1008FF369 /* CryptoAlgorithmAES_CFB.h */; };
                57152B5A21CB3E88000C37CA /* ApduCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 57152B5821CB2E3B000C37CA /* ApduCommand.h */; settings = {ATTRIBUTES = (Private, ); }; };
                57152B5C21CC1902000C37CA /* ApduResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 57152B5621CB2E3A000C37CA /* ApduResponse.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               57152B6E21CD995D000C37CA /* U2fCommandConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = 57152B6C21CD995C000C37CA /* U2fCommandConstructor.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               57152B7C21DD8BA1000C37CA /* U2fResponseConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = 57152B7A21DD8BA1000C37CA /* U2fResponseConverter.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               57152B8021DDA581000C37CA /* WebAuthenticationUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 57152B7E21DDA581000C37CA /* WebAuthenticationUtils.h */; settings = {ATTRIBUTES = (Private, ); }; };
                571F21891DA57C54005C9EFD /* JSSubtleCrypto.h in Headers */ = {isa = PBXBuildFile; fileRef = 571F21881DA57C54005C9EFD /* JSSubtleCrypto.h */; };
                572093D31DDCEB9A00310AB0 /* CryptoAlgorithmAesCbcCfbParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 572093D21DDCEB9A00310AB0 /* CryptoAlgorithmAesCbcCfbParams.h */; };
                5721A9871ECE53B10081295A /* CryptoDigestAlgorithm.h in Headers */ = {isa = PBXBuildFile; fileRef = 5721A9861ECE53B10081295A /* CryptoDigestAlgorithm.h */; };
                573489391DAC6B6E00DC0667 /* CryptoAlgorithmParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = 573489381DAC6B6D00DC0667 /* CryptoAlgorithmParameters.h */; };
                5739E12F1DAC7F7800E14383 /* JSCryptoAlgorithmParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = 5739E12E1DAC7F7800E14383 /* JSCryptoAlgorithmParameters.h */; };
                573F5332216806E10045587A /* FidoHidMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 57BAF28A2167D303008E954E /* FidoHidMessage.h */; settings = {ATTRIBUTES = (Private, ); }; };
-               573F533721680D150045587A /* FidoParsingUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 573F533521680D140045587A /* FidoParsingUtils.h */; };
-               574F55E1204F3B23002948C6 /* COSEConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 574F55DC204F3732002948C6 /* COSEConstants.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               573F533721680D150045587A /* FidoParsingUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 573F533521680D140045587A /* FidoParsingUtils.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               574F55E1204F3B23002948C6 /* WebAuthenticationConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 574F55DC204F3732002948C6 /* WebAuthenticationConstants.h */; settings = {ATTRIBUTES = (Private, ); }; };
                5750A9751E68D00000705C4A /* CryptoKeyEC.h in Headers */ = {isa = PBXBuildFile; fileRef = 5750A9731E68D00000705C4A /* CryptoKeyEC.h */; };
                5750A97E1E6A13EF00705C4A /* CryptoAlgorithmEcKeyParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 5750A97D1E6A13EF00705C4A /* CryptoAlgorithmEcKeyParams.h */; };
                5750A9821E6A150800705C4A /* JSEcKeyParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 5750A9801E6A150800705C4A /* JSEcKeyParams.h */; };
                57152B5621CB2E3A000C37CA /* ApduResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ApduResponse.h; path = Modules/webauthn/apdu/ApduResponse.h; sourceTree = SOURCE_ROOT; };
                57152B5721CB2E3A000C37CA /* ApduCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ApduCommand.cpp; path = Modules/webauthn/apdu/ApduCommand.cpp; sourceTree = SOURCE_ROOT; };
                57152B5821CB2E3B000C37CA /* ApduCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ApduCommand.h; path = Modules/webauthn/apdu/ApduCommand.h; sourceTree = SOURCE_ROOT; };
+               57152B6A21CD995C000C37CA /* U2fCommandConstructor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = U2fCommandConstructor.cpp; sourceTree = "<group>"; };
+               57152B6C21CD995C000C37CA /* U2fCommandConstructor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = U2fCommandConstructor.h; sourceTree = "<group>"; };
+               57152B7A21DD8BA1000C37CA /* U2fResponseConverter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = U2fResponseConverter.h; sourceTree = "<group>"; };
+               57152B7B21DD8BA1000C37CA /* U2fResponseConverter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = U2fResponseConverter.cpp; sourceTree = "<group>"; };
+               57152B7E21DDA581000C37CA /* WebAuthenticationUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebAuthenticationUtils.h; sourceTree = "<group>"; };
+               57152B7F21DDA581000C37CA /* WebAuthenticationUtils.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = WebAuthenticationUtils.cpp; sourceTree = "<group>"; };
                571F21881DA57C54005C9EFD /* JSSubtleCrypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSSubtleCrypto.h; sourceTree = "<group>"; };
                571F218A1DA57C7A005C9EFD /* JSSubtleCrypto.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSSubtleCrypto.cpp; sourceTree = "<group>"; };
                572093D11DDCEA4B00310AB0 /* AesCbcCfbParams.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = AesCbcCfbParams.idl; sourceTree = "<group>"; };
                573F533621680D150045587A /* FidoParsingUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FidoParsingUtils.cpp; sourceTree = "<group>"; };
                574AC7531DAC367D00E9744C /* CryptoAlgorithmParameters.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CryptoAlgorithmParameters.idl; sourceTree = "<group>"; };
                574D42791D594FF6002CF50E /* GlobalCrypto.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GlobalCrypto.idl; sourceTree = "<group>"; };
-               574F55DC204F3732002948C6 /* COSEConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = COSEConstants.h; sourceTree = "<group>"; };
+               574F55DC204F3732002948C6 /* WebAuthenticationConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebAuthenticationConstants.h; sourceTree = "<group>"; };
                574F55E2204F3CBF002948C6 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; };
                5750A9721E68D00000705C4A /* CryptoKeyEC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CryptoKeyEC.cpp; sourceTree = "<group>"; };
                5750A9731E68D00000705C4A /* CryptoKeyEC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoKeyEC.h; sourceTree = "<group>"; };
                                578A4C0A2167D29600D08F34 /* FidoHidPacket.h */,
                                573F533621680D150045587A /* FidoParsingUtils.cpp */,
                                573F533521680D140045587A /* FidoParsingUtils.h */,
+                               57152B6A21CD995C000C37CA /* U2fCommandConstructor.cpp */,
+                               57152B6C21CD995C000C37CA /* U2fCommandConstructor.h */,
+                               57152B7B21DD8BA1000C37CA /* U2fResponseConverter.cpp */,
+                               57152B7A21DD8BA1000C37CA /* U2fResponseConverter.h */,
                        );
                        path = fido;
                        sourceTree = "<group>";
                                57303BD120087A8300355965 /* AuthenticatorResponse.idl */,
                                57DCED8C21487EDB0016B847 /* AuthenticatorTransport.h */,
                                57DCED8E21487EDB0016B847 /* AuthenticatorTransport.idl */,
-                               574F55DC204F3732002948C6 /* COSEConstants.h */,
                                57D8462C1FEAF68F00CA3682 /* PublicKeyCredential.cpp */,
                                57D8462B1FEAF68F00CA3682 /* PublicKeyCredential.h */,
                                57D8462D1FEAF68F00CA3682 /* PublicKeyCredential.idl */,
                                57303BF02009846100355965 /* PublicKeyCredentialType.idl */,
                                572B402321768D85000AD43E /* UserVerificationRequirement.h */,
                                572B402521768D85000AD43E /* UserVerificationRequirement.idl */,
+                               574F55DC204F3732002948C6 /* WebAuthenticationConstants.h */,
+                               57152B7F21DDA581000C37CA /* WebAuthenticationUtils.cpp */,
+                               57152B7E21DDA581000C37CA /* WebAuthenticationUtils.h */,
                        );
                        path = webauthn;
                        sourceTree = "<group>";
                                3F8020371E9E47C500DEC61D /* CoreAudioCaptureDeviceManager.h in Headers */,
                                07AFF4221EFB144900B545B3 /* CoreAudioCaptureSourceIOS.h in Headers */,
                                CD7D33481C7A16BF00041293 /* CoreVideoSoftLink.h in Headers */,
-                               574F55E1204F3B23002948C6 /* COSEConstants.h in Headers */,
                                862F129E18C1576F005C54AF /* CountedUserActivity.h in Headers */,
                                A80E6D040A1989CA007FB8C5 /* Counter.h in Headers */,
                                BC5EB9790E82069200B25965 /* CounterContent.h in Headers */,
                                4BAFD0E1219242A000C0AB64 /* TypedOMCSSUnitValue.h in Headers */,
                                4BAFD0D921921EA000C0AB64 /* TypedOMCSSUnparsedValue.h in Headers */,
                                93309E1A099E64920056E581 /* TypingCommand.h in Headers */,
+                               57152B6E21CD995D000C37CA /* U2fCommandConstructor.h in Headers */,
+                               57152B7C21DD8BA1000C37CA /* U2fResponseConverter.h in Headers */,
                                85031B4E0A44EFC700F992E0 /* UIEvent.h in Headers */,
                                83FE7CA71DA9F1A70037237C /* UIEventInit.h in Headers */,
                                85031B4F0A44EFC700F992E0 /* UIEventWithKeyState.h in Headers */,
                                417F7AEE2139BF6800FBA7EC /* WebAudioBufferList.h in Headers */,
                                41B2A6261EF1BF6D002B9D7A /* WebAudioSourceProvider.h in Headers */,
                                07D637401BB0B11300256CE9 /* WebAudioSourceProviderAVFObjC.h in Headers */,
+                               574F55E1204F3B23002948C6 /* WebAuthenticationConstants.h in Headers */,
+                               57152B8021DDA581000C37CA /* WebAuthenticationUtils.h in Headers */,
                                1F36EA9C1E21BA1700621E25 /* WebBackgroundTaskController.h in Headers */,
                                A5B81CB51FAA44620037D1E6 /* WebConsoleAgent.h in Headers */,
                                9BBA2CAB1F679E0C00FD1C1E /* WebContentReader.h in Headers */,
index 0335e45..3b6f5a9 100644 (file)
@@ -1,3 +1,21 @@
+2019-01-06  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Import U2F command/response converters from Chromium
+        https://bugs.webkit.org/show_bug.cgi?id=193150
+        <rdar://problem/47054028>
+
+        Reviewed by Brent Fulgham.
+
+        Moves helper functions to WebAuthenticationUtils.
+
+        * UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm:
+        (WebKit::LocalAuthenticatorInternal::produceHashSet):
+        (WebKit::LocalAuthenticator::continueMakeCredentialAfterAttested):
+        (): Deleted.
+        (WebKit::LocalAuthenticatorInternal::buildAuthData): Deleted.
+        * UIProcess/WebAuthentication/Mock/MockHidConnection.cpp:
+        (WebKit::MockHidConnection::feedReports):
+
 2019-01-04  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Use save sheet instead of dialog where possible
index 0257c0d..efc3e16 100644 (file)
 
 #import <Security/SecItem.h>
 #import <WebCore/CBORWriter.h>
-#import <WebCore/COSEConstants.h>
 #import <WebCore/ExceptionData.h>
 #import <WebCore/PublicKeyCredentialCreationOptions.h>
 #import <WebCore/PublicKeyCredentialData.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>
@@ -50,50 +51,10 @@ 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.
-// FIXME(rdar://problem/38320512): Define Apple AAGUID.
-const uint8_t AAGUID[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 16 bytes
 // Credential ID is currently SHA-1 of the corresponding public key.
-// FIXME(183534): Assume little endian here.
-const union {
-    uint16_t integer;
-    uint8_t bytes[2];
-} credentialIdLength = {0x0014};
-const size_t ES256KeySizeInBytes = 32;
-const size_t authDataPrefixFixedSize = 37; // hash(32) + flags(1) + counter(4)
+const uint16_t credentialIdLength = 20;
 
 #if PLATFORM(IOS_FAMILY)
-// https://www.w3.org/TR/webauthn/#sec-authenticator-data
-static Vector<uint8_t> buildAuthData(const String& rpId, const uint8_t flags, uint32_t counter, const Vector<uint8_t>& optionalAttestedCredentialData)
-{
-    Vector<uint8_t> authData;
-    authData.reserveInitialCapacity(authDataPrefixFixedSize + optionalAttestedCredentialData.size());
-
-    // RP ID hash
-    auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
-    // FIXME(183534): Test IDN.
-    ASSERT(rpId.isAllASCII());
-    auto asciiRpId = rpId.ascii();
-    crypto->addBytes(asciiRpId.data(), asciiRpId.length());
-    authData = crypto->computeHash();
-
-    // FLAGS
-    authData.append(flags);
-
-    // COUNTER
-    // FIXME(183534): Assume little endian here.
-    union {
-        uint32_t integer;
-        uint8_t bytes[4];
-    } counterUnion;
-    counterUnion.integer = counter;
-    authData.append(counterUnion.bytes, sizeof(counterUnion.bytes));
-
-    // ATTESTED CRED. DATA
-    authData.appendVector(optionalAttestedCredentialData);
-
-    return authData;
-}
-
 static inline bool emptyTransportsOrContain(const Vector<AuthenticatorTransport>& transports, AuthenticatorTransport target)
 {
     return transports.isEmpty() ? true : transports.contains(target);
@@ -105,7 +66,7 @@ static inline HashSet<String> produceHashSet(const Vector<PublicKeyCredentialDes
     for (auto& credentialDescriptor : credentialDescriptors) {
         if (emptyTransportsOrContain(credentialDescriptor.transports, AuthenticatorTransport::Internal)
             && credentialDescriptor.type == PublicKeyCredentialType::PublicKey
-            && credentialDescriptor.idVector.size() == credentialIdLength.integer)
+            && credentialDescriptor.idVector.size() == credentialIdLength)
             result.add(String(reinterpret_cast<const char*>(credentialDescriptor.idVector.data()), credentialDescriptor.idVector.size()));
     }
     return result;
@@ -307,22 +268,10 @@ void LocalAuthenticator::continueMakeCredentialAfterAttested(SecKeyRef privateKe
     // FIXME(183533): store the counter.
     uint32_t counter = 0;
 
-    // FIXME(183534): attestedCredentialData could throttle.
     // Step 11. https://www.w3.org/TR/webauthn/#attested-credential-data
-    Vector<uint8_t> attestedCredentialData;
+    // credentialPublicKey
+    Vector<uint8_t> cosePublicKey;
     {
-        // aaguid
-        attestedCredentialData.append(AAGUID, sizeof(AAGUID));
-
-        // credentialIdLength
-        ASSERT(credentialId.size() == credentialIdLength.integer);
-        // FIXME(183534): Assume little endian here.
-        attestedCredentialData.append(credentialIdLength.bytes, sizeof(uint16_t));
-
-        // credentialId
-        attestedCredentialData.appendVector(credentialId);
-
-        // credentialPublicKey
         RetainPtr<CFDataRef> publicKeyDataRef;
         {
             auto publicKey = adoptCF(SecKeyCopyPublicKey(privateKey));
@@ -334,29 +283,18 @@ void LocalAuthenticator::continueMakeCredentialAfterAttested(SecKeyRef privateKe
                 receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s });
                 return;
             }
-            ASSERT(((NSData *)publicKeyDataRef.get()).length == (1 + 2 * ES256KeySizeInBytes)); // 04 | X | Y
+            ASSERT(((NSData *)publicKeyDataRef.get()).length == (1 + 2 * ES256FieldElementLength)); // 04 | X | Y
         }
 
         // COSE Encoding
-        // FIXME(183535): Improve CBOR encoder to work with bytes directly.
-        Vector<uint8_t> x(ES256KeySizeInBytes);
-        [(NSData *)publicKeyDataRef.get() getBytes: x.data() range:NSMakeRange(1, ES256KeySizeInBytes)];
-        Vector<uint8_t> y(ES256KeySizeInBytes);
-        [(NSData *)publicKeyDataRef.get() getBytes: y.data() range:NSMakeRange(1 + ES256KeySizeInBytes, ES256KeySizeInBytes)];
-        cbor::CBORValue::MapValue publicKeyMap;
-        publicKeyMap[cbor::CBORValue(COSE::kty)] = cbor::CBORValue(COSE::EC2);
-        publicKeyMap[cbor::CBORValue(COSE::alg)] = cbor::CBORValue(COSE::ES256);
-        publicKeyMap[cbor::CBORValue(COSE::crv)] = cbor::CBORValue(COSE::P_256);
-        publicKeyMap[cbor::CBORValue(COSE::x)] = cbor::CBORValue(WTFMove(x));
-        publicKeyMap[cbor::CBORValue(COSE::y)] = cbor::CBORValue(WTFMove(y));
-        auto cosePublicKey = cbor::CBORWriter::write(cbor::CBORValue(WTFMove(publicKeyMap)));
-        if (!cosePublicKey) {
-            LOG_ERROR("Couldn't encode the public key into COSE binaries.");
-            receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s });
-            return;
-        }
-        attestedCredentialData.appendVector(cosePublicKey.value());
+        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(requestData().creationOptions.rp.id, makeCredentialFlags, counter, attestedCredentialData);
@@ -386,19 +324,9 @@ void LocalAuthenticator::continueMakeCredentialAfterAttested(SecKeyRef privateKe
             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));
 
-    cbor::CBORValue::MapValue attestationObjectMap;
-    attestationObjectMap[cbor::CBORValue("authData")] = cbor::CBORValue(authData);
-    attestationObjectMap[cbor::CBORValue("fmt")] = cbor::CBORValue("Apple");
-    attestationObjectMap[cbor::CBORValue("attStmt")] = cbor::CBORValue(WTFMove(attestationStatementMap));
-    auto attestationObject = cbor::CBORWriter::write(cbor::CBORValue(WTFMove(attestationObjectMap)));
-    if (!attestationObject) {
-        LOG_ERROR("Couldn't encode the attestation object.");
-        receiveRespond(ExceptionData { UnknownError, "Unknown internal error."_s });
-        return;
-    }
-
-    receiveRespond(PublicKeyCredentialData { ArrayBuffer::create(credentialId.data(), credentialId.size()), true, nullptr, ArrayBuffer::create(attestationObject.value().data(), attestationObject.value().size()), nullptr, nullptr, nullptr });
+    receiveRespond(PublicKeyCredentialData { ArrayBuffer::create(credentialId.data(), credentialId.size()), true, nullptr, ArrayBuffer::create(attestationObject.data(), attestationObject.size()), nullptr, nullptr, nullptr });
 #endif // !PLATFORM(IOS_FAMILY)
 }
 
index b7fc808..0b17699 100644 (file)
@@ -31,6 +31,7 @@
 #include <WebCore/AuthenticatorGetInfoResponse.h>
 #include <WebCore/CBORReader.h>
 #include <WebCore/FidoConstants.h>
+#include <WebCore/WebAuthenticationConstants.h>
 #include <wtf/BlockPtr.h>
 #include <wtf/CryptographicallyRandomNumber.h>
 #include <wtf/RunLoop.h>
@@ -202,7 +203,7 @@ void MockHidConnection::feedReports()
 
     Optional<FidoHidMessage> message;
     if (m_stage == Mock::Stage::Info && m_subStage == Mock::SubStage::Msg) {
-        auto infoData = encodeAsCBOR(AuthenticatorGetInfoResponse({ ProtocolVersion::kCtap }, Vector<uint8_t>(kAaguidLength, 0u)));
+        auto infoData = encodeAsCBOR(AuthenticatorGetInfoResponse({ ProtocolVersion::kCtap }, Vector<uint8_t>(aaguidLength, 0u)));
         infoData.insert(0, static_cast<uint8_t>(CtapDeviceResponseCode::kSuccess)); // Prepend status code.
         if (stagesMatch() && m_configuration.hid->error == Mock::Error::WrongChannelId)
             message = FidoHidMessage::create(m_currentChannel - 1, FidoHidDeviceCommand::kCbor, infoData);
index 97104d3..0a92697 100644 (file)
@@ -1,3 +1,28 @@
+2019-01-06  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Import U2F command/response converters from Chromium
+        https://bugs.webkit.org/show_bug.cgi?id=193150
+        <rdar://problem/47054028>
+
+        Reviewed by Brent Fulgham.
+
+        Adds API tests.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebCore/CtapResponseTest.cpp:
+        (TestWebKitAPI::getTestAttestedCredentialDataBytes):
+        (TestWebKitAPI::getTestAuthenticatorDataBytes):
+        (TestWebKitAPI::getTestAttestationObjectBytes):
+        (TestWebKitAPI::getTestSignResponse):
+        (TestWebKitAPI::getTestU2fRegisterResponse):
+        (TestWebKitAPI::TEST):
+        (TestWebKitAPI::convertToVector): Deleted.
+        * TestWebKitAPI/Tests/WebCore/FidoTestData.h:
+        * TestWebKitAPI/Tests/WebCore/U2fCommandConstructorTest.cpp: Added.
+        (TestWebKitAPI::constructMakeCredentialRequest):
+        (TestWebKitAPI::constructGetAssertionRequest):
+        (TestWebKitAPI::TEST):
+
 2019-01-05  Oriol Brufau  <obrufau@igalia.com>
 
         [GTK] Add python3-setuptools, libunistring-dev, bison and flex dependencies
index 65349c5..c966f7b 100644 (file)
                5714ECBB1CA8BFE400051AC8 /* DownloadRequestOriginalURLFrame.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5714ECBA1CA8BFD100051AC8 /* DownloadRequestOriginalURLFrame.html */; };
                5714ECBD1CA8C22A00051AC8 /* DownloadRequestOriginalURL2.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5714ECBC1CA8C21800051AC8 /* DownloadRequestOriginalURL2.html */; };
                57152B5E21CC2045000C37CA /* ApduTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 57152B5D21CC2045000C37CA /* ApduTest.cpp */; };
+               57152B7821DD4E8D000C37CA /* U2fCommandConstructorTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 57D1D75E21DCB7A80093E86A /* U2fCommandConstructorTest.cpp */; };
                571F7FD01F2961FB00946648 /* IndexedDBStructuredCloneBackwardCompatibility.sqlite3-wal in Copy Resources */ = {isa = PBXBuildFile; fileRef = 571F7FCF1F2961E100946648 /* IndexedDBStructuredCloneBackwardCompatibility.sqlite3-wal */; };
                572B403421769A88000AD43E /* CtapRequestTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 572B403321769A88000AD43E /* CtapRequestTest.cpp */; };
                572B404421781B43000AD43E /* CtapResponseTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 572B404321781B42000AD43E /* CtapResponseTest.cpp */; };
                5797FE2F1EB15A5F00B2F4A0 /* NavigationClientDefaultCrypto.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NavigationClientDefaultCrypto.cpp; sourceTree = "<group>"; };
                5797FE321EB15A8900B2F4A0 /* navigation-client-default-crypto.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "navigation-client-default-crypto.html"; sourceTree = "<group>"; };
                5798E2AF1CAF5C2800C5CBA0 /* ProvisionalURLNotChange.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ProvisionalURLNotChange.mm; sourceTree = "<group>"; };
+               57D1D75E21DCB7A80093E86A /* U2fCommandConstructorTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = U2fCommandConstructorTest.cpp; sourceTree = "<group>"; };
                57F10D921C7E7B3800ECDF30 /* IsNavigationActionTrusted.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = IsNavigationActionTrusted.mm; sourceTree = "<group>"; };
                57F4AA9F208FA83D00A68E9E /* SSLKeyGenerator.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SSLKeyGenerator.mm; sourceTree = "<group>"; };
                57F56A5B1C7F8A4000F31D7E /* IsNavigationActionTrusted.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = IsNavigationActionTrusted.html; sourceTree = "<group>"; };
                                93A258981F92FF15003E510C /* TextCodec.cpp */,
                                CDC2C7141797089D00E627FB /* TimeRanges.cpp */,
                                7AD3FE8D1D75FB8D00B169A4 /* TransformationMatrix.cpp */,
+                               57D1D75E21DCB7A80093E86A /* U2fCommandConstructorTest.cpp */,
                                E3A1E77E21B25B39008C6007 /* URLParserTextEncoding.cpp */,
                                9C64DC311D76198A004B598E /* YouTubePluginReplacement.cpp */,
                        );
                                CE3524F91B1441C40028A7C5 /* TextFieldDidBeginAndEndEditing.cpp in Sources */,
                                7CCE7EDD1A411A9200447C4C /* TimeRanges.cpp in Sources */,
                                7CCE7ED31A411A7E00447C4C /* TypingStyleCrash.mm in Sources */,
+                               57152B7821DD4E8D000C37CA /* U2fCommandConstructorTest.cpp in Sources */,
                                5CB40B4E1F4B98D3007DC7B9 /* UIDelegate.mm in Sources */,
                                F46849BE1EEF58E400B937FE /* UIPasteboardTests.mm in Sources */,
                                E3A1E77F21B25B39008C6007 /* URLParserTextEncoding.cpp in Sources */,
index 5aaa05d..de3f209 100644 (file)
 #include <WebCore/CBORWriter.h>
 #include <WebCore/DeviceResponseConverter.h>
 #include <WebCore/FidoConstants.h>
+#include <WebCore/U2fResponseConverter.h>
+#include <WebCore/WebAuthenticationUtils.h>
 
 namespace TestWebKitAPI {
+using namespace WebCore;
 using namespace fido;
 
 constexpr uint8_t kTestAuthenticatorGetInfoResponseWithNoVersion[] = {
@@ -216,10 +219,107 @@ constexpr uint8_t kTestAuthenticatorGetInfoResponseWithIncorrectAaguid[] = {
     0x81, 0x01,
 };
 
+// The attested credential data, excluding the public key bytes. Append
+// with kTestECPublicKeyCOSE to get the complete attestation data.
+constexpr uint8_t kTestAttestedCredentialDataPrefix[] = {
+    // 16-byte aaguid
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    // 2-byte length
+    0x00, 0x40,
+    // 64-byte key handle
+    0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26,
+    0x35, 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3,
+    0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94,
+    0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, 0x39, 0x6B, 0x64,
+    0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08,
+    0xFE, 0x42, 0x00, 0x38,
+};
+
+// The authenticator data, excluding the attested credential data bytes. Append
+// with attested credential data to get the complete authenticator data.
+constexpr uint8_t kTestAuthenticatorDataPrefix[] = {
+    // sha256 hash of rp id.
+    0x11, 0x94, 0x22, 0x8D, 0xA8, 0xFD, 0xBD, 0xEE, 0xFD, 0x26, 0x1B, 0xD7,
+    0xB6, 0x59, 0x5C, 0xFD, 0x70, 0xA5, 0x0D, 0x70, 0xC6, 0x40, 0x7B, 0xCF,
+    0x01, 0x3D, 0xE9, 0x6D, 0x4E, 0xFB, 0x17, 0xDE,
+    // flags (TUP and AT bits set)
+    0x41,
+    // counter
+    0x00, 0x00, 0x00, 0x00
+};
+
+// Components of the CBOR needed to form an authenticator object.
+// Combined diagnostic notation:
+// {"fmt": "fido-u2f", "attStmt": {"sig": h'30...}, "authData": h'D4C9D9...'}
+constexpr uint8_t kFormatFidoU2fCBOR[] = {
+    // map(3)
+    0xA3,
+    // text(3)
+    0x63,
+    // "fmt"
+    0x66, 0x6D, 0x74,
+    // text(8)
+    0x68,
+    // "fido-u2f"
+    0x66, 0x69, 0x64, 0x6F, 0x2D, 0x75, 0x32, 0x66
+};
+
+constexpr uint8_t kAttStmtCBOR[] = {
+    // text(7)
+    0x67,
+    // "attStmt"
+    0x61, 0x74, 0x74, 0x53, 0x74, 0x6D, 0x74
+};
+
+constexpr uint8_t kAuthDataCBOR[] = {
+    // text(8)
+    0x68,
+    // "authData"
+    0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61,
+    // bytes(196). i.e., the authenticator_data byte array corresponding to
+    // kTestAuthenticatorDataPrefix|, |kTestAttestedCredentialDataPrefix|,
+    // and test_data::kTestECPublicKeyCOSE.
+    0x58, 0xC4
+};
+
 constexpr uint8_t kTestDeviceAaguid[] = {
     0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1F, 0x9E, 0xDC, 0x7D
 };
 
+Vector<uint8_t> getTestAttestedCredentialDataBytes()
+{
+    // Combine kTestAttestedCredentialDataPrefix and kTestECPublicKeyCOSE.
+    auto testAttestedData = convertBytesToVector(kTestAttestedCredentialDataPrefix, sizeof(kTestAttestedCredentialDataPrefix));
+    testAttestedData.append(TestData::kTestECPublicKeyCOSE, sizeof(TestData::kTestECPublicKeyCOSE));
+    return testAttestedData;
+}
+
+Vector<uint8_t> getTestAuthenticatorDataBytes()
+{
+    // Build the test authenticator data.
+    auto testAuthenticatorData = convertBytesToVector(kTestAuthenticatorDataPrefix, sizeof(kTestAuthenticatorDataPrefix));
+    auto testAttestedData = getTestAttestedCredentialDataBytes();
+    testAuthenticatorData.appendVector(testAttestedData);
+    return testAuthenticatorData;
+}
+
+Vector<uint8_t> getTestAttestationObjectBytes()
+{
+    auto testAuthenticatorObject = convertBytesToVector(kFormatFidoU2fCBOR, sizeof(kFormatFidoU2fCBOR));
+    testAuthenticatorObject.append(kAttStmtCBOR, sizeof(kAttStmtCBOR));
+    testAuthenticatorObject.append(TestData::kU2fAttestationStatementCBOR, sizeof(TestData::kU2fAttestationStatementCBOR));
+    testAuthenticatorObject.append(kAuthDataCBOR, sizeof(kAuthDataCBOR));
+    auto testAuthenticatorData = getTestAuthenticatorDataBytes();
+    testAuthenticatorObject.appendVector(testAuthenticatorData);
+    return testAuthenticatorObject;
+}
+
+Vector<uint8_t> getTestSignResponse()
+{
+    return convertBytesToVector(TestData::kTestU2fSignResponse, sizeof(TestData::kTestU2fSignResponse));
+}
+
 // Get a subset of the response for testing error handling.
 Vector<uint8_t> getTestCorruptedSignResponse(size_t length)
 {
@@ -239,11 +339,13 @@ Vector<uint8_t> getTestCredentialRawIdBytes()
     return testCredentialRawIdBytes;
 }
 
-Vector<uint8_t> convertToVector(const uint8_t byteArray[], const size_t length)
+// Return a malformed U2fRegisterResponse.
+Vector<uint8_t> getTestU2fRegisterResponse(size_t prefixSize, const uint8_t appendix[], size_t appendixSize)
 {
     Vector<uint8_t> result;
-    result.reserveInitialCapacity(length);
-    result.append(byteArray, length);
+    result.reserveInitialCapacity(prefixSize + appendixSize);
+    result.append(TestData::kTestU2fRegisterResponse, prefixSize);
+    result.append(appendix, appendixSize);
     return result;
 }
 
@@ -251,9 +353,9 @@ Vector<uint8_t> convertToVector(const uint8_t byteArray[], const size_t length)
 // https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#commands
 TEST(CTAPResponseTest, TestReadMakeCredentialResponse)
 {
-    auto makeCredentialResponse = readCTAPMakeCredentialResponse(convertToVector(TestData::kTestMakeCredentialResponse, sizeof(TestData::kTestMakeCredentialResponse)));
+    auto makeCredentialResponse = readCTAPMakeCredentialResponse(convertBytesToVector(TestData::kTestMakeCredentialResponse, sizeof(TestData::kTestMakeCredentialResponse)));
     ASSERT_TRUE(makeCredentialResponse);
-    auto cborAttestationObject = cbor::CBORReader::read(convertToVector(reinterpret_cast<uint8_t*>(makeCredentialResponse->attestationObject->data()), makeCredentialResponse->attestationObject->byteLength()));
+    auto cborAttestationObject = cbor::CBORReader::read(convertBytesToVector(reinterpret_cast<uint8_t*>(makeCredentialResponse->attestationObject->data()), makeCredentialResponse->attestationObject->byteLength()));
     ASSERT_TRUE(cborAttestationObject);
     ASSERT_TRUE(cborAttestationObject->isMap());
 
@@ -266,7 +368,7 @@ TEST(CTAPResponseTest, TestReadMakeCredentialResponse)
     it = attestationObjectMap.find(cbor::CBORValue(kAuthDataKey));
     ASSERT_TRUE(it != attestationObjectMap.end());
     ASSERT_TRUE(it->second.isByteString());
-    EXPECT_EQ(it->second.getByteString(), convertToVector(TestData::kCtap2MakeCredentialAuthData, sizeof(TestData::kCtap2MakeCredentialAuthData)));
+    EXPECT_EQ(it->second.getByteString(), convertBytesToVector(TestData::kCtap2MakeCredentialAuthData, sizeof(TestData::kCtap2MakeCredentialAuthData)));
 
     it = attestationObjectMap.find(cbor::CBORValue(kAttestationStatementKey));
     ASSERT_TRUE(it != attestationObjectMap.end());
@@ -282,7 +384,7 @@ TEST(CTAPResponseTest, TestReadMakeCredentialResponse)
     attStmtIt = attestationStatementMap.find(cbor::CBORValue("sig"));
     ASSERT_TRUE(attStmtIt != attestationStatementMap.end());
     ASSERT_TRUE(attStmtIt->second.isByteString());
-    EXPECT_EQ(attStmtIt->second.getByteString(), convertToVector(TestData::kCtap2MakeCredentialSignature, sizeof(TestData::kCtap2MakeCredentialSignature)));
+    EXPECT_EQ(attStmtIt->second.getByteString(), convertBytesToVector(TestData::kCtap2MakeCredentialSignature, sizeof(TestData::kCtap2MakeCredentialSignature)));
 
     attStmtIt = attestationStatementMap.find(cbor::CBORValue("x5c"));
     ASSERT_TRUE(attStmtIt != attestationStatementMap.end());
@@ -290,7 +392,7 @@ TEST(CTAPResponseTest, TestReadMakeCredentialResponse)
     ASSERT_TRUE(certificate.isArray());
     ASSERT_EQ(certificate.getArray().size(), 1u);
     ASSERT_TRUE(certificate.getArray()[0].isByteString());
-    EXPECT_EQ(certificate.getArray()[0].getByteString(), convertToVector(TestData::kCtap2MakeCredentialCertificate, sizeof(TestData::kCtap2MakeCredentialCertificate)));
+    EXPECT_EQ(certificate.getArray()[0].getByteString(), convertBytesToVector(TestData::kCtap2MakeCredentialCertificate, sizeof(TestData::kCtap2MakeCredentialCertificate)));
     EXPECT_EQ(makeCredentialResponse->rawId->byteLength(), sizeof(TestData::kCtap2MakeCredentialCredentialId));
     EXPECT_EQ(memcmp(makeCredentialResponse->rawId->data(), TestData::kCtap2MakeCredentialCredentialId, sizeof(TestData::kCtap2MakeCredentialCredentialId)), 0);
 }
@@ -299,7 +401,7 @@ TEST(CTAPResponseTest, TestReadMakeCredentialResponse)
 // https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html
 TEST(CTAPResponseTest, TestReadGetAssertionResponse)
 {
-    auto getAssertionResponse = readCTAPGetAssertionResponse(convertToVector(TestData::kDeviceGetAssertionResponse, sizeof(TestData::kDeviceGetAssertionResponse)));
+    auto getAssertionResponse = readCTAPGetAssertionResponse(convertBytesToVector(TestData::kDeviceGetAssertionResponse, sizeof(TestData::kDeviceGetAssertionResponse)));
     ASSERT_TRUE(getAssertionResponse);
 
     EXPECT_EQ(getAssertionResponse->authenticatorData->byteLength(), sizeof(TestData::kCtap2GetAssertionAuthData));
@@ -308,9 +410,156 @@ TEST(CTAPResponseTest, TestReadGetAssertionResponse)
     EXPECT_EQ(memcmp(getAssertionResponse->signature->data(), TestData::kCtap2GetAssertionSignature, sizeof(TestData::kCtap2GetAssertionSignature)), 0);
 }
 
+// Test that U2F register response is properly parsed.
+TEST(CTAPResponseTest, TestParseRegisterResponseData)
+{
+    auto response = readU2fRegisterResponse(TestData::kRelyingPartyId, convertBytesToVector(TestData::kTestU2fRegisterResponse, sizeof(TestData::kTestU2fRegisterResponse)));
+    ASSERT_TRUE(response);
+    EXPECT_EQ(response->rawId->byteLength(), sizeof(TestData::kU2fSignKeyHandle));
+    EXPECT_EQ(memcmp(response->rawId->data(), TestData::kU2fSignKeyHandle, sizeof(TestData::kU2fSignKeyHandle)), 0);
+    EXPECT_TRUE(response->isAuthenticatorAttestationResponse);
+    auto expectedAttestationObject = getTestAttestationObjectBytes();
+    EXPECT_EQ(response->attestationObject->byteLength(), expectedAttestationObject.size());
+    EXPECT_EQ(memcmp(response->attestationObject->data(), expectedAttestationObject.data(), expectedAttestationObject.size()), 0);
+}
+
+// Test malformed user public key.
+TEST(CTAPResponseTest, TestParseIncorrectRegisterResponseData1)
+{
+    const uint8_t testData1[] = { 0x05 };
+    auto response = readU2fRegisterResponse(TestData::kRelyingPartyId, convertBytesToVector(testData1, sizeof(testData1)));
+    EXPECT_FALSE(response);
+
+    const uint8_t testData2[] = { 0x05, 0x00 };
+    response = readU2fRegisterResponse(TestData::kRelyingPartyId, convertBytesToVector(testData2, sizeof(testData2)));
+    EXPECT_FALSE(response);
+
+    const uint8_t testData3[] = { 0x05, 0x04, 0x00 };
+    response = readU2fRegisterResponse(TestData::kRelyingPartyId, convertBytesToVector(testData3, sizeof(testData3)));
+    EXPECT_FALSE(response);
+}
+
+// Test malformed key handle.
+TEST(CTAPResponseTest, TestParseIncorrectRegisterResponseData2)
+{
+    auto response = readU2fRegisterResponse(TestData::kRelyingPartyId, getTestU2fRegisterResponse(kU2fKeyHandleLengthOffset, nullptr, 0));
+    EXPECT_FALSE(response);
+
+    const uint8_t testData[] = { 0x40 };
+    response = readU2fRegisterResponse(TestData::kRelyingPartyId, getTestU2fRegisterResponse(kU2fKeyHandleLengthOffset, testData, sizeof(testData)));
+    EXPECT_FALSE(response);
+}
+
+// Test malformed X.509.
+TEST(CTAPResponseTest, TestParseIncorrectRegisterResponseData3)
+{
+    const auto prefix = kU2fKeyHandleOffset + 64;
+    auto response = readU2fRegisterResponse(TestData::kRelyingPartyId, getTestU2fRegisterResponse(prefix, nullptr, 0));
+    EXPECT_FALSE(response);
+
+    const uint8_t testData1[] = { 0x40 };
+    response = readU2fRegisterResponse(TestData::kRelyingPartyId, getTestU2fRegisterResponse(prefix, testData1, sizeof(testData1)));
+    EXPECT_FALSE(response);
+
+    const uint8_t testData2[] = { 0x30 };
+    response = readU2fRegisterResponse(TestData::kRelyingPartyId, getTestU2fRegisterResponse(prefix, testData2, sizeof(testData2)));
+    EXPECT_FALSE(response);
+
+    const uint8_t testData3[] = { 0x30, 0x82 };
+    response = readU2fRegisterResponse(TestData::kRelyingPartyId, getTestU2fRegisterResponse(prefix, testData3, sizeof(testData3)));
+    EXPECT_FALSE(response);
+
+    const uint8_t testData4[] = { 0x30, 0xC1 };
+    response = readU2fRegisterResponse(TestData::kRelyingPartyId, getTestU2fRegisterResponse(prefix, testData4, sizeof(testData4)));
+    EXPECT_FALSE(response);
+
+    const uint8_t testData5[] = { 0x30, 0x82, 0x02, 0x4A };
+    response = readU2fRegisterResponse(TestData::kRelyingPartyId, getTestU2fRegisterResponse(prefix, testData5, sizeof(testData5)));
+    EXPECT_FALSE(response);
+}
+
+// Test malformed signature.
+TEST(CTAPResponseTest, TestParseIncorrectRegisterResponseData4)
+{
+    const auto prefix = sizeof(TestData::kTestU2fRegisterResponse);
+    auto response = readU2fRegisterResponse(TestData::kRelyingPartyId, getTestU2fRegisterResponse(prefix - 71, nullptr, 0));
+    EXPECT_FALSE(response);
+
+    const uint8_t testData[] = { 0x40, 0x40, 0x40 };
+    response = readU2fRegisterResponse(TestData::kRelyingPartyId, getTestU2fRegisterResponse(prefix, testData, sizeof(testData)));
+    EXPECT_FALSE(response);
+}
+
+// Test malformed X.509 but pass.
+TEST(CTAPResponseTest, TestParseIncorrectRegisterResponseData5)
+{
+    const auto prefix = kU2fKeyHandleOffset + 64;
+    const auto signatureSize = 71;
+    const auto suffix = sizeof(TestData::kTestU2fRegisterResponse) - signatureSize;
+
+    Vector<uint8_t> testData1;
+    testData1.append(TestData::kTestU2fRegisterResponse, prefix);
+    testData1.append(0x30);
+    testData1.append(0x01);
+    testData1.append(0x00);
+    testData1.append(TestData::kTestU2fRegisterResponse + suffix, signatureSize);
+    auto response = readU2fRegisterResponse(TestData::kRelyingPartyId, testData1);
+    EXPECT_TRUE(response);
+
+    Vector<uint8_t> testData2;
+    testData2.append(TestData::kTestU2fRegisterResponse, prefix);
+    testData2.append(0x30);
+    testData2.append(0x81);
+    testData2.append(0x01);
+    testData2.append(0x00);
+    testData2.append(TestData::kTestU2fRegisterResponse + suffix, signatureSize);
+    response = readU2fRegisterResponse(TestData::kRelyingPartyId, testData2);
+    EXPECT_TRUE(response);
+}
+
+// Tests that U2F authenticator data is properly serialized.
+TEST(CTAPResponseTest, TestParseSignResponseData)
+{
+    auto response = readFromU2fSignResponse(TestData::kRelyingPartyId, getTestCredentialRawIdBytes(), getTestSignResponse());
+    ASSERT_TRUE(response);
+    EXPECT_EQ(response->rawId->byteLength(), sizeof(TestData::kU2fSignKeyHandle));
+    EXPECT_EQ(memcmp(response->rawId->data(), TestData::kU2fSignKeyHandle, sizeof(TestData::kU2fSignKeyHandle)), 0);
+    EXPECT_FALSE(response->isAuthenticatorAttestationResponse);
+    EXPECT_EQ(response->authenticatorData->byteLength(), sizeof(TestData::kTestSignAuthenticatorData));
+    EXPECT_EQ(memcmp(response->authenticatorData->data(), TestData::kTestSignAuthenticatorData, sizeof(TestData::kTestSignAuthenticatorData)), 0);
+    EXPECT_EQ(response->signature->byteLength(), sizeof(TestData::kU2fSignature));
+    EXPECT_EQ(memcmp(response->signature->data(), TestData::kU2fSignature, sizeof(TestData::kU2fSignature)), 0);
+}
+
+TEST(CTAPResponseTest, TestParseU2fSignWithNullKeyHandle)
+{
+    auto response = readFromU2fSignResponse(TestData::kRelyingPartyId, Vector<uint8_t>(), getTestSignResponse());
+    EXPECT_FALSE(response);
+}
+
+TEST(CTAPResponseTest, TestParseU2fSignWithNullResponse)
+{
+    auto response = readFromU2fSignResponse(TestData::kRelyingPartyId, getTestCredentialRawIdBytes(), Vector<uint8_t>());
+    EXPECT_FALSE(response);
+}
+
+TEST(CTAPResponseTest, TestParseU2fSignWithCorruptedCounter)
+{
+    // A sign response of less than 5 bytes.
+    auto response = readFromU2fSignResponse(TestData::kRelyingPartyId, getTestCredentialRawIdBytes(), getTestCorruptedSignResponse(3));
+    EXPECT_FALSE(response);
+}
+
+TEST(CTAPResponseTest, TestParseU2fSignWithCorruptedSignature)
+{
+    // A sign response no more than 5 bytes.
+    auto response = readFromU2fSignResponse(TestData::kRelyingPartyId, getTestCredentialRawIdBytes(), getTestCorruptedSignResponse(5));
+    EXPECT_FALSE(response);
+}
+
 TEST(CTAPResponseTest, TestReadGetInfoResponse)
 {
-    auto getInfoResponse = readCTAPGetInfoResponse(convertToVector(TestData::kTestGetInfoResponsePlatformDevice, sizeof(TestData::kTestGetInfoResponsePlatformDevice)));
+    auto getInfoResponse = readCTAPGetInfoResponse(convertBytesToVector(TestData::kTestGetInfoResponsePlatformDevice, sizeof(TestData::kTestGetInfoResponsePlatformDevice)));
     ASSERT_TRUE(getInfoResponse);
     ASSERT_TRUE(getInfoResponse->maxMsgSize());
     EXPECT_EQ(*getInfoResponse->maxMsgSize(), 1200u);
@@ -325,14 +574,14 @@ TEST(CTAPResponseTest, TestReadGetInfoResponse)
 
 TEST(CTAPResponseTest, TestReadGetInfoResponseWithIncorrectFormat)
 {
-    EXPECT_FALSE(readCTAPGetInfoResponse(convertToVector(kTestAuthenticatorGetInfoResponseWithNoVersion, sizeof(kTestAuthenticatorGetInfoResponseWithNoVersion))));
-    EXPECT_FALSE(readCTAPGetInfoResponse(convertToVector(kTestAuthenticatorGetInfoResponseWithDuplicateVersion, sizeof(kTestAuthenticatorGetInfoResponseWithDuplicateVersion))));
-    EXPECT_FALSE(readCTAPGetInfoResponse(convertToVector(kTestAuthenticatorGetInfoResponseWithIncorrectAaguid, sizeof(kTestAuthenticatorGetInfoResponseWithIncorrectAaguid))));
+    EXPECT_FALSE(readCTAPGetInfoResponse(convertBytesToVector(kTestAuthenticatorGetInfoResponseWithNoVersion, sizeof(kTestAuthenticatorGetInfoResponseWithNoVersion))));
+    EXPECT_FALSE(readCTAPGetInfoResponse(convertBytesToVector(kTestAuthenticatorGetInfoResponseWithDuplicateVersion, sizeof(kTestAuthenticatorGetInfoResponseWithDuplicateVersion))));
+    EXPECT_FALSE(readCTAPGetInfoResponse(convertBytesToVector(kTestAuthenticatorGetInfoResponseWithIncorrectAaguid, sizeof(kTestAuthenticatorGetInfoResponseWithIncorrectAaguid))));
 }
 
 TEST(CTAPResponseTest, TestSerializeGetInfoResponse)
 {
-    AuthenticatorGetInfoResponse response({ ProtocolVersion::kCtap, ProtocolVersion::kU2f }, convertToVector(kTestDeviceAaguid, sizeof(kTestDeviceAaguid)));
+    AuthenticatorGetInfoResponse response({ ProtocolVersion::kCtap, ProtocolVersion::kU2f }, convertBytesToVector(kTestDeviceAaguid, sizeof(kTestDeviceAaguid)));
     response.setExtensions({ "uvm", "hmac-secret" });
     AuthenticatorSupportedOptions options;
     options.setSupportsResidentKey(true);
index 60d37f5..38e03c8 100644 (file)
@@ -42,10 +42,30 @@ namespace TestData {
 constexpr uint8_t kClientDataHash[] = {
     0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xec, 0x17, 0x20, 0x2e, 0x42,
     0x50, 0x5f, 0x8e, 0xd2, 0xb1, 0x6a, 0xe2, 0x2f, 0x16, 0xbb, 0x05,
-    0xb8, 0x8c, 0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41};
+    0xb8, 0x8c, 0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41
+};
 
 constexpr uint8_t kUserId[] = {0x10, 0x98, 0x23, 0x72, 0x35, 0x40, 0x98, 0x72};
 
+constexpr char kRelyingPartyId[] = "acme.com";
+
+constexpr uint8_t kU2fRegisterCommandApdu[] = {
+    // CLA, INS, P1, P2 APDU instructions
+    0x00, 0x01, 0x00, 0x00,
+    // Data length in 3 bytes in big endian order.
+    0x00, 0x00, 0x40,
+    // Challenge parameter -- see kClientDataHash
+    0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xec, 0x17, 0x20, 0x2e, 0x42,
+    0x50, 0x5f, 0x8e, 0xd2, 0xb1, 0x6a, 0xe2, 0x2f, 0x16, 0xbb, 0x05,
+    0xb8, 0x8c, 0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41,
+    // Application parameter
+    0x11, 0x94, 0x22, 0x8D, 0xA8, 0xFD, 0xBD, 0xEE, 0xFD, 0x26, 0x1B, 0xD7,
+    0xB6, 0x59, 0x5C, 0xFD, 0x70, 0xA5, 0x0D, 0x70, 0xC6, 0x40, 0x7B, 0xCF,
+    0x01, 0x3D, 0xE9, 0x6D, 0x4E, 0xFB, 0x17, 0xDE,
+    // Max response length
+    0x00, 0x00,
+};
+
 // Sample U2F sign request parameters used in example 7 of the CTAP spec.
 // https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#using-the-ctap2-authenticatormakecredential-command-with-ctap1-u2f-authenticators
 constexpr uint8_t kU2fSignKeyHandle[] = {
@@ -57,8 +77,169 @@ constexpr uint8_t kU2fSignKeyHandle[] = {
     0xCC, 0xD4, 0x15, 0xCD, 0x08, 0xFE, 0x42, 0x00, 0x38,
 };
 
+// Signed signature of above |kU2fSignKeyHandle|.
+constexpr uint8_t kU2fSignature[] = {
+    0x30, 0x44, 0x02, 0x20, 0x7B, 0xDE, 0x0A, 0x52, 0xAC, 0x1F, 0x4C, 0x8B,
+    0x27, 0xE0, 0x03, 0xA3, 0x70, 0xCD, 0x66, 0xA4, 0xC7, 0x11, 0x8D, 0xD2,
+    0x2D, 0x54, 0x47, 0x83, 0x5F, 0x45, 0xB9, 0x9C, 0x68, 0x42, 0x3F, 0xF7,
+    0x02, 0x20, 0x3C, 0x51, 0x7B, 0x47, 0x87, 0x7F, 0x85, 0x78, 0x2D, 0xE1,
+    0x00, 0x86, 0xA7, 0x83, 0xD1, 0xE7, 0xDF, 0x4E, 0x36, 0x39, 0xE7, 0x71,
+    0xF5, 0xF6, 0xAF, 0xA3, 0x5A, 0xAD, 0x53, 0x73, 0x85, 0x8E,
+};
+
+constexpr uint8_t kU2fSignCommandApdu[] = {
+    // CLA, INS, P1, P2 APDU instruction parameters
+    0x00, 0x02, 0x03, 0x00,
+    // Data Length (3 bytes in big endian order)
+    0x00, 0x00, 0x81,
+    // Challenge parameter -- see kClientDataHash
+    0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xec, 0x17, 0x20, 0x2e, 0x42,
+    0x50, 0x5f, 0x8e, 0xd2, 0xb1, 0x6a, 0xe2, 0x2f, 0x16, 0xbb, 0x05,
+    0xb8, 0x8c, 0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41,
+    // Application parameter
+    0x11, 0x94, 0x22, 0x8D, 0xA8, 0xFD, 0xBD, 0xEE, 0xFD, 0x26, 0x1B, 0xD7,
+    0xB6, 0x59, 0x5C, 0xFD, 0x70, 0xA5, 0x0D, 0x70, 0xC6, 0x40, 0x7B, 0xCF,
+    0x01, 0x3D, 0xE9, 0x6D, 0x4E, 0xFB, 0x17, 0xDE,
+    // Key handle length
+    0x40,
+    // Key handle
+    0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26,
+    0x35, 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3,
+    0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94,
+    0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, 0x39, 0x6B, 0x64,
+    0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08,
+    0xFE, 0x42, 0x00, 0x38,
+    // Max response length
+    0x00, 0x00,
+};
+
+constexpr uint8_t kU2fCheckOnlySignCommandApdu[] = {
+    // CLA, INS, P1, P2 APDU instruction parameters
+    0x00, 0x02, 0x07, 0x00,
+    // Data Length (3 bytes in big endian order).
+    0x00, 0x00, 0x81,
+    // Challenge parameter -- see kClientDataHash
+    0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xec, 0x17, 0x20, 0x2e, 0x42,
+    0x50, 0x5f, 0x8e, 0xd2, 0xb1, 0x6a, 0xe2, 0x2f, 0x16, 0xbb, 0x05,
+    0xb8, 0x8c, 0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41,
+    // Application parameter
+    0x11, 0x94, 0x22, 0x8D, 0xA8, 0xFD, 0xBD, 0xEE, 0xFD, 0x26, 0x1B, 0xD7,
+    0xB6, 0x59, 0x5C, 0xFD, 0x70, 0xA5, 0x0D, 0x70, 0xC6, 0x40, 0x7B, 0xCF,
+    0x01, 0x3D, 0xE9, 0x6D, 0x4E, 0xFB, 0x17, 0xDE,
+    // Key handle length
+    0x40,
+    // Key handle
+    0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26,
+    0x35, 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3,
+    0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94,
+    0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, 0x39, 0x6B, 0x64,
+    0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08,
+    0xFE, 0x42, 0x00, 0x38,
+    // Max response length
+    0x00, 0x00,
+};
+
+constexpr uint8_t kU2fFakeRegisterCommand[] = {
+    // CLA, INS, P1, P2 APDU instructions
+    0x00, 0x01, 0x00, 0x00,
+    // Data length in 3 bytes in big endian order.
+    0x00, 0x00, 0x40,
+    // Bogus challenge parameter
+    0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+    0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+    0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+    // Bogus application parameter
+    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+    // Maximum response length
+    0x00, 0x00,
+};
+
 // U2F responses ---------------------------------------------------------------
 
+// U2F response blob produced by a U2F registration request used in example 6
+// of the CTAP spec.
+// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html
+constexpr uint8_t kTestU2fRegisterResponse[] = {
+    // Reserved byte
+    0x05,
+    // User public key
+    0x04, 0xE8, 0x76, 0x25, 0x89, 0x6E, 0xE4, 0xE4, 0x6D, 0xC0, 0x32, 0x76,
+    0x6E, 0x80, 0x87, 0x96, 0x2F, 0x36, 0xDF, 0x9D, 0xFE, 0x8B, 0x56, 0x7F,
+    0x37, 0x63, 0x01, 0x5B, 0x19, 0x90, 0xA6, 0x0E, 0x14, 0x27, 0xDE, 0x61,
+    0x2D, 0x66, 0x41, 0x8B, 0xDA, 0x19, 0x50, 0x58, 0x1E, 0xBC, 0x5C, 0x8C,
+    0x1D, 0xAD, 0x71, 0x0C, 0xB1, 0x4C, 0x22, 0xF8, 0xC9, 0x70, 0x45, 0xF4,
+    0x61, 0x2F, 0xB2, 0x0C, 0x91,
+    // Key handle length
+    0x40,
+    // Key handle
+    0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26,
+    0x35, 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3,
+    0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94,
+    0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, 0x39, 0x6B, 0x64,
+    0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08,
+    0xFE, 0x42, 0x00, 0x38,
+    // X.509 Certificate
+    0x30, 0x82, 0x02, 0x4A, 0x30, 0x82, 0x01, 0x32, 0xA0, 0x03,
+    0x02, 0x01, 0x02, 0x02, 0x04, 0x04, 0x6C, 0x88, 0x22, 0x30, 0x0D, 0x06,
+    0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00,
+    0x30, 0x2E, 0x31, 0x2C, 0x30, 0x2A, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+    0x23, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6F, 0x20, 0x55, 0x32, 0x46, 0x20,
+    0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69,
+    0x61, 0x6C, 0x20, 0x34, 0x35, 0x37, 0x32, 0x30, 0x30, 0x36, 0x33, 0x31,
+    0x30, 0x20, 0x17, 0x0D, 0x31, 0x34, 0x30, 0x38, 0x30, 0x31, 0x30, 0x30,
+    0x30, 0x30, 0x30, 0x30, 0x5A, 0x18, 0x0F, 0x32, 0x30, 0x35, 0x30, 0x30,
+    0x39, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x30, 0x2C,
+    0x31, 0x2A, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x21, 0x59,
+    0x75, 0x62, 0x69, 0x63, 0x6F, 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, 0x45,
+    0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, 0x32, 0x34, 0x39, 0x31,
+    0x38, 0x32, 0x33, 0x32, 0x34, 0x37, 0x37, 0x30, 0x30, 0x59, 0x30, 0x13,
+    0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A,
+    0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x3C,
+    0xCA, 0xB9, 0x2C, 0xCB, 0x97, 0x28, 0x7E, 0xE8, 0xE6, 0x39, 0x43, 0x7E,
+    0x21, 0xFC, 0xD6, 0xB6, 0xF1, 0x65, 0xB2, 0xD5, 0xA3, 0xF3, 0xDB, 0x13,
+    0x1D, 0x31, 0xC1, 0x6B, 0x74, 0x2B, 0xB4, 0x76, 0xD8, 0xD1, 0xE9, 0x90,
+    0x80, 0xEB, 0x54, 0x6C, 0x9B, 0xBD, 0xF5, 0x56, 0xE6, 0x21, 0x0F, 0xD4,
+    0x27, 0x85, 0x89, 0x9E, 0x78, 0xCC, 0x58, 0x9E, 0xBE, 0x31, 0x0F, 0x6C,
+    0xDB, 0x9F, 0xF4, 0xA3, 0x3B, 0x30, 0x39, 0x30, 0x22, 0x06, 0x09, 0x2B,
+    0x06, 0x01, 0x04, 0x01, 0x82, 0xC4, 0x0A, 0x02, 0x04, 0x15, 0x31, 0x2E,
+    0x33, 0x2E, 0x36, 0x2E, 0x31, 0x2E, 0x34, 0x2E, 0x31, 0x2E, 0x34, 0x31,
+    0x34, 0x38, 0x32, 0x2E, 0x31, 0x2E, 0x32, 0x30, 0x13, 0x06, 0x0B, 0x2B,
+    0x06, 0x01, 0x04, 0x01, 0x82, 0xE5, 0x1C, 0x02, 0x01, 0x01, 0x04, 0x04,
+    0x03, 0x02, 0x04, 0x30, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
+    0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00,
+    0x9F, 0x9B, 0x05, 0x22, 0x48, 0xBC, 0x4C, 0xF4, 0x2C, 0xC5, 0x99, 0x1F,
+    0xCA, 0xAB, 0xAC, 0x9B, 0x65, 0x1B, 0xBE, 0x5B, 0xDC, 0xDC, 0x8E, 0xF0,
+    0xAD, 0x2C, 0x1C, 0x1F, 0xFB, 0x36, 0xD1, 0x87, 0x15, 0xD4, 0x2E, 0x78,
+    0xB2, 0x49, 0x22, 0x4F, 0x92, 0xC7, 0xE6, 0xE7, 0xA0, 0x5C, 0x49, 0xF0,
+    0xE7, 0xE4, 0xC8, 0x81, 0xBF, 0x2E, 0x94, 0xF4, 0x5E, 0x4A, 0x21, 0x83,
+    0x3D, 0x74, 0x56, 0x85, 0x1D, 0x0F, 0x6C, 0x14, 0x5A, 0x29, 0x54, 0x0C,
+    0x87, 0x4F, 0x30, 0x92, 0xC9, 0x34, 0xB4, 0x3D, 0x22, 0x2B, 0x89, 0x62,
+    0xC0, 0xF4, 0x10, 0xCE, 0xF1, 0xDB, 0x75, 0x89, 0x2A, 0xF1, 0x16, 0xB4,
+    0x4A, 0x96, 0xF5, 0xD3, 0x5A, 0xDE, 0xA3, 0x82, 0x2F, 0xC7, 0x14, 0x6F,
+    0x60, 0x04, 0x38, 0x5B, 0xCB, 0x69, 0xB6, 0x5C, 0x99, 0xE7, 0xEB, 0x69,
+    0x19, 0x78, 0x67, 0x03, 0xC0, 0xD8, 0xCD, 0x41, 0xE8, 0xF7, 0x5C, 0xCA,
+    0x44, 0xAA, 0x8A, 0xB7, 0x25, 0xAD, 0x8E, 0x79, 0x9F, 0xF3, 0xA8, 0x69,
+    0x6A, 0x6F, 0x1B, 0x26, 0x56, 0xE6, 0x31, 0xB1, 0xE4, 0x01, 0x83, 0xC0,
+    0x8F, 0xDA, 0x53, 0xFA, 0x4A, 0x8F, 0x85, 0xA0, 0x56, 0x93, 0x94, 0x4A,
+    0xE1, 0x79, 0xA1, 0x33, 0x9D, 0x00, 0x2D, 0x15, 0xCA, 0xBD, 0x81, 0x00,
+    0x90, 0xEC, 0x72, 0x2E, 0xF5, 0xDE, 0xF9, 0x96, 0x5A, 0x37, 0x1D, 0x41,
+    0x5D, 0x62, 0x4B, 0x68, 0xA2, 0x70, 0x7C, 0xAD, 0x97, 0xBC, 0xDD, 0x17,
+    0x85, 0xAF, 0x97, 0xE2, 0x58, 0xF3, 0x3D, 0xF5, 0x6A, 0x03, 0x1A, 0xA0,
+    0x35, 0x6D, 0x8E, 0x8D, 0x5E, 0xBC, 0xAD, 0xC7, 0x4E, 0x07, 0x16, 0x36,
+    0xC6, 0xB1, 0x10, 0xAC, 0xE5, 0xCC, 0x9B, 0x90, 0xDF, 0xEA, 0xCA, 0xE6,
+    0x40, 0xFF, 0x1B, 0xB0, 0xF1, 0xFE, 0x5D, 0xB4, 0xEF, 0xF7, 0xA9, 0x5F,
+    0x06, 0x07, 0x33, 0xF5,
+    // Signature
+    0x30, 0x45, 0x02, 0x20, 0x32, 0x47, 0x79, 0xC6, 0x8F, 0x33, 0x80, 0x28,
+    0x8A, 0x11, 0x97, 0xB6, 0x09, 0x5F, 0x7A, 0x6E, 0xB9, 0xB1, 0xB1, 0xC1,
+    0x27, 0xF6, 0x6A, 0xE1, 0x2A, 0x99, 0xFE, 0x85, 0x32, 0xEC, 0x23, 0xB9,
+    0x02, 0x21, 0x00, 0xE3, 0x95, 0x16, 0xAC, 0x4D, 0x61, 0xEE, 0x64, 0x04,
+    0x4D, 0x50, 0xB4, 0x15, 0xA6, 0xA4, 0xD4, 0xD8, 0x4B, 0xA6, 0xD8, 0x95,
+    0xCB, 0x5A, 0xB7, 0xA1, 0xAA, 0x7D, 0x08, 0x1D, 0xE3, 0x41, 0xFA,
+};
+
 // EC public key encoded in COSE_Key format extracted from above
 // |kTestU2fRegisterResponse|.
 constexpr uint8_t kTestECPublicKeyCOSE[] = {
@@ -204,6 +385,19 @@ constexpr uint8_t kTestU2fSignResponse[] = {
     0xF5, 0xF6, 0xAF, 0xA3, 0x5A, 0xAD, 0x53, 0x73, 0x85, 0x8E,
 };
 
+// The authenticator data for sign responses extracted from above
+// |kTestU2fSignResponse|.
+constexpr uint8_t kTestSignAuthenticatorData[] = {
+    // SHA256 hash of kTestRelyingPartyId
+    0x11, 0x94, 0x22, 0x8D, 0xA8, 0xFD, 0xBD, 0xEE, 0xFD, 0x26, 0x1B, 0xD7,
+    0xB6, 0x59, 0x5C, 0xFD, 0x70, 0xA5, 0x0D, 0x70, 0xC6, 0x40, 0x7B, 0xCF,
+    0x01, 0x3D, 0xE9, 0x6D, 0x4E, 0xFB, 0x17, 0xDE,
+    // Flags (TUP bit set)
+    0x01,
+    // Counter
+    0x00, 0x00, 0x00, 0x3B,
+};
+
 // CTAP requests ---------------------------------------------------------------
 constexpr uint8_t kCtapMakeCredentialRequest[] = {
     // authenticatorMakeCredential command
diff --git a/Tools/TestWebKitAPI/Tests/WebCore/U2fCommandConstructorTest.cpp b/Tools/TestWebKitAPI/Tests/WebCore/U2fCommandConstructorTest.cpp
new file mode 100644 (file)
index 0000000..7d745f9
--- /dev/null
@@ -0,0 +1,226 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright (C) 2019 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:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * 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.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+// OWNER OR 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"
+
+#if ENABLE(WEB_AUTHN)
+
+#include "FidoTestData.h"
+#include <WebCore/FidoConstants.h>
+#include <WebCore/PublicKeyCredentialCreationOptions.h>
+#include <WebCore/PublicKeyCredentialRequestOptions.h>
+#include <WebCore/U2fCommandConstructor.h>
+#include <WebCore/WebAuthenticationConstants.h>
+#include <WebCore/WebAuthenticationUtils.h>
+
+namespace TestWebKitAPI {
+using namespace WebCore;
+using namespace fido;
+
+PublicKeyCredentialCreationOptions constructMakeCredentialRequest()
+{
+    PublicKeyCredentialCreationOptions::RpEntity rp;
+    rp.id = "acme.com";
+    rp.name = "acme.com";
+
+    PublicKeyCredentialCreationOptions::UserEntity user;
+    user.idVector = convertBytesToVector(TestData::kUserId, sizeof(TestData::kUserId));
+    user.name = "johnpsmith@example.com";
+    user.displayName = "John P. Smith";
+    user.icon = "https://pics.acme.com/00/p/aBjjjpqPb.png";
+
+    PublicKeyCredentialCreationOptions::Parameters params;
+    params.type = PublicKeyCredentialType::PublicKey;
+    params.alg = COSE::ES256;
+
+    PublicKeyCredentialCreationOptions options;
+    options.rp = WTFMove(rp);
+    options.user = WTFMove(user);
+    options.pubKeyCredParams.append(WTFMove(params));
+
+    return options;
+}
+
+PublicKeyCredentialRequestOptions constructGetAssertionRequest()
+{
+    PublicKeyCredentialRequestOptions options;
+    options.rpId = "acme.com";
+    return options;
+}
+
+TEST(U2fCommandConstructorTest, TestConvertCtapMakeCredentialToU2fRegister)
+{
+    const auto makeCredentialParam = constructMakeCredentialRequest();
+
+    EXPECT_TRUE(isConvertibleToU2fRegisterCommand(makeCredentialParam));
+
+    const auto u2fRegisterCommand = convertToU2fRegisterCommand(convertBytesToVector(TestData::kClientDataHash, sizeof(TestData::kClientDataHash)), makeCredentialParam);
+    ASSERT_TRUE(u2fRegisterCommand);
+    EXPECT_EQ(*u2fRegisterCommand, convertBytesToVector(TestData::kU2fRegisterCommandApdu, sizeof(TestData::kU2fRegisterCommandApdu)));
+}
+
+TEST(U2fCommandConstructorTest, TestConvertCtapMakeCredentialToU2fCheckOnlySign)
+{
+    auto makeCredentialParam = constructMakeCredentialRequest();
+    PublicKeyCredentialDescriptor credentialDescriptor;
+    credentialDescriptor.type = PublicKeyCredentialType::PublicKey;
+    credentialDescriptor.idVector = convertBytesToVector(TestData::kU2fSignKeyHandle, sizeof(TestData::kU2fSignKeyHandle));
+    Vector<PublicKeyCredentialDescriptor> excludeList;
+    excludeList.append(credentialDescriptor);
+    makeCredentialParam.excludeCredentials = WTFMove(excludeList);
+    EXPECT_TRUE(isConvertibleToU2fRegisterCommand(makeCredentialParam));
+
+    const auto u2fCheckOnlySign = convertToU2fCheckOnlySignCommand(convertBytesToVector(TestData::kClientDataHash, sizeof(TestData::kClientDataHash)), makeCredentialParam, credentialDescriptor);
+    ASSERT_TRUE(u2fCheckOnlySign);
+    EXPECT_EQ(*u2fCheckOnlySign, convertBytesToVector(TestData::kU2fCheckOnlySignCommandApdu, sizeof(TestData::kU2fCheckOnlySignCommandApdu)));
+}
+
+TEST(U2fCommandConstructorTest, TestConvertCtapMakeCredentialToU2fCheckOnlySignWithInvalidCredentialType)
+{
+    auto makeCredentialParam = constructMakeCredentialRequest();
+    PublicKeyCredentialDescriptor credentialDescriptor;
+    credentialDescriptor.type = static_cast<PublicKeyCredentialType>(-1);
+    credentialDescriptor.idVector = convertBytesToVector(TestData::kU2fSignKeyHandle, sizeof(TestData::kU2fSignKeyHandle));
+    Vector<PublicKeyCredentialDescriptor> excludeList;
+    excludeList.append(credentialDescriptor);
+    makeCredentialParam.excludeCredentials = WTFMove(excludeList);
+    EXPECT_TRUE(isConvertibleToU2fRegisterCommand(makeCredentialParam));
+
+    const auto u2fCheckOnlySign = convertToU2fCheckOnlySignCommand(convertBytesToVector(TestData::kClientDataHash, sizeof(TestData::kClientDataHash)), makeCredentialParam, credentialDescriptor);
+    EXPECT_FALSE(u2fCheckOnlySign);
+}
+
+TEST(U2fCommandConstructorTest, TestU2fRegisterCredentialAlgorithmRequirement)
+{
+    PublicKeyCredentialCreationOptions::RpEntity rp;
+    rp.id = "acme.com";
+    rp.name = "acme.com";
+
+    PublicKeyCredentialCreationOptions::UserEntity user;
+    user.idVector = convertBytesToVector(TestData::kUserId, sizeof(TestData::kUserId));
+    user.name = "johnpsmith@example.com";
+    user.displayName = "John P. Smith";
+    user.icon = "https://pics.acme.com/00/p/aBjjjpqPb.png";
+
+    PublicKeyCredentialCreationOptions::Parameters params;
+    params.type = PublicKeyCredentialType::PublicKey;
+    params.alg = -257;
+
+    PublicKeyCredentialCreationOptions makeCredentialParam;
+    makeCredentialParam.rp = WTFMove(rp);
+    makeCredentialParam.user = WTFMove(user);
+    makeCredentialParam.pubKeyCredParams.append(WTFMove(params));
+
+    EXPECT_FALSE(isConvertibleToU2fRegisterCommand(makeCredentialParam));
+}
+
+TEST(U2fCommandConstructorTest, TestU2fRegisterUserVerificationRequirement)
+{
+    auto makeCredentialParam = constructMakeCredentialRequest();
+    PublicKeyCredentialCreationOptions::AuthenticatorSelectionCriteria selection;
+    selection.userVerification = UserVerificationRequirement::Required;
+    makeCredentialParam.authenticatorSelection = WTFMove(selection);
+
+    EXPECT_FALSE(isConvertibleToU2fRegisterCommand(makeCredentialParam));
+}
+
+TEST(U2fCommandConstructorTest, TestU2fRegisterResidentKeyRequirement)
+{
+    auto makeCredentialParam = constructMakeCredentialRequest();
+    PublicKeyCredentialCreationOptions::AuthenticatorSelectionCriteria selection;
+    selection.requireResidentKey = true;
+    makeCredentialParam.authenticatorSelection = WTFMove(selection);
+
+    EXPECT_FALSE(isConvertibleToU2fRegisterCommand(makeCredentialParam));
+}
+
+TEST(U2fCommandConstructorTest, TestConvertCtapGetAssertionToU2fSignRequest)
+{
+    auto getAssertionReq = constructGetAssertionRequest();
+    PublicKeyCredentialDescriptor credentialDescriptor;
+    credentialDescriptor.type = PublicKeyCredentialType::PublicKey;
+    credentialDescriptor.idVector = convertBytesToVector(TestData::kU2fSignKeyHandle, sizeof(TestData::kU2fSignKeyHandle));
+    Vector<PublicKeyCredentialDescriptor> allowedList;
+    allowedList.append(WTFMove(credentialDescriptor));
+    getAssertionReq.allowCredentials = WTFMove(allowedList);
+    EXPECT_TRUE(isConvertibleToU2fSignCommand(getAssertionReq));
+
+    const auto u2fSignCommand = convertToU2fSignCommand(convertBytesToVector(TestData::kClientDataHash, sizeof(TestData::kClientDataHash)), getAssertionReq, convertBytesToVector(TestData::kU2fSignKeyHandle, sizeof(TestData::kU2fSignKeyHandle)));
+    ASSERT_TRUE(u2fSignCommand);
+    EXPECT_EQ(*u2fSignCommand, convertBytesToVector(TestData::kU2fSignCommandApdu, sizeof(TestData::kU2fSignCommandApdu)));
+}
+
+TEST(U2fCommandConstructorTest, TestU2fSignAllowListRequirement)
+{
+    auto getAssertionReq = constructGetAssertionRequest();
+    EXPECT_FALSE(isConvertibleToU2fSignCommand(getAssertionReq));
+}
+
+TEST(U2fCommandConstructorTest, TestU2fSignUserVerificationRequirement)
+{
+    auto getAssertionReq = constructGetAssertionRequest();
+    PublicKeyCredentialDescriptor credentialDescriptor;
+    credentialDescriptor.type = PublicKeyCredentialType::PublicKey;
+    credentialDescriptor.idVector = convertBytesToVector(TestData::kU2fSignKeyHandle, sizeof(TestData::kU2fSignKeyHandle));
+    Vector<PublicKeyCredentialDescriptor> allowedList;
+    allowedList.append(WTFMove(credentialDescriptor));
+    getAssertionReq.allowCredentials = WTFMove(allowedList);
+    getAssertionReq.userVerification = UserVerificationRequirement::Required;
+
+    EXPECT_FALSE(isConvertibleToU2fSignCommand(getAssertionReq));
+}
+
+TEST(U2fCommandConstructorTest, TestCreateSignWithIncorrectKeyHandle)
+{
+    auto getAssertionReq = constructGetAssertionRequest();
+    PublicKeyCredentialDescriptor credentialDescriptor;
+    credentialDescriptor.type = PublicKeyCredentialType::PublicKey;
+    credentialDescriptor.idVector = convertBytesToVector(TestData::kU2fSignKeyHandle, sizeof(TestData::kU2fSignKeyHandle));
+    Vector<PublicKeyCredentialDescriptor> allowedList;
+    allowedList.append(WTFMove(credentialDescriptor));
+    getAssertionReq.allowCredentials = WTFMove(allowedList);
+    ASSERT_TRUE(isConvertibleToU2fSignCommand(getAssertionReq));
+
+    Vector<uint8_t> keyHandle(kMaxKeyHandleLength, 0xff);
+    const auto validSignCommand = convertToU2fSignCommand(convertBytesToVector(TestData::kClientDataHash, sizeof(TestData::kClientDataHash)), getAssertionReq, keyHandle);
+    EXPECT_TRUE(validSignCommand);
+
+    keyHandle.append(0xff);
+    const auto invalidSignCommand = convertToU2fSignCommand(convertBytesToVector(TestData::kClientDataHash, sizeof(TestData::kClientDataHash)), getAssertionReq, keyHandle);
+    EXPECT_FALSE(invalidSignCommand);
+}
+
+TEST(U2fCommandConstructorTest, TestConstructBogusU2fRegistrationCommand)
+{
+    EXPECT_EQ(constructBogusU2fRegistrationCommand(), convertBytesToVector(TestData::kU2fFakeRegisterCommand, sizeof(TestData::kU2fFakeRegisterCommand)));
+}
+
+} // namespace TestWebKitAPI
+
+#endif // ENABLE(WEB_AUTHN)