[WebAuthN] Import an APDU coder from Chromium
authorjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Dec 2018 23:06:54 +0000 (23:06 +0000)
committerjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Dec 2018 23:06:54 +0000 (23:06 +0000)
https://bugs.webkit.org/show_bug.cgi?id=192949
<rdar://problem/46879933>

Reviewed by Brent Fulgham.

Source/WebCore:

This patch imports an APDU coder from Chromium. Here is the documentation:
https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#u2f-message-framing
APDU is a binary format to frame any U2F requests/responses into binaries. It is equivalent to CBOR in CTAP2.

Here is a list of files that are imported from Chromium:
https://cs.chromium.org/chromium/src/components/apdu/apdu_command.cc?rcl=a2f290c10d132f53518e7f99d5635ee814ff8090
https://cs.chromium.org/chromium/src/components/apdu/apdu_command.h?rcl=867b103481f6f4ccc79a69bba16c11eefac3cdb6
https://cs.chromium.org/chromium/src/components/apdu/apdu_response.cc?rcl=867b103481f6f4ccc79a69bba16c11eefac3cdb6
https://cs.chromium.org/chromium/src/components/apdu/apdu_response.h?rcl=867b103481f6f4ccc79a69bba16c11eefac3cdb6
https://cs.chromium.org/chromium/src/components/apdu/apdu_unittest.cc?rcl=867b103481f6f4ccc79a69bba16c11eefac3cdb6

Covered by API tests.

* Modules/webauthn/apdu/ApduCommand.cpp: Added.
(apdu::ApduCommand::createFromMessage):
(apdu::ApduCommand::ApduCommand):
(apdu::ApduCommand::getEncodedCommand const):
* Modules/webauthn/apdu/ApduCommand.h: Added.
* Modules/webauthn/apdu/ApduResponse.cpp: Added.
(apdu::ApduResponse::createFromMessage):
(apdu::ApduResponse::ApduResponse):
(apdu::ApduResponse::getEncodedResponse const):
* Modules/webauthn/apdu/ApduResponse.h: Added.
* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebCore/ApduTest.cpp: Added.
(TestWebKitAPI::TEST):

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

Source/WebCore/ChangeLog
Source/WebCore/Modules/webauthn/apdu/ApduCommand.cpp [new file with mode: 0644]
Source/WebCore/Modules/webauthn/apdu/ApduCommand.h [new file with mode: 0644]
Source/WebCore/Modules/webauthn/apdu/ApduResponse.cpp [new file with mode: 0644]
Source/WebCore/Modules/webauthn/apdu/ApduResponse.h [new file with mode: 0644]
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebCore/ApduTest.cpp [new file with mode: 0644]

index b42513e..415b202 100644 (file)
@@ -1,3 +1,37 @@
+2018-12-21  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Import an APDU coder from Chromium
+        https://bugs.webkit.org/show_bug.cgi?id=192949
+        <rdar://problem/46879933>
+
+        Reviewed by Brent Fulgham.
+
+        This patch imports an APDU coder from Chromium. Here is the documentation:
+        https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#u2f-message-framing
+        APDU is a binary format to frame any U2F requests/responses into binaries. It is equivalent to CBOR in CTAP2.
+
+        Here is a list of files that are imported from Chromium:
+        https://cs.chromium.org/chromium/src/components/apdu/apdu_command.cc?rcl=a2f290c10d132f53518e7f99d5635ee814ff8090
+        https://cs.chromium.org/chromium/src/components/apdu/apdu_command.h?rcl=867b103481f6f4ccc79a69bba16c11eefac3cdb6
+        https://cs.chromium.org/chromium/src/components/apdu/apdu_response.cc?rcl=867b103481f6f4ccc79a69bba16c11eefac3cdb6
+        https://cs.chromium.org/chromium/src/components/apdu/apdu_response.h?rcl=867b103481f6f4ccc79a69bba16c11eefac3cdb6
+        https://cs.chromium.org/chromium/src/components/apdu/apdu_unittest.cc?rcl=867b103481f6f4ccc79a69bba16c11eefac3cdb6
+
+        Covered by API tests.
+
+        * Modules/webauthn/apdu/ApduCommand.cpp: Added.
+        (apdu::ApduCommand::createFromMessage):
+        (apdu::ApduCommand::ApduCommand):
+        (apdu::ApduCommand::getEncodedCommand const):
+        * Modules/webauthn/apdu/ApduCommand.h: Added.
+        * Modules/webauthn/apdu/ApduResponse.cpp: Added.
+        (apdu::ApduResponse::createFromMessage):
+        (apdu::ApduResponse::ApduResponse):
+        (apdu::ApduResponse::getEncodedResponse const):
+        * Modules/webauthn/apdu/ApduResponse.h: Added.
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+
 2018-12-21  Jer Noble  <jer.noble@apple.com>
 
         Convert raw CDMSessionMediaSourceAVFObjC pointer in MediaPlayerPrivateMediaSourceAVFObjC
