[WebAuthn] User Verification (UV) option present on a CTAP2 authenticatorMakeCredential while the authenticator has not advertised support for it
https://bugs.webkit.org/show_bug.cgi?id=204111
<rdar://problem/57019604>

Reviewed by Brent Fulgham.

Source/WebCore:

Covered by API tests.

* Modules/webauthn/fido/DeviceRequestConverter.cpp:
(fido::encodeMakeCredenitalRequestAsCBOR):
(fido::encodeGetAssertionRequestAsCBOR):
Only set UV if RP requires it.

Tools:

* TestWebKitAPI/Tests/WebCore/CtapRequestTest.cpp:
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/WebCore/FidoTestData.h:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@254710 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 8d64028..deefad2 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,18 @@
+2020-01-16  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] User Verification (UV) option present on a CTAP2 authenticatorMakeCredential while the authenticator has not advertised support for it
+        https://bugs.webkit.org/show_bug.cgi?id=204111
+        <rdar://problem/57019604>
+
+        Reviewed by Brent Fulgham.
+
+        Covered by API tests.
+
+        * Modules/webauthn/fido/DeviceRequestConverter.cpp:
+        (fido::encodeMakeCredenitalRequestAsCBOR):
+        (fido::encodeGetAssertionRequestAsCBOR):
+        Only set UV if RP requires it.
+
 2020-01-16  Brady Eidson  <beidson@apple.com>
 
         Make the callAsyncJavaScriptFunction function actually be async (so await works).
diff --git a/Source/WebCore/Modules/webauthn/fido/DeviceRequestConverter.cpp b/Source/WebCore/Modules/webauthn/fido/DeviceRequestConverter.cpp
index 155e5d1..eaf41d5 100644
--- a/Source/WebCore/Modules/webauthn/fido/DeviceRequestConverter.cpp
+++ b/Source/WebCore/Modules/webauthn/fido/DeviceRequestConverter.cpp
@@ -119,7 +119,8 @@
         case UserVerificationRequirement::Discouraged:
             requireUserVerification = false;
         }
-        optionMap[CBORValue(kUserVerificationMapKey)] = CBORValue(requireUserVerification);
+        if (requireUserVerification)
+            optionMap[CBORValue(kUserVerificationMapKey)] = CBORValue(requireUserVerification);
     }
     if (!optionMap.empty())
         cborMap[CBORValue(7)] = CBORValue(WTFMove(optionMap));
@@ -164,7 +165,8 @@
     case UserVerificationRequirement::Discouraged:
         requireUserVerification = false;
     }
-    optionMap[CBORValue(kUserVerificationMapKey)] = CBORValue(requireUserVerification);
+    if (requireUserVerification)
+        optionMap[CBORValue(kUserVerificationMapKey)] = CBORValue(requireUserVerification);
     optionMap[CBORValue(kUserPresenceMapKey)] = CBORValue(true);
 
     if (!optionMap.empty())
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index 1c81d3f..75a4b68 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,15 @@
+2020-01-16  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] User Verification (UV) option present on a CTAP2 authenticatorMakeCredential while the authenticator has not advertised support for it
+        https://bugs.webkit.org/show_bug.cgi?id=204111
+        <rdar://problem/57019604>
+
+        Reviewed by Brent Fulgham.
+
+        * TestWebKitAPI/Tests/WebCore/CtapRequestTest.cpp:
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/Tests/WebCore/FidoTestData.h:
+
 2020-01-16  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [bmalloc] IsoHeap's initial setup should be small
diff --git a/Tools/TestWebKitAPI/Tests/WebCore/CtapRequestTest.cpp b/Tools/TestWebKitAPI/Tests/WebCore/CtapRequestTest.cpp
index da075a0..332739f 100644
--- a/Tools/TestWebKitAPI/Tests/WebCore/CtapRequestTest.cpp
+++ b/Tools/TestWebKitAPI/Tests/WebCore/CtapRequestTest.cpp
@@ -69,6 +69,29 @@
     EXPECT_EQ(memcmp(serializedData.data(), TestData::kCtapMakeCredentialRequest, serializedData.size()), 0);
 }
 
