${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)
THROW_ENCODING("cbor_encode_text_stringz() failed with " << static_cast<int>(err));
}
-void Container::AppendTextString(const std::string_view& text)
+void Container::AppendTextString(const std::string_view &text)
{
assert(!m_appendingToChild);
Container &operator=(Container &&) = delete;
void AppendTextStringZ(const char *value);
- void AppendTextString(const std::string_view& text);
+ 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);
#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")
--- /dev/null
+/*
+ * 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);
+}
+
--- /dev/null
+/*
+ * 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;
+};
+
${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
--- /dev/null
+/*
+ * 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());
+}