[WebAuthn] Add quirk needed to support legacy Google NFC Titan security keys
https://bugs.webkit.org/show_bug.cgi?id=204024
<rdar://problem/56962320>

Reviewed by Brent Fulgham.

Source/WebCore:

Covered by manual tests.

* Modules/webauthn/fido/FidoConstants.h:

Source/WebKit:

Some legacy U2F keys such as Google T1 Titan don't understand the FIDO applet command. Instead,
they are configured to only have the FIDO applet. Therefore, when the above command fails, we
use U2F_VERSION command to double check if the connected tag can actually speak U2F, indicating
we are interacting with one of these legacy keys.

* UIProcess/WebAuthentication/Cocoa/NfcConnection.mm:
(WebKit::fido::compareVersion):
(WebKit::fido::trySelectFidoApplet):
(WebKit::NfcConnection::transact const):
(WebKit::NfcConnection::didDetectTags):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@252297 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 9f6fbb3..ac8d0ec 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,15 @@
+2019-11-08  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] Add quirk needed to support legacy Google NFC Titan security keys
+        https://bugs.webkit.org/show_bug.cgi?id=204024
+        <rdar://problem/56962320>
+
+        Reviewed by Brent Fulgham.
+
+        Covered by manual tests.
+
+        * Modules/webauthn/fido/FidoConstants.h:
+
 2019-11-08  Peng Liu  <peng.liu6@apple.com>
 
         Entering/Exiting Picture-in-Picture mode through webkitSetPresentationMode() does not fire events (enterpictureinpicture and leavepictureinpicture) defined in the spec
diff --git a/Source/WebCore/Modules/webauthn/fido/FidoConstants.h b/Source/WebCore/Modules/webauthn/fido/FidoConstants.h
index 65dd8cd..746829b 100644
--- a/Source/WebCore/Modules/webauthn/fido/FidoConstants.h
+++ b/Source/WebCore/Modules/webauthn/fido/FidoConstants.h
@@ -223,6 +223,13 @@
 const uint32_t kCtapHidUsagePage = 0xF1D0;
 const uint32_t kCtapHidUsage = 0x01;
 
+// U2F_VERSION command
+// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#getversion-request-and-response---u2f_version
+const uint8_t kCtapNfcU2fVersionCommand[] = {
+    0x00, 0x03, 0x00, 0x00, // CLA, INS, P1, P2
+    0x00, // L
+};
+
 // CTAPNFC Applet selection command and responses
 // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#nfc-applet-selection
 const uint8_t kCtapNfcAppletSelectionCommand[] = {
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index 0b2ea2c..486af351 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,22 @@
+2019-11-08  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] Add quirk needed to support legacy Google NFC Titan security keys
+        https://bugs.webkit.org/show_bug.cgi?id=204024
+        <rdar://problem/56962320>
+
+        Reviewed by Brent Fulgham.
+
+        Some legacy U2F keys such as Google T1 Titan don't understand the FIDO applet command. Instead,
+        they are configured to only have the FIDO applet. Therefore, when the above command fails, we
+        use U2F_VERSION command to double check if the connected tag can actually speak U2F, indicating
+        we are interacting with one of these legacy keys.
+
+        * UIProcess/WebAuthentication/Cocoa/NfcConnection.mm:
+        (WebKit::fido::compareVersion):
+        (WebKit::fido::trySelectFidoApplet):
+        (WebKit::NfcConnection::transact const):
+        (WebKit::NfcConnection::didDetectTags):
+
 2019-11-08  Jonathan Bedard  <jbedard@apple.com>
 
         Unreviewed, rolling out r252260.
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Cocoa/NfcConnection.mm b/Source/WebKit/UIProcess/WebAuthentication/Cocoa/NfcConnection.mm
index 8ed71ee..bcaea46 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/Cocoa/NfcConnection.mm
+++ b/Source/WebKit/UIProcess/WebAuthentication/Cocoa/NfcConnection.mm
@@ -39,10 +39,33 @@
 namespace {
 inline bool compareVersion(NSData *data, const uint8_t version[], size_t versionSize)
 {
+    if (!data)
+        return false;
     if (data.length != versionSize)
         return false;
     return !memcmp(data.bytes, version, versionSize);
 }
+
+// Confirm the FIDO applet is avaliable.
+// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#nfc-applet-selection
+static bool trySelectFidoApplet(NFReaderSession *session)
+{
+    auto *versionData = [session transceive:adoptNS([[NSData alloc] initWithBytes:kCtapNfcAppletSelectionCommand length:sizeof(kCtapNfcAppletSelectionCommand)]).get()];
+    if (compareVersion(versionData, kCtapNfcAppletSelectionU2f, sizeof(kCtapNfcAppletSelectionU2f))
+        || compareVersion(versionData, kCtapNfcAppletSelectionCtap, sizeof(kCtapNfcAppletSelectionCtap)))
+        return true;
+
+    // Some legacy U2F keys such as Google T1 Titan don't understand the FIDO applet command. Instead,
+    // they are configured to only have the FIDO applet. Therefore, when the above command fails, we
+    // use U2F_VERSION command to double check if the connected tag can actually speak U2F, indicating
+    // we are interacting with one of these legacy keys.
+    versionData = [session transceive:adoptNS([[NSData alloc] initWithBytes:kCtapNfcU2fVersionCommand length:sizeof(kCtapNfcU2fVersionCommand)]).get()];
+    if (compareVersion(versionData, kCtapNfcAppletSelectionU2f, sizeof(kCtapNfcAppletSelectionU2f)))
+        return true;
+
+    return false;
+}
+
 } // namespace
 
 Ref<NfcConnection> NfcConnection::create(RetainPtr<NFReaderSession>&& session, NfcService& service)
@@ -68,10 +91,8 @@
 Vector<uint8_t> NfcConnection::transact(Vector<uint8_t>&& data) const
 {
     Vector<uint8_t> response;
-    @autoreleasepool {
-        auto responseData = [m_session transceive:[NSData dataWithBytes:data.data() length:data.size()]];
-        response.append(reinterpret_cast<const uint8_t*>(responseData.bytes), responseData.length);
-    }
+    auto *responseData = [m_session transceive:adoptNS([[NSData alloc] initWithBytes:data.data() length:data.size()]).get()];
+    response.append(reinterpret_cast<const uint8_t*>(responseData.bytes), responseData.length);
     return response;
 }
 
@@ -104,14 +125,9 @@
         if (tag.type != NFTagTypeGeneric4A || ![m_session connectTag:tag])
             continue;
 
-        // Confirm the FIDO applet is avaliable before return.
-        // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#nfc-applet-selection
-        @autoreleasepool {
-            auto versionData = [m_session transceive:[NSData dataWithBytes:kCtapNfcAppletSelectionCommand length:sizeof(kCtapNfcAppletSelectionCommand)]];
-            if (!versionData || (!compareVersion(versionData, kCtapNfcAppletSelectionU2f, sizeof(kCtapNfcAppletSelectionU2f)) && !compareVersion(versionData, kCtapNfcAppletSelectionCtap, sizeof(kCtapNfcAppletSelectionCtap)))) {
-                [m_session disconnectTag];
-                continue;
-            }
+        if (!trySelectFidoApplet(m_session.get())) {
+            [m_session disconnectTag];
+            continue;
         }
 
         m_service->didConnectTag();