Get assertion command serialization 32/307732/16
authorKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Tue, 12 Mar 2024 17:35:31 +0000 (18:35 +0100)
committerKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Tue, 26 Mar 2024 12:58:52 +0000 (13:58 +0100)
Change-Id: Ic6f57137714bb6442c60903271e459212334f526

srcs/message.cpp
srcs/message.h
tests/message_tests.cpp

index 89fd634ab630f864f69e0505dd43f019748595e1..76bd94d43580cca58e32503ebd4c7b1435e6acfd 100644 (file)
@@ -177,6 +177,17 @@ constexpr int64_t KEY_MC_RSP_EP_ATT = 0x04;
 constexpr int64_t KEY_MC_RSP_LARGE_BLOB_KEY = 0x05;
 constexpr int64_t KEY_MC_RSP_UNSIGNED_EXTENSIONS_OUTPUT = 0x06;
 
+// Get Assertion command
+constexpr int64_t KEY_GA_CMD_RP_ID = 0x01;
+constexpr int64_t KEY_GA_CMD_CLIENT_DATA_HASH = 0x02;
+constexpr int64_t KEY_GA_CMD_ALLOW_LIST = 0x03;
+constexpr int64_t KEY_GA_CMD_EXTENSIONS = 0x04;
+constexpr int64_t KEY_GA_CMD_OPTIONS = 0x05;
+constexpr int64_t KEY_GA_CMD_PIN_UV_AUTH_PARAM = 0x06;
+constexpr int64_t KEY_GA_CMD_PIN_UV_AUTH_PROTOCOL = 0x07;
+constexpr int64_t KEY_GA_CMD_ENTERPRISE_ATTESTATION = 0x08;
+constexpr int64_t KEY_GA_CMD_ATTESTATION_FORMAT_PREFERENCE = 0x09;
+
 void SerializePubkeyCredDescriptors(CborEncoding::SortedMap &map,
                                     const CborEncoding::Key &key,
                                     const wauthn_pubkey_cred_descriptors_s &credentials)
@@ -427,6 +438,61 @@ void MakeCredentialCommand::Serialize(Buffer &output) const
     output.resize(prefixSize + encoder.GetBufferSize());
 }
 
+GetAssertionCommand::GetAssertionCommand(const wauthn_client_data_s &clientData,
+                                         const wauthn_pubkey_cred_request_options_s &options,
+                                         PostHandshakeResponse::Options supportedOptions)
+: CtapCommand(Method::GET_ASSERTION, clientData, std::move(supportedOptions)), m_options(options)
+{
+}
+
+void GetAssertionCommand::Serialize(Buffer &output) const
+{
+    CtapCommand::Serialize(output);
+
+    constexpr size_t CBOR_MAX_SIZE = 1024;
+    size_t prefixSize = output.size();
+    output.resize(prefixSize + CBOR_MAX_SIZE);
+
+    auto encoder = CborEncoding::Encoder::Create(output.data() + prefixSize, CBOR_MAX_SIZE);
+    {
+        size_t keys = 2;
+        if (m_options.allow_credentials)
+            keys++;
+
+        bool uv = (m_options.user_verification == UVR_REQUIRED) ||
+            (m_options.user_verification == UVR_PREFERRED && m_supportedOptions.uv);
+        if (uv)
+            keys++;
+
+        auto map = encoder.OpenMap(keys);
+
+        ValidateDomain(m_options.rpId);
+        map.AppendTextStringZAt(KEY_GA_CMD_RP_ID, m_options.rpId);
+
+        map.AppendByteStringAt(KEY_GA_CMD_CLIENT_DATA_HASH, m_clientDataHash);
+
+        if (m_options.allow_credentials)
+            SerializePubkeyCredDescriptors(
+                map, KEY_GA_CMD_ALLOW_LIST, *m_options.allow_credentials);
+
+        // TODO 0x04 extensions
+
+        if (uv) {
+            auto optionsMap = map.OpenMapAt(KEY_GA_CMD_OPTIONS, 1);
+            optionsMap.AppendBooleanAt("uv", true);
+        }
+
+        // TODO 0x06 pinUvAuthParam
+
+        // TODO 0x07 pinUvAuthProtocol
+
+        // TODO 0x08 enterpriseAttestation
+
+        // TODO 0x09 attestationFormatsPreference
+    }
+    output.resize(prefixSize + encoder.GetBufferSize());
+}
+
 void CtapResponse::Deserialize(BufferView &input)
 {
     // Deserialize CTAP response status code
index d03575faf6b1b38b2fc97a0d0be92a3f9f17d53b..6f202642e0470959cf1cfbe7d08e0be7d455fb66 100644 (file)
@@ -132,6 +132,19 @@ private:
     const wauthn_pubkey_cred_creation_options_s &m_options;
 };
 