diff --git a/Source/WebCore/Modules/webauthn/apdu/ApduCommand.cpp b/Source/WebCore/Modules/webauthn/apdu/ApduCommand.cpp
new file mode 100644 (file)
index 0000000..74581d7
--- /dev/null
@@ -0,0 +1,149 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright (C) 2018 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * 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 "ApduCommand.h"
+
+#if ENABLE(WEB_AUTHN)
+
+namespace apdu {
+
+namespace {
+
+// APDU command data length is 2 bytes encoded in big endian order.
+uint16_t parseMessageLength(const Vector<uint8_t>& message, size_t offset)
+{
+    ASSERT(message.size() >= offset + 2);
+    return (message[offset] << 8) | message[offset + 1];
+}
+
+} // namespace
+
+Optional<ApduCommand> ApduCommand::createFromMessage(const Vector<uint8_t>& message)
+{
+    if (message.size() < kApduMinHeader || message.size() > kApduMaxLength)
+        return WTF::nullopt;
+
+    uint8_t cla = message[0];
+    uint8_t ins = message[1];
+    uint8_t p1 = message[2];
+    uint8_t p2 = message[3];
+
+    size_t responseLength = 0;
+    Vector<uint8_t> data;
+
+    switch (message.size()) {
+    // No data present; no expected response.
+    case kApduMinHeader:
+        break;
+    // Invalid encoding sizes.
+    case kApduMinHeader + 1:
+    case kApduMinHeader + 2:
+        return WTF::nullopt;
+    // No data present; response expected.
+    case kApduMinHeader + 3:
+        // Fifth byte must be 0.
+        if (message[4])
+            return WTF::nullopt;
+        responseLength = parseMessageLength(message, kApduCommandLengthOffset);
+        // Special case where response length of 0x0000 corresponds to 65536
+        // as defined in ISO7816-4.
+        if (!responseLength)
+            responseLength = kApduMaxResponseLength;
+        break;
+    default:
+        // Fifth byte must be 0.
+        if (message[4])
+            return WTF::nullopt;
+        auto dataLength = parseMessageLength(message, kApduCommandLengthOffset);
+
+        if (message.size() == dataLength + kApduCommandDataOffset) {
+            // No response expected.
+            data.appendRange(message.begin() + kApduCommandDataOffset, message.end());
+        } else if (message.size() == dataLength + kApduCommandDataOffset + 2) {
+            // Maximum response size is stored in final 2 bytes.
+            data.appendRange(message.begin() + kApduCommandDataOffset, message.end() - 2);
+            auto responseLengthOffset = kApduCommandDataOffset + dataLength;
+            responseLength = parseMessageLength(message, responseLengthOffset);
+            // Special case where response length of 0x0000 corresponds to 65536
+            // as defined in ISO7816-4.
+            if (!responseLength)
+                responseLength = kApduMaxResponseLength;
+        } else
+            return WTF::nullopt;
+        break;
+    }
+
+    return ApduCommand(cla, ins, p1, p2, responseLength, WTFMove(data));
+}
+
+ApduCommand::ApduCommand(uint8_t cla, uint8_t ins, uint8_t p1, uint8_t p2, size_t responseLength, Vector<uint8_t>&& data)
+    : m_cla(cla)
+    , m_ins(ins)
+    , m_p1(p1)
+    , m_p2(p2)
+    , m_responseLength(responseLength)
+    , m_data(WTFMove(data))
+{
+}
+
+Vector<uint8_t> ApduCommand::getEncodedCommand() const
+{
+    Vector<uint8_t> encoded = { m_cla, m_ins, m_p1, m_p2 };
+
+    // If data exists, request size (Lc) is encoded in 3 bytes, with the first
+    // byte always being null, and the other two bytes being a big-endian
+    // representation of the request size. If data length is 0, response size (Le)
+    // will be prepended with a null byte.
+    if (!m_data.isEmpty()) {
+        size_t dataLength = m_data.size();
+
+        encoded.append(0x0);
+        if (dataLength > kApduMaxDataLength)
+            dataLength = kApduMaxDataLength;
+        encoded.append((dataLength >> 8) & 0xff);
+        encoded.append(dataLength & 0xff);
+        encoded.appendRange(m_data.begin(), m_data.begin() + dataLength);
+    } else if (m_responseLength > 0)
+        encoded.append(0x0);
+
+    if (m_responseLength > 0) {
+        size_t responseLength = m_responseLength;
+        if (responseLength > kApduMaxResponseLength)
+            responseLength = kApduMaxResponseLength;
+        // A zero value represents a response length of 65,536 bytes.
+        encoded.append((responseLength >> 8) & 0xff);
+        encoded.append(responseLength & 0xff);
+    }
+    return encoded;
+}
+
+} // namespace apdu
+
+#endif // ENABLE(WEB_AUTHN)
diff --git a/Source/WebCore/Modules/webauthn/apdu/ApduCommand.h b/Source/WebCore/Modules/webauthn/apdu/ApduCommand.h
new file mode 100644 (file)
index 0000000..c57987e
--- /dev/null
@@ -0,0 +1,107 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright (C) 2018 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * 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/Noncopyable.h>
+#include <wtf/Optional.h>
+#include <wtf/Vector.h>
+
+namespace apdu {
+
+// APDU commands are defined as part of ISO 7816-4. Commands can be serialized
+// into either short length encodings, where the maximum data length is 256
+// bytes, or an extended length encoding, where the maximum data length is 65536
+// bytes. This class implements only the extended length encoding. Serialized
+// commands consist of a CLA byte, denoting the class of instruction, an INS
+// byte, denoting the instruction code, P1 and P2, each one byte denoting
+// instruction parameters, a length field (Lc), a data field of length Lc, and
+// a maximum expected response length (Le).
+class WEBCORE_EXPORT ApduCommand {
+    WTF_MAKE_NONCOPYABLE(ApduCommand);
+public:
+    // Constructs an APDU command from the serialized message data.
+    static Optional<ApduCommand> createFromMessage(const Vector<uint8_t>&);
+
+    ApduCommand() = default;
+    ApduCommand(
+        uint8_t cla,
+        uint8_t ins,
+        uint8_t p1,
+        uint8_t p2,
+        size_t responseLength,
+        Vector<uint8_t>&& data);
+    ApduCommand(ApduCommand&&) = default;
+    ApduCommand& operator=(ApduCommand&&) = default;
+
+    // Returns serialized message data.
+    Vector<uint8_t> getEncodedCommand() const;
+
+    void setCla(uint8_t cla) { m_cla = cla; }
+    void setIns(uint8_t ins) { m_ins = ins; }
+    void setP1(uint8_t p1) { m_p1 = p1; }
+    void setP2(uint8_t p2) { m_p2 = p2; }
+    void setData(Vector<uint8_t>&& data) { m_data = WTFMove(data); }
+    void setResponseLength(size_t responseLength) { m_responseLength = responseLength; }
+
+    uint8_t cla() const { return m_cla; }
+    uint8_t ins() const { return m_ins; }
+    uint8_t p1() const { return m_p1; }
+    uint8_t p2() const { return m_p2; }
+    size_t responseLength() const { return m_responseLength; }
+    const Vector<uint8_t>& data() const { return m_data; }
+
+    static constexpr size_t kApduMaxResponseLength = 65536;
+
+    static constexpr size_t kApduMinHeader = 4;
+    static constexpr size_t kApduMaxHeader = 7;
+    static constexpr size_t kApduCommandDataOffset = 7;
+    static constexpr size_t kApduCommandLengthOffset = 5;
+
+    // As defined in ISO7816-4, extended length APDU request data is limited to
+    // 16 bits in length with a maximum value of 65535. Response data length is
+    // also limited to 16 bits in length with a value of 0x0000 corresponding to
+    // a length of 65536.
+    static constexpr size_t kApduMaxDataLength = 65535;
+    static constexpr size_t kApduMaxLength = kApduMaxDataLength + kApduMaxHeader + 2;
+
+private:
+    uint8_t m_cla { 0 };
+    uint8_t m_ins { 0 };
+    uint8_t m_p1 { 0 };
+    uint8_t m_p2 { 0 };
+    size_t m_responseLength { 0 };
+    Vector<uint8_t> m_data;
+};
+
+} // namespace apdu
+
+#endif // ENABLE(WEB_AUTHN)
diff --git a/Source/WebCore/Modules/webauthn/apdu/ApduResponse.cpp b/Source/WebCore/Modules/webauthn/apdu/ApduResponse.cpp
new file mode 100644 (file)
index 0000000..5145b0a
--- /dev/null
@@ -0,0 +1,68 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright (C) 2018 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * 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 "ApduResponse.h"
+
+#if ENABLE(WEB_AUTHN)
+
+namespace apdu {
+
+// static
+Optional<ApduResponse> ApduResponse::createFromMessage(const Vector<uint8_t>& data)
+{
+    // Invalid message size, data is appended by status byte.
+    if (data.size() < 2)
+        return WTF::nullopt;
+
+    uint16_t statusBytes = data[data.size() - 2] << 8;
+    statusBytes |= data[data.size() - 1];
+
+    Vector<uint8_t> newData;
+    newData.appendRange(data.begin(), data.end() - 2);
+    return ApduResponse(WTFMove(newData), static_cast<Status>(statusBytes));
+}
+
+ApduResponse::ApduResponse(Vector<uint8_t>&& data, Status responseStatus)
+    : m_data(WTFMove(data))
+    , m_responseStatus(responseStatus)
+{
+}
+
+Vector<uint8_t> ApduResponse::getEncodedResponse() const
+{
+    Vector<uint8_t> encodedResponse = m_data;
+    encodedResponse.append(static_cast<uint16_t>(m_responseStatus) >> 8 & 0xff);
+    encodedResponse.append(static_cast<uint16_t>(m_responseStatus) & 0xff);
+    return encodedResponse;
+}
+
+} // namespace apdu
+
+#endif // ENABLE(WEB_AUTHN)
diff --git a/Source/WebCore/Modules/webauthn/apdu/ApduResponse.h b/Source/WebCore/Modules/webauthn/apdu/ApduResponse.h
new file mode 100644 (file)
index 0000000..19114c6
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright (C) 2018 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * 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/Noncopyable.h>
+#include <wtf/Optional.h>
+#include <wtf/Vector.h>
+
+namespace apdu {
+
+// APDU responses are defined as part of ISO 7816-4. Serialized responses
+// consist of a data field of varying length, up to a maximum 65536, and a
+// two byte status field.
+class WEBCORE_EXPORT ApduResponse {
+    WTF_MAKE_NONCOPYABLE(ApduResponse);
+public:
+    // Status bytes are specified in ISO 7816-4.
+    enum class Status : uint16_t {
+        SW_NO_ERROR = 0x9000,
+        SW_CONDITIONS_NOT_SATISFIED = 0x6985,
+        SW_WRONG_DATA = 0x6A80,
+        SW_WRONG_LENGTH = 0x6700,
+        SW_INS_NOT_SUPPORTED = 0x6D00,
+    };
+
+    // Create a APDU response from the serialized message.
+    static Optional<ApduResponse> createFromMessage(const Vector<uint8_t>& data);
+
+    ApduResponse(Vector<uint8_t>&& data, Status);
+    ApduResponse(ApduResponse&& that) = default;
+    ApduResponse& operator=(ApduResponse&& that) = default;
+
+    Vector<uint8_t> getEncodedResponse() const;
+
+    const Vector<uint8_t>& data() const { return m_data; }
+    Status status() const { return m_responseStatus; }
+
+private:
+    Vector<uint8_t> m_data;
+    Status m_responseStatus;
+};
+
+} // namespace apdu
+
+#endif // ENABLE(WEB_AUTHN)
index 4ef7004..e7d934a 100644 (file)
@@ -256,6 +256,8 @@ Modules/webaudio/WaveShaperProcessor.cpp
 Modules/webauthn/AuthenticatorCoordinator.cpp
 Modules/webauthn/AuthenticatorCoordinatorClient.cpp
 Modules/webauthn/PublicKeyCredential.cpp
