Implement post handshake response parsing 08/307508/13
authorKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Mon, 11 Mar 2024 09:42:21 +0000 (10:42 +0100)
committerKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Thu, 21 Mar 2024 15:29:40 +0000 (16:29 +0100)
Change-Id: I32c21f1d25aa91b282ca64698d6df3dfd7ad4bf1

srcs/CMakeLists.txt
srcs/cbor_encoding.cpp
srcs/cbor_encoding.h
srcs/exception.h
srcs/message.cpp [new file with mode: 0644]
srcs/message.h [new file with mode: 0644]
tests/CMakeLists.txt
tests/message_tests.cpp [new file with mode: 0644]

index 2cb3adb423494c20cbb6224e04833488791ace80..bc0c49b2f60a11b126b6ecad7ef464991200000a 100644 (file)
@@ -81,6 +81,7 @@ SET(WEBAUTHN_BLE_SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/encrypted_tunnel.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/cbor_parsing.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/message.cpp
 )
 SET(WEBAUTHN_BLE_SOURCES ${WEBAUTHN_BLE_SOURCES} PARENT_SCOPE)
 
index 016858bccc4687c0c778df49f71066c7a03ec33f..8a18d52335f8be7771633dce68b3381f287b2212 100644 (file)
@@ -223,7 +223,7 @@ void Container::AppendTextStringZ(const char *value)
         THROW_ENCODING("cbor_encode_text_stringz() failed with " << static_cast<int>(err));
 }
 