+TEST(CTAPRequestTest, TestConstructMakeCredentialRequestParamNoUVNoRK)
+{
+    PublicKeyCredentialCreationOptions::RpEntity rp;
+    rp.name = "Acme";
+    rp.id = "acme.com";
+
+    PublicKeyCredentialCreationOptions::UserEntity user;
+    user.name = "johnpsmith@example.com";
+    user.icon = "https://pics.acme.com/00/p/aBjjjpqPb.png";
+    user.idVector.append(TestData::kUserId, sizeof(TestData::kUserId));
+    user.displayName = "John P. Smith";
+
+    Vector<PublicKeyCredentialCreationOptions::Parameters> params { { PublicKeyCredentialType::PublicKey, 7 }, { PublicKeyCredentialType::PublicKey, 257 } };
+    PublicKeyCredentialCreationOptions::AuthenticatorSelectionCriteria selection { PublicKeyCredentialCreationOptions::AuthenticatorAttachment::Platform, false, UserVerificationRequirement::Discouraged };
+
+    PublicKeyCredentialCreationOptions options { rp, user, { }, params, WTF::nullopt, { }, selection, AttestationConveyancePreference::None, WTF::nullopt };
+    Vector<uint8_t> hash;
+    hash.append(TestData::kClientDataHash, sizeof(TestData::kClientDataHash));
+    auto serializedData = encodeMakeCredenitalRequestAsCBOR(hash, options, AuthenticatorSupportedOptions::UserVerificationAvailability::kSupportedButNotConfigured);
+    EXPECT_EQ(serializedData.size(), sizeof(TestData::kCtapMakeCredentialRequestShort));
+    EXPECT_EQ(memcmp(serializedData.data(), TestData::kCtapMakeCredentialRequestShort, serializedData.size()), 0);
+}
+
 TEST(CTAPRequestTest, TestConstructMakeCredentialRequestParamWithPin)
 {
     PublicKeyCredentialCreationOptions::RpEntity rp;
@@ -133,6 +156,43 @@
     EXPECT_EQ(memcmp(serializedData.data(), TestData::kTestComplexCtapGetAssertionRequest, serializedData.size()), 0);
 }
 
+TEST(CTAPRequestTest, TestConstructGetAssertionRequestNoUV)
+{
+    PublicKeyCredentialRequestOptions options;
+    options.rpId = "acme.com";
+
+    PublicKeyCredentialDescriptor descriptor1;
+    descriptor1.type = PublicKeyCredentialType::PublicKey;
+    const uint8_t id1[] = {
+        0xf2, 0x20, 0x06, 0xde, 0x4f, 0x90, 0x5a, 0xf6, 0x8a, 0x43, 0x94,
+        0x2f, 0x02, 0x4f, 0x2a, 0x5e, 0xce, 0x60, 0x3d, 0x9c, 0x6d, 0x4b,
+        0x3d, 0xf8, 0xbe, 0x08, 0xed, 0x01, 0xfc, 0x44, 0x26, 0x46, 0xd0,
+        0x34, 0x85, 0x8a, 0xc7, 0x5b, 0xed, 0x3f, 0xd5, 0x80, 0xbf, 0x98,
+        0x08, 0xd9, 0x4f, 0xcb, 0xee, 0x82, 0xb9, 0xb2, 0xef, 0x66, 0x77,
+        0xaf, 0x0a, 0xdc, 0xc3, 0x58, 0x52, 0xea, 0x6b, 0x9e };
+    descriptor1.idVector.append(id1, sizeof(id1));
+    options.allowCredentials.append(descriptor1);
+
+    PublicKeyCredentialDescriptor descriptor2;
+    descriptor2.type = PublicKeyCredentialType::PublicKey;
+    const uint8_t id2[] = {
+        0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+        0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+        0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+        0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+        0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 };
+    descriptor2.idVector.append(id2, sizeof(id2));
+    options.allowCredentials.append(descriptor2);
+
+    options.userVerification = UserVerificationRequirement::Discouraged;
+
+    Vector<uint8_t> hash;
+    hash.append(TestData::kClientDataHash, sizeof(TestData::kClientDataHash));
+    auto serializedData = encodeGetAssertionRequestAsCBOR(hash, options, AuthenticatorSupportedOptions::UserVerificationAvailability::kSupportedButNotConfigured);
+    EXPECT_EQ(serializedData.size(), sizeof(TestData::kTestComplexCtapGetAssertionRequestShort));
+    EXPECT_EQ(memcmp(serializedData.data(), TestData::kTestComplexCtapGetAssertionRequestShort, serializedData.size()), 0);
+}
+
 TEST(CTAPRequestTest, TestConstructGetAssertionRequestWithPin)
 {
     PublicKeyCredentialRequestOptions options;
diff --git a/Tools/TestWebKitAPI/Tests/WebCore/FidoTestData.h b/Tools/TestWebKitAPI/Tests/WebCore/FidoTestData.h
index fc38dab..ee77077 100644
--- a/Tools/TestWebKitAPI/Tests/WebCore/FidoTestData.h
+++ b/Tools/TestWebKitAPI/Tests/WebCore/FidoTestData.h
@@ -528,6 +528,81 @@
     0xf5
 };
 