+Modules/webauthn/apdu/ApduCommand.cpp
+Modules/webauthn/apdu/ApduResponse.cpp
 Modules/webauthn/cbor/CBORReader.cpp
 Modules/webauthn/cbor/CBORValue.cpp
 Modules/webauthn/cbor/CBORWriter.cpp
index 5c5cc1f..de452c0 100644 (file)
                5706A6961DDE5C9500A03B14 /* CryptoAlgorithmRsaOaepParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 5706A6951DDE5C9500A03B14 /* CryptoAlgorithmRsaOaepParams.h */; };
                5706A6981DDE5E4600A03B14 /* JSRsaOaepParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 5706A6971DDE5E4600A03B14 /* JSRsaOaepParams.h */; };
                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, ); }; };
                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 */; };
                5706A6991DDE5E8500A03B14 /* JSRsaOaepParams.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSRsaOaepParams.cpp; sourceTree = "<group>"; };
                571252681E524EB1008FF369 /* CryptoAlgorithmAES_CFB.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoAlgorithmAES_CFB.h; sourceTree = "<group>"; };
                5712526A1E52527C008FF369 /* CryptoAlgorithmAES_CFB.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CryptoAlgorithmAES_CFB.cpp; sourceTree = "<group>"; };
+               57152B5521CB2E3A000C37CA /* ApduResponse.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ApduResponse.cpp; path = Modules/webauthn/apdu/ApduResponse.cpp; sourceTree = SOURCE_ROOT; };
+               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; };
                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>"; };
                        path = "unified-sources";
                        sourceTree = "<group>";
                };
+               57152B5321CB2CE3000C37CA /* apdu */ = {
+                       isa = PBXGroup;
+                       children = (
+                               57152B5721CB2E3A000C37CA /* ApduCommand.cpp */,
+                               57152B5821CB2E3B000C37CA /* ApduCommand.h */,
+                               57152B5521CB2E3A000C37CA /* ApduResponse.cpp */,
+                               57152B5621CB2E3A000C37CA /* ApduResponse.h */,
+                       );
+                       path = apdu;
+                       sourceTree = "<group>";
+               };
                57303BB32006C6ED00355965 /* cbor */ = {
                        isa = PBXGroup;
                        children = (
                57D8462A1FEAF57F00CA3682 /* webauthn */ = {
                        isa = PBXGroup;
                        children = (
+                               57152B5321CB2CE3000C37CA /* apdu */,
                                57303BB32006C6ED00355965 /* cbor */,
                                578A4BFA2166AE0000D08F34 /* fido */,
                                57303C272009B2FC00355965 /* AuthenticatorAssertionResponse.h */,
                                714C7C671FDAD2A900F2BEE1 /* AnimationPlaybackEventInit.h in Headers */,
                                71025ECD1F99F0CE004A250C /* AnimationTimeline.h in Headers */,
                                0F580FAF149800D400FB5BD8 /* AnimationUtilities.h in Headers */,
+                               57152B5A21CB3E88000C37CA /* ApduCommand.h in Headers */,
+                               57152B5C21CC1902000C37CA /* ApduResponse.h in Headers */,
                                93309DD7099E64920056E581 /* AppendNodeCommand.h in Headers */,
                                A1DF5A941F7EC4320058A477 /* ApplePayContactField.h in Headers */,
                                A12C59EE2035FC9B0012236B /* ApplePayError.h in Headers */,
index 2ae467f..b222027 100644 (file)
@@ -1,3 +1,15 @@
+2018-12-21  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Import an APDU coder from Chromium
+        https://bugs.webkit.org/show_bug.cgi?id=192949
+        <rdar://problem/46879933>
+
+        Reviewed by Brent Fulgham.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebCore/ApduTest.cpp: Added.
+        (TestWebKitAPI::TEST):
+
 2018-12-21  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         Setting the file wrapper and content type of an attachment to a PDF should update its image
index 3928fef..2314328 100644 (file)
                5714ECB91CA8B5B000051AC8 /* DownloadRequestOriginalURL.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5714ECB81CA8B58800051AC8 /* DownloadRequestOriginalURL.html */; };
                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 */; };
                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 */; };
                5714ECB81CA8B58800051AC8 /* DownloadRequestOriginalURL.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = DownloadRequestOriginalURL.html; sourceTree = "<group>"; };
                5714ECBA1CA8BFD100051AC8 /* DownloadRequestOriginalURLFrame.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = DownloadRequestOriginalURLFrame.html; sourceTree = "<group>"; };
                5714ECBC1CA8C21800051AC8 /* DownloadRequestOriginalURL2.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = DownloadRequestOriginalURL2.html; sourceTree = "<group>"; };
+               57152B5D21CC2045000C37CA /* ApduTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ApduTest.cpp; sourceTree = "<group>"; };
                571F7FCF1F2961E100946648 /* IndexedDBStructuredCloneBackwardCompatibility.sqlite3-wal */ = {isa = PBXFileReference; lastKnownFileType = file; path = "IndexedDBStructuredCloneBackwardCompatibility.sqlite3-wal"; sourceTree = "<group>"; };
                572B403321769A88000AD43E /* CtapRequestTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CtapRequestTest.cpp; sourceTree = "<group>"; };
                572B40352176A029000AD43E /* FidoTestData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FidoTestData.h; sourceTree = "<group>"; };
                                3162AE9A1E6F2F8F000E4DBC /* mac */,
                                ABF510632A19B8AC7EC40E17 /* AbortableTaskQueue.cpp */,
                                7A909A6F1D877475007E10F8 /* AffineTransform.cpp */,
+                               57152B5D21CC2045000C37CA /* ApduTest.cpp */,
                                6354F4D01F7C3AB500D89DF3 /* ApplicationManifestParser.cpp */,
                                93A720E518F1A0E800A848E1 /* CalculationValue.cpp */,
                                07C046C91E42573E007201E7 /* CARingBuffer.cpp */,
                                7A909A7D1D877480007E10F8 /* AffineTransform.cpp in Sources */,
                                A1DF74321C41B65800A2F4D0 /* AlwaysRevalidatedURLSchemes.mm in Sources */,
                                2DE71AFE1D49C0BD00904094 /* AnimatedResize.mm in Sources */,
+                               57152B5E21CC2045000C37CA /* ApduTest.cpp in Sources */,
                                63F668221F97F7F90032EE51 /* ApplicationManifest.mm in Sources */,
                                6354F4D11F7C3AB500D89DF3 /* ApplicationManifestParser.cpp in Sources */,
                                834138C7203261CA00F26960 /* AsyncPolicyForNavigationResponse.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebCore/ApduTest.cpp b/Tools/TestWebKitAPI/Tests/WebCore/ApduTest.cpp
new file mode 100644 (file)
index 0000000..3196550
--- /dev/null
@@ -0,0 +1,194 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright (C) 2018 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * 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 <WebCore/ApduCommand.h>
+#include <WebCore/ApduResponse.h>
+
+namespace TestWebKitAPI {
+
+using namespace apdu;
+
+TEST(ApduTest, TestDeserializeBasic)
+{
+    uint8_t cla = 0xAA;
+    uint8_t ins = 0xAB;
+    uint8_t p1 = 0xAC;
+    uint8_t p2 = 0xAD;
+    Vector<uint8_t> message({ cla, ins, p1, p2 });
+    auto cmd = ApduCommand::createFromMessage(message);
+    ASSERT_TRUE(cmd);
+    EXPECT_EQ(0u, cmd->responseLength());
+    EXPECT_TRUE(cmd->data().isEmpty());
+    EXPECT_EQ(cla, cmd->cla());
+    EXPECT_EQ(ins, cmd->ins());
+    EXPECT_EQ(p1, cmd->p1());
+    EXPECT_EQ(p2, cmd->p2());
+    // Invalid length.
+    message = { cla, ins, p1 };
+    EXPECT_FALSE(ApduCommand::createFromMessage(message));
+    message.append(p2);
+    message.append(0);
+    // Set APDU command data size as maximum.
+    message.append(0xFF);
+    message.append(0xFF);
+    message.resize(message.size() + ApduCommand::kApduMaxDataLength);
+    // Set maximum response size.
+    message.append(0);
+    message.append(0);
+    // |message| is APDU encoded byte array with maximum data length.
+    EXPECT_TRUE(ApduCommand::createFromMessage(message));
+    message.append(0);
+    // |message| encoding containing data of size  maximum data length + 1.
+    EXPECT_FALSE(ApduCommand::createFromMessage(message));
+}
+
+TEST(ApduTest, TestDeserializeComplex)
+{
+    uint8_t cla = 0xAA;
+    uint8_t ins = 0xAB;
+    uint8_t p1 = 0xAC;
+    uint8_t p2 = 0xAD;
+    Vector<uint8_t> data(ApduCommand::kApduMaxDataLength - ApduCommand::kApduMaxHeader - 2, 0x7F);
+    Vector<uint8_t> message = { cla, ins, p1, p2, 0 };
+    message.append((data.size() >> 8) & 0xff);
+    message.append(data.size() & 0xff);
+    message.appendVector(data);
+
+    // Create a message with no response expected.
+    auto cmdNoResponse = ApduCommand::createFromMessage(message);
+    ASSERT_TRUE(cmdNoResponse);
+    EXPECT_EQ(0u, cmdNoResponse->responseLength());
+    EXPECT_EQ(data, cmdNoResponse->data());
+    EXPECT_EQ(cla, cmdNoResponse->cla());
+    EXPECT_EQ(ins, cmdNoResponse->ins());
+    EXPECT_EQ(p1, cmdNoResponse->p1());
+    EXPECT_EQ(p2, cmdNoResponse->p2());
+
+    // Add response length to message.
+    message.append(0xF1);
+    message.append(0xD0);
+    auto cmd = ApduCommand::createFromMessage(message);
+    ASSERT_TRUE(cmd);
+    EXPECT_EQ(data, cmd->data());
+    EXPECT_EQ(cla, cmd->cla());
+    EXPECT_EQ(ins, cmd->ins());
+    EXPECT_EQ(p1, cmd->p1());
+    EXPECT_EQ(p2, cmd->p2());
+    EXPECT_EQ(static_cast<size_t>(0xF1D0), cmd->responseLength());
+}
+
+TEST(ApduTest, TestDeserializeResponse)
+{
+    ApduResponse::Status status;
+    Vector<uint8_t> testVector;
+    // Invalid length.
+    Vector<uint8_t> message({ 0xAA });
+    EXPECT_FALSE(ApduResponse::createFromMessage(message));
+    // Valid length and status.
+    status = ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED;
+    message = { static_cast<uint8_t>(static_cast<uint16_t>(status) >> 8), static_cast<uint8_t>(status) };
+    auto response = ApduResponse::createFromMessage(message);
+    ASSERT_TRUE(response);
+    EXPECT_EQ(ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED, response->status());
+    EXPECT_EQ(response->data(), Vector<uint8_t>());
+    // Valid length and status.
+    status = ApduResponse::Status::SW_NO_ERROR;
+    message = { static_cast<uint8_t>(static_cast<uint16_t>(status) >> 8), static_cast<uint8_t>(status)};
+    testVector = { 0x01, 0x02, 0xEF, 0xFF };
+    message.insertVector(0, testVector);
+    response = ApduResponse::createFromMessage(message);
+    ASSERT_TRUE(response);
+    EXPECT_EQ(ApduResponse::Status::SW_NO_ERROR, response->status());
+    EXPECT_EQ(response->data(), testVector);
+}
+
+TEST(ApduTest, TestSerializeCommand)
+{
+    ApduCommand cmd;
+    cmd.setCla(0xA);
+    cmd.setIns(0xB);
+    cmd.setP1(0xC);
+    cmd.setP2(0xD);
+    // No data, no response expected.
+    Vector<uint8_t> expected({ 0xA, 0xB, 0xC, 0xD });
+    ASSERT(expected == cmd.getEncodedCommand());
+    auto deserializedCmd = ApduCommand::createFromMessage(expected);
+    ASSERT_TRUE(deserializedCmd);
+    EXPECT_EQ(expected, deserializedCmd->getEncodedCommand());
+    // No data, response expected.
+    cmd.setResponseLength(0xCAFE);
+    expected = { 0xA, 0xB, 0xC, 0xD, 0x0, 0xCA, 0xFE };
+    EXPECT_EQ(expected, cmd.getEncodedCommand());
+    deserializedCmd = ApduCommand::createFromMessage(expected);
+    ASSERT_TRUE(deserializedCmd);
+    EXPECT_EQ(expected, deserializedCmd->getEncodedCommand());
+    // Data exists, response expected.
+    Vector<uint8_t> data({ 0x1, 0x2, 0x3, 0x4 });
+    cmd.setData(WTFMove(data));
+    expected = { 0xA, 0xB, 0xC, 0xD, 0x0,  0x0, 0x4, 0x1, 0x2, 0x3, 0x4, 0xCA, 0xFE };
+    EXPECT_EQ(expected, cmd.getEncodedCommand());
+    deserializedCmd = ApduCommand::createFromMessage(expected);
+    ASSERT_TRUE(deserializedCmd);
+    EXPECT_EQ(expected, deserializedCmd->getEncodedCommand());
+    // Data exists, no response expected.
+    cmd.setResponseLength(0);
+    expected = { 0xA, 0xB, 0xC, 0xD, 0x0, 0x0, 0x4, 0x1, 0x2, 0x3, 0x4 };
+    EXPECT_EQ(expected, cmd.getEncodedCommand());
+    EXPECT_EQ(expected, ApduCommand::createFromMessage(expected)->getEncodedCommand());
+}
+
+TEST(ApduTest, TestSerializeEdgeCases)
+{
+    ApduCommand cmd;
+    cmd.setCla(0xA);
+    cmd.setIns(0xB);
+    cmd.setP1(0xC);
+    cmd.setP2(0xD);
+    // Set response length to maximum, which should serialize to 0x0000.
+    cmd.setResponseLength(ApduCommand::kApduMaxResponseLength);
+    Vector<uint8_t> expected({ 0xA, 0xB, 0xC, 0xD, 0x0, 0x0, 0x0 });
+    EXPECT_EQ(expected, cmd.getEncodedCommand());
+    auto deserializedCmd = ApduCommand::createFromMessage(expected);
+    ASSERT_TRUE(deserializedCmd);
+    EXPECT_EQ(expected, deserializedCmd->getEncodedCommand());
+    // Maximum data size.
+    Vector<uint8_t> oversized(ApduCommand::kApduMaxDataLength);
+    cmd.setData(WTFMove(oversized));
+    deserializedCmd = ApduCommand::createFromMessage(cmd.getEncodedCommand());
+    ASSERT_TRUE(deserializedCmd);
+    EXPECT_EQ(cmd.getEncodedCommand(), deserializedCmd->getEncodedCommand());
+}
+
+} // namespace TestWebKitAPI
+
+#endif // ENABLE(WEB_AUTHN)