WebKit Bugzilla
Attachment 357855 Details for
Bug 192949
: [WebAuthN] Import an APDU coder from Chromium
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch
bug-192949-20181220131202.patch (text/plain), 37.47 KB, created by
Jiewen Tan
on 2018-12-20 13:12:02 PST
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Jiewen Tan
Created:
2018-12-20 13:12:02 PST
Size:
37.47 KB
patch
obsolete
>Subversion Revision: 239355 >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index fefaecdbeac9e870174436f23e01ddf734806122..07114b7bc21c1830f6d8779ffb61386eca0ee1c6 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,37 @@ >+2018-12-20 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 NOBODY (OOPS!). >+ >+ 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-18 Ryosuke Niwa <rniwa@webkit.org> > > Some iOS app crash in FrameLoader::checkCompleted >diff --git a/Source/WebCore/Modules/webauthn/apdu/ApduCommand.cpp b/Source/WebCore/Modules/webauthn/apdu/ApduCommand.cpp >new file mode 100644 >index 0000000000000000000000000000000000000000..858aaf7629422243195cdd86018e177e8514d123 >--- /dev/null >+++ b/Source/WebCore/Modules/webauthn/apdu/ApduCommand.cpp >@@ -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 >+ >+std::optional<ApduCommand> ApduCommand::createFromMessage(const Vector<uint8_t>& message) >+{ >+ if (message.size() < kApduMinHeader || message.size() > kApduMaxLength) >+ return std::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 std::nullopt; >+ // No data present; response expected. >+ case kApduMinHeader + 3: >+ // Fifth byte must be 0. >+ if (message[4]) >+ return std::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 std::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 std::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 >index 0000000000000000000000000000000000000000..950f80b5050de7ea72c8130705ae11f812e056fd >--- /dev/null >+++ b/Source/WebCore/Modules/webauthn/apdu/ApduCommand.h >@@ -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 std::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 >index 0000000000000000000000000000000000000000..4eca747ca469d28185aa51613b3f01915e0e356f >--- /dev/null >+++ b/Source/WebCore/Modules/webauthn/apdu/ApduResponse.cpp >@@ -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 >+std::optional<ApduResponse> ApduResponse::createFromMessage(const Vector<uint8_t>& data) >+{ >+ // Invalid message size, data is appended by status byte. >+ if (data.size() < 2) >+ return std::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 >index 0000000000000000000000000000000000000000..c838e81fb119c1dac6048163f1089dc0b25749a3 >--- /dev/null >+++ b/Source/WebCore/Modules/webauthn/apdu/ApduResponse.h >@@ -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 std::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) >diff --git a/Source/WebCore/Sources.txt b/Source/WebCore/Sources.txt >index e70db839441c88f160a0f2e6b9e0e9c552d6a7d4..16ad2b9794447cab96b71799d863efbea435ac52 100644 >--- a/Source/WebCore/Sources.txt >+++ b/Source/WebCore/Sources.txt >@@ -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 >diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj >index 63e9a683ffa6e1a496c09746f73457f6df1b0485..15edbe36ba3154374ed56b0bcd0b47fb0136e1ab 100644 >--- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj >+++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj >@@ -1782,6 +1782,8 @@ > 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 */; }; >@@ -8589,6 +8591,10 @@ > 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>"; }; >@@ -19261,6 +19267,17 @@ > 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 = ( >@@ -19338,6 +19355,7 @@ > 57D8462A1FEAF57F00CA3682 /* webauthn */ = { > isa = PBXGroup; > children = ( >+ 57152B5321CB2CE3000C37CA /* apdu */, > 57303BB32006C6ED00355965 /* cbor */, > 578A4BFA2166AE0000D08F34 /* fido */, > 57303C272009B2FC00355965 /* AuthenticatorAssertionResponse.h */, >@@ -27839,6 +27857,8 @@ > 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 */, >diff --git a/Tools/ChangeLog b/Tools/ChangeLog >index cf5a6af1c5ec1e06ac6833b17eb5e63c3e4a6b33..5978d02f12b345f083fab82a439c53f436beb6aa 100644 >--- a/Tools/ChangeLog >+++ b/Tools/ChangeLog >@@ -1,3 +1,15 @@ >+2018-12-20 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 NOBODY (OOPS!). >+ >+ * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: >+ * TestWebKitAPI/Tests/WebCore/ApduTest.cpp: Added. >+ (TestWebKitAPI::TEST): >+ > 2018-12-18 Jonathan Bedard <jbedard@apple.com> > > webkitpy: Ignore device type for test when using --force >diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj >index 7e8ed3be51484308e0d8a1a9b78ba358eff554a5..79c60a73fb5e72408d1266f35f0d4fcef8d8fc1c 100644 >--- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj >+++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj >@@ -256,6 +256,7 @@ > 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 */; }; >@@ -1602,6 +1603,7 @@ > 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>"; }; >@@ -2613,6 +2615,7 @@ > 3162AE9A1E6F2F8F000E4DBC /* mac */, > ABF510632A19B8AC7EC40E17 /* AbortableTaskQueue.cpp */, > 7A909A6F1D877475007E10F8 /* AffineTransform.cpp */, >+ 57152B5D21CC2045000C37CA /* ApduTest.cpp */, > 6354F4D01F7C3AB500D89DF3 /* ApplicationManifestParser.cpp */, > 93A720E518F1A0E800A848E1 /* CalculationValue.cpp */, > 07C046C91E42573E007201E7 /* CARingBuffer.cpp */, >@@ -3834,6 +3837,7 @@ > 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 >index 0000000000000000000000000000000000000000..3196550066d5eaef11484f9d571ab92d69648538 >--- /dev/null >+++ b/Tools/TestWebKitAPI/Tests/WebCore/ApduTest.cpp >@@ -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)
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 192949
:
357855
|
357899