| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <algorithm> |
| |
| #include "base/containers/contains.h" |
| #include "components/cbor/reader.h" |
| #include "components/cbor/values.h" |
| #include "components/cbor/writer.h" |
| #include "device/fido/attestation_statement_formats.h" |
| #include "device/fido/authenticator_get_assertion_response.h" |
| #include "device/fido/authenticator_make_credential_response.h" |
| #include "device/fido/device_response_converter.h" |
| #include "device/fido/fido_constants.h" |
| #include "device/fido/fido_parsing_utils.h" |
| #include "device/fido/fido_test_data.h" |
| #include "device/fido/fido_transport_protocol.h" |
| #include "device/fido/fido_types.h" |
| #include "device/fido/opaque_attestation_statement.h" |
| #include "device/fido/p256_public_key.h" |
| #include "device/fido/public_key.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace device { |
| |
| namespace { |
| |
| // clang-format off |
| constexpr uint8_t kTestAuthenticatorGetInfoResponseWithNoVersion[] = { |
| // Success status byte |
| 0x00, |
| // Map of 6 elements |
| 0xA6, |
| // Key(01) - versions |
| 0x01, |
| // Array(0) |
| 0x80, |
| // Key(02) - extensions |
| 0x02, |
| // Array(2) |
| 0x82, |
| // "uvm" |
| 0x63, 0x75, 0x76, 0x6D, |
| // "hmac-secret" |
| 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, |
| // Key(03) - AAGUID |
| 0x03, |
| // Bytes(16) |
| 0x50, 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, |
| 0x11, 0x1F, 0x9E, 0xDC, 0x7D, |
| // Key(04) - options |
| 0x04, |
| // Map(05) |
| 0xA5, |
| // Key - "rk" |
| 0x62, 0x72, 0x6B, |
| // true |
| 0xF5, |
| // Key - "up" |
| 0x62, 0x75, 0x70, |
| // true |
| 0xF5, |
| // Key - "uv" |
| 0x62, 0x75, 0x76, |
| // true |
| 0xF5, |
| // Key - "plat" |
| 0x64, 0x70, 0x6C, 0x61, 0x74, |
| // true |
| 0xF5, |
| // Key - "clientPin" |
| 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, |
| // false |
| 0xF4, |
| // Key(05) - Max message size |
| 0x05, |
| // 1200 |
| 0x19, 0x04, 0xB0, |
| // Key(06) - Pin protocols |
| 0x06, |
| // Array[1] |
| 0x81, 0x01, |
| }; |
| |
| constexpr uint8_t kTestAuthenticatorGetInfoResponseWithDuplicateVersion[] = { |
| // Success status byte |
| 0x00, |
| // Map of 6 elements |
| 0xA6, |
| // Key(01) - versions |
| 0x01, |
| // Array(03) |
| 0x83, |
| // "U2F_V9" |
| 0x66, 0x55, 0x32, 0x46, 0x5F, 0x56, 0x39, |
| // "U2F_V9" |
| 0x66, 0x55, 0x32, 0x46, 0x5F, 0x56, 0x39, |
| // "U2F_V2" |
| 0x66, 0x55, 0x32, 0x46, 0x5F, 0x56, 0x32, |
| // Key(02) - extensions |
| 0x02, |
| // Array(2) |
| 0x82, |
| // "uvm" |
| 0x63, 0x75, 0x76, 0x6D, |
| // "hmac-secret" |
| 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, |
| // Key(03) - AAGUID |
| 0x03, |
| // Bytes(16) |
| 0x50, 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, |
| 0x11, 0x1F, 0x9E, 0xDC, 0x7D, |
| // Key(04) - options |
| 0x04, |
| // Map(05) |
| 0xA5, |
| // Key - "rk" |
| 0x62, 0x72, 0x6B, |
| // true |
| 0xF5, |
| // Key - "up" |
| 0x62, 0x75, 0x70, |
| // true |
| 0xF5, |
| // Key - "uv" |
| 0x62, 0x75, 0x76, |
| // true |
| 0xF5, |
| // Key - "plat" |
| 0x64, 0x70, 0x6C, 0x61, 0x74, |
| // true |
| 0xF5, |
| // Key - "clientPin" |
| 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, |
| // false |
| 0xF4, |
| // Key(05) - Max message size |
| 0x05, |
| // 1200 |
| 0x19, 0x04, 0xB0, |
| // Key(06) - Pin protocols |
| 0x06, |
| // Array[1] |
| 0x81, 0x01, |
| }; |
| |
| constexpr uint8_t kTestAuthenticatorGetInfoResponseWithCtap2_1[] = { |
| // Success status byte |
| 0x00, |
| // Map of 6 elements |
| 0xA6, |
| // Key(01) - versions |
| 0x01, |
| // Array(03) |
| 0x83, |
| // "U2F_V2" |
| 0x66, 'U', '2', 'F', '_', 'V', '2', |
| // "FIDO_2_0" |
| 0x68, 'F', 'I', 'D', 'O', '_', '2', '_', '0', |
| // "FIDO_2_1" |
| 0x68, 'F', 'I', 'D', 'O', '_', '2', '_', '1', |
| // Key(02) - extensions |
| 0x02, |
| // Array(2) |
| 0x82, |
| // "uvm" |
| 0x63, 0x75, 0x76, 0x6D, |
| // "hmac-secret" |
| 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, |
| // Key(03) - AAGUID |
| 0x03, |
| // Bytes(16) |
| 0x50, 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, |
| 0x11, 0x1F, 0x9E, 0xDC, 0x7D, |
| // Key(04) - options |
| 0x04, |
| // Map(05) |
| 0xA5, |
| // Key - "rk" |
| 0x62, 0x72, 0x6B, |
| // true |
| 0xF5, |
| // Key - "up" |
| 0x62, 0x75, 0x70, |
| // true |
| 0xF5, |
| // Key - "uv" |
| 0x62, 0x75, 0x76, |
| // true |
| 0xF5, |
| // Key - "plat" |
| 0x64, 0x70, 0x6C, 0x61, 0x74, |
| // true |
| 0xF5, |
| // Key - "clientPin" |
| 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, |
| // false |
| 0xF4, |
| // Key(05) - Max message size |
| 0x05, |
| // 1200 |
| 0x19, 0x04, 0xB0, |
| // Key(06) - Pin protocols |
| 0x06, |
| // Array[1] |
| 0x81, 0x01, |
| }; |
| |
| constexpr uint8_t kTestAuthenticatorGetInfoResponseOnlyCtap2_1[] = { |
| // Success status byte |
| 0x00, |
| // Map of 6 elements |
| 0xA6, |
| // Key(01) - versions |
| 0x01, |
| // Array(01) |
| 0x81, |
| // "FIDO_2_1" |
| 0x68, 'F', 'I', 'D', 'O', '_', '2', '_', '1', |
| // Key(02) - extensions |
| 0x02, |
| // Array(2) |
| 0x82, |
| // "uvm" |
| 0x63, 0x75, 0x76, 0x6D, |
| // "hmac-secret" |
| 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, |
| // Key(03) - AAGUID |
| 0x03, |
| // Bytes(16) |
| 0x50, 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, |
| 0x11, 0x1F, 0x9E, 0xDC, 0x7D, |
| // Key(04) - options |
| 0x04, |
| // Map(05) |
| 0xA5, |
| // Key - "rk" |
| 0x62, 0x72, 0x6B, |
| // true |
| 0xF5, |
| // Key - "up" |
| 0x62, 0x75, 0x70, |
| // true |
| 0xF5, |
| // Key - "uv" |
| 0x62, 0x75, 0x76, |
| // true |
| 0xF5, |
| // Key - "plat" |
| 0x64, 0x70, 0x6C, 0x61, 0x74, |
| // true |
| 0xF5, |
| // Key - "clientPin" |
| 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, |
| // false |
| 0xF4, |
| // Key(05) - Max message size |
| 0x05, |
| // 1200 |
| 0x19, 0x04, 0xB0, |
| // Key(06) - Pin protocols |
| 0x06, |
| // Array[1] |
| 0x81, 0x01, |
| }; |
| |
| constexpr uint8_t kTestAuthenticatorGetInfoResponseWithIncorrectAaguid[] = { |
| // Success status byte |
| 0x00, |
| // Map of 6 elements |
| 0xA6, |
| // Key(01) - versions |
| 0x01, |
| // Array(01) |
| 0x81, |
| // "U2F_V2" |
| 0x66, 0x55, 0x32, 0x46, 0x5F, 0x56, 0x32, |
| // Key(02) - extensions |
| 0x02, |
| // Array(2) |
| 0x82, |
| // "uvm" |
| 0x63, 0x75, 0x76, 0x6D, |
| // "hmac-secret" |
| 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, |
| // Key(03) - AAGUID |
| 0x03, |
| // Bytes(17) - FIDO2 device AAGUID must be 16 bytes long in order to be |
| // correct. |
| 0x51, 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, |
| 0x11, 0x1F, 0x9E, 0xDC, 0x7D, 0x00, |
| // Key(04) - options |
| 0x04, |
| // Map(05) |
| 0xA5, |
| // Key - "rk" |
| 0x62, 0x72, 0x6B, |
| // true |
| 0xF5, |
| // Key - "up" |
| 0x62, 0x75, 0x70, |
| // true |
| 0xF5, |
| // Key - "uv" |
| 0x62, 0x75, 0x76, |
| // true |
| 0xF5, |
| // Key - "plat" |
| 0x64, 0x70, 0x6C, 0x61, 0x74, |
| // true |
| 0xF5, |
| // Key - "clientPin" |
| 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, |
| // false |
| 0xF4, |
| // Key(05) - Max message size |
| 0x05, |
| // 1200 |
| 0x19, 0x04, 0xB0, |
| // Key(06) - Pin protocols |
| 0x06, |
| // Array[1] |
| 0x81, 0x01, |
| }; |
| |
| // The attested credential data, excluding the public key bytes. Append |
| // with kTestECPublicKeyCOSE to get the complete attestation data. |
| constexpr uint8_t kTestAttestedCredentialDataPrefix[] = { |
| // 16-byte aaguid |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| // 2-byte length |
| 0x00, 0x40, |
| // 64-byte key handle |
| 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, 0x9C, 0x26, |
| 0x35, 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, 0x36, 0xC3, |
| 0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, 0xFF, 0x94, |
| 0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, 0x39, 0x6B, 0x64, |
| 0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, 0xCD, 0x08, |
| 0xFE, 0x42, 0x00, 0x38, |
| }; |
| |
| // The authenticator data, excluding the attested credential data bytes. Append |
| // with attested credential data to get the complete authenticator data. |
| constexpr uint8_t kTestAuthenticatorDataPrefix[] = { |
| // sha256 hash of rp id. |
| 0x11, 0x94, 0x22, 0x8D, 0xA8, 0xFD, 0xBD, 0xEE, 0xFD, 0x26, 0x1B, 0xD7, |
| 0xB6, 0x59, 0x5C, 0xFD, 0x70, 0xA5, 0x0D, 0x70, 0xC6, 0x40, 0x7B, 0xCF, |
| 0x01, 0x3D, 0xE9, 0x6D, 0x4E, 0xFB, 0x17, 0xDE, |
| // flags (TUP and AT bits set) |
| 0x41, |
| // counter |
| 0x00, 0x00, 0x00, 0x00}; |
| |
| // Components of the CBOR needed to form an authenticator object. |
| // Combined diagnostic notation: |
| // {"fmt": "fido-u2f", "attStmt": {"sig": h'30...}, "authData": h'D4C9D9...'} |
| constexpr uint8_t kFormatFidoU2fCBOR[] = { |
| // map(3) |
| 0xA3, |
| // text(3) |
| 0x63, |
| // "fmt" |
| 0x66, 0x6D, 0x74, |
| // text(8) |
| 0x68, |
| // "fido-u2f" |
| 0x66, 0x69, 0x64, 0x6F, 0x2D, 0x75, 0x32, 0x66}; |
| |
| constexpr uint8_t kAttStmtCBOR[] = { |
| // text(7) |
| 0x67, |
| // "attStmt" |
| 0x61, 0x74, 0x74, 0x53, 0x74, 0x6D, 0x74}; |
| |
| constexpr uint8_t kAuthDataCBOR[] = { |
| // text(8) |
| 0x68, |
| // "authData" |
| 0x61, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, |
| // bytes(196). i.e., the authenticator_data byte array corresponding to |
| // kTestAuthenticatorDataPrefix|, |kTestAttestedCredentialDataPrefix|, |
| // and test_data::kTestECPublicKeyCOSE. |
| 0x58, 0xC4}; |
| // clang-format on |
| |
| constexpr std::array<uint8_t, kAaguidLength> kTestDeviceAaguid = { |
| {0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, 0x11, |
| 0x1F, 0x9E, 0xDC, 0x7D}}; |
| |
| std::vector<uint8_t> GetTestAttestedCredentialDataBytes() { |
| // Combine kTestAttestedCredentialDataPrefix and kTestECPublicKeyCOSE. |
| auto test_attested_data = |
| fido_parsing_utils::Materialize(kTestAttestedCredentialDataPrefix); |
| fido_parsing_utils::Append(&test_attested_data, |
| test_data::kTestECPublicKeyCOSE); |
| return test_attested_data; |
| } |
| |
| std::vector<uint8_t> GetTestAuthenticatorDataBytes() { |
| // Build the test authenticator data. |
| auto test_authenticator_data = |
| fido_parsing_utils::Materialize(kTestAuthenticatorDataPrefix); |
| auto test_attested_data = GetTestAttestedCredentialDataBytes(); |
| fido_parsing_utils::Append(&test_authenticator_data, test_attested_data); |
| return test_authenticator_data; |
| } |
| |
| std::vector<uint8_t> GetTestAttestationObjectBytes() { |
| auto test_authenticator_object = |
| fido_parsing_utils::Materialize(kFormatFidoU2fCBOR); |
| fido_parsing_utils::Append(&test_authenticator_object, kAttStmtCBOR); |
| fido_parsing_utils::Append(&test_authenticator_object, |
| test_data::kU2fAttestationStatementCBOR); |
| fido_parsing_utils::Append(&test_authenticator_object, kAuthDataCBOR); |
| auto test_authenticator_data = GetTestAuthenticatorDataBytes(); |
| fido_parsing_utils::Append(&test_authenticator_object, |
| test_authenticator_data); |
| return test_authenticator_object; |
| } |
| |
| std::vector<uint8_t> GetTestSignResponse() { |
| return fido_parsing_utils::Materialize(test_data::kTestU2fSignResponse); |
| } |
| |
| // Get a subset of the response for testing error handling. |
| std::vector<uint8_t> GetTestCorruptedSignResponse(size_t length) { |
| DCHECK_LE(length, std::size(test_data::kTestU2fSignResponse)); |
| return fido_parsing_utils::Materialize(fido_parsing_utils::ExtractSpan( |
| test_data::kTestU2fSignResponse, 0, length)); |
| } |
| |
| // Return a key handle used for GetAssertion request. |
| std::vector<uint8_t> GetTestCredentialRawIdBytes() { |
| return fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle); |
| } |
| |
| // DecodeCBOR parses a CBOR structure, ignoring the first byte of |in|, which is |
| // assumed to be a CTAP2 status byte. |
| absl::optional<cbor::Value> DecodeCBOR(base::span<const uint8_t> in) { |
| CHECK(!in.empty()); |
| return cbor::Reader::Read(in.subspan(1)); |
| } |
| |
| } // namespace |
| |
| // Leveraging example 4 of section 6.1 of the spec https://fidoalliance.org |
| // /specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd- |
| // 20170927.html |
| TEST(CTAPResponseTest, TestReadMakeCredentialResponse) { |
| auto make_credential_response = ReadCTAPMakeCredentialResponse( |
| FidoTransportProtocol::kUsbHumanInterfaceDevice, |
| DecodeCBOR(test_data::kTestMakeCredentialResponse)); |
| ASSERT_TRUE(make_credential_response); |
| auto cbor_attestation_object = cbor::Reader::Read( |
| make_credential_response->GetCBOREncodedAttestationObject()); |
| ASSERT_TRUE(cbor_attestation_object); |
| ASSERT_TRUE(cbor_attestation_object->is_map()); |
| |
| const auto& attestation_object_map = cbor_attestation_object->GetMap(); |
| auto it = attestation_object_map.find(cbor::Value(kFormatKey)); |
| ASSERT_TRUE(it != attestation_object_map.end()); |
| ASSERT_TRUE(it->second.is_string()); |
| EXPECT_EQ(it->second.GetString(), "packed"); |
| |
| it = attestation_object_map.find(cbor::Value(kAuthDataKey)); |
| ASSERT_TRUE(it != attestation_object_map.end()); |
| ASSERT_TRUE(it->second.is_bytestring()); |
| EXPECT_THAT( |
| it->second.GetBytestring(), |
| ::testing::ElementsAreArray(test_data::kCtap2MakeCredentialAuthData)); |
| |
| it = attestation_object_map.find(cbor::Value(kAttestationStatementKey)); |
| ASSERT_TRUE(it != attestation_object_map.end()); |
| ASSERT_TRUE(it->second.is_map()); |
| |
| const auto& attestation_statement_map = it->second.GetMap(); |
| auto attStmt_it = attestation_statement_map.find(cbor::Value("alg")); |
| |
| ASSERT_TRUE(attStmt_it != attestation_statement_map.end()); |
| ASSERT_TRUE(attStmt_it->second.is_integer()); |
| EXPECT_EQ(attStmt_it->second.GetInteger(), -7); |
| |
| attStmt_it = attestation_statement_map.find(cbor::Value("sig")); |
| ASSERT_TRUE(attStmt_it != attestation_statement_map.end()); |
| ASSERT_TRUE(attStmt_it->second.is_bytestring()); |
| EXPECT_THAT( |
| attStmt_it->second.GetBytestring(), |
| ::testing::ElementsAreArray(test_data::kCtap2MakeCredentialSignature)); |
| |
| attStmt_it = attestation_statement_map.find(cbor::Value("x5c")); |
| ASSERT_TRUE(attStmt_it != attestation_statement_map.end()); |
| const auto& certificate = attStmt_it->second; |
| ASSERT_TRUE(certificate.is_array()); |
| ASSERT_EQ(certificate.GetArray().size(), 1u); |
| ASSERT_TRUE(certificate.GetArray()[0].is_bytestring()); |
| EXPECT_THAT( |
| certificate.GetArray()[0].GetBytestring(), |
| ::testing::ElementsAreArray(test_data::kCtap2MakeCredentialCertificate)); |
| EXPECT_THAT( |
| make_credential_response->attestation_object().GetCredentialId(), |
| ::testing::ElementsAreArray(test_data::kCtap2MakeCredentialCredentialId)); |
| } |
| |
| TEST(CTAPResponseTest, TestMakeCredentialNoneAttestationResponse) { |
| auto make_credential_response = ReadCTAPMakeCredentialResponse( |
| FidoTransportProtocol::kUsbHumanInterfaceDevice, |
| DecodeCBOR(test_data::kTestMakeCredentialResponse)); |
| ASSERT_TRUE(make_credential_response); |
| make_credential_response->EraseAttestationStatement( |
| AttestationObject::AAGUID::kErase); |
| EXPECT_THAT(make_credential_response->GetCBOREncodedAttestationObject(), |
| ::testing::ElementsAreArray(test_data::kNoneAttestationResponse)); |
| } |
| |
| // Leveraging example 5 of section 6.1 of the CTAP spec. |
| // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html |
| TEST(CTAPResponseTest, TestReadGetAssertionResponse) { |
| auto get_assertion_response = ReadCTAPGetAssertionResponse( |
| FidoTransportProtocol::kBluetoothLowEnergy, |
| DecodeCBOR(test_data::kDeviceGetAssertionResponse)); |
| ASSERT_TRUE(get_assertion_response); |
| EXPECT_EQ(*get_assertion_response->transport_used, |
| FidoTransportProtocol::kBluetoothLowEnergy); |
| ASSERT_TRUE(get_assertion_response->num_credentials); |
| EXPECT_EQ(*get_assertion_response->num_credentials, 1u); |
| |
| EXPECT_THAT( |
| get_assertion_response->authenticator_data.SerializeToByteArray(), |
| ::testing::ElementsAreArray(test_data::kCtap2GetAssertionAuthData)); |
| EXPECT_THAT( |
| get_assertion_response->signature, |
| ::testing::ElementsAreArray(test_data::kCtap2GetAssertionSignature)); |
| } |
| |
| // Test that U2F register response is properly parsed. |
| TEST(CTAPResponseTest, TestParseRegisterResponseData) { |
| auto response = |
| AuthenticatorMakeCredentialResponse::CreateFromU2fRegisterResponse( |
| FidoTransportProtocol::kUsbHumanInterfaceDevice, |
| test_data::kApplicationParameter, |
| test_data::kTestU2fRegisterResponse); |
| ASSERT_TRUE(response); |
| EXPECT_THAT(response->attestation_object().GetCredentialId(), |
| ::testing::ElementsAreArray(test_data::kU2fSignKeyHandle)); |
| EXPECT_EQ(GetTestAttestationObjectBytes(), |
| response->GetCBOREncodedAttestationObject()); |
| } |
| |
| // These test the parsing of the U2F raw bytes of the registration response. |
| // Test that an EC public key serializes to CBOR properly. |
| TEST(CTAPResponseTest, TestSerializedPublicKey) { |
| auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( |
| static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), |
| test_data::kTestU2fRegisterResponse); |
| ASSERT_TRUE(public_key); |
| EXPECT_THAT(public_key->cose_key_bytes, |
| ::testing::ElementsAreArray(test_data::kTestECPublicKeyCOSE)); |
| } |
| |
| // Test that the attestation statement cbor map is constructed properly. |
| TEST(CTAPResponseTest, TestParseU2fAttestationStatementCBOR) { |
| auto fido_attestation_statement = |
| FidoAttestationStatement::CreateFromU2fRegisterResponse( |
| test_data::kTestU2fRegisterResponse); |
| ASSERT_TRUE(fido_attestation_statement); |
| auto cbor = cbor::Writer::Write(AsCBOR(*fido_attestation_statement)); |
| ASSERT_TRUE(cbor); |
| EXPECT_THAT(*cbor, ::testing::ElementsAreArray( |
| test_data::kU2fAttestationStatementCBOR)); |
| } |
| |
| // Tests that well-formed attested credential data serializes properly. |
| TEST(CTAPResponseTest, TestSerializeAttestedCredentialData) { |
| auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( |
| static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), |
| test_data::kTestU2fRegisterResponse); |
| auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( |
| test_data::kTestU2fRegisterResponse, std::move(public_key)); |
| ASSERT_TRUE(attested_data); |
| EXPECT_EQ(GetTestAttestedCredentialDataBytes(), |
| attested_data->SerializeAsBytes()); |
| } |
| |
| // Tests that well-formed authenticator data serializes properly. |
| TEST(CTAPResponseTest, TestSerializeAuthenticatorData) { |
| auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( |
| static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), |
| test_data::kTestU2fRegisterResponse); |
| auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( |
| test_data::kTestU2fRegisterResponse, std::move(public_key)); |
| |
| constexpr uint8_t flags = |
| static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence) | |
| static_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); |
| |
| AuthenticatorData authenticator_data(test_data::kApplicationParameter, flags, |
| std::array<uint8_t, 4>{} /* counter */, |
| std::move(attested_data)); |
| |
| EXPECT_EQ(GetTestAuthenticatorDataBytes(), |
| authenticator_data.SerializeToByteArray()); |
| } |
| |
| // Tests that a U2F attestation object serializes properly. |
| TEST(CTAPResponseTest, TestSerializeU2fAttestationObject) { |
| auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( |
| static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), |
| test_data::kTestU2fRegisterResponse); |
| auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( |
| test_data::kTestU2fRegisterResponse, std::move(public_key)); |
| |
| constexpr uint8_t flags = |
| static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence) | |
| static_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); |
| AuthenticatorData authenticator_data(test_data::kApplicationParameter, flags, |
| std::array<uint8_t, 4>{} /* counter */, |
| std::move(attested_data)); |
| |
| // Construct the attestation statement. |
| auto fido_attestation_statement = |
| FidoAttestationStatement::CreateFromU2fRegisterResponse( |
| test_data::kTestU2fRegisterResponse); |
| |
| // Construct the attestation object. |
| auto attestation_object = std::make_unique<AttestationObject>( |
| std::move(authenticator_data), std::move(fido_attestation_statement)); |
| |
| ASSERT_TRUE(attestation_object); |
| EXPECT_EQ(GetTestAttestationObjectBytes(), |
| cbor::Writer::Write(AsCBOR(*attestation_object)) |
| .value_or(std::vector<uint8_t>())); |
| } |
| |
| // Tests that U2F authenticator data is properly serialized. |
| TEST(CTAPResponseTest, TestSerializeAuthenticatorDataForSign) { |
| constexpr uint8_t flags = |
| static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence); |
| |
| EXPECT_THAT( |
| AuthenticatorData(test_data::kApplicationParameter, flags, |
| test_data::kTestSignatureCounter, absl::nullopt) |
| .SerializeToByteArray(), |
| ::testing::ElementsAreArray(test_data::kTestSignAuthenticatorData)); |
| } |
| |
| TEST(CTAPResponseTest, TestParseSignResponseData) { |
| auto response = AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( |
| test_data::kApplicationParameter, GetTestSignResponse(), |
| GetTestCredentialRawIdBytes()); |
| ASSERT_TRUE(response); |
| EXPECT_EQ(GetTestCredentialRawIdBytes(), response->credential->id); |
| EXPECT_THAT( |
| response->authenticator_data.SerializeToByteArray(), |
| ::testing::ElementsAreArray(test_data::kTestSignAuthenticatorData)); |
| EXPECT_THAT(response->signature, |
| ::testing::ElementsAreArray(test_data::kU2fSignature)); |
| } |
| |
| TEST(CTAPResponseTest, TestParseU2fSignWithNullNullKeyHandle) { |
| auto response = AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( |
| test_data::kApplicationParameter, GetTestSignResponse(), |
| std::vector<uint8_t>()); |
| EXPECT_FALSE(response); |
| } |
| |
| TEST(CTAPResponseTest, TestParseU2fSignWithNullResponse) { |
| auto response = AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( |
| test_data::kApplicationParameter, std::vector<uint8_t>(), |
| GetTestCredentialRawIdBytes()); |
| EXPECT_FALSE(response); |
| } |
| |
| TEST(CTAPResponseTest, TestParseU2fSignWithCTAP2Flags) { |
| std::vector<uint8_t> sign_response = GetTestSignResponse(); |
| // Set two flags that should only be set in CTAP2 responses and expect parsing |
| // to fail. |
| sign_response[0] |= |
| static_cast<uint8_t>(AuthenticatorData::Flag::kExtensionDataIncluded); |
| sign_response[0] |= |
| static_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); |
| |
| auto response = AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( |
| test_data::kApplicationParameter, sign_response, |
| GetTestCredentialRawIdBytes()); |
| EXPECT_FALSE(response); |
| } |
| |
| TEST(CTAPResponseTest, TestParseU2fSignWithNullCorruptedCounter) { |
| // A sign response of less than 5 bytes. |
| auto response = AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( |
| test_data::kApplicationParameter, GetTestCorruptedSignResponse(3), |
| GetTestCredentialRawIdBytes()); |
| EXPECT_FALSE(response); |
| } |
| |
| TEST(CTAPResponseTest, TestParseU2fSignWithNullCorruptedSignature) { |
| // A sign response no more than 5 bytes. |
| auto response = AuthenticatorGetAssertionResponse::CreateFromU2fSignResponse( |
| test_data::kApplicationParameter, GetTestCorruptedSignResponse(5), |
| GetTestCredentialRawIdBytes()); |
| EXPECT_FALSE(response); |
| } |
| |
| TEST(CTAPResponseTest, TestReadGetInfoResponse) { |
| auto get_info_response = |
| ReadCTAPGetInfoResponse(test_data::kTestGetInfoResponsePlatformDevice); |
| ASSERT_TRUE(get_info_response); |
| ASSERT_TRUE(get_info_response->max_msg_size); |
| EXPECT_EQ(*get_info_response->max_msg_size, 1200u); |
| EXPECT_TRUE( |
| base::Contains(get_info_response->versions, ProtocolVersion::kCtap2)); |
| EXPECT_TRUE( |
| base::Contains(get_info_response->versions, ProtocolVersion::kU2f)); |
| EXPECT_EQ(get_info_response->ctap2_versions.size(), 1u); |
| EXPECT_TRUE(base::Contains(get_info_response->ctap2_versions, |
| Ctap2Version::kCtap2_0)); |
| EXPECT_TRUE(get_info_response->options.is_platform_device); |
| EXPECT_TRUE(get_info_response->options.supports_resident_key); |
| EXPECT_TRUE(get_info_response->options.supports_user_presence); |
| EXPECT_EQ(AuthenticatorSupportedOptions::UserVerificationAvailability:: |
| kSupportedAndConfigured, |
| get_info_response->options.user_verification_availability); |
| EXPECT_EQ(AuthenticatorSupportedOptions::ClientPinAvailability:: |
| kSupportedButPinNotSet, |
| get_info_response->options.client_pin_availability); |
| } |
| |
| TEST(CTAPResponseTest, TestReadGetInfoResponseWithDuplicateVersion) { |
| uint8_t |
| get_info[sizeof(kTestAuthenticatorGetInfoResponseWithDuplicateVersion)]; |
| memcpy(get_info, kTestAuthenticatorGetInfoResponseWithDuplicateVersion, |
| sizeof(get_info)); |
| // Should fail to parse with duplicate versions. |
| EXPECT_FALSE(ReadCTAPGetInfoResponse(get_info)); |
| |
| // Find the first of the duplicate versions and change it to a different |
| // value. That should be sufficient to make the data parsable. |
| static const char kU2Fv9[] = "U2F_V9"; |
| uint8_t* first_version = |
| std::search(get_info, get_info + sizeof(get_info), kU2Fv9, kU2Fv9 + 6); |
| ASSERT_TRUE(first_version); |
| memcpy(first_version, "U2F_V3", 6); |
| absl::optional<AuthenticatorGetInfoResponse> response = |
| ReadCTAPGetInfoResponse(get_info); |
| ASSERT_TRUE(response); |
| EXPECT_EQ(1u, response->versions.size()); |
| EXPECT_TRUE(response->versions.contains(ProtocolVersion::kU2f)); |
| EXPECT_EQ(response->ctap2_versions.size(), 0u); |
| } |
| |
| TEST(CTAPResponseTest, TestReadGetInfoResponseWithCtap2_1) { |
| auto response = |
| ReadCTAPGetInfoResponse(kTestAuthenticatorGetInfoResponseWithCtap2_1); |
| ASSERT_TRUE(response); |
| EXPECT_EQ(2u, response->versions.size()); |
| EXPECT_TRUE(response->versions.contains(ProtocolVersion::kU2f)); |
| EXPECT_TRUE(response->versions.contains(ProtocolVersion::kCtap2)); |
| EXPECT_EQ(response->ctap2_versions.size(), 2u); |
| EXPECT_TRUE(base::Contains(response->ctap2_versions, Ctap2Version::kCtap2_0)); |
| EXPECT_TRUE(base::Contains(response->ctap2_versions, Ctap2Version::kCtap2_1)); |
| } |
| |
| // Tests that an authenticator returning only the string "FIDO_2_1" is properly |
| // recognized as a CTAP 2.1 authenticator. |
| TEST(CTAPResponseTest, TestReadGetInfoResponseOnlyCtap2_1) { |
| auto response = |
| ReadCTAPGetInfoResponse(kTestAuthenticatorGetInfoResponseOnlyCtap2_1); |
| ASSERT_TRUE(response); |
| EXPECT_EQ(1u, response->versions.size()); |
| EXPECT_TRUE(response->versions.contains(ProtocolVersion::kCtap2)); |
| EXPECT_EQ(response->ctap2_versions.size(), 1u); |
| EXPECT_TRUE(base::Contains(response->ctap2_versions, Ctap2Version::kCtap2_1)); |
| } |
| |
| TEST(CTAPResponseTest, TestReadGetInfoResponseWithIncorrectFormat) { |
| EXPECT_FALSE( |
| ReadCTAPGetInfoResponse(kTestAuthenticatorGetInfoResponseWithNoVersion)); |
| EXPECT_FALSE(ReadCTAPGetInfoResponse( |
| kTestAuthenticatorGetInfoResponseWithIncorrectAaguid)); |
| } |
| |
| TEST(CTAPResponseTest, TestSerializeGetInfoResponse) { |
| AuthenticatorGetInfoResponse response( |
| {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, |
| {Ctap2Version::kCtap2_0}, kTestDeviceAaguid); |
| response.extensions.emplace({std::string("uvm"), std::string("hmac-secret")}); |
| AuthenticatorSupportedOptions options; |
| options.supports_resident_key = true; |
| options.is_platform_device = true; |
| options.client_pin_availability = AuthenticatorSupportedOptions:: |
| ClientPinAvailability::kSupportedButPinNotSet; |
| options.user_verification_availability = AuthenticatorSupportedOptions:: |
| UserVerificationAvailability::kSupportedAndConfigured; |
| response.options = std::move(options); |
| response.max_msg_size = 1200; |
| response.pin_protocols.emplace({PINUVAuthProtocol::kV1}); |
| response.algorithms.reset(); |
| |
| EXPECT_THAT(AuthenticatorGetInfoResponse::EncodeToCBOR(response), |
| ::testing::ElementsAreArray( |
| base::make_span(test_data::kTestGetInfoResponsePlatformDevice) |
| .subspan(1))); |
| } |
| |
| TEST(CTAPResponseTest, TestSerializeMakeCredentialResponse) { |
| constexpr uint8_t kCoseEncodedPublicKey[] = { |
| // clang-format off |
| // map(5) |
| 0xa5, |
| 0x01, // unsigned(1) kty |
| 0x02, // unsigned(2) EC2 |
| 0x03, // unsigned(3) alg |
| 0x26, // negative(6) ES256 |
| 0x20, // negative(0) crv |
| 0x01, // unsigned(1) P-256 |
| 0x21, // negative(1) x |
| // byte(32) |
| 0x58, 0x20, 0xf7, 0xc4, 0xf4, 0xa6, 0xf1, 0xd7, 0x95, 0x38, 0xdf, 0xa4, |
| 0xc9, 0xac, 0x50, 0x84, 0x8d, 0xf7, 0x08, 0xbc, 0x1c, 0x99, 0xf5, 0xe6, |
| 0x0e, 0x51, 0xb4, 0x2a, 0x52, 0x1b, 0x35, 0xd3, 0xb6, 0x9a, |
| 0x22, // negative(2) y |
| // byte(32) |
| 0x58, 0x20, 0xde, 0x7b, 0x7d, 0x6c, 0xa5, 0x64, 0xe7, 0x0e, 0xa3, 0x21, |
| 0xa4, 0xd5, 0xd9, 0x6e, 0xa0, 0x0e, 0xf0, 0xe2, 0xdb, 0x89, 0xdd, 0x61, |
| 0xd4, 0x89, 0x4c, 0x15, 0xac, 0x58, 0x5b, 0xd2, 0x36, 0x84, |
| // clang-format on |
| }; |
| |
| const auto application_parameter = |
| base::make_span(test_data::kApplicationParameter) |
| .subspan<0, kRpIdHashLength>(); |
| // Starting signature counter value set by example 4 of the CTAP spec. The |
| // signature counter can start at any value but it should never decrease. |
| // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html |
| std::array<uint8_t, kSignCounterLength> signature_counter = { |
| {0x00, 0x00, 0x00, 0x0b}}; |
| auto flag = |
| base::strict_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence) | |
| base::strict_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); |
| AttestedCredentialData attested_credential_data( |
| kTestDeviceAaguid, |
| std::array<uint8_t, kCredentialIdLengthLength>{ |
| {0x00, 0x10}} /* credential_id_length */, |
| fido_parsing_utils::Materialize( |
| test_data::kCtap2MakeCredentialCredentialId), |
| std::make_unique<PublicKey>( |
| static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), |
| kCoseEncodedPublicKey, absl::nullopt)); |
| AuthenticatorData authenticator_data(application_parameter, flag, |
| signature_counter, |
| std::move(attested_credential_data)); |
| |
| cbor::Value::MapValue attestation_map; |
| attestation_map.emplace("alg", -7); |
| attestation_map.emplace("sig", fido_parsing_utils::Materialize( |
| test_data::kCtap2MakeCredentialSignature)); |
| cbor::Value::ArrayValue certificate_chain; |
| certificate_chain.emplace_back(fido_parsing_utils::Materialize( |
| test_data::kCtap2MakeCredentialCertificate)); |
| attestation_map.emplace("x5c", std::move(certificate_chain)); |
| AuthenticatorMakeCredentialResponse response( |
| FidoTransportProtocol::kUsbHumanInterfaceDevice, |
| AttestationObject( |
| std::move(authenticator_data), |
| std::make_unique<OpaqueAttestationStatement>( |
| "packed", cbor::Value(std::move(attestation_map))))); |
| EXPECT_THAT( |
| AsCTAPStyleCBORBytes(response), |
| ::testing::ElementsAreArray( |
| base::make_span(test_data::kTestMakeCredentialResponse).subspan(1))); |
| } |
| |
| } // namespace device |