+// Contains message type (1B) | Command code (1B) | CBOR map
+class GetAssertionCommand : public CtapCommand {
+public:
+    GetAssertionCommand(const wauthn_client_data_s &clientData,
+                        const wauthn_pubkey_cred_request_options_s &options,
+                        PostHandshakeResponse::Options supportedOptions);
+
+    void Serialize(Buffer &output) const override;
+
+private:
+    const wauthn_pubkey_cred_request_options_s &m_options;
+};
+
 class IIncomingNotifyingMessage : public IIncomingMessage {
 public:
     virtual void Notify(IMessageObserver &observer) = 0;
index d749c17e025fa3b005bc2fe72f40b08e6b16f67b..3b4ad2dbb56a440d9a98fd83f25bc89054479dd4 100644 (file)
@@ -122,6 +122,73 @@ constexpr uint8_t SAMPLE_MAKE_CREDENTIAL_REQUEST[] = {
     // clang-format on
 };
 
+// from chromium/device/fido/fido_test_data.h
+constexpr uint8_t SAMPLE_GET_ASSERTION_REQUEST[] = {
+    // clang-format off
+    // CTAP message type (not present in chromium, added in webauthn-ble)
+    0x01,
+    // authenticatorGetAssertion command
+    0x02,
+    // map(4)
+    0xa4,
+    // key(01) -rpId
+    0x01,
+    // value - "acme.com"
+    0x68, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+    // key(02) - client data hash
+    0x02,
+    // bytes(32) -- see kClientDataHash
+    0x58, 0x20, 0x8d, 0xd8, 0x74, 0x4d, 0x79, 0x3, 0xb0, 0xa3, 0x53, 0x8a, 0x49,
+    0xea, 0xfa, 0xae, 0xc8, 0x33, 0xac, 0xbf, 0xd2, 0x85, 0xa5, 0xdf, 0x44, 0x3,
+    0xa2, 0xe, 0x4e, 0x13, 0xe3, 0xd5, 0x3e, 0x50,
+    // key(03) - allow list
+    0x03,
+    // value - array(2)
+    0x82,
+    // map(2)
+    0xa2,
+    // key - "id"
+    0x62, 0x69, 0x64,
+    // value - credential ID
+    0x58, 0x40, 0xf2, 0x20, 0x06, 0xde, 0x4f, 0x90, 0x5a, 0xf6, 0x8a, 0x43,
+    0x94, 0x2f, 0x02, 0x4f, 0x2a, 0x5e, 0xce, 0x60, 0x3d, 0x9c, 0x6d, 0x4b,
+    0x3d, 0xf8, 0xbe, 0x08, 0xed, 0x01, 0xfc, 0x44, 0x26, 0x46, 0xd0, 0x34,
+    0x85, 0x8a, 0xc7, 0x5b, 0xed, 0x3f, 0xd5, 0x80, 0xbf, 0x98, 0x08, 0xd9,
+    0x4f, 0xcb, 0xee, 0x82, 0xb9, 0xb2, 0xef, 0x66, 0x77, 0xaf, 0x0a, 0xdc,
+    0xc3, 0x58, 0x52, 0xea, 0x6b, 0x9e,
+    // key - "type"
+    0x64, 0x74, 0x79, 0x70, 0x65,
+    // value - "public-key"
+    0x6a, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
+    // map(2)
+    0xa2,
+    // key - "id"
+    0x62, 0x69, 0x64,
+    // value - credential ID
+    0x58, 0x32, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+    0x03, 0x03, 0x03, 0x03,
+    // key - "type"
+    0x64, 0x74, 0x79, 0x70, 0x65,
+    // value - "public-key"
+    0x6a, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
+    // unsigned(5) - options
+    0x05,
+    // map(1) <- "up" key skipped in webauthn-ble as there's no way to express it in wauthn_pubkey_cred_request_options_s
+    0xa1,
+    // key -"up"
+    //0x62, 0x75, 0x70,
+    // value - False(20)
+    //0xf4,
+    // key - "uv"
+    0x62, 0x75, 0x76,
+    // value - True(21)
+    0xf5,
+    // clang-format on
+};
+
 } // namespace
 
 TEST(Messages, ParsePostHandshakeMessage1)
