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)
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
// 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)
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;