-void Container::AppendTextString(const std::string_viewtext)
+void Container::AppendTextString(const std::string_view &text)
 {
     assert(!m_appendingToChild);
 
index 4df09a8ebf264cee727b173f6a87e67389e3164e..8aa9c7a71a172357598c516cb163908d620a121c 100644 (file)
@@ -62,7 +62,7 @@ public:
     Container &operator=(Container &&) = delete;
 
     void AppendTextStringZ(const char *value);
-    void AppendTextString(const std::string_viewtext);
+    void AppendTextString(const std::string_view &text);
     void AppendInt64(int64_t value);
     void AppendByteString(const Buffer &value);
     void AppendByteString(const wauthn_const_buffer_s &value);
index d09097528dd9f1d799d08d641830c47f795902e6..eea336f0bb5478f2a89708427ae421e0bea22a2a 100644 (file)
@@ -62,3 +62,4 @@ typedef Exception<WAUTHN_ERROR_CANCELLED> Cancelled;
 #define THROW_INVALID_STATE(...) LOGGED_THROW(Exception::InvalidState, __VA_ARGS__)
 #define THROW_CANCELLED() LOGGED_THROW(Exception::Cancelled, "Operation cancelled")
 #define THROW_ENCODING(...) LOGGED_THROW(Exception::EncodingFailed, __VA_ARGS__)
+#define THROW_MEMORY() LOGGED_THROW(Exception::MemoryError, "Memory error")
diff --git a/srcs/message.cpp b/srcs/message.cpp
new file mode 100644 (file)
index 0000000..8ffb196
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "cbor_parsing.h"
+#include "exception.h"
+#include "message.h"
+
+namespace {
+
+// Get Info response
+constexpr int64_t KEY_GI_BUFFER = 0x01;
+constexpr int64_t KEY_GI_RSP_VERSIONS = 0x01;
+constexpr int64_t KEY_GI_RSP_EXTENSTIONS = 0x02;
+constexpr int64_t KEY_GI_RSP_AAGUID = 0x03;
+constexpr int64_t KEY_GI_RSP_OPTIONS = 0x04;
+constexpr int64_t KEY_GI_RSP_MAX_MSG_SIZE = 0x05;
+constexpr int64_t KEY_GI_RSP_PIN_UV_AUTH_PROTOCOLS = 0x06;
+constexpr int64_t KEY_GI_RSP_MAX_CREDENTIAL_COUNT_IN_LIST = 0x07;
+constexpr int64_t KEY_GI_RSP_MAX_CREDENTIAL_ID_LENGTH = 0x08;
+constexpr int64_t KEY_GI_RSP_TRANSPORTS = 0x09;
+constexpr int64_t KEY_GI_RSP_ALGORITHMS = 0x0A;
+constexpr int64_t KEY_GI_RSP_MAX_SERIALIZED_LARGE_BLOB_ARRAY = 0x0B;
+constexpr int64_t KEY_GI_RSP_FORCE_PIN_CHANGE = 0x0C;
+constexpr int64_t KEY_GI_RSP_MIN_PIN_LENGTH = 0x0D;
+constexpr int64_t KEY_GI_RSP_FIRMWARE_VERSION = 0x0E;
+constexpr int64_t KEY_GI_RSP_MAX_CRED_BLOB_LENGTH = 0x0F;
+constexpr int64_t KEY_GI_RSP_MAX_RPIDS_FOR_SET_MIN_PIN_LENGTH = 0x10;
+constexpr int64_t KEY_GI_RSP_PREFERRED_PLATFORM_UV_ATTEMPTS = 0x11;
+constexpr int64_t KEY_GI_RSP_UV_MODALITY = 0x12;
+constexpr int64_t KEY_GI_RSP_CERTIFICATIONS = 0x13;
+constexpr int64_t KEY_GI_RSP_REMAINING_DISCOVERABLE_CREDENTIALS = 0x14;
+constexpr int64_t KEY_GI_RSP_VENDOR_PROTOTYPE_CONFIG_COMMANDS = 0x15;
+constexpr int64_t KEY_GI_RSP_ATTESTATION_FORMATS = 0x16;
+constexpr int64_t KEY_GI_RSP_UV_COUNT_SINCE_LAST_PIN_ENTRY = 0x17;
+constexpr int64_t KEY_GI_RSP_LONG_TOUCH_FOR_RESET = 0x18;
+
+} // namespace
+
+void PostHandshakeResponse::Deserialize(BufferView &input)
+{
+    auto helper = CborParsing::Parser::Create(input.data(), input.size());
+    auto map = helper.EnterMap();
+
+    auto getInfoBuffer = map.GetByteStringAt(KEY_GI_BUFFER).value();
+    auto getInfoHelper = CborParsing::Parser::Create(getInfoBuffer.data(), getInfoBuffer.size());
+
+    auto getInfoMap = getInfoHelper.EnterMap();
+    {
+        auto versionArray = getInfoMap.EnterArrayAt(KEY_GI_RSP_VERSIONS).value();
+        for (size_t i = 0; i < versionArray.Length(); i++)
+            m_versions.emplace_back(versionArray.GetTextString());
+    }
+
+    if (auto extensionsArray = getInfoMap.EnterArrayAt(KEY_GI_RSP_EXTENSTIONS)) {
+        for (size_t i = 0; i < extensionsArray->Length(); i++)
+            m_extensions.emplace_back(extensionsArray->GetTextString());
+    }
+
+    m_aaguid = getInfoMap.GetByteStringAt(KEY_GI_RSP_AAGUID).value();
+
+    if (auto optionsMap = getInfoMap.EnterMapAt(KEY_GI_RSP_OPTIONS)) {
+        m_options.rk = optionsMap->GetBooleanAt("rk").value_or(false);
+        m_options.uv = optionsMap->GetBooleanAt("uv").value_or(false);
+        // TODO other options
+    }
+
+    m_maxMsgSize = getInfoMap.GetUint64At(KEY_GI_RSP_MAX_MSG_SIZE);
+
+    if (auto pinUvAuthProtocolsArray = getInfoMap.EnterArrayAt(KEY_GI_RSP_PIN_UV_AUTH_PROTOCOLS)) {
+        for (size_t i = 0; i < pinUvAuthProtocolsArray->Length(); i++)
+            m_pinUvAuthProtocols.emplace_back(pinUvAuthProtocolsArray->GetUint64());
+    }
+
+    m_maxCredentialCountInList = getInfoMap.GetUint64At(KEY_GI_RSP_MAX_CREDENTIAL_COUNT_IN_LIST);
+
+    m_maxCredentialIdLength = getInfoMap.GetUint64At(KEY_GI_RSP_MAX_CREDENTIAL_ID_LENGTH);
+
+    if (auto transportsArray = getInfoMap.EnterArrayAt(KEY_GI_RSP_TRANSPORTS)) {
+        for (size_t i = 0; i < transportsArray->Length(); i++)
+            m_transports.emplace_back(transportsArray->GetTextString());
+    }
+
+    if (auto algorithmsArray = getInfoMap.EnterArrayAt(KEY_GI_RSP_ALGORITHMS)) {
+        for (size_t i = 0; i < algorithmsArray->Length(); i++) {
+            auto paramMap = algorithmsArray->EnterMap();
+
+            auto alg = paramMap.GetInt64At("alg").value();
+            auto type = paramMap.GetTextStringAt("type").value();
+
+            m_algorithms.emplace_back(PublicKeyCredentialParameters{std::move(type), alg});
+        }
+    }
+
+    m_maxSerializedLargeBlobArray =
+        getInfoMap.GetUint64At(KEY_GI_RSP_MAX_SERIALIZED_LARGE_BLOB_ARRAY);
+
+    m_forcePINChange = getInfoMap.GetBooleanAt(KEY_GI_RSP_FORCE_PIN_CHANGE);
+
+    m_minPINLength = getInfoMap.GetUint64At(KEY_GI_RSP_MIN_PIN_LENGTH);
+
+    m_firmwareVersion = getInfoMap.GetUint64At(KEY_GI_RSP_FIRMWARE_VERSION);
+
+    m_maxCredBlobLength = getInfoMap.GetUint64At(KEY_GI_RSP_MAX_CRED_BLOB_LENGTH);
+
+    m_maxRPIDsForSetMinPINLength =
+        getInfoMap.GetUint64At(KEY_GI_RSP_MAX_RPIDS_FOR_SET_MIN_PIN_LENGTH);
+
+    m_preferredPlatformUvAttempts =
+        getInfoMap.GetUint64At(KEY_GI_RSP_PREFERRED_PLATFORM_UV_ATTEMPTS);
+
+    m_uvModality = getInfoMap.GetUint64At(KEY_GI_RSP_UV_MODALITY);
+
+    if (auto certificationsMap = getInfoMap.EnterMapAt(KEY_GI_RSP_CERTIFICATIONS)) {
+        for (size_t i = 0; i < certificationsMap->Length(); i++) {
+            auto name = std::get<std::string>(certificationsMap->PeekKey());
+            auto value = certificationsMap->GetUint64At(CborParsing::CURRENT_KEY).value();
+            m_certifications.emplace(std::move(name), value);
+        }
+    }
+
+    m_remainingDiscoverableCredentials =
+        getInfoMap.GetUint64At(KEY_GI_RSP_REMAINING_DISCOVERABLE_CREDENTIALS);
+
+    if (auto vendorPrototypeConfigCommandsArray =
+            getInfoMap.EnterArrayAt(KEY_GI_RSP_VENDOR_PROTOTYPE_CONFIG_COMMANDS)) {
+        for (size_t i = 0; i < vendorPrototypeConfigCommandsArray->Length(); i++) {
+            m_vendorPrototypeConfigCommands.emplace_back(
+                vendorPrototypeConfigCommandsArray->GetUint64());
+        }
+    }
+
+    if (auto attestationFormatsArray = getInfoMap.EnterArrayAt(KEY_GI_RSP_ATTESTATION_FORMATS)) {
+        for (size_t i = 0; i < attestationFormatsArray->Length(); i++)
+            m_attestationFormats.emplace_back(attestationFormatsArray->GetTextString());
+    }
+
+    m_uvCountSinceLastPinEntry = getInfoMap.GetUint64At(KEY_GI_RSP_UV_COUNT_SINCE_LAST_PIN_ENTRY);
+
+    m_longTouchForReset = getInfoMap.GetBooleanAt(KEY_GI_RSP_LONG_TOUCH_FOR_RESET);
+}
+
diff --git a/srcs/message.h b/srcs/message.h
new file mode 100644 (file)
index 0000000..b38ad61
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#pragma once
+
+#include "common.h"
+
+#include <cstdint>
+#include <optional>
+#include <unordered_map>
+
+class IIncomingMessage {
+public:
+    virtual ~IIncomingMessage() = default;
+    virtual void Deserialize(BufferView &input) = 0;
+};
+
+// Contains only CBOR map
+class PostHandshakeResponse : public IIncomingMessage {
+public:
+    void Deserialize(BufferView &input) override;
+
+    std::vector<std::string> m_versions;
+    std::vector<std::string> m_extensions;
+    Buffer m_aaguid;
+
+    struct Options {
+        bool rk = false;
+        bool uv = false;
+        // TODO other options
+    } m_options;
+
+    std::optional<uint64_t> m_maxMsgSize;
+    std::vector<uint64_t> m_pinUvAuthProtocols;
+    std::optional<uint64_t> m_maxCredentialCountInList;
+    std::optional<uint64_t> m_maxCredentialIdLength;
+    std::vector<std::string> m_transports;
+
+    struct PublicKeyCredentialParameters {
+        std::string m_type;
+        int64_t m_alg;
+    };
+
+    std::vector<PublicKeyCredentialParameters> m_algorithms;
+    std::optional<uint64_t> m_maxSerializedLargeBlobArray;
+    std::optional<bool> m_forcePINChange;
+    std::optional<uint64_t> m_minPINLength;
+    std::optional<uint64_t> m_firmwareVersion;
+    std::optional<uint64_t> m_maxCredBlobLength;
+    std::optional<uint64_t> m_maxRPIDsForSetMinPINLength;
+    std::optional<uint64_t> m_preferredPlatformUvAttempts;
+    std::optional<uint64_t> m_uvModality;
+    std::unordered_map<std::string, uint64_t> m_certifications;
+    std::optional<uint64_t> m_remainingDiscoverableCredentials;
+    std::vector<uint64_t> m_vendorPrototypeConfigCommands;
+    std::vector<std::string> m_attestationFormats;
+    std::optional<uint64_t> m_uvCountSinceLastPinEntry;
+    std::optional<bool> m_longTouchForReset;
+};
+
index f97c79b49d6cc424c58cac8eae94dae67c7614e1..ceef83743daab72bd157b2abd58489d97f142d7a 100644 (file)
@@ -42,6 +42,7 @@ SET(UNIT_TESTS_SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/handshake_tests.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/encrypted_tunnel_tests.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/base64_tests.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/message_tests.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/crypto/hkdf_unittest.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/crypto/hmac_unittest.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/crypto/sha2_unittest.cpp
diff --git a/tests/message_tests.cpp b/tests/message_tests.cpp
new file mode 100644 (file)
index 0000000..57c6633
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "message.h"
+
+#include <gtest/gtest.h>
+
+namespace {
+template <typename T>
+void AssertEq(const T &left, const T &right)
+{
+    ASSERT_EQ(left, right);
+}
+
+} // namespace
+
+TEST(Messages, ParsePostHandshakeMessage1)
+{
+    // example iPhone post handshake message received after handshake
+    const uint8_t blob[] =
+        "\xa1\x01\x58\x4f\xa5\x01\x82\x68\x46\x49\x44\x4f\x5f\x32\x5f\x30\x68\x46\x49\x44\x4f\x5f"
+        "\x32\x5f\x31\x02\x81\x69\x6c\x61\x72\x67\x65\x42\x6c\x6f\x62\x03\x50\xf2\x4a\x8e\x70\xd0"
+        "\xd3\xf8\x2c\x29\x37\x32\x52\x3c\xc4\xde\x5a\x04\xa2\x62\x72\x6b\xf5\x62\x75\x76\xf5\x09"
+        "\x82\x68\x69\x6e\x74\x65\x72\x6e\x61\x6c\x66\x68\x79\x62\x72\x69\x64";
+
+    BufferView view(blob, sizeof(blob) - 1);
+
+    // read post handshake message
+    PostHandshakeResponse msg;
+    ASSERT_NO_THROW(msg.Deserialize(view));
+
+    AssertEq(msg.m_versions, {"FIDO_2_0", "FIDO_2_1"});
+    AssertEq(msg.m_extensions, {"largeBlob"});
+    AssertEq(msg.m_aaguid,
+             {0xf2,
+              0x4a,
+              0x8e,
+              0x70,
+              0xd0,
+              0xd3,
+              0xf8,
+              0x2c,
+              0x29,
+              0x37,
+              0x32,
+              0x52,
+              0x3c,
+              0xc4,
+              0xde,
+              0x5a});
+    ASSERT_TRUE(msg.m_options.uv);
+    ASSERT_TRUE(msg.m_options.rk);
+    ASSERT_FALSE(msg.m_maxMsgSize.has_value());
+    ASSERT_TRUE(msg.m_pinUvAuthProtocols.empty());
+    ASSERT_FALSE(msg.m_maxCredentialCountInList.has_value());
+    ASSERT_FALSE(msg.m_maxCredentialIdLength.has_value());
+    AssertEq(msg.m_transports, {"internal", "hybrid"});
+    ASSERT_TRUE(msg.m_algorithms.empty());
+    ASSERT_FALSE(msg.m_maxSerializedLargeBlobArray.has_value());
+    ASSERT_FALSE(msg.m_forcePINChange.has_value());
+    ASSERT_FALSE(msg.m_minPINLength.has_value());
+    ASSERT_FALSE(msg.m_firmwareVersion.has_value());
+    ASSERT_FALSE(msg.m_maxCredBlobLength.has_value());
+    ASSERT_FALSE(msg.m_maxRPIDsForSetMinPINLength.has_value());
+    ASSERT_FALSE(msg.m_preferredPlatformUvAttempts.has_value());
+    ASSERT_FALSE(msg.m_uvModality.has_value());
+    ASSERT_TRUE(msg.m_certifications.empty());
+    ASSERT_FALSE(msg.m_remainingDiscoverableCredentials.has_value());
+    ASSERT_TRUE(msg.m_vendorPrototypeConfigCommands.empty());
+    ASSERT_TRUE(msg.m_attestationFormats.empty());
+    ASSERT_FALSE(msg.m_uvCountSinceLastPinEntry.has_value());
+    ASSERT_FALSE(msg.m_longTouchForReset.has_value());
+}
+
+TEST(Messages, ParsePostHandshakeMessage2)
+{
+    // example Android post handshake message received after handshake
+    const uint8_t blob[] =
+        "\xa1\x01\x58\x53\xa5\x01\x82\x68\x46\x49\x44\x4f\x5f\x32\x5f\x30\x68\x46\x49\x44\x4f\x5f"
+        "\x32\x5f\x31\x02\x81\x63\x70\x72\x66\x03\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        "\x00\x00\x00\x00\x00\x04\xa4\x62\x72\x6b\xf5\x62\x75\x70\xf5\x62\x75\x76\xf5\x64\x70\x6c"
+        "\x61\x74\xf4\x09\x82\x68\x69\x6e\x74\x65\x72\x6e\x61\x6c\x66\x68\x79\x62\x72\x69\x64";
+
+    BufferView view(blob, sizeof(blob) - 1);
+
+    // read post handshake message
+    PostHandshakeResponse msg;
+    ASSERT_NO_THROW(msg.Deserialize(view));
+
+    AssertEq(msg.m_versions, {"FIDO_2_0", "FIDO_2_1"});
+    AssertEq(msg.m_extensions, {"prf"});
+    AssertEq(msg.m_aaguid, Buffer(16, 0x00));
+    ASSERT_TRUE(msg.m_options.uv);
+    ASSERT_TRUE(msg.m_options.rk);
+    ASSERT_FALSE(msg.m_maxMsgSize.has_value());
+    ASSERT_TRUE(msg.m_pinUvAuthProtocols.empty());
+    ASSERT_FALSE(msg.m_maxCredentialCountInList.has_value());
+    ASSERT_FALSE(msg.m_maxCredentialIdLength.has_value());
+    AssertEq(msg.m_transports, {"internal", "hybrid"});
+    ASSERT_TRUE(msg.m_algorithms.empty());
+    ASSERT_FALSE(msg.m_maxSerializedLargeBlobArray.has_value());
+    ASSERT_FALSE(msg.m_forcePINChange.has_value());
+    ASSERT_FALSE(msg.m_minPINLength.has_value());
+    ASSERT_FALSE(msg.m_firmwareVersion.has_value());
+    ASSERT_FALSE(msg.m_maxCredBlobLength.has_value());
+    ASSERT_FALSE(msg.m_maxRPIDsForSetMinPINLength.has_value());
+    ASSERT_FALSE(msg.m_preferredPlatformUvAttempts.has_value());
+    ASSERT_FALSE(msg.m_uvModality.has_value());
+    ASSERT_TRUE(msg.m_certifications.empty());
+    ASSERT_FALSE(msg.m_remainingDiscoverableCredentials.has_value());
+    ASSERT_TRUE(msg.m_vendorPrototypeConfigCommands.empty());
+    ASSERT_TRUE(msg.m_attestationFormats.empty());
+    ASSERT_FALSE(msg.m_uvCountSinceLastPinEntry.has_value());
+    ASSERT_FALSE(msg.m_longTouchForReset.has_value());
+}