+constexpr uint8_t kCtapMakeCredentialRequestShort[] = {
+    // authenticatorMakeCredential command
+    0x01,
+    // map(4)
+    0xa4,
+    // key(1) - clientDataHash
+    0x01,
+    // bytes(32)
+    0x58, 0x20, 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,
+    // key(2) - rp
+    0x02,
+    // map(2)
+    0xa2,
+    // key - "id"
+    0x62, 0x69, 0x64,
+    // value - "acme.com"
+    0x68, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+    // key -  "name"
+    0x64, 0x6e, 0x61, 0x6d, 0x65,
+    // value - "Acme"
+    0x64, 0x41, 0x63, 0x6d, 0x65,
+    // key(3) - user
+    0x03,
+    // map(4)
+    0xa4,
+    // key - "id"
+    0x62, 0x69, 0x64,
+    // value - user id
+    0x48, 0x10, 0x98, 0x23, 0x72, 0x35, 0x40, 0x98, 0x72,
+    // key - "icon"
+    0x64, 0x69, 0x63, 0x6f, 0x6e,
+    // value - "https://pics.acme.com/00/p/aBjjjpqPb.png"
+    0x78, 0x28, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x70, 0x69,
+    0x63, 0x73, 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+    0x30, 0x30, 0x2f, 0x70, 0x2f, 0x61, 0x42, 0x6a, 0x6a, 0x6a, 0x70, 0x71,
+    0x50, 0x62, 0x2e, 0x70, 0x6e, 0x67,
+    // key - "name"
+    0x64, 0x6e, 0x61, 0x6d, 0x65,
+    // value - "johnpsmith@example.com"
+    0x76, 0x6a, 0x6f, 0x68, 0x6e, 0x70, 0x73, 0x6d, 0x69, 0x74, 0x68, 0x40,
+    0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+    // key - "displayName"
+    0x6b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65,
+    // value - "John P. Smith"
+    0x6d, 0x4a, 0x6f, 0x68, 0x6e, 0x20, 0x50, 0x2e, 0x20, 0x53, 0x6d, 0x69,
+    0x74, 0x68,
+    // key(4) - pubKeyCredParams
+    0x04,
+    // array(2)
+    0x82,
+    // map(2)
+    0xa2,
+    // key - "alg"
+    0x63, 0x61, 0x6c, 0x67,
+    // value - 7
+    0x07,
+    // key - "type"
+    0x64, 0x74, 0x79, 0x70, 0x65,
+    // value - "public-key"
+    0x6a, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
+    // map(2)
+    0xa2,
+    // key - "alg"
+    0x63, 0x61, 0x6c, 0x67,
+    // value - 257
+    0x19, 0x01, 0x01,
+    // key - "type"
+    0x64, 0x74, 0x79, 0x70, 0x65, // "type"
+    // value - "public-key"
+    0x6a, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
+};
+
+
 constexpr uint8_t kCtapMakeCredentialRequestWithPin[] = {
     // authenticatorMakeCredential command
     0x01,
@@ -685,6 +760,64 @@
     0xf5,
 };
 
+constexpr uint8_t kTestComplexCtapGetAssertionRequestShort[] = {
+    // authenticatorGetAssertion command
+    0x02,
+    // map(4)
+    0xa4,
+    // key(01) -rpId
+    0x01,
+    // value - "acme.com"
+    0x68, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+    // key(02) - client data hash
+    0x02,
+    // value - bytes(32)
+    0x58, 0x20, 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,
+    // key(03) - allow list
+    0x03,
+    // value - array(2)
+    0x82,
+    // map(2)
+    0xa2,
+    // key - "id"
+    0x62, 0x69, 0x64,
+    // value - credential ID
+    0x58, 0x40, 0xf2, 0x20, 0x06, 0xde, 0x4f, 0x90, 0x5a, 0xf6, 0x8a, 0x43,
+    0x94, 0x2f, 0x02, 0x4f, 0x2a, 0x5e, 0xce, 0x60, 0x3d, 0x9c, 0x6d, 0x4b,
+    0x3d, 0xf8, 0xbe, 0x08, 0xed, 0x01, 0xfc, 0x44, 0x26, 0x46, 0xd0, 0x34,
+    0x85, 0x8a, 0xc7, 0x5b, 0xed, 0x3f, 0xd5, 0x80, 0xbf, 0x98, 0x08, 0xd9,
+    0x4f, 0xcb, 0xee, 0x82, 0xb9, 0xb2, 0xef, 0x66, 0x77, 0xaf, 0x0a, 0xdc,
+    0xc3, 0x58, 0x52, 0xea, 0x6b, 0x9e,
+    // key - "type"
+    0x64, 0x74, 0x79, 0x70, 0x65,
+    // value - "public-key"
+    0x6a, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
+    // map(2)
+    0xa2,
+    // key - "id"
+    0x62, 0x69, 0x64,
+    // value - credential ID
+    0x58, 0x32, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+    0x03, 0x03, 0x03, 0x03,
+    // key - "type"
+    0x64, 0x74, 0x79, 0x70, 0x65,
+    // value - "public-key"
+    0x6a, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
+    // unsigned(5) - options
+    0x05,
+    // map(1)
+    0xa1,
+    // key -"up"
+    0x62, 0x75, 0x70,
+    // value - True(21)
+    0xf5,
+};
+
 constexpr uint8_t kTestComplexCtapGetAssertionRequestWithPin[] = {
     // authenticatorGetAssertion command
     0x02,