@@ -415,6 +482,67 @@ TEST(Messages, SerializeMakeCredential2)
     AssertEq(buffer, SAMPLE_MAKE_CREDENTIAL_REQUEST, sizeof(SAMPLE_MAKE_CREDENTIAL_REQUEST));
 }
 
+TEST(Messages, SerializeGetAssertion)
+{
+    unsigned char client_data_json_data[] =
+        R"({"challenge":"foobar","new_keys_may_be_added_here":"do not compare clientDataJSON against a template. See https://goo.gl/yabPex","origin":"https://google.com","type":"webauthn.create"})";
+    wauthn_const_buffer_s client_data_json;
+    client_data_json.data = client_data_json_data;
+    client_data_json.size = sizeof(client_data_json_data) - 1; // -1 to remove zero-termination
+    wauthn_client_data_s client_data;
+    client_data.hash_alg = WAUTHN_HASH_ALGORITHM_SHA_256;
+    client_data.client_data_json = &client_data_json;
+
+    char rp_id[] = "acme.com";
+
+    wauthn_pubkey_cred_descriptor_s pubkey_cred_descriptor[2];
+
+    unsigned char credential_id0[] =
+        "\xf2\x20\x06\xde\x4f\x90\x5a\xf6\x8a\x43\x94\x2f\x02\x4f\x2a\x5e\xce\x60\x3d\x9c\x6d\x4b"
+        "\x3d\xf8\xbe\x08\xed\x01\xfc\x44\x26\x46\xd0\x34\x85\x8a\xc7\x5b\xed\x3f\xd5\x80\xbf\x98"
+        "\x08\xd9\x4f\xcb\xee\x82\xb9\xb2\xef\x66\x77\xaf\x0a\xdc\xc3\x58\x52\xea\x6b\x9e";
+    wauthn_const_buffer_s desc_id0;
+    desc_id0.data = credential_id0;
+    desc_id0.size = sizeof(credential_id0) - 1; // -1 to remove zero-termination
+    pubkey_cred_descriptor[0].type = PCT_PUBLIC_KEY;
+    pubkey_cred_descriptor[0].id = &desc_id0;
+    pubkey_cred_descriptor[0].transports = WAUTHN_TRANSPORT_NONE;
+
+    unsigned char credential_id1[] =
+        "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03"
+        "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03"
+        "\x03\x03\x03\x03\x03\x03";
+    wauthn_const_buffer_s desc_id1;
+    desc_id1.data = credential_id1;
+    desc_id1.size = sizeof(credential_id1) - 1; // -1 to remove zero-termination
+    pubkey_cred_descriptor[1].type = PCT_PUBLIC_KEY;
+    pubkey_cred_descriptor[1].id = &desc_id1;
+    pubkey_cred_descriptor[1].transports = WAUTHN_TRANSPORT_NONE;
+
+    wauthn_pubkey_cred_descriptors_s pubkey_cred_descriptors;
+    pubkey_cred_descriptors.size =
+        sizeof(pubkey_cred_descriptor) / sizeof(pubkey_cred_descriptor[0]);
+    pubkey_cred_descriptors.descriptors = pubkey_cred_descriptor;
+
+    wauthn_pubkey_cred_request_options_s options;
+    options.timeout = 0; // not serialized
+    options.rpId = rp_id;
+    options.allow_credentials = &pubkey_cred_descriptors;
+    options.user_verification = UVR_REQUIRED;
+    options.hints = nullptr;
+    options.attestation = AP_NONE;
+    options.attestation_formats = nullptr;
+    options.extensions = nullptr;
+    options.linked_device = nullptr;
+
+    // serialize GA command
+    Buffer buffer;
+    GetAssertionCommand msg(client_data, options, {false, false});
+
+    ASSERT_NO_THROW(msg.Serialize(buffer));
+    AssertEq(buffer, SAMPLE_GET_ASSERTION_REQUEST, sizeof(SAMPLE_GET_ASSERTION_REQUEST));
+}
+
 TEST(Messages, ShutdownMessage)
 {
     Buffer buffer;