diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/device/fido | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/device/fido')
83 files changed, 2433 insertions, 994 deletions
diff --git a/chromium/device/fido/BUILD.gn b/chromium/device/fido/BUILD.gn index b1867a8b1b6..686c8bab4ac 100644 --- a/chromium/device/fido/BUILD.gn +++ b/chromium/device/fido/BUILD.gn @@ -8,114 +8,29 @@ import("//third_party/protobuf/proto_library.gni") component("fido") { sources = [ - "attestation_object.cc", - "attestation_object.h", - "attestation_statement.cc", - "attestation_statement.h", - "attestation_statement_formats.cc", - "attestation_statement_formats.h", "attested_credential_data.cc", "attested_credential_data.h", - "authenticator_data.cc", - "authenticator_data.h", - "authenticator_get_assertion_response.cc", - "authenticator_get_assertion_response.h", - "authenticator_get_info_response.cc", - "authenticator_get_info_response.h", - "authenticator_make_credential_response.cc", - "authenticator_make_credential_response.h", "authenticator_selection_criteria.cc", "authenticator_selection_criteria.h", - "authenticator_supported_options.cc", - "authenticator_supported_options.h", - "bio/enroller.cc", - "bio/enroller.h", - "bio/enrollment.cc", - "bio/enrollment.h", - "bio/enrollment_handler.cc", - "bio/enrollment_handler.h", - "ble_adapter_manager.cc", - "ble_adapter_manager.h", + "cable/cable_discovery_data.cc", "cable/cable_discovery_data.h", - "cable/fido_ble_connection.cc", - "cable/fido_ble_connection.h", - "cable/fido_ble_frames.cc", - "cable/fido_ble_frames.h", - "cable/fido_ble_transaction.cc", - "cable/fido_ble_transaction.h", - "cable/fido_ble_uuids.cc", - "cable/fido_ble_uuids.h", - "cable/fido_cable_device.cc", - "cable/fido_cable_device.h", - "cable/fido_cable_discovery.cc", - "cable/fido_cable_discovery.h", - "cable/fido_cable_handshake_handler.cc", - "cable/fido_cable_handshake_handler.h", "cable/noise.cc", "cable/noise.h", "cable/v2_handshake.cc", "cable/v2_handshake.h", "cbor_extract.cc", - "client_data.cc", - "client_data.h", - "credential_management.cc", - "credential_management.h", - "credential_management_handler.cc", - "credential_management_handler.h", - "ctap2_device_operation.h", - "ctap_empty_authenticator_request.cc", - "ctap_empty_authenticator_request.h", - "ctap_get_assertion_request.cc", - "ctap_get_assertion_request.h", - "ctap_make_credential_request.cc", - "ctap_make_credential_request.h", - "device_operation.h", - "device_response_converter.cc", - "device_response_converter.h", + "ed25519_public_key.cc", + "ed25519_public_key.h", "features.cc", "features.h", - "fido_authenticator.cc", - "fido_authenticator.h", "fido_constants.cc", "fido_constants.h", - "fido_device.cc", - "fido_device.h", - "fido_device_authenticator.cc", - "fido_device_authenticator.h", - "fido_device_discovery.cc", - "fido_device_discovery.h", - "fido_discovery_base.cc", - "fido_discovery_base.h", - "fido_discovery_factory.cc", - "fido_discovery_factory.h", "fido_parsing_utils.cc", "fido_parsing_utils.h", - "fido_request_handler_base.cc", - "fido_request_handler_base.h", - "fido_task.cc", - "fido_task.h", "fido_transport_protocol.cc", "fido_transport_protocol.h", - "fido_types.h", - "get_assertion_request_handler.cc", - "get_assertion_request_handler.h", - "get_assertion_task.cc", - "get_assertion_task.h", - "hid/fido_hid_message.cc", - "hid/fido_hid_message.h", - "hid/fido_hid_packet.cc", - "hid/fido_hid_packet.h", - "make_credential_request_handler.cc", - "make_credential_request_handler.h", - "make_credential_task.cc", - "make_credential_task.h", - "opaque_attestation_statement.cc", - "opaque_attestation_statement.h", "p256_public_key.cc", "p256_public_key.h", - "pin.cc", - "pin.h", - "platform_credential_store.h", "public_key.cc", "public_key.h", "public_key_credential_descriptor.cc", @@ -126,26 +41,8 @@ component("fido") { "public_key_credential_rp_entity.h", "public_key_credential_user_entity.cc", "public_key_credential_user_entity.h", - "reset_request_handler.cc", - "reset_request_handler.h", - "response_data.cc", - "response_data.h", "rsa_public_key.cc", "rsa_public_key.h", - "set_pin_request_handler.cc", - "set_pin_request_handler.h", - "u2f_command_constructor.cc", - "u2f_command_constructor.h", - "u2f_register_operation.cc", - "u2f_register_operation.h", - "u2f_sign_operation.cc", - "u2f_sign_operation.h", - "virtual_ctap2_device.cc", - "virtual_ctap2_device.h", - "virtual_fido_device.cc", - "virtual_fido_device.h", - "virtual_u2f_device.cc", - "virtual_u2f_device.h", ] defines = [ "IS_DEVICE_FIDO_IMPL" ] @@ -171,13 +68,119 @@ component("fido") { libs = [] # Extended for mac. - # HID is not supported on Android. + # Android implementation of FIDO is delegated to GMSCore. if (!is_android) { sources += [ + "attestation_object.cc", + "attestation_object.h", + "attestation_statement.cc", + "attestation_statement.h", + "attestation_statement_formats.cc", + "attestation_statement_formats.h", + "authenticator_data.cc", + "authenticator_data.h", + "authenticator_get_assertion_response.cc", + "authenticator_get_assertion_response.h", + "authenticator_get_info_response.cc", + "authenticator_get_info_response.h", + "authenticator_make_credential_response.cc", + "authenticator_make_credential_response.h", + "authenticator_supported_options.cc", + "authenticator_supported_options.h", + "bio/enroller.cc", + "bio/enroller.h", + "bio/enrollment.cc", + "bio/enrollment.h", + "bio/enrollment_handler.cc", + "bio/enrollment_handler.h", + "ble_adapter_manager.cc", + "ble_adapter_manager.h", + "cable/fido_ble_connection.cc", + "cable/fido_ble_connection.h", + "cable/fido_ble_frames.cc", + "cable/fido_ble_frames.h", + "cable/fido_ble_transaction.cc", + "cable/fido_ble_transaction.h", + "cable/fido_ble_uuids.cc", + "cable/fido_ble_uuids.h", + "cable/fido_cable_device.cc", + "cable/fido_cable_device.h", + "cable/fido_cable_discovery.cc", + "cable/fido_cable_discovery.h", + "cable/fido_cable_handshake_handler.cc", + "cable/fido_cable_handshake_handler.h", + "client_data.cc", + "client_data.h", + "credential_management.cc", + "credential_management.h", + "credential_management_handler.cc", + "credential_management_handler.h", + "ctap2_device_operation.h", + "ctap_empty_authenticator_request.cc", + "ctap_empty_authenticator_request.h", + "ctap_get_assertion_request.cc", + "ctap_get_assertion_request.h", + "ctap_make_credential_request.cc", + "ctap_make_credential_request.h", + "device_operation.h", + "device_response_converter.cc", + "device_response_converter.h", + "fido_authenticator.cc", + "fido_authenticator.h", + "fido_device.cc", + "fido_device.h", + "fido_device_authenticator.cc", + "fido_device_authenticator.h", + "fido_device_discovery.cc", + "fido_device_discovery.h", + "fido_discovery_base.cc", + "fido_discovery_base.h", + "fido_discovery_factory.cc", + "fido_discovery_factory.h", + "fido_request_handler_base.cc", + "fido_request_handler_base.h", + "fido_task.cc", + "fido_task.h", + "fido_types.h", + "get_assertion_request_handler.cc", + "get_assertion_request_handler.h", + "get_assertion_task.cc", + "get_assertion_task.h", "hid/fido_hid_device.cc", "hid/fido_hid_device.h", "hid/fido_hid_discovery.cc", "hid/fido_hid_discovery.h", + "hid/fido_hid_message.cc", + "hid/fido_hid_message.h", + "hid/fido_hid_packet.cc", + "hid/fido_hid_packet.h", + "make_credential_request_handler.cc", + "make_credential_request_handler.h", + "make_credential_task.cc", + "make_credential_task.h", + "opaque_attestation_statement.cc", + "opaque_attestation_statement.h", + "pin.cc", + "pin.h", + "platform_credential_store.h", + "reset_request_handler.cc", + "reset_request_handler.h", + "response_data.cc", + "response_data.h", + "set_pin_request_handler.cc", + "set_pin_request_handler.h", + "u2f_command_constructor.cc", + "u2f_command_constructor.h", + "u2f_register_operation.cc", + "u2f_register_operation.h", + "u2f_sign_operation.cc", + "u2f_sign_operation.h", + "virtual_ctap2_device.cc", + "virtual_ctap2_device.h", + "virtual_fido_device.cc", + "virtual_fido_device.h", + "virtual_u2f_device.cc", + "virtual_u2f_device.h", ] deps += [ @@ -336,13 +339,7 @@ is_linux_without_udev = is_linux && !use_udev source_set("test_support") { testonly = true - sources = [ - "fake_fido_discovery.cc", - "fake_fido_discovery.h", - "test_callback_receiver.h", - "virtual_fido_device_factory.cc", - "virtual_fido_device_factory.h", - ] + sources = [ "test_callback_receiver.h" ] deps = [ "//base", "//components/apdu", @@ -356,11 +353,20 @@ source_set("test_support") { # Android doesn't compile. Linux requires udev. if (!is_linux_without_udev && !is_android) { sources += [ + "fake_fido_discovery.cc", + "fake_fido_discovery.h", "hid/fake_hid_impl_for_testing.cc", "hid/fake_hid_impl_for_testing.h", ] } + if (!is_android) { + sources += [ + "virtual_fido_device_factory.cc", + "virtual_fido_device_factory.h", + ] + } + if (is_mac) { sources += [ "mac/fake_keychain.h", diff --git a/chromium/device/fido/attestation_statement_formats_unittest.cc b/chromium/device/fido/attestation_statement_formats_unittest.cc index f7f4d0b666b..809fac65265 100644 --- a/chromium/device/fido/attestation_statement_formats_unittest.cc +++ b/chromium/device/fido/attestation_statement_formats_unittest.cc @@ -84,7 +84,7 @@ constexpr uint8_t kCertificates[] = { TEST(PackedAttestationStatementTest, CBOR) { EXPECT_THAT( *cbor::Writer::Write(AsCBOR(PackedAttestationStatement( - CoseAlgorithmIdentifier::kCoseEs256, + CoseAlgorithmIdentifier::kEs256, fido_parsing_utils::Materialize(kSignature), {fido_parsing_utils::Materialize(kCertificates)}))), testing::ElementsAreArray(test_data::kPackedAttestationStatementCBOR)); @@ -92,7 +92,7 @@ TEST(PackedAttestationStatementTest, CBOR) { TEST(PackedAttestationStatementTest, CBOR_NoCerts) { EXPECT_THAT(*cbor::Writer::Write(AsCBOR(PackedAttestationStatement( - CoseAlgorithmIdentifier::kCoseEs256, + CoseAlgorithmIdentifier::kEs256, fido_parsing_utils::Materialize(kSignature), {}))), testing::ElementsAreArray( test_data::kPackedAttestationStatementCBORNoCerts)); diff --git a/chromium/device/fido/attested_credential_data.cc b/chromium/device/fido/attested_credential_data.cc index e1c4f9e6789..1365828d755 100644 --- a/chromium/device/fido/attested_credential_data.cc +++ b/chromium/device/fido/attested_credential_data.cc @@ -10,12 +10,19 @@ #include "base/numerics/safe_math.h" #include "components/cbor/reader.h" #include "components/device_event_log/device_event_log.h" +#include "device/fido/cbor_extract.h" +#include "device/fido/ed25519_public_key.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_parsing_utils.h" #include "device/fido/p256_public_key.h" #include "device/fido/public_key.h" #include "device/fido/rsa_public_key.h" +using device::cbor_extract::IntKey; +using device::cbor_extract::Is; +using device::cbor_extract::StepOrByte; +using device::cbor_extract::Stop; + namespace device { // static @@ -57,70 +64,88 @@ AttestedCredentialData::ConsumeFromCtapResponse( const cbor::Value::MapValue& public_key_map = public_key_cbor->GetMap(); - // kAlg is required to be present. See - // https://www.w3.org/TR/webauthn/#credentialpublickey - // COSE allows it to be a string or an integer. However, WebAuthn defines - // COSEAlgorithmIdentifier to be a long[1], thus only integer-based algorithms - // can be negotiated. - // - // [1] https://www.w3.org/TR/webauthn/#alg-identifier - const auto it = - public_key_map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kAlg))); - if (it == public_key_map.end() || !it->second.is_integer()) { - FIDO_LOG(ERROR) << "Public key is missing algorithm identifier"; + struct COSEKey { + const int64_t* alg; + const int64_t* kty; + } cose_key; + + static constexpr cbor_extract::StepOrByte<COSEKey> kSteps[] = { + // clang-format off + + // kAlg is required to be present. See + // https://www.w3.org/TR/webauthn/#credentialpublickey + // COSE allows it to be a string or an integer. However, WebAuthn defines + // COSEAlgorithmIdentifier to be a long[1], thus only integer-based + // algorithms can be negotiated. + // + // [1] https://www.w3.org/TR/webauthn/#alg-identifier + ELEMENT(Is::kRequired, COSEKey, alg), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kAlg)), + + // kKty is required in COSE keys: + // https://tools.ietf.org/html/rfc8152#section-7 + ELEMENT(Is::kRequired, COSEKey, kty), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kKty)), + + Stop<COSEKey>(), + // clang-format on + }; + + if (!cbor_extract::Extract<COSEKey>(&cose_key, kSteps, public_key_map)) { + FIDO_LOG(ERROR) << "Failed to parse COSE key"; return base::nullopt; } // In WebIDL, a |long| is an |int32_t|[1]. // // [1] https://heycam.github.io/webidl/#idl-long - const int64_t algorithm64 = it->second.GetInteger(); + const int64_t algorithm64 = *cose_key.alg; if (algorithm64 > std::numeric_limits<int32_t>::max() || algorithm64 < std::numeric_limits<int32_t>::min()) { FIDO_LOG(ERROR) << "COSE algorithm in public key is out of range"; return base::nullopt; } const int32_t algorithm = static_cast<int32_t>(algorithm64); + const int64_t key_type = *cose_key.kty; std::unique_ptr<PublicKey> public_key; - // kKty is required in COSE keys: - // https://tools.ietf.org/html/rfc8152#section-7 - auto public_key_type = - public_key_map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kKty))); - if (public_key_type == public_key_map.end()) { - FIDO_LOG(ERROR) << "COSE key missing kty"; - return base::nullopt; - } + if (key_type == static_cast<int64_t>(CoseKeyTypes::kEC2) || + key_type == static_cast<int64_t>(CoseKeyTypes::kOKP)) { + auto curve = public_key_map.find( + cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticCurve))); + if (curve == public_key_map.end() || !curve->second.is_integer()) { + return base::nullopt; + } + const int64_t curve_id = curve->second.GetInteger(); - if (public_key_type->second.is_unsigned()) { - const int64_t key_type = public_key_type->second.GetUnsigned(); - if (key_type == static_cast<int64_t>(CoseKeyTypes::kEC2)) { - auto curve = public_key_map.find( - cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticCurve))); - if (curve == public_key_map.end() || !curve->second.is_integer()) { + if (key_type == static_cast<int64_t>(CoseKeyTypes::kEC2) && + curve_id == static_cast<int64_t>(CoseCurves::kP256)) { + auto p256_key = P256PublicKey::ExtractFromCOSEKey( + algorithm, public_key_cbor_bytes, public_key_map); + if (!p256_key) { + FIDO_LOG(ERROR) << "Invalid P-256 public key"; return base::nullopt; } - - if (curve->second.GetInteger() == - static_cast<int64_t>(CoseCurves::kP256)) { - auto p256_key = P256PublicKey::ExtractFromCOSEKey( - algorithm, public_key_cbor_bytes, public_key_map); - if (!p256_key) { - FIDO_LOG(ERROR) << "Invalid P-256 public key"; - return base::nullopt; - } - public_key = std::move(p256_key); - } - } else if (key_type == static_cast<int64_t>(CoseKeyTypes::kRSA)) { - auto rsa_key = RSAPublicKey::ExtractFromCOSEKey( + public_key = std::move(p256_key); + } else if (key_type == static_cast<int64_t>(CoseKeyTypes::kOKP) && + curve_id == static_cast<int64_t>(CoseCurves::kEd25519)) { + auto ed25519_key = Ed25519PublicKey::ExtractFromCOSEKey( algorithm, public_key_cbor_bytes, public_key_map); - if (!rsa_key) { - FIDO_LOG(ERROR) << "Invalid RSA public key"; + if (!ed25519_key) { + FIDO_LOG(ERROR) << "Invalid Ed25519 public key"; return base::nullopt; } - public_key = std::move(rsa_key); + public_key = std::move(ed25519_key); + } + } else if (key_type == static_cast<int64_t>(CoseKeyTypes::kRSA)) { + auto rsa_key = RSAPublicKey::ExtractFromCOSEKey( + algorithm, public_key_cbor_bytes, public_key_map); + if (!rsa_key) { + FIDO_LOG(ERROR) << "Invalid RSA public key"; + return base::nullopt; } + public_key = std::move(rsa_key); } if (!public_key) { @@ -204,7 +229,7 @@ std::vector<uint8_t> AttestedCredentialData::SerializeAsBytes() const { fido_parsing_utils::Append(&attestation_data, aaguid_); fido_parsing_utils::Append(&attestation_data, credential_id_length_); fido_parsing_utils::Append(&attestation_data, credential_id_); - fido_parsing_utils::Append(&attestation_data, public_key_->cose_key_bytes()); + fido_parsing_utils::Append(&attestation_data, public_key_->cose_key_bytes); return attestation_data; } diff --git a/chromium/device/fido/attested_credential_data.h b/chromium/device/fido/attested_credential_data.h index 1999430ab54..a78c334c76c 100644 --- a/chromium/device/fido/attested_credential_data.h +++ b/chromium/device/fido/attested_credential_data.h @@ -18,7 +18,7 @@ namespace device { -class PublicKey; +struct PublicKey; // https://www.w3.org/TR/2017/WD-webauthn-20170505/#sec-attestation-data class COMPONENT_EXPORT(DEVICE_FIDO) AttestedCredentialData { diff --git a/chromium/device/fido/authenticator_data.cc b/chromium/device/fido/authenticator_data.cc index b6681e6990f..09479b23a31 100644 --- a/chromium/device/fido/authenticator_data.cc +++ b/chromium/device/fido/authenticator_data.cc @@ -21,6 +21,26 @@ namespace { constexpr size_t kAttestedCredentialDataOffset = kRpIdHashLength + kFlagsLength + kSignCounterLength; +uint8_t AuthenticatorDataFlags(bool user_present, + bool user_verified, + bool has_attested_credential_data, + bool has_extension_data) { + return (user_present ? base::strict_cast<uint8_t>( + AuthenticatorData::Flag::kTestOfUserPresence) + : 0) | + (user_verified ? base::strict_cast<uint8_t>( + AuthenticatorData::Flag::kTestOfUserVerification) + : 0) | + (has_attested_credential_data + ? base::strict_cast<uint8_t>( + AuthenticatorData::Flag::kAttestation) + : 0) | + (has_extension_data + ? base::strict_cast<uint8_t>( + AuthenticatorData::Flag::kExtensionDataIncluded) + : 0); +} + } // namespace // static @@ -71,13 +91,12 @@ base::Optional<AuthenticatorData> AuthenticatorData::DecodeAuthenticatorData( } AuthenticatorData::AuthenticatorData( - base::span<const uint8_t, kRpIdHashLength> application_parameter, + base::span<const uint8_t, kRpIdHashLength> rp_id_hash, uint8_t flags, base::span<const uint8_t, kSignCounterLength> counter, base::Optional<AttestedCredentialData> data, base::Optional<cbor::Value> extensions) - : application_parameter_( - fido_parsing_utils::Materialize(application_parameter)), + : application_parameter_(fido_parsing_utils::Materialize(rp_id_hash)), flags_(flags), counter_(fido_parsing_utils::Materialize(counter)), attested_data_(std::move(data)), @@ -89,6 +108,25 @@ AuthenticatorData::AuthenticatorData( !!attested_data_); } +AuthenticatorData::AuthenticatorData( + base::span<const uint8_t, kRpIdHashLength> rp_id_hash, + bool user_present, + bool user_verified, + uint32_t sign_counter, + base::Optional<AttestedCredentialData> attested_credential_data, + base::Optional<cbor::Value> extensions) + : AuthenticatorData( + rp_id_hash, + AuthenticatorDataFlags(user_present, + user_verified, + attested_credential_data.has_value(), + extensions.has_value()), + std::array<uint8_t, kSignCounterLength>{ + (sign_counter >> 24) & 0xff, (sign_counter >> 16) & 0xff, + (sign_counter >> 8) & 0xff, sign_counter & 0xff}, + std::move(attested_credential_data), + std::move(extensions)) {} + AuthenticatorData::AuthenticatorData(AuthenticatorData&& other) = default; AuthenticatorData& AuthenticatorData::operator=(AuthenticatorData&& other) = default; diff --git a/chromium/device/fido/authenticator_data.h b/chromium/device/fido/authenticator_data.h index 3f4f440ab5a..8a80e3d1c81 100644 --- a/chromium/device/fido/authenticator_data.h +++ b/chromium/device/fido/authenticator_data.h @@ -38,14 +38,22 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorData { // The attested credential |data| must be specified iff |flags| have // kAttestation set; and |extensions| must be specified iff |flags| have // kExtensionDataIncluded set. + AuthenticatorData(base::span<const uint8_t, kRpIdHashLength> rp_id_hash, + uint8_t flags, + base::span<const uint8_t, kSignCounterLength> sign_counter, + base::Optional<AttestedCredentialData> data, + base::Optional<cbor::Value> extensions = base::nullopt); + + // Creates an AuthenticatorData with flags and signature counter encoded + // according to the supplied arguments. AuthenticatorData( - base::span<const uint8_t, kRpIdHashLength> application_parameter, - uint8_t flags, - base::span<const uint8_t, kSignCounterLength> counter, - base::Optional<AttestedCredentialData> data, - base::Optional<cbor::Value> extensions = base::nullopt); + base::span<const uint8_t, kRpIdHashLength> rp_id_hash, + bool user_present, + bool user_verified, + uint32_t sign_counter, + base::Optional<AttestedCredentialData> attested_credential_data, + base::Optional<cbor::Value> extensions); - // Moveable. AuthenticatorData(AuthenticatorData&& other); AuthenticatorData& operator=(AuthenticatorData&& other); diff --git a/chromium/device/fido/authenticator_get_info_response.cc b/chromium/device/fido/authenticator_get_info_response.cc index 616fa0683bb..fa5bd5efda1 100644 --- a/chromium/device/fido/authenticator_get_info_response.cc +++ b/chromium/device/fido/authenticator_get_info_response.cc @@ -27,9 +27,14 @@ cbor::Value::ArrayValue ToArrayValue(const Container& container) { AuthenticatorGetInfoResponse::AuthenticatorGetInfoResponse( base::flat_set<ProtocolVersion> in_versions, + base::flat_set<Ctap2Version> in_ctap2_versions, base::span<const uint8_t, kAaguidLength> in_aaguid) : versions(std::move(in_versions)), - aaguid(fido_parsing_utils::Materialize(in_aaguid)) {} + ctap2_versions(std::move(in_ctap2_versions)), + aaguid(fido_parsing_utils::Materialize(in_aaguid)) { + DCHECK_NE(base::Contains(versions, ProtocolVersion::kCtap2), + ctap2_versions.empty()); +} AuthenticatorGetInfoResponse::AuthenticatorGetInfoResponse( AuthenticatorGetInfoResponse&& that) = default; @@ -44,8 +49,27 @@ std::vector<uint8_t> AuthenticatorGetInfoResponse::EncodeToCBOR( const AuthenticatorGetInfoResponse& response) { cbor::Value::ArrayValue version_array; for (const auto& version : response.versions) { - version_array.emplace_back( - version == ProtocolVersion::kCtap2 ? kCtap2Version : kU2fVersion); + switch (version) { + case ProtocolVersion::kCtap2: + for (const auto& ctap2_version : response.ctap2_versions) { + switch (ctap2_version) { + case Ctap2Version::kCtap2_0: + version_array.emplace_back(kCtap2Version); + break; + case Ctap2Version::kCtap2_1: + version_array.emplace_back(kCtap2_1Version); + break; + case Ctap2Version::kUnknown: + NOTREACHED(); + } + } + break; + case ProtocolVersion::kU2f: + version_array.emplace_back(kU2fVersion); + break; + case ProtocolVersion::kUnknown: + NOTREACHED(); + } } cbor::Value::MapValue device_info_map; device_info_map.emplace(1, std::move(version_array)); @@ -79,7 +103,12 @@ std::vector<uint8_t> AuthenticatorGetInfoResponse::EncodeToCBOR( std::vector<cbor::Value> algorithms_cbor; algorithms_cbor.reserve(response.algorithms.size()); for (const auto& algorithm : response.algorithms) { - algorithms_cbor.emplace_back(cbor::Value(algorithm)); + // Entries are PublicKeyCredentialParameters + // https://w3c.github.io/webauthn/#dictdef-publickeycredentialparameters + cbor::Value::MapValue entry; + entry.emplace("type", "public-key"); + entry.emplace("alg", algorithm); + algorithms_cbor.emplace_back(cbor::Value(entry)); } device_info_map.emplace(10, std::move(algorithms_cbor)); } diff --git a/chromium/device/fido/authenticator_get_info_response.h b/chromium/device/fido/authenticator_get_info_response.h index 95f4fde3e52..fc4149ebd99 100644 --- a/chromium/device/fido/authenticator_get_info_response.h +++ b/chromium/device/fido/authenticator_get_info_response.h @@ -26,6 +26,7 @@ namespace device { struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetInfoResponse { public: AuthenticatorGetInfoResponse(base::flat_set<ProtocolVersion> versions, + base::flat_set<Ctap2Version> in_ctap2_version, base::span<const uint8_t, kAaguidLength> aaguid); AuthenticatorGetInfoResponse(AuthenticatorGetInfoResponse&& that); AuthenticatorGetInfoResponse& operator=(AuthenticatorGetInfoResponse&& other); @@ -35,6 +36,7 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetInfoResponse { const AuthenticatorGetInfoResponse& response); base::flat_set<ProtocolVersion> versions; + base::flat_set<Ctap2Version> ctap2_versions; std::array<uint8_t, kAaguidLength> aaguid; base::Optional<uint32_t> max_msg_size; base::Optional<uint32_t> max_credential_count_in_list; @@ -42,7 +44,7 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetInfoResponse { base::Optional<std::vector<uint8_t>> pin_protocols; base::Optional<std::vector<std::string>> extensions; std::vector<int32_t> algorithms = { - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), }; AuthenticatorSupportedOptions options; diff --git a/chromium/device/fido/authenticator_make_credential_response.cc b/chromium/device/fido/authenticator_make_credential_response.cc index 8c23cd74c5c..fbdab796383 100644 --- a/chromium/device/fido/authenticator_make_credential_response.cc +++ b/chromium/device/fido/authenticator_make_credential_response.cc @@ -25,7 +25,7 @@ AuthenticatorMakeCredentialResponse::CreateFromU2fRegisterResponse( base::span<const uint8_t, kRpIdHashLength> relying_party_id_hash, base::span<const uint8_t> u2f_data) { auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), u2f_data); + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), u2f_data); if (!public_key) return base::nullopt; diff --git a/chromium/device/fido/authenticator_supported_options.cc b/chromium/device/fido/authenticator_supported_options.cc index 27b266ef6e1..92fc0e49b4f 100644 --- a/chromium/device/fido/authenticator_supported_options.cc +++ b/chromium/device/fido/authenticator_supported_options.cc @@ -83,7 +83,7 @@ cbor::Value AsCBOR(const AuthenticatorSupportedOptions& options) { break; } - if (options.supports_uv_token) { + if (options.supports_pin_uv_auth_token) { option_map.emplace(kUvTokenMapKey, true); } diff --git a/chromium/device/fido/authenticator_supported_options.h b/chromium/device/fido/authenticator_supported_options.h index 72567d9fa80..7469c018bb9 100644 --- a/chromium/device/fido/authenticator_supported_options.h +++ b/chromium/device/fido/authenticator_supported_options.h @@ -86,9 +86,9 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSupportedOptions { // authenticator. ClientPinAvailability client_pin_availability = ClientPinAvailability::kNotSupported; - // Indicates whether the authenticator is capable of handling built in user - // verification based tokens. - bool supports_uv_token = false; + // Indicates whether the authenticator supports CTAP 2.1 pinUvAuthToken for + // establishing user verification via client PIN or a built-in sensor. + bool supports_pin_uv_auth_token = false; // Indicates whether the authenticator supports an extension for passing // information from the collectedClientData structure with a CTAP request. bool supports_android_client_data_ext = false; diff --git a/chromium/device/fido/bio/enrollment_handler.cc b/chromium/device/fido/bio/enrollment_handler.cc index d3e03a5da50..91264d427be 100644 --- a/chromium/device/fido/bio/enrollment_handler.cc +++ b/chromium/device/fido/bio/enrollment_handler.cc @@ -9,6 +9,7 @@ #include "components/device_event_log/device_event_log.h" #include "device/fido/fido_authenticator.h" #include "device/fido/fido_constants.h" +#include "device/fido/pin.h" namespace device { @@ -196,8 +197,10 @@ void BioEnrollmentHandler::OnHavePIN(std::string pin) { DCHECK_EQ(state_, State::kWaitingForPIN); state_ = State::kGettingPINToken; authenticator_->GetPINToken( - std::move(pin), base::BindOnce(&BioEnrollmentHandler::OnHavePINToken, - weak_factory_.GetWeakPtr())); + std::move(pin), {pin::Permissions::kBioEnrollment}, + /*rp_id=*/base::nullopt, + base::BindOnce(&BioEnrollmentHandler::OnHavePINToken, + weak_factory_.GetWeakPtr())); } void BioEnrollmentHandler::OnHavePINToken( diff --git a/chromium/device/fido/bio/enrollment_handler.h b/chromium/device/fido/bio/enrollment_handler.h index 32672020044..439d3d1fe63 100644 --- a/chromium/device/fido/bio/enrollment_handler.h +++ b/chromium/device/fido/bio/enrollment_handler.h @@ -18,7 +18,6 @@ #include "device/fido/fido_constants.h" #include "device/fido/fido_discovery_factory.h" #include "device/fido/fido_request_handler_base.h" -#include "device/fido/pin.h" namespace device { diff --git a/chromium/device/fido/ble_adapter_manager_unittest.cc b/chromium/device/fido/ble_adapter_manager_unittest.cc index 5267ce8fea2..37e61ea2326 100644 --- a/chromium/device/fido/ble_adapter_manager_unittest.cc +++ b/chromium/device/fido/ble_adapter_manager_unittest.cc @@ -95,6 +95,9 @@ class FidoBleAdapterManagerTest : public ::testing::Test { public: FidoBleAdapterManagerTest() { BluetoothAdapterFactory::SetAdapterForTesting(adapter_); + bluetooth_config_ = + BluetoothAdapterFactory::Get()->InitGlobalValuesForTesting(); + bluetooth_config_->SetLESupported(true); fido_discovery_factory_->ForgeNextCableDiscovery( test::FakeFidoDiscovery::StartMode::kAutomatic); @@ -133,6 +136,8 @@ class FidoBleAdapterManagerTest : public ::testing::Test { std::make_unique<test::FakeFidoDiscoveryFactory>(); std::unique_ptr<FakeFidoRequestHandlerBase> fake_request_handler_; + std::unique_ptr<BluetoothAdapterFactory::GlobalValuesForTesting> + bluetooth_config_; }; TEST_F(FidoBleAdapterManagerTest, AdapterNotPresent) { diff --git a/chromium/device/fido/cable/cable_discovery_data.cc b/chromium/device/fido/cable/cable_discovery_data.cc new file mode 100644 index 00000000000..81dd67fef59 --- /dev/null +++ b/chromium/device/fido/cable/cable_discovery_data.cc @@ -0,0 +1,253 @@ +// Copyright 2020 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 "device/fido/cable/cable_discovery_data.h" + +#include <cstring> + +#include "base/time/time.h" +#include "crypto/random.h" +#include "device/fido/fido_parsing_utils.h" +#include "third_party/boringssl/src/include/openssl/aes.h" +#include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/ec.h" +#include "third_party/boringssl/src/include/openssl/hkdf.h" +#include "third_party/boringssl/src/include/openssl/mem.h" +#include "third_party/boringssl/src/include/openssl/obj.h" + +namespace device { + +namespace { + +enum class QRValue : uint8_t { + QR_SECRET = 0, + IDENTITY_KEY_SEED = 1, +}; + +void DeriveQRValue(base::span<const uint8_t, 32> qr_generator_key, + const int64_t tick, + QRValue type, + base::span<uint8_t> out) { + uint8_t hkdf_input[sizeof(uint64_t) + 1]; + memcpy(hkdf_input, &tick, sizeof(uint64_t)); + hkdf_input[sizeof(uint64_t)] = base::strict_cast<uint8_t>(type); + + bool ok = HKDF(out.data(), out.size(), EVP_sha256(), qr_generator_key.data(), + qr_generator_key.size(), + /*salt=*/nullptr, 0, hkdf_input, sizeof(hkdf_input)); + DCHECK(ok); +} + +} // namespace + +CableDiscoveryData::CableDiscoveryData() = default; + +CableDiscoveryData::CableDiscoveryData( + CableDiscoveryData::Version version, + const CableEidArray& client_eid, + const CableEidArray& authenticator_eid, + const CableSessionPreKeyArray& session_pre_key) + : version(version) { + CHECK_EQ(Version::V1, version); + v1.emplace(); + v1->client_eid = client_eid; + v1->authenticator_eid = authenticator_eid; + v1->session_pre_key = session_pre_key; +} + +CableDiscoveryData::CableDiscoveryData( + base::span<const uint8_t, kCableQRSecretSize> qr_secret, + base::span<const uint8_t, kCableIdentityKeySeedSize> identity_key_seed) { + InitFromQRSecret(qr_secret); + v2->local_identity_seed = fido_parsing_utils::Materialize(identity_key_seed); +} + +// static +base::Optional<CableDiscoveryData> CableDiscoveryData::FromQRData( + base::span<const uint8_t, + kCableCompressedPublicKeySize + kCableQRSecretSize> qr_data) { + auto qr_secret = qr_data.subspan(kCableCompressedPublicKeySize); + CableDiscoveryData discovery_data; + discovery_data.InitFromQRSecret(base::span<const uint8_t, kCableQRSecretSize>( + qr_secret.data(), qr_secret.size())); + + bssl::UniquePtr<EC_GROUP> p256( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get())); + if (!EC_POINT_oct2point(p256.get(), point.get(), qr_data.data(), + kCableCompressedPublicKeySize, /*ctx=*/nullptr)) { + return base::nullopt; + } + CableAuthenticatorIdentityKey& identity_key = + discovery_data.v2->peer_identity.emplace(); + CHECK_EQ(identity_key.size(), + EC_POINT_point2oct( + p256.get(), point.get(), POINT_CONVERSION_UNCOMPRESSED, + identity_key.data(), identity_key.size(), /*ctx=*/nullptr)); + + return discovery_data; +} + +CableDiscoveryData::CableDiscoveryData(const CableDiscoveryData& data) = + default; + +CableDiscoveryData& CableDiscoveryData::operator=( + const CableDiscoveryData& other) = default; + +CableDiscoveryData::~CableDiscoveryData() = default; + +bool CableDiscoveryData::operator==(const CableDiscoveryData& other) const { + if (version != other.version) { + return false; + } + + switch (version) { + case CableDiscoveryData::Version::V1: + return v1->client_eid == other.v1->client_eid && + v1->authenticator_eid == other.v1->authenticator_eid && + v1->session_pre_key == other.v1->session_pre_key; + + case CableDiscoveryData::Version::V2: + return v2->eid_gen_key == other.v2->eid_gen_key && + v2->psk_gen_key == other.v2->psk_gen_key && + v2->peer_identity == other.v2->peer_identity && + v2->peer_name == other.v2->peer_name; + + case CableDiscoveryData::Version::INVALID: + CHECK(false); + return false; + } +} + +base::Optional<CableNonce> CableDiscoveryData::Match( + const CableEidArray& eid) const { + switch (version) { + case Version::V1: { + if (eid != v1->authenticator_eid) { + return base::nullopt; + } + + // The nonce is the first eight bytes of the EID. + CableNonce nonce; + const bool ok = + fido_parsing_utils::ExtractArray(v1->client_eid, 0, &nonce); + DCHECK(ok); + return nonce; + } + + case Version::V2: { + // Attempt to decrypt the EID with the EID generator key and check whether + // it has a valid structure. + AES_KEY key; + CHECK(AES_set_decrypt_key(v2->eid_gen_key.data(), + /*bits=*/8 * v2->eid_gen_key.size(), + &key) == 0); + static_assert(kCableEphemeralIdSize == AES_BLOCK_SIZE, + "EIDs are not AES blocks"); + CableEidArray decrypted; + AES_decrypt(/*in=*/eid.data(), /*out=*/decrypted.data(), &key); + const uint8_t kZeroTrailer[8] = {0}; + static_assert(8 + sizeof(kZeroTrailer) == + std::tuple_size<decltype(decrypted)>::value, + "Trailer is wrong size"); + if (CRYPTO_memcmp(kZeroTrailer, decrypted.data() + 8, + sizeof(kZeroTrailer)) != 0) { + return base::nullopt; + } + + CableNonce nonce; + static_assert( + sizeof(nonce) <= std::tuple_size<decltype(decrypted)>::value, + "nonce too large"); + memcpy(nonce.data(), decrypted.data(), sizeof(nonce)); + return nonce; + } + + case Version::INVALID: + DCHECK(false); + return base::nullopt; + } +} + +// static +QRGeneratorKey CableDiscoveryData::NewQRKey() { + QRGeneratorKey key; + crypto::RandBytes(key.data(), key.size()); + return key; +} + +// static +int64_t CableDiscoveryData::CurrentTimeTick() { + // The ticks are currently 256ms. + return base::TimeTicks::Now().since_origin().InMilliseconds() >> 8; +} + +// static +std::array<uint8_t, kCableQRSecretSize> CableDiscoveryData::DeriveQRSecret( + base::span<const uint8_t, 32> qr_generator_key, + const int64_t tick) { + std::array<uint8_t, kCableQRSecretSize> ret; + DeriveQRValue(qr_generator_key, tick, QRValue::QR_SECRET, ret); + return ret; +} + +// static +CableIdentityKeySeed CableDiscoveryData::DeriveIdentityKeySeed( + base::span<const uint8_t, 32> qr_generator_key, + const int64_t tick) { + std::array<uint8_t, kCableIdentityKeySeedSize> ret; + DeriveQRValue(qr_generator_key, tick, QRValue::IDENTITY_KEY_SEED, ret); + return ret; +} + +// static +CableQRData CableDiscoveryData::DeriveQRData( + base::span<const uint8_t, 32> qr_generator_key, + const int64_t tick) { + auto identity_key_seed = DeriveIdentityKeySeed(qr_generator_key, tick); + bssl::UniquePtr<EC_GROUP> p256( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + bssl::UniquePtr<EC_KEY> identity_key(EC_KEY_derive_from_secret( + p256.get(), identity_key_seed.data(), identity_key_seed.size())); + const EC_POINT* public_key = EC_KEY_get0_public_key(identity_key.get()); + CableQRData qr_data; + static_assert( + qr_data.size() == kCableCompressedPublicKeySize + kCableQRSecretSize, + "this code needs to be updated"); + CHECK_EQ(kCableCompressedPublicKeySize, + EC_POINT_point2oct(p256.get(), public_key, + POINT_CONVERSION_COMPRESSED, qr_data.data(), + kCableCompressedPublicKeySize, /*ctx=*/nullptr)); + + auto qr_secret = CableDiscoveryData::DeriveQRSecret(qr_generator_key, tick); + memcpy(&qr_data.data()[kCableCompressedPublicKeySize], qr_secret.data(), + qr_secret.size()); + + return qr_data; +} + +CableDiscoveryData::V2Data::V2Data() = default; +CableDiscoveryData::V2Data::V2Data(const V2Data&) = default; +CableDiscoveryData::V2Data::~V2Data() = default; + +void CableDiscoveryData::InitFromQRSecret( + base::span<const uint8_t, kCableQRSecretSize> qr_secret) { + version = Version::V2; + v2.emplace(); + + static const char kEIDGen[] = "caBLE QR to EID generator key"; + bool ok = + HKDF(v2->eid_gen_key.data(), v2->eid_gen_key.size(), EVP_sha256(), + qr_secret.data(), qr_secret.size(), /*salt=*/nullptr, 0, + reinterpret_cast<const uint8_t*>(kEIDGen), sizeof(kEIDGen) - 1); + DCHECK(ok); + + static const char kPSKGen[] = "caBLE QR to PSK generator key"; + ok = HKDF(v2->psk_gen_key.data(), v2->psk_gen_key.size(), EVP_sha256(), + qr_secret.data(), qr_secret.size(), /*salt=*/nullptr, 0, + reinterpret_cast<const uint8_t*>(kPSKGen), sizeof(kPSKGen) - 1); + DCHECK(ok); +} + +} // namespace device diff --git a/chromium/device/fido/cable/cable_discovery_data.h b/chromium/device/fido/cable/cable_discovery_data.h index 8fa8a08b095..6aa17acb8ef 100644 --- a/chromium/device/fido/cable/cable_discovery_data.h +++ b/chromium/device/fido/cable/cable_discovery_data.h @@ -10,6 +10,7 @@ #include "base/component_export.h" #include "base/containers/span.h" +#include "device/fido/fido_constants.h" namespace device { @@ -39,7 +40,7 @@ using CableEidGeneratorKey = std::array<uint8_t, 32>; using CablePskGeneratorKey = std::array<uint8_t, 32>; // CableAuthenticatorIdentityKey is a P-256 public value used to authenticate a // paired phone. -using CableAuthenticatorIdentityKey = std::array<uint8_t, 65>; +using CableAuthenticatorIdentityKey = std::array<uint8_t, kP256X962Length>; using CableIdentityKeySeed = std::array<uint8_t, kCableIdentityKeySeedSize>; using CableQRData = std::array<uint8_t, kCableQRDataSize>; diff --git a/chromium/device/fido/cable/fido_ble_connection.cc b/chromium/device/fido/cable/fido_ble_connection.cc index a460fdfeff5..6b94db1640d 100644 --- a/chromium/device/fido/cable/fido_ble_connection.cc +++ b/chromium/device/fido/cable/fido_ble_connection.cc @@ -253,22 +253,20 @@ void FidoBleConnection::WriteControlPoint(const std::vector<uint8_t>& data, return; } -#if defined(OS_MACOSX) // Attempt a write without response for performance reasons. Fall back to a - // confirmed write in case of failure, e.g. when the characteristic does not - // provide the required property. - if (control_point->WriteWithoutResponse(data)) { - FIDO_LOG(DEBUG) << "Write without response succeeded."; - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), true)); - return; - } -#endif // defined(OS_MACOSX) + // confirmed write when the characteristic does not provide the required + // property. + BluetoothRemoteGattCharacteristic::WriteType write_type = + (control_point->GetProperties() & + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE) + ? BluetoothRemoteGattCharacteristic::WriteType::kWithoutResponse + : BluetoothRemoteGattCharacteristic::WriteType::kWithResponse; FIDO_LOG(DEBUG) << "Wrote Control Point."; auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); control_point->WriteRemoteCharacteristic( - data, base::BindOnce(OnWriteRemoteCharacteristic, copyable_callback), + data, write_type, + base::BindOnce(OnWriteRemoteCharacteristic, copyable_callback), base::BindOnce(OnWriteRemoteCharacteristicError, copyable_callback)); } @@ -418,6 +416,7 @@ void FidoBleConnection::WriteServiceRevision(ServiceRevision service_revision) { fido_service->GetCharacteristic(*service_revision_bitfield_id_) ->WriteRemoteCharacteristic( {static_cast<uint8_t>(service_revision)}, + BluetoothRemoteGattCharacteristic::WriteType::kWithResponse, base::BindOnce(OnWriteRemoteCharacteristic, copyable_callback), base::BindOnce(OnWriteRemoteCharacteristicError, copyable_callback)); } diff --git a/chromium/device/fido/cable/fido_ble_connection_unittest.cc b/chromium/device/fido/cable/fido_ble_connection_unittest.cc index bdd9f99a583..49f25b9f340 100644 --- a/chromium/device/fido/cable/fido_ble_connection_unittest.cc +++ b/chromium/device/fido/cable/fido_ble_connection_unittest.cc @@ -167,7 +167,8 @@ class FidoBleConnectionTest : public ::testing::Test { ON_CALL(*fido_service_revision_bitfield_, WriteRemoteCharacteristic_) .WillByDefault(Invoke( - [=](auto&, base::OnceClosure& callback, + [=](auto&, BluetoothRemoteGattCharacteristic::WriteType, + base::OnceClosure& callback, const BluetoothRemoteGattCharacteristic::ErrorCallback&) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, std::move(callback)); @@ -283,19 +284,15 @@ class FidoBleConnectionTest : public ::testing::Test { } void SetNextWriteControlPointResponse(bool success) { -// For performance reasons we try writes without responses first on macOS. -#if defined(OS_MACOSX) - EXPECT_CALL(*fido_control_point_, WriteWithoutResponse) - .WillOnce(Return(success)); - if (success) - return; -#else - EXPECT_CALL(*fido_control_point_, WriteWithoutResponse).Times(0); -#endif // defined(OS_MACOSX) - - EXPECT_CALL(*fido_control_point_, WriteRemoteCharacteristic_(_, _, _)) + EXPECT_CALL( + *fido_control_point_, + WriteRemoteCharacteristic_( + _, BluetoothRemoteGattCharacteristic::WriteType::kWithoutResponse, + _, _)) .WillOnce(Invoke([success]( - const auto& data, base::OnceClosure& callback, + const auto& data, + BluetoothRemoteGattCharacteristic::WriteType, + base::OnceClosure& callback, BluetoothRemoteGattCharacteristic::ErrorCallback& error_callback) { base::ThreadTaskRunnerHandle::Get()->PostTask( @@ -309,10 +306,15 @@ class FidoBleConnectionTest : public ::testing::Test { void SetNextWriteServiceRevisionResponse(std::vector<uint8_t> expected_data, bool success) { - EXPECT_CALL(*fido_service_revision_bitfield_, - WriteRemoteCharacteristic_(expected_data, _, _)) + EXPECT_CALL( + *fido_service_revision_bitfield_, + WriteRemoteCharacteristic_( + expected_data, + BluetoothRemoteGattCharacteristic::WriteType::kWithResponse, _, _)) .WillOnce(Invoke([success]( - const auto& data, base::OnceClosure& callback, + const auto& data, + BluetoothRemoteGattCharacteristic::WriteType, + base::OnceClosure& callback, BluetoothRemoteGattCharacteristic::ErrorCallback& error_callback) { base::ThreadTaskRunnerHandle::Get()->PostTask( @@ -337,7 +339,7 @@ class FidoBleConnectionTest : public ::testing::Test { std::make_unique<NiceMockBluetoothGattCharacteristic>( fido_service_, "fido_control_point", BluetoothUUID(kFidoControlPointUUID), kIsLocal, - BluetoothGattCharacteristic::PROPERTY_WRITE, + BluetoothGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE, BluetoothGattCharacteristic::PERMISSION_NONE); fido_control_point_ = fido_control_point.get(); fido_service_->AddMockCharacteristic(std::move(fido_control_point)); diff --git a/chromium/device/fido/cable/fido_cable_discovery.cc b/chromium/device/fido/cable/fido_cable_discovery.cc index 368d657c0a9..beb8a0fec72 100644 --- a/chromium/device/fido/cable/fido_cable_discovery.cc +++ b/chromium/device/fido/cable/fido_cable_discovery.cc @@ -20,7 +20,6 @@ #include "base/time/time.h" #include "build/build_config.h" #include "components/device_event_log/device_event_log.h" -#include "crypto/random.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_advertisement.h" #include "device/bluetooth/bluetooth_discovery_session.h" @@ -29,12 +28,6 @@ #include "device/fido/cable/fido_cable_handshake_handler.h" #include "device/fido/features.h" #include "device/fido/fido_parsing_utils.h" -#include "third_party/boringssl/src/include/openssl/aes.h" -#include "third_party/boringssl/src/include/openssl/digest.h" -#include "third_party/boringssl/src/include/openssl/ec.h" -#include "third_party/boringssl/src/include/openssl/hkdf.h" -#include "third_party/boringssl/src/include/openssl/mem.h" -#include "third_party/boringssl/src/include/openssl/obj.h" namespace device { @@ -107,238 +100,8 @@ std::unique_ptr<BluetoothAdvertisement::Data> ConstructAdvertisementData( return advertisement_data; } -enum class QRValue : uint8_t { - QR_SECRET = 0, - IDENTITY_KEY_SEED = 1, -}; - -void DeriveQRValue(base::span<const uint8_t, 32> qr_generator_key, - const int64_t tick, - QRValue type, - base::span<uint8_t> out) { - uint8_t hkdf_input[sizeof(uint64_t) + 1]; - memcpy(hkdf_input, &tick, sizeof(uint64_t)); - hkdf_input[sizeof(uint64_t)] = base::strict_cast<uint8_t>(type); - - bool ok = HKDF(out.data(), out.size(), EVP_sha256(), qr_generator_key.data(), - qr_generator_key.size(), - /*salt=*/nullptr, 0, hkdf_input, sizeof(hkdf_input)); - DCHECK(ok); -} - } // namespace -// CableDiscoveryData ------------------------------------- - -CableDiscoveryData::CableDiscoveryData() = default; - -CableDiscoveryData::CableDiscoveryData( - CableDiscoveryData::Version version, - const CableEidArray& client_eid, - const CableEidArray& authenticator_eid, - const CableSessionPreKeyArray& session_pre_key) - : version(version) { - CHECK_EQ(Version::V1, version); - v1.emplace(); - v1->client_eid = client_eid; - v1->authenticator_eid = authenticator_eid; - v1->session_pre_key = session_pre_key; -} - -CableDiscoveryData::CableDiscoveryData( - base::span<const uint8_t, kCableQRSecretSize> qr_secret, - base::span<const uint8_t, kCableIdentityKeySeedSize> identity_key_seed) { - InitFromQRSecret(qr_secret); - v2->local_identity_seed = fido_parsing_utils::Materialize(identity_key_seed); -} - -// static -base::Optional<CableDiscoveryData> CableDiscoveryData::FromQRData( - base::span<const uint8_t, - kCableCompressedPublicKeySize + kCableQRSecretSize> qr_data) { - auto qr_secret = qr_data.subspan(kCableCompressedPublicKeySize); - CableDiscoveryData discovery_data; - discovery_data.InitFromQRSecret(base::span<const uint8_t, kCableQRSecretSize>( - qr_secret.data(), qr_secret.size())); - - bssl::UniquePtr<EC_GROUP> p256( - EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); - bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get())); - if (!EC_POINT_oct2point(p256.get(), point.get(), qr_data.data(), - kCableCompressedPublicKeySize, /*ctx=*/nullptr)) { - return base::nullopt; - } - CableAuthenticatorIdentityKey& identity_key = - discovery_data.v2->peer_identity.emplace(); - CHECK_EQ(identity_key.size(), - EC_POINT_point2oct( - p256.get(), point.get(), POINT_CONVERSION_UNCOMPRESSED, - identity_key.data(), identity_key.size(), /*ctx=*/nullptr)); - - return discovery_data; -} - -CableDiscoveryData::CableDiscoveryData(const CableDiscoveryData& data) = - default; - -CableDiscoveryData& CableDiscoveryData::operator=( - const CableDiscoveryData& other) = default; - -CableDiscoveryData::~CableDiscoveryData() = default; - -bool CableDiscoveryData::operator==(const CableDiscoveryData& other) const { - if (version != other.version) { - return false; - } - - switch (version) { - case CableDiscoveryData::Version::V1: - return v1->client_eid == other.v1->client_eid && - v1->authenticator_eid == other.v1->authenticator_eid && - v1->session_pre_key == other.v1->session_pre_key; - - case CableDiscoveryData::Version::V2: - return v2->eid_gen_key == other.v2->eid_gen_key && - v2->psk_gen_key == other.v2->psk_gen_key && - v2->peer_identity == other.v2->peer_identity && - v2->peer_name == other.v2->peer_name; - - case CableDiscoveryData::Version::INVALID: - CHECK(false); - return false; - } -} - -base::Optional<CableNonce> CableDiscoveryData::Match( - const CableEidArray& eid) const { - switch (version) { - case Version::V1: { - if (eid != v1->authenticator_eid) { - return base::nullopt; - } - - // The nonce is the first eight bytes of the EID. - CableNonce nonce; - const bool ok = - fido_parsing_utils::ExtractArray(v1->client_eid, 0, &nonce); - DCHECK(ok); - return nonce; - } - - case Version::V2: { - // Attempt to decrypt the EID with the EID generator key and check whether - // it has a valid structure. - AES_KEY key; - CHECK(AES_set_decrypt_key(v2->eid_gen_key.data(), - /*bits=*/8 * v2->eid_gen_key.size(), - &key) == 0); - static_assert(kCableEphemeralIdSize == AES_BLOCK_SIZE, - "EIDs are not AES blocks"); - CableEidArray decrypted; - AES_decrypt(/*in=*/eid.data(), /*out=*/decrypted.data(), &key); - const uint8_t kZeroTrailer[8] = {0}; - static_assert(8 + sizeof(kZeroTrailer) == - std::tuple_size<decltype(decrypted)>::value, - "Trailer is wrong size"); - if (CRYPTO_memcmp(kZeroTrailer, decrypted.data() + 8, - sizeof(kZeroTrailer)) != 0) { - return base::nullopt; - } - - CableNonce nonce; - static_assert( - sizeof(nonce) <= std::tuple_size<decltype(decrypted)>::value, - "nonce too large"); - memcpy(nonce.data(), decrypted.data(), sizeof(nonce)); - return nonce; - } - - case Version::INVALID: - DCHECK(false); - return base::nullopt; - } -} - -// static -QRGeneratorKey CableDiscoveryData::NewQRKey() { - QRGeneratorKey key; - crypto::RandBytes(key.data(), key.size()); - return key; -} - -// static -int64_t CableDiscoveryData::CurrentTimeTick() { - // The ticks are currently 256ms. - return base::TimeTicks::Now().since_origin().InMilliseconds() >> 8; -} - -// static -std::array<uint8_t, kCableQRSecretSize> CableDiscoveryData::DeriveQRSecret( - base::span<const uint8_t, 32> qr_generator_key, - const int64_t tick) { - std::array<uint8_t, kCableQRSecretSize> ret; - DeriveQRValue(qr_generator_key, tick, QRValue::QR_SECRET, ret); - return ret; -} - -// static -CableIdentityKeySeed CableDiscoveryData::DeriveIdentityKeySeed( - base::span<const uint8_t, 32> qr_generator_key, - const int64_t tick) { - std::array<uint8_t, kCableIdentityKeySeedSize> ret; - DeriveQRValue(qr_generator_key, tick, QRValue::IDENTITY_KEY_SEED, ret); - return ret; -} - -// static -CableQRData CableDiscoveryData::DeriveQRData( - base::span<const uint8_t, 32> qr_generator_key, - const int64_t tick) { - auto identity_key_seed = DeriveIdentityKeySeed(qr_generator_key, tick); - bssl::UniquePtr<EC_GROUP> p256( - EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); - bssl::UniquePtr<EC_KEY> identity_key(EC_KEY_derive_from_secret( - p256.get(), identity_key_seed.data(), identity_key_seed.size())); - const EC_POINT* public_key = EC_KEY_get0_public_key(identity_key.get()); - CableQRData qr_data; - static_assert( - qr_data.size() == kCableCompressedPublicKeySize + kCableQRSecretSize, - "this code needs to be updated"); - CHECK_EQ(kCableCompressedPublicKeySize, - EC_POINT_point2oct(p256.get(), public_key, - POINT_CONVERSION_COMPRESSED, qr_data.data(), - kCableCompressedPublicKeySize, /*ctx=*/nullptr)); - - auto qr_secret = CableDiscoveryData::DeriveQRSecret(qr_generator_key, tick); - memcpy(&qr_data.data()[kCableCompressedPublicKeySize], qr_secret.data(), - qr_secret.size()); - - return qr_data; -} - -CableDiscoveryData::V2Data::V2Data() = default; -CableDiscoveryData::V2Data::V2Data(const V2Data&) = default; -CableDiscoveryData::V2Data::~V2Data() = default; - -void CableDiscoveryData::InitFromQRSecret( - base::span<const uint8_t, kCableQRSecretSize> qr_secret) { - version = Version::V2; - v2.emplace(); - - static const char kEIDGen[] = "caBLE QR to EID generator key"; - bool ok = - HKDF(v2->eid_gen_key.data(), v2->eid_gen_key.size(), EVP_sha256(), - qr_secret.data(), qr_secret.size(), /*salt=*/nullptr, 0, - reinterpret_cast<const uint8_t*>(kEIDGen), sizeof(kEIDGen) - 1); - DCHECK(ok); - - static const char kPSKGen[] = "caBLE QR to PSK generator key"; - ok = HKDF(v2->psk_gen_key.data(), v2->psk_gen_key.size(), EVP_sha256(), - qr_secret.data(), qr_secret.size(), /*salt=*/nullptr, 0, - reinterpret_cast<const uint8_t*>(kPSKGen), sizeof(kPSKGen) - 1); - DCHECK(ok); -} - // FidoCableDiscovery::CableV1DiscoveryEvent --------------------------------- // CableV1DiscoveryEvent enumerates several things that can occur during a caBLE @@ -352,12 +115,14 @@ enum class FidoCableDiscovery::CableV1DiscoveryEvent : int { kAdapterManuallyPowered = 4, kAdapterPoweredOff = 5, kScanningStarted = 6, + kStartScanningFailed = 12, + kScanningStoppedUnexpectedly = 13, kAdvertisementRegistered = 7, kFirstCableDeviceFound = 8, kFirstCableDeviceGATTConnected = 9, kFirstCableHandshakeSucceeded = 10, kFirstCableDeviceTimeout = 11, - kMaxValue = kFirstCableDeviceTimeout, + kMaxValue = kScanningStoppedUnexpectedly, }; // FidoCableDiscovery::Result ------------------------------------------------- @@ -493,10 +258,6 @@ void FidoCableDiscovery::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) { if (has_v1_discovery_data_) { RecordCableV1DiscoveryEventOnce(CableV1DiscoveryEvent::kAdapterPresent); - if (adapter->IsPowered()) { - RecordCableV1DiscoveryEventOnce( - CableV1DiscoveryEvent::kAdapterAlreadyPowered); - } } DCHECK(!adapter_); @@ -506,6 +267,10 @@ void FidoCableDiscovery::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) { adapter_->AddObserver(this); if (adapter_->IsPowered()) { + if (has_v1_discovery_data_) { + RecordCableV1DiscoveryEventOnce( + CableV1DiscoveryEvent::kAdapterAlreadyPowered); + } OnSetPowered(); } @@ -589,6 +354,22 @@ void FidoCableDiscovery::AdapterPoweredChanged(BluetoothAdapter* adapter, #endif // defined(OS_WIN) } +void FidoCableDiscovery::AdapterDiscoveringChanged(BluetoothAdapter* adapter, + bool is_scanning) { + FIDO_LOG(DEBUG) << "AdapterDiscoveringChanged() is_scanning=" << is_scanning; + + // Ignore updates while we're not scanning for caBLE devices ourselves. Other + // things in Chrome may start or stop scans at any time. + if (!discovery_session_) { + return; + } + + if (has_v1_discovery_data_ && !is_scanning) { + RecordCableV1DiscoveryEventOnce( + CableV1DiscoveryEvent::kScanningStoppedUnexpectedly); + } +} + void FidoCableDiscovery::FidoCableDeviceConnected(FidoCableDevice* device, bool success) { if (!success || !IsObservedV1Device(device->GetAddress())) { @@ -637,6 +418,10 @@ void FidoCableDiscovery::OnStartDiscoverySession( void FidoCableDiscovery::OnStartDiscoverySessionError() { FIDO_LOG(ERROR) << "Failed to start caBLE discovery"; + if (has_v1_discovery_data_) { + RecordCableV1DiscoveryEventOnce( + CableV1DiscoveryEvent::kStartScanningFailed); + } } void FidoCableDiscovery::StartAdvertisement() { diff --git a/chromium/device/fido/cable/fido_cable_discovery.h b/chromium/device/fido/cable/fido_cable_discovery.h index 286c28d72be..5381ff80477 100644 --- a/chromium/device/fido/cable/fido_cable_discovery.h +++ b/chromium/device/fido/cable/fido_cable_discovery.h @@ -149,6 +149,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery void DeviceRemoved(BluetoothAdapter* adapter, BluetoothDevice* device) override; void AdapterPoweredChanged(BluetoothAdapter* adapter, bool powered) override; + void AdapterDiscoveringChanged(BluetoothAdapter* adapter, + bool discovering) override; // FidoCableDevice::Observer: void FidoCableDeviceConnected(FidoCableDevice* device, bool success) override; @@ -158,10 +160,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery std::unique_ptr<BluetoothDiscoverySession> discovery_session_; std::vector<CableDiscoveryData> discovery_data_; + // active_authenticator_eids_ contains authenticator EIDs for which a // handshake is currently running. Further advertisements for the same EIDs // will be ignored. std::set<CableEidArray> active_authenticator_eids_; + // active_devices_ contains the BLE addresses of devices for which a handshake // is already running. Further advertisements from these devices will be // ignored. However, devices may rotate their BLE address at will so this is @@ -185,6 +189,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery // that the device-log isn't spammed. mutable base::flat_map<std::string, std::unique_ptr<ObservedDeviceData>> observed_devices_; + // noted_obsolete_eids_ remembers QR-code EIDs that have been logged as // valid-but-expired in order to avoid spamming the device-log. mutable base::flat_set<CableEidArray> noted_obsolete_eids_; diff --git a/chromium/device/fido/cable/fido_cable_discovery_unittest.cc b/chromium/device/fido/cable/fido_cable_discovery_unittest.cc index 167891d4003..020dc238c1a 100644 --- a/chromium/device/fido/cable/fido_cable_discovery_unittest.cc +++ b/chromium/device/fido/cable/fido_cable_discovery_unittest.cc @@ -153,11 +153,9 @@ class CableMockBluetoothAdvertisement : public BluetoothAdvertisement { void ExpectUnregisterAndSucceed() { EXPECT_CALL(*this, Unregister(_, _)) - .WillOnce( - ::testing::WithArg<0>(::testing::Invoke([](const auto& success_cb) { - base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, - success_cb); - }))); + .WillOnce(::testing::WithArg<0>([](const auto& success_cb) { + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, success_cb); + })); } private: @@ -254,7 +252,7 @@ class CableMockAdapter : public MockBluetoothAdapter { advertisement = base::MakeRefCounted<CableMockBluetoothAdvertisement>(); EXPECT_CALL(*advertisement, Unregister(_, _)) .WillRepeatedly(::testing::WithArg<0>( - [](const auto& callback) { callback.Run(); })); + [](auto callback) { std::move(callback).Run(); })); } EXPECT_CALL(*this, diff --git a/chromium/device/fido/cable/fido_cable_handshake_handler.cc b/chromium/device/fido/cable/fido_cable_handshake_handler.cc index 07abf0c17bb..2a120e1ebe8 100644 --- a/chromium/device/fido/cable/fido_cable_handshake_handler.cc +++ b/chromium/device/fido/cable/fido_cable_handshake_handler.cc @@ -178,15 +178,12 @@ FidoCableV1HandshakeHandler::GetEncryptionKeyAfterSuccessfulHandshake( /*derived_key_length=*/32); } -// kP256PointSize is the number of bytes in an X9.62 encoding of a P-256 point. -static constexpr size_t kP256PointSize = 65; - FidoCableV2HandshakeHandler::FidoCableV2HandshakeHandler( FidoCableDevice* cable_device, base::span<const uint8_t, 32> psk_gen_key, base::span<const uint8_t, 8> nonce, base::span<const uint8_t, kCableEphemeralIdSize> eid, - base::Optional<base::span<const uint8_t, kP256PointSize>> peer_identity, + base::Optional<base::span<const uint8_t, kP256X962Length>> peer_identity, base::Optional<base::span<const uint8_t, kCableIdentityKeySeedSize>> local_seed, base::RepeatingCallback<void(std::unique_ptr<CableDiscoveryData>)> diff --git a/chromium/device/fido/cable/noise.cc b/chromium/device/fido/cable/noise.cc index b5526867f21..7926500005f 100644 --- a/chromium/device/fido/cable/noise.cc +++ b/chromium/device/fido/cable/noise.cc @@ -8,8 +8,11 @@ #include "crypto/aead.h" #include "crypto/sha2.h" +#include "device/fido/fido_constants.h" #include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/ec.h" #include "third_party/boringssl/src/include/openssl/hkdf.h" +#include "third_party/boringssl/src/include/openssl/obj.h" #include "third_party/boringssl/src/include/openssl/sha.h" namespace { @@ -121,6 +124,16 @@ base::Optional<std::vector<uint8_t>> Noise::DecryptAndHash( return plaintext; } +void Noise::MixHashPoint(const EC_POINT* point) { + uint8_t x962[kP256X962Length]; + bssl::UniquePtr<EC_GROUP> p256( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + CHECK_EQ(sizeof(x962), + EC_POINT_point2oct(p256.get(), point, POINT_CONVERSION_UNCOMPRESSED, + x962, sizeof(x962), /*ctx=*/nullptr)); + MixHash(x962); +} + std::tuple<std::array<uint8_t, 32>, std::array<uint8_t, 32>> Noise::traffic_keys() const { return HKDF2(chaining_key_, base::span<const uint8_t>()); diff --git a/chromium/device/fido/cable/noise.h b/chromium/device/fido/cable/noise.h index 8bd4d8c906c..0372f452b82 100644 --- a/chromium/device/fido/cable/noise.h +++ b/chromium/device/fido/cable/noise.h @@ -12,6 +12,7 @@ #include "base/component_export.h" #include "base/containers/span.h" +#include "third_party/boringssl/src/include/openssl/base.h" namespace device { @@ -41,6 +42,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) Noise { base::Optional<std::vector<uint8_t>> DecryptAndHash( base::span<const uint8_t> ciphertext); + // MaxHashPoint calls |MixHash| with the uncompressed, X9.62 serialization of + // |point|. + void MixHashPoint(const EC_POINT* point); + // traffic_keys() calls Split from the protocol spec but, rather than // returning CipherState objects, returns the raw keys. std::tuple<std::array<uint8_t, 32>, std::array<uint8_t, 32>> traffic_keys() diff --git a/chromium/device/fido/cable/v2_handshake.cc b/chromium/device/fido/cable/v2_handshake.cc index 98351ac37cb..b9f85a20624 100644 --- a/chromium/device/fido/cable/v2_handshake.cc +++ b/chromium/device/fido/cable/v2_handshake.cc @@ -170,7 +170,7 @@ HandshakeInitiator::HandshakeInitiator( base::span<const uint8_t, 32> psk_gen_key, base::span<const uint8_t, 8> nonce, base::span<const uint8_t, kCableEphemeralIdSize> eid, - base::Optional<base::span<const uint8_t, kP256PointSize>> peer_identity, + base::Optional<base::span<const uint8_t, kP256X962Length>> peer_identity, base::Optional<base::span<const uint8_t, kCableIdentityKeySeedSize>> local_seed) : eid_(fido_parsing_utils::Materialize(eid)) { @@ -182,7 +182,10 @@ HandshakeInitiator::HandshakeInitiator( peer_identity_ = fido_parsing_utils::Materialize(*peer_identity); } if (local_seed) { - local_seed_ = fido_parsing_utils::Materialize(*local_seed); + bssl::UniquePtr<EC_GROUP> p256( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + local_identity_.reset(EC_KEY_derive_from_secret( + p256.get(), local_seed->data(), local_seed->size())); } } @@ -192,16 +195,18 @@ std::vector<uint8_t> HandshakeInitiator::BuildInitialMessage() { if (peer_identity_) { noise_.Init(Noise::HandshakeType::kNKpsk0); noise_.MixHash(kPairedPrologue); + noise_.MixHash(*peer_identity_); } else { noise_.Init(Noise::HandshakeType::kKNpsk0); noise_.MixHash(kQRPrologue); + noise_.MixHashPoint(EC_KEY_get0_public_key(local_identity_.get())); } noise_.MixKeyAndHash(psk_); ephemeral_key_.reset(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); const EC_GROUP* group = EC_KEY_get0_group(ephemeral_key_.get()); CHECK(EC_KEY_generate_key(ephemeral_key_.get())); - uint8_t ephemeral_key_public_bytes[kP256PointSize]; + uint8_t ephemeral_key_public_bytes[kP256X962Length]; CHECK_EQ(sizeof(ephemeral_key_public_bytes), EC_POINT_point2oct( group, EC_KEY_get0_public_key(ephemeral_key_.get()), @@ -244,11 +249,11 @@ std::vector<uint8_t> HandshakeInitiator::BuildInitialMessage() { base::Optional<std::pair<std::unique_ptr<Crypter>, base::Optional<std::unique_ptr<CableDiscoveryData>>>> HandshakeInitiator::ProcessResponse(base::span<const uint8_t> response) { - if (response.size() < kP256PointSize) { + if (response.size() < kP256X962Length) { return base::nullopt; } - auto peer_point_bytes = response.subspan(0, kP256PointSize); - auto ciphertext = response.subspan(kP256PointSize); + auto peer_point_bytes = response.subspan(0, kP256X962Length); + auto ciphertext = response.subspan(kP256X962Length); bssl::UniquePtr<EC_POINT> peer_point( EC_POINT_new(EC_KEY_get0_group(ephemeral_key_.get()))); @@ -267,12 +272,10 @@ HandshakeInitiator::ProcessResponse(base::span<const uint8_t> response) { noise_.MixKey(peer_point_bytes); noise_.MixKey(shared_key_ee); - if (local_seed_) { + if (local_identity_) { uint8_t shared_key_se[32]; - bssl::UniquePtr<EC_KEY> identity_key(EC_KEY_derive_from_secret( - group, local_seed_->data(), local_seed_->size())); if (ECDH_compute_key(shared_key_se, sizeof(shared_key_se), peer_point.get(), - identity_key.get(), + local_identity_.get(), /*kdf=*/nullptr) != sizeof(shared_key_se)) { return base::nullopt; } @@ -354,7 +357,7 @@ base::Optional<std::unique_ptr<Crypter>> RespondToHandshake( base::span<const uint8_t> peer_point_bytes; base::span<const uint8_t> ciphertext; if (!CBS_get_span(&cbs, &eid, device::kCableEphemeralIdSize) || - !CBS_get_span(&cbs, &peer_point_bytes, kP256PointSize) || + !CBS_get_span(&cbs, &peer_point_bytes, kP256X962Length) || !CBS_get_span(&cbs, &ciphertext, 16) || CBS_len(&cbs) != 0) { return base::nullopt; } @@ -368,9 +371,11 @@ base::Optional<std::unique_ptr<Crypter>> RespondToHandshake( if (identity) { noise.Init(device::Noise::HandshakeType::kNKpsk0); noise.MixHash(kPairedPrologue); + noise.MixHashPoint(EC_KEY_get0_public_key(identity)); } else { noise.Init(device::Noise::HandshakeType::kKNpsk0); noise.MixHash(kQRPrologue); + noise.MixHashPoint(peer_identity); } std::array<uint8_t, 32> psk; @@ -410,7 +415,7 @@ base::Optional<std::unique_ptr<Crypter>> RespondToHandshake( return base::nullopt; } - uint8_t ephemeral_key_public_bytes[kP256PointSize]; + uint8_t ephemeral_key_public_bytes[kP256X962Length]; CHECK_EQ(sizeof(ephemeral_key_public_bytes), EC_POINT_point2oct( group, EC_KEY_get0_public_key(ephemeral_key.get()), diff --git a/chromium/device/fido/cable/v2_handshake.h b/chromium/device/fido/cable/v2_handshake.h index fdfdeb774d4..0b11f3a339d 100644 --- a/chromium/device/fido/cable/v2_handshake.h +++ b/chromium/device/fido/cable/v2_handshake.h @@ -104,7 +104,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) HandshakeInitiator { std::array<uint8_t, 32> psk_; base::Optional<std::array<uint8_t, kP256PointSize>> peer_identity_; - base::Optional<std::array<uint8_t, kCableIdentityKeySeedSize>> local_seed_; + bssl::UniquePtr<EC_KEY> local_identity_; bssl::UniquePtr<EC_KEY> ephemeral_key_; }; diff --git a/chromium/device/fido/cbor_extract.cc b/chromium/device/fido/cbor_extract.cc index ebbcc56ae60..ebde9b1d01f 100644 --- a/chromium/device/fido/cbor_extract.cc +++ b/chromium/device/fido/cbor_extract.cc @@ -64,8 +64,8 @@ class Extractor { step_i_ += key.size() + 1; map_it = map.find(cbor::Value(std::move(key))); } else { - map_it = map.find( - cbor::Value(static_cast<int64_t>(key_or_string_indicator))); + map_it = map.find(cbor::Value(static_cast<int64_t>( + static_cast<int8_t>(key_or_string_indicator)))); } const void** output = nullptr; diff --git a/chromium/device/fido/cbor_extract.h b/chromium/device/fido/cbor_extract.h index c24f8310533..5a99ac6d0c9 100644 --- a/chromium/device/fido/cbor_extract.h +++ b/chromium/device/fido/cbor_extract.h @@ -8,6 +8,7 @@ #include "base/callback_forward.h" #include "base/component_export.h" #include "base/containers/span.h" +#include "base/memory/checked_ptr.h" #include "components/cbor/values.h" namespace device { @@ -51,8 +52,8 @@ namespace cbor_extract { // Output values are also pointers into the input cbor::Value, so that cannot // be destroyed until processing is complete. // -// Keys for the element are either specified by IntKey<S>(x), where x < 255, or -// StringKey<S>() followed by a NUL-terminated string: +// Keys for the element are either specified by IntKey<S>(x), where -128 <= x < +// 127, or StringKey<S>() followed by a NUL-terminated string: // // static constexpr StepOrByte<MyObj> kSteps[] = { // ELEMENT(Is::kRequired, MyObj, value), @@ -124,7 +125,7 @@ template <typename S> struct StepOrByte { // STRING_KEY is the magic value of |u8| that indicates that this is not an // integer key, but the a NUL-terminated string follows. - static constexpr uint8_t STRING_KEY = 255; + static constexpr uint8_t STRING_KEY = 127; constexpr explicit StepOrByte(const internal::Step& in_step) : step(in_step) {} @@ -145,8 +146,10 @@ struct StepOrByte { offsetof(clas, member)) template <typename S> -constexpr StepOrByte<S> IntKey(unsigned key) { - if (key >= 256 || key == StepOrByte<S>::STRING_KEY) { +constexpr StepOrByte<S> IntKey(int key) { + if (key > std::numeric_limits<int8_t>::max() || + key < std::numeric_limits<int8_t>::min() || + key == StepOrByte<S>::STRING_KEY) { // It's a compile-time error if __builtin_unreachable is reachable. __builtin_unreachable(); } @@ -214,6 +217,14 @@ constexpr StepOrByte<S> Element(const Is required, } template <typename S> +constexpr StepOrByte<S> Element( + const Is required, + CheckedPtr<const std::vector<uint8_t>> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kBytestring); +} + +template <typename S> constexpr StepOrByte<S> Element(const Is required, const std::string* S::*member, uintptr_t offset) { @@ -222,6 +233,13 @@ constexpr StepOrByte<S> Element(const Is required, template <typename S> constexpr StepOrByte<S> Element(const Is required, + CheckedPtr<const std::string> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kString); +} + +template <typename S> +constexpr StepOrByte<S> Element(const Is required, const int64_t* S::*member, uintptr_t offset) { return ElementImpl<S>(required, offset, internal::Type::kInt); @@ -229,12 +247,27 @@ constexpr StepOrByte<S> Element(const Is required, template <typename S> constexpr StepOrByte<S> Element(const Is required, + CheckedPtr<const int64_t> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kInt); +} + +template <typename S> +constexpr StepOrByte<S> Element(const Is required, const std::vector<cbor::Value>* S::*member, uintptr_t offset) { return ElementImpl<S>(required, offset, internal::Type::kArray); } template <typename S> +constexpr StepOrByte<S> Element( + const Is required, + CheckedPtr<const std::vector<cbor::Value>> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kArray); +} + +template <typename S> constexpr StepOrByte<S> Element(const Is required, const cbor::Value* S::*member, uintptr_t offset) { @@ -243,11 +276,25 @@ constexpr StepOrByte<S> Element(const Is required, template <typename S> constexpr StepOrByte<S> Element(const Is required, + CheckedPtr<const cbor::Value> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kValue); +} + +template <typename S> +constexpr StepOrByte<S> Element(const Is required, const bool* S::*member, uintptr_t offset) { return ElementImpl<S>(required, offset, internal::Type::kBoolean); } +template <typename S> +constexpr StepOrByte<S> Element(const Is required, + CheckedPtr<const bool> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kBoolean); +} + COMPONENT_EXPORT(DEVICE_FIDO) bool Extract(base::span<const void*> outputs, base::span<const StepOrByte<void>> steps, diff --git a/chromium/device/fido/cbor_extract_unittest.cc b/chromium/device/fido/cbor_extract_unittest.cc index 714feca8195..a70eb5e1d83 100644 --- a/chromium/device/fido/cbor_extract_unittest.cc +++ b/chromium/device/fido/cbor_extract_unittest.cc @@ -36,7 +36,8 @@ struct MakeCredRequest { const std::vector<cbor::Value>* excluded_credentials; const bool* resident_key; const bool* user_verification; - const bool* u8_test; + const bool* large_test; + const bool* negative_test; }; TEST(CBORExtract, Basic) { @@ -78,7 +79,8 @@ TEST(CBORExtract, Basic) { make_cred.emplace(4, std::move(cred_params)); make_cred.emplace(5, std::move(excluded_creds)); make_cred.emplace(7, std::move(options)); - make_cred.emplace(0xf0, false); + make_cred.emplace(100, false); + make_cred.emplace(-3, true); static constexpr cbor_extract::StepOrByte<MakeCredRequest> kMakeCredParseSteps[] = { @@ -111,8 +113,11 @@ TEST(CBORExtract, Basic) { StringKey<MakeCredRequest>(), 'u', 'v', '\0', Stop<MakeCredRequest>(), - ELEMENT(Is::kRequired, MakeCredRequest, u8_test), - IntKey<MakeCredRequest>(0xf0), + ELEMENT(Is::kRequired, MakeCredRequest, large_test), + IntKey<MakeCredRequest>(100), + + ELEMENT(Is::kRequired, MakeCredRequest, negative_test), + IntKey<MakeCredRequest>(-3), Stop<MakeCredRequest>(), // clang-format on @@ -129,7 +134,8 @@ TEST(CBORExtract, Basic) { EXPECT_EQ(make_cred_request.excluded_credentials->size(), 3u); EXPECT_TRUE(*make_cred_request.resident_key); EXPECT_TRUE(make_cred_request.user_verification == nullptr); - EXPECT_FALSE(*make_cred_request.u8_test); + EXPECT_FALSE(*make_cred_request.large_test); + EXPECT_TRUE(*make_cred_request.negative_test); std::vector<int64_t> algs; EXPECT_TRUE(cbor_extract::ForEachPublicKeyEntry( diff --git a/chromium/device/fido/credential_management_handler.cc b/chromium/device/fido/credential_management_handler.cc index e1e0f2c57d5..8d8c6fa44bc 100644 --- a/chromium/device/fido/credential_management_handler.cc +++ b/chromium/device/fido/credential_management_handler.cc @@ -118,7 +118,8 @@ void CredentialManagementHandler::OnHavePIN(std::string pin) { state_ = State::kGettingPINToken; authenticator_->GetPINToken( - std::move(pin), + std::move(pin), {pin::Permissions::kCredentialManagement}, + /*rp_id=*/base::nullopt, base::BindOnce(&CredentialManagementHandler::OnHavePINToken, weak_factory_.GetWeakPtr())); } diff --git a/chromium/device/fido/ctap_response_unittest.cc b/chromium/device/fido/ctap_response_unittest.cc index 1a20bec1c3a..ffc487f46eb 100644 --- a/chromium/device/fido/ctap_response_unittest.cc +++ b/chromium/device/fido/ctap_response_unittest.cc @@ -15,6 +15,7 @@ #include "device/fido/fido_constants.h" #include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.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" @@ -25,6 +26,7 @@ namespace device { namespace { +// clang-format off constexpr uint8_t kTestAuthenticatorGetInfoResponseWithNoVersion[] = { // Success status byte 0x00, @@ -143,6 +145,126 @@ constexpr uint8_t kTestAuthenticatorGetInfoResponseWithDuplicateVersion[] = { 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, @@ -261,6 +383,7 @@ constexpr uint8_t kAuthDataCBOR[] = { // 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, @@ -426,10 +549,10 @@ TEST(CTAPResponseTest, TestParseRegisterResponseData) { // Test that an EC public key serializes to CBOR properly. TEST(CTAPResponseTest, TestSerializedPublicKey) { auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), test_data::kTestU2fRegisterResponse); ASSERT_TRUE(public_key); - EXPECT_THAT(public_key->cose_key_bytes(), + EXPECT_THAT(public_key->cose_key_bytes, ::testing::ElementsAreArray(test_data::kTestECPublicKeyCOSE)); } @@ -448,7 +571,7 @@ TEST(CTAPResponseTest, TestParseU2fAttestationStatementCBOR) { // Tests that well-formed attested credential data serializes properly. TEST(CTAPResponseTest, TestSerializeAttestedCredentialData) { auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), test_data::kTestU2fRegisterResponse); auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( test_data::kTestU2fRegisterResponse, std::move(public_key)); @@ -460,7 +583,7 @@ TEST(CTAPResponseTest, TestSerializeAttestedCredentialData) { // Tests that well-formed authenticator data serializes properly. TEST(CTAPResponseTest, TestSerializeAuthenticatorData) { auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), test_data::kTestU2fRegisterResponse); auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( test_data::kTestU2fRegisterResponse, std::move(public_key)); @@ -480,7 +603,7 @@ TEST(CTAPResponseTest, TestSerializeAuthenticatorData) { // Tests that a U2F attestation object serializes properly. TEST(CTAPResponseTest, TestSerializeU2fAttestationObject) { auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), test_data::kTestU2fRegisterResponse); auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( test_data::kTestU2fRegisterResponse, std::move(public_key)); @@ -587,6 +710,9 @@ TEST(CTAPResponseTest, TestReadGetInfoResponse) { 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); @@ -618,6 +744,31 @@ TEST(CTAPResponseTest, TestReadGetInfoResponseWithDuplicateVersion) { 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) { @@ -629,7 +780,8 @@ TEST(CTAPResponseTest, TestReadGetInfoResponseWithIncorrectFormat) { TEST(CTAPResponseTest, TestSerializeGetInfoResponse) { AuthenticatorGetInfoResponse response( - {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, kTestDeviceAaguid); + {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; @@ -691,7 +843,7 @@ TEST(CTAPResponseTest, TestSerializeMakeCredentialResponse) { fido_parsing_utils::Materialize( test_data::kCtap2MakeCredentialCredentialId), std::make_unique<PublicKey>( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), kCoseEncodedPublicKey, base::nullopt)); AuthenticatorData authenticator_data(application_parameter, flag, signature_counter, diff --git a/chromium/device/fido/device_response_converter.cc b/chromium/device/fido/device_response_converter.cc index a394c21f96b..5bbf8c4ea98 100644 --- a/chromium/device/fido/device_response_converter.cc +++ b/chromium/device/fido/device_response_converter.cc @@ -32,7 +32,7 @@ namespace { constexpr size_t kResponseCodeLength = 1; ProtocolVersion ConvertStringToProtocolVersion(base::StringPiece version) { - if (version == kCtap2Version) + if (version == kCtap2Version || version == kCtap2_1Version) return ProtocolVersion::kCtap2; if (version == kU2fVersion) return ProtocolVersion::kU2f; @@ -40,6 +40,15 @@ ProtocolVersion ConvertStringToProtocolVersion(base::StringPiece version) { return ProtocolVersion::kUnknown; } +Ctap2Version ConvertStringToCtap2Version(base::StringPiece version) { + if (version == kCtap2Version) + return Ctap2Version::kCtap2_0; + if (version == kCtap2_1Version) + return Ctap2Version::kCtap2_1; + + return Ctap2Version::kUnknown; +} + // Converts a CBOR unsigned integer value to a uint32_t. The conversion is // clamped at uint32_max. uint32_t CBORUnsignedToUint32Safe(const cbor::Value& value) { @@ -195,6 +204,7 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( } base::flat_set<ProtocolVersion> protocol_versions; + base::flat_set<Ctap2Version> ctap2_versions; base::flat_set<base::StringPiece> advertised_protocols; for (const auto& version : it->second.GetArray()) { if (!version.is_string()) @@ -212,12 +222,11 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( continue; } - if (!protocol_versions.insert(protocol).second) { - // A duplicate value will have already caused an error therefore hitting - // this suggests that |ConvertStringToProtocolVersion| is non-injective. - NOTREACHED(); - return base::nullopt; + if (protocol == ProtocolVersion::kCtap2) { + ctap2_versions.insert(ConvertStringToCtap2Version(version_string)); } + + protocol_versions.insert(protocol); } if (protocol_versions.empty()) @@ -230,7 +239,7 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( } AuthenticatorGetInfoResponse response( - std::move(protocol_versions), + std::move(protocol_versions), ctap2_versions, base::make_span<kAaguidLength>(it->second.GetBytestring())); AuthenticatorSupportedOptions options; @@ -363,7 +372,7 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( if (!option_map_it->second.is_bool()) { return base::nullopt; } - options.supports_uv_token = option_map_it->second.GetBool(); + options.supports_pin_uv_auth_token = option_map_it->second.GetBool(); } option_map_it = option_map.find(CBOR(kDefaultCredProtectKey)); @@ -422,6 +431,47 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( response.max_credential_id_length = CBORUnsignedToUint32Safe(it->second); } + it = response_map.find(CBOR(10)); + if (it != response_map.end()) { + if (!it->second.is_array()) { + return base::nullopt; + } + + response.algorithms.clear(); + + const std::vector<cbor::Value>& algorithms = it->second.GetArray(); + for (const auto& algorithm : algorithms) { + // Entries are PublicKeyCredentialParameters + // https://w3c.github.io/webauthn/#dictdef-publickeycredentialparameters + if (!algorithm.is_map()) { + return base::nullopt; + } + + const auto& map = algorithm.GetMap(); + const auto type_it = map.find(CBOR("type")); + if (type_it == map.end() || !type_it->second.is_string()) { + return base::nullopt; + } + + if (type_it->second.GetString() != "public-key") { + continue; + } + + const auto alg_it = map.find(CBOR("alg")); + if (alg_it == map.end() || !alg_it->second.is_integer()) { + return base::nullopt; + } + + const int64_t alg = alg_it->second.GetInteger(); + if (alg < std::numeric_limits<int32_t>::min() || + alg > std::numeric_limits<int32_t>::max()) { + continue; + } + + response.algorithms.push_back(alg); + } + } + return base::Optional<AuthenticatorGetInfoResponse>(std::move(response)); } diff --git a/chromium/device/fido/ed25519_public_key.cc b/chromium/device/fido/ed25519_public_key.cc new file mode 100644 index 00000000000..ff86b9e7c8f --- /dev/null +++ b/chromium/device/fido/ed25519_public_key.cc @@ -0,0 +1,89 @@ +// Copyright 2020 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 "device/fido/ed25519_public_key.h" + +#include <utility> + +#include "components/cbor/writer.h" +#include "device/fido/cbor_extract.h" +#include "device/fido/fido_constants.h" +#include "third_party/boringssl/src/include/openssl/bn.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "third_party/boringssl/src/include/openssl/mem.h" +#include "third_party/boringssl/src/include/openssl/obj.h" +#include "third_party/boringssl/src/include/openssl/rsa.h" + +using device::cbor_extract::IntKey; +using device::cbor_extract::Is; +using device::cbor_extract::StepOrByte; +using device::cbor_extract::Stop; + +namespace device { + +// static +std::unique_ptr<PublicKey> Ed25519PublicKey::ExtractFromCOSEKey( + int32_t algorithm, + base::span<const uint8_t> cbor_bytes, + const cbor::Value::MapValue& map) { + // See https://tools.ietf.org/html/rfc8152#section-13.2 + struct COSEKey { + const int64_t* kty; + const int64_t* crv; + const std::vector<uint8_t>* key; + } cose_key; + + static constexpr cbor_extract::StepOrByte<COSEKey> kSteps[] = { + // clang-format off + ELEMENT(Is::kRequired, COSEKey, kty), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kKty)), + + ELEMENT(Is::kRequired, COSEKey, crv), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticCurve)), + + ELEMENT(Is::kRequired, COSEKey, key), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticX)), + + Stop<COSEKey>(), + // clang-format on + }; + + if (!cbor_extract::Extract<COSEKey>(&cose_key, kSteps, map) || + *cose_key.kty != static_cast<int64_t>(CoseKeyTypes::kOKP) || + *cose_key.crv != static_cast<int64_t>(CoseCurves::kEd25519) || + cose_key.key->size() != 32) { + return nullptr; + } + + // The COSE RFC says that "This contains the x-coordinate for the EC point". + // The RFC authors do not appear to understand what's going on because it + // actually just contains the Ed25519 public key, which you would expect, and + // which also encodes the y-coordinate as a sign bit. + // + // We could attempt to check whether |key| contains a quadratic residue, as it + // should. But that would involve diving into the guts of Ed25519 too much. + + bssl::UniquePtr<EVP_PKEY> pkey( + EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, /*engine=*/nullptr, + cose_key.key->data(), cose_key.key->size())); + if (!pkey) { + return nullptr; + } + + bssl::ScopedCBB cbb; + uint8_t* der_bytes = nullptr; + size_t der_bytes_len = 0; + CHECK(CBB_init(cbb.get(), /* initial size */ 128) && + EVP_marshal_public_key(cbb.get(), pkey.get()) && + CBB_finish(cbb.get(), &der_bytes, &der_bytes_len)); + + std::vector<uint8_t> der_bytes_vec(der_bytes, der_bytes + der_bytes_len); + OPENSSL_free(der_bytes); + + return std::make_unique<PublicKey>(algorithm, cbor_bytes, + std::move(der_bytes_vec)); +} + +} // namespace device diff --git a/chromium/device/fido/ed25519_public_key.h b/chromium/device/fido/ed25519_public_key.h new file mode 100644 index 00000000000..20ad4115d3e --- /dev/null +++ b/chromium/device/fido/ed25519_public_key.h @@ -0,0 +1,28 @@ +// Copyright 2020 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. + +#ifndef DEVICE_FIDO_ED25519_PUBLIC_KEY_H_ +#define DEVICE_FIDO_ED25519_PUBLIC_KEY_H_ + +#include <stdint.h> +#include <memory> + +#include "base/component_export.h" +#include "base/containers/span.h" +#include "components/cbor/values.h" +#include "device/fido/public_key.h" + +namespace device { + +class COMPONENT_EXPORT(DEVICE_FIDO) Ed25519PublicKey { + public: + static std::unique_ptr<PublicKey> ExtractFromCOSEKey( + int32_t algorithm, + base::span<const uint8_t> cbor_bytes, + const cbor::Value::MapValue& map); +}; + +} // namespace device + +#endif // DEVICE_FIDO_ED25519_PUBLIC_KEY_H_ diff --git a/chromium/device/fido/fido_authenticator.cc b/chromium/device/fido/fido_authenticator.cc index 1555716ef4b..72a09dbfe9b 100644 --- a/chromium/device/fido/fido_authenticator.cc +++ b/chromium/device/fido/fido_authenticator.cc @@ -26,6 +26,8 @@ void FidoAuthenticator::GetPinRetries( void FidoAuthenticator::GetPINToken( std::string pin, + const std::vector<pin::Permissions>& permissions, + base::Optional<std::string> rp_id, FidoAuthenticator::GetTokenCallback callback) { NOTREACHED(); } @@ -35,7 +37,12 @@ void FidoAuthenticator::GetUvRetries( NOTREACHED(); } +bool FidoAuthenticator::CanGetUvToken() { + return false; +} + void FidoAuthenticator::GetUvToken( + base::Optional<std::string> rp_id, FidoAuthenticator::GetTokenCallback callback) { NOTREACHED(); } @@ -121,6 +128,10 @@ void FidoAuthenticator::BioEnrollDelete(const pin::TokenResponse&, NOTREACHED(); } +base::Optional<base::span<const int32_t>> FidoAuthenticator::GetAlgorithms() { + return base::nullopt; +} + void FidoAuthenticator::Reset(ResetCallback callback) { std::move(callback).Run(CtapDeviceResponseCode::kCtap1ErrInvalidCommand, base::nullopt); @@ -134,4 +145,8 @@ bool FidoAuthenticator::SupportsCredProtectExtension() const { return Options() && Options()->supports_cred_protect; } +bool FidoAuthenticator::SupportsHMACSecretExtension() const { + return false; +} + } // namespace device diff --git a/chromium/device/fido/fido_authenticator.h b/chromium/device/fido/fido_authenticator.h index dde7bc6b4aa..a26ef40485f 100644 --- a/chromium/device/fido/fido_authenticator.h +++ b/chromium/device/fido/fido_authenticator.h @@ -9,6 +9,7 @@ #include "base/callback_forward.h" #include "base/component_export.h" +#include "base/containers/span.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/optional.h" @@ -96,11 +97,23 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator { // GetPINToken uses the given PIN to request a PinUvAuthToken from an // authenticator. It is only valid to call this method if |Options| indicates // that the authenticator supports PINs. - virtual void GetPINToken(std::string pin, GetTokenCallback callback); + // |permissions| are flags indicating which commands the token may be used + // for. + // |rp_id| binds the token to operations related to a given RP ID. |rp_id| + // must be set if |permissions| includes MakeCredential or GetAssertion. + virtual void GetPINToken(std::string pin, + const std::vector<pin::Permissions>& permissions, + base::Optional<std::string> rp_id, + GetTokenCallback callback); + // Returns |true| if the authenticator supports GetUvToken. + virtual bool CanGetUvToken(); // GetUvToken uses internal user verification to request a PinUvAuthToken from - // an authenticator. It is only valid to call this method if |Options| - // indicates that the authenticator supports UV tokens. - virtual void GetUvToken(GetTokenCallback callback); + // an authenticator. It is only valid to call this method if CanGetUvToken() + // returns true. + // |rp_id| must be set if the PinUvAuthToken will be used for MakeCredential + // or GetAssertion. + virtual void GetUvToken(base::Optional<std::string> rp_id, + GetTokenCallback callback); // SetPIN sets a new PIN on a device that does not currently have one. The // length of |pin| must respect |pin::kMinLength| and |pin::kMaxLength|. It is // only valid to call this method if |Options| indicates that the @@ -187,6 +200,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator { std::vector<uint8_t> template_id, BioEnrollmentCallback); + // GetAlgorithms returns the list of supported COSEAlgorithmIdentifiers, or + // |nullopt| if this is unknown and thus all requests should be tried in case + // they work. + virtual base::Optional<base::span<const int32_t>> GetAlgorithms(); + // Reset triggers a reset operation on the authenticator. This erases all // stored resident keys and any configured PIN. virtual void Reset(ResetCallback callback); @@ -195,6 +213,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator { virtual base::string16 GetDisplayName() const = 0; virtual ProtocolVersion SupportedProtocol() const; virtual bool SupportsCredProtectExtension() const; + virtual bool SupportsHMACSecretExtension() const; virtual const base::Optional<AuthenticatorSupportedOptions>& Options() const = 0; virtual base::Optional<FidoTransportProtocol> AuthenticatorTransport() diff --git a/chromium/device/fido/fido_constants.cc b/chromium/device/fido/fido_constants.cc index ceffcf68f7f..219cfa7c9fc 100644 --- a/chromium/device/fido/fido_constants.cc +++ b/chromium/device/fido/fido_constants.cc @@ -65,6 +65,7 @@ const char kCableClientHelloMessage[] = "caBLE v1 client hello"; const char kCtap2Version[] = "FIDO_2_0"; const char kU2fVersion[] = "U2F_V2"; +const char kCtap2_1Version[] = "FIDO_2_1"; const char kExtensionHmacSecret[] = "hmac-secret"; const char kExtensionCredProtect[] = "credProtect"; diff --git a/chromium/device/fido/fido_constants.h b/chromium/device/fido/fido_constants.h index 144d1fb6df7..ef44add20cd 100644 --- a/chromium/device/fido/fido_constants.h +++ b/chromium/device/fido/fido_constants.h @@ -63,6 +63,9 @@ constexpr size_t kAaguidLength = 16; // integer: https://www.w3.org/TR/webauthn/#sec-attested-credential-data constexpr size_t kCredentialIdLengthLength = 2; +// Length of an X9.62-encoded, uncompresed, P-256 public key. +constexpr size_t kP256X962Length = 1 /* type byte */ + 32 /* x */ + 32 /* y */; + // CTAP protocol device response code, as specified in // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#authenticator-api enum class CtapDeviceResponseCode : uint8_t { @@ -245,17 +248,28 @@ enum class CoseKeyKey : int { // Enumerates COSE key types. See // https://tools.ietf.org/html/rfc8152#section-13 enum class CoseKeyTypes : int { + kOKP = 1, kEC2 = 2, kRSA = 3, + // kInvalidForTesting is a random 32-bit number used to test unknown key + // types. + kInvalidForTesting = 146919568, }; // Enumerates COSE elliptic curves. See // https://tools.ietf.org/html/rfc8152#section-13.1 -enum class CoseCurves : int { kP256 = 1 }; +enum class CoseCurves : int { + kP256 = 1, + kEd25519 = 6, +}; enum class CoseAlgorithmIdentifier : int { - kCoseEs256 = -7, - kCoseRs256 = -257, + kEs256 = -7, + kEdDSA = -8, + kRs256 = -257, + // kInvalidForTesting is a random 32-bit number used to test unknown + // algorithms. + kInvalidForTesting = 146919568, }; // APDU instruction code for U2F request encoding. @@ -371,10 +385,21 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCableAuthenticatorHelloMessage[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCableClientHelloMessage[]; -// TODO(hongjunchoi): Add url to the official spec once it's standardized. +enum class Ctap2Version { + kUnknown = 0, + kCtap2_0 = 1, + kCtap2_1 = 2, +}; + +// Protocol version strings. +// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetInfo COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCtap2Version[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kU2fVersion[]; +// The version identifier for CTAP 2.1. +// TODO(nsatragno): link to the spec once this is standardized. +COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCtap2_1Version[]; + COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionHmacSecret[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionCredProtect[]; COMPONENT_EXPORT(DEVICE_FIDO) diff --git a/chromium/device/fido/fido_device_authenticator.cc b/chromium/device/fido/fido_device_authenticator.cc index e55a322c4f3..ee1033ef93f 100644 --- a/chromium/device/fido/fido_device_authenticator.cc +++ b/chromium/device/fido/fido_device_authenticator.cc @@ -4,6 +4,7 @@ #include "device/fido/fido_device_authenticator.h" +#include <numeric> #include <utility> #include "base/bind.h" @@ -20,9 +21,46 @@ #include "device/fido/get_assertion_task.h" #include "device/fido/make_credential_task.h" #include "device/fido/pin.h" +#include "device/fido/u2f_command_constructor.h" namespace device { +namespace { + +// Helper method for determining correct bio enrollment version. +BioEnrollmentRequest::Version GetBioEnrollmentRequestVersion( + const AuthenticatorSupportedOptions& options) { + DCHECK(options.bio_enrollment_availability_preview != + AuthenticatorSupportedOptions::BioEnrollmentAvailability:: + kNotSupported || + options.bio_enrollment_availability != + AuthenticatorSupportedOptions::BioEnrollmentAvailability:: + kNotSupported); + return options.bio_enrollment_availability != + AuthenticatorSupportedOptions::BioEnrollmentAvailability:: + kNotSupported + ? BioEnrollmentRequest::kDefault + : BioEnrollmentRequest::kPreview; +} + +CredentialManagementRequest::Version GetCredentialManagementRequestVersion( + const AuthenticatorSupportedOptions& options) { + DCHECK(options.supports_credential_management_preview || + options.supports_credential_management); + return options.supports_credential_management + ? CredentialManagementRequest::kDefault + : CredentialManagementRequest::kPreview; +} + +uint8_t PermissionsToByte(const std::vector<pin::Permissions>& permissions) { + return std::accumulate(permissions.begin(), permissions.end(), 0, + [](uint8_t byte, pin::Permissions flag) { + return byte |= static_cast<uint8_t>(flag); + }); +} + +} // namespace + FidoDeviceAuthenticator::FidoDeviceAuthenticator( std::unique_ptr<FidoDevice> device) : device_(std::move(device)) {} @@ -59,12 +97,38 @@ void FidoDeviceAuthenticator::InitializeAuthenticatorDone( void FidoDeviceAuthenticator::MakeCredential(CtapMakeCredentialRequest request, MakeCredentialCallback callback) { - RunTask<MakeCredentialTask>(std::move(request), std::move(callback)); + // If the authenticator has UV configured then UV will be required in + // order to create a credential (as specified by CTAP 2.0), even if + // user-verification is "discouraged". However, if the request is U2F-only + // then that doesn't apply and UV must be set to discouraged so that the + // request can be translated to U2F. + if (!request.pin_auth && + options_->user_verification_availability == + AuthenticatorSupportedOptions::UserVerificationAvailability:: + kSupportedAndConfigured && + !request.is_u2f_only) { + request.user_verification = UserVerificationRequirement::kRequired; + } else { + request.user_verification = UserVerificationRequirement::kDiscouraged; + } + + RunTask<MakeCredentialTask, AuthenticatorMakeCredentialResponse, + CtapMakeCredentialRequest>(std::move(request), std::move(callback)); } void FidoDeviceAuthenticator::GetAssertion(CtapGetAssertionRequest request, GetAssertionCallback callback) { - RunTask<GetAssertionTask>(std::move(request), std::move(callback)); + if (!request.pin_auth && + options_->user_verification_availability == + AuthenticatorSupportedOptions::UserVerificationAvailability:: + kSupportedAndConfigured && + request.user_verification != UserVerificationRequirement::kDiscouraged) { + request.user_verification = UserVerificationRequirement::kRequired; + } else { + request.user_verification = UserVerificationRequirement::kDiscouraged; + } + RunTask<GetAssertionTask, AuthenticatorGetAssertionResponse, + CtapGetAssertionRequest>(std::move(request), std::move(callback)); } void FidoDeviceAuthenticator::GetNextAssertion(GetAssertionCallback callback) { @@ -109,30 +173,62 @@ void FidoDeviceAuthenticator::GetPinRetries(GetRetriesCallback callback) { void FidoDeviceAuthenticator::GetEphemeralKey( GetEphemeralKeyCallback callback) { + if (cached_ephemeral_key_.has_value()) { + std::move(callback).Run(CtapDeviceResponseCode::kSuccess, + cached_ephemeral_key_); + return; + } + DCHECK(Options()); DCHECK( Options()->client_pin_availability != AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported || - Options()->supports_uv_token); + Options()->supports_pin_uv_auth_token); RunOperation<pin::KeyAgreementRequest, pin::KeyAgreementResponse>( - pin::KeyAgreementRequest(), std::move(callback), + pin::KeyAgreementRequest(), + base::BindOnce(&FidoDeviceAuthenticator::OnHaveEphemeralKey, + weak_factory_.GetWeakPtr(), std::move(callback)), base::BindOnce(&pin::KeyAgreementResponse::Parse)); } -void FidoDeviceAuthenticator::GetPINToken(std::string pin, - GetTokenCallback callback) { +void FidoDeviceAuthenticator::OnHaveEphemeralKey( + GetEphemeralKeyCallback callback, + CtapDeviceResponseCode status, + base::Optional<pin::KeyAgreementResponse> key) { + if (status != CtapDeviceResponseCode::kSuccess) { + std::move(callback).Run(status, base::nullopt); + } + DCHECK(key.has_value()); + + cached_ephemeral_key_.emplace(std::move(key.value())); + std::move(callback).Run(CtapDeviceResponseCode::kSuccess, + cached_ephemeral_key_); +} + +void FidoDeviceAuthenticator::GetPINToken( + std::string pin, + const std::vector<pin::Permissions>& permissions, + base::Optional<std::string> rp_id, + GetTokenCallback callback) { DCHECK(Options()); DCHECK(Options()->client_pin_availability != AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported); + DCHECK_NE(permissions.size(), 0u); + DCHECK(!((base::Contains(permissions, pin::Permissions::kMakeCredential)) || + base::Contains(permissions, pin::Permissions::kGetAssertion)) || + rp_id); GetEphemeralKey(base::BindOnce( &FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken, - weak_factory_.GetWeakPtr(), std::move(pin), std::move(callback))); + weak_factory_.GetWeakPtr(), std::move(pin), + PermissionsToByte(permissions), std::move(rp_id), std::move(callback))); } void FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken( std::string pin, + uint8_t permissions, + base::Optional<std::string> rp_id, GetTokenCallback callback, CtapDeviceResponseCode status, base::Optional<pin::KeyAgreementResponse> key) { @@ -140,6 +236,16 @@ void FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken( std::move(callback).Run(status, base::nullopt); return; } + + if (Options()->supports_pin_uv_auth_token) { + pin::PinTokenWithPermissionsRequest request(pin, *key, permissions, rp_id); + std::array<uint8_t, 32> shared_key = request.shared_key(); + RunOperation<pin::PinTokenWithPermissionsRequest, pin::TokenResponse>( + std::move(request), std::move(callback), + base::BindOnce(&pin::TokenResponse::Parse, std::move(shared_key))); + return; + } + pin::PinTokenRequest request(pin, *key); std::array<uint8_t, 32> shared_key = request.shared_key(); RunOperation<pin::PinTokenRequest, pin::TokenResponse>( @@ -216,7 +322,6 @@ FidoDeviceAuthenticator::WillNeedPINToMakeCredential( if (Options()->user_verification_availability == AuthenticatorSupportedOptions::UserVerificationAvailability:: kSupportedAndConfigured) { - // TODO(crbug.com/1056317): implement inline bioenrollment. return device_support == ClientPinAvailability::kSupportedAndPinSet && can_collect_pin ? MakeCredentialPINDisposition::kUsePINForFallback @@ -226,11 +331,12 @@ FidoDeviceAuthenticator::WillNeedPINToMakeCredential( // CTAP 2.0 requires a PIN for credential creation once a PIN has been set. // Thus, if fallback to U2F isn't possible, a PIN will be needed if set. - const bool supports_u2f = + const bool u2f_fallback_possible = device()->device_info() && - device()->device_info()->versions.contains(ProtocolVersion::kU2f); + device()->device_info()->versions.contains(ProtocolVersion::kU2f) && + IsConvertibleToU2fRegisterCommand(request); if (device_support == ClientPinAvailability::kSupportedAndPinSet && - !supports_u2f) { + !u2f_fallback_possible) { if (can_collect_pin) { return MakeCredentialPINDisposition::kUsePIN; } else { @@ -257,7 +363,7 @@ FidoDeviceAuthenticator::WillNeedPINToMakeCredential( // else the device supports U2F (because the alternative was handled above) // and we'll use a U2F fallback to create a credential without a PIN. DCHECK(device_support != ClientPinAvailability::kSupportedAndPinSet || - supports_u2f); + u2f_fallback_possible); // TODO(agl): perhaps CTAP2 is indicated when, for example, hmac-secret is // requested? if (request.user_verification == UserVerificationRequirement::kDiscouraged) { @@ -326,10 +432,7 @@ void FidoDeviceAuthenticator::GetCredentialsMetadata( RunOperation<CredentialManagementRequest, CredentialsMetadataResponse>( CredentialManagementRequest::ForGetCredsMetadata( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview, - pin_token), + GetCredentialManagementRequestVersion(*Options()), pin_token), std::move(callback), base::BindOnce(&CredentialsMetadataResponse::Parse)); } @@ -359,10 +462,7 @@ void FidoDeviceAuthenticator::EnumerateCredentials( state.callback = std::move(callback); RunOperation<CredentialManagementRequest, EnumerateRPsResponse>( CredentialManagementRequest::ForEnumerateRPsBegin( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview, - pin_token), + GetCredentialManagementRequestVersion(*Options()), pin_token), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateRPsDone, weak_factory_.GetWeakPtr(), std::move(state)), base::BindOnce(&EnumerateRPsResponse::Parse, /*expect_rp_count=*/true), @@ -394,9 +494,9 @@ void FidoDeviceAuthenticator::OperationClearProxy( // RunTask starts a |FidoTask| and ensures that |task_| is reset when the given // callback is called. -template <typename Task, typename Request, typename Response> +template <typename Task, typename Response, typename... RequestArgs> void FidoDeviceAuthenticator::RunTask( - Request request, + RequestArgs&&... request_args, base::OnceCallback<void(CtapDeviceResponseCode, base::Optional<Response>)> callback) { DCHECK(!task_); @@ -405,7 +505,7 @@ void FidoDeviceAuthenticator::RunTask( << "InitializeAuthenticator() must be called first."; task_ = std::make_unique<Task>( - device_.get(), std::move(request), + device_.get(), std::forward<RequestArgs>(request_args)..., base::BindOnce( &FidoDeviceAuthenticator::TaskClearProxy<CtapDeviceResponseCode, base::Optional<Response>>, @@ -459,10 +559,8 @@ void FidoDeviceAuthenticator::OnEnumerateRPsDone( state.responses.emplace_back(std::move(*response->rp)); auto request = CredentialManagementRequest::ForEnumerateCredentialsBegin( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview, - state.pin_token, std::move(*response->rp_id_hash)); + GetCredentialManagementRequestVersion(*Options()), state.pin_token, + std::move(*response->rp_id_hash)); RunOperation<CredentialManagementRequest, EnumerateCredentialsResponse>( std::move(request), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateCredentialsDone, @@ -490,9 +588,7 @@ void FidoDeviceAuthenticator::OnEnumerateCredentialsDone( state.current_rp_credential_count) { RunOperation<CredentialManagementRequest, EnumerateCredentialsResponse>( CredentialManagementRequest::ForEnumerateCredentialsGetNext( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview), + GetCredentialManagementRequestVersion(*Options())), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateCredentialsDone, weak_factory_.GetWeakPtr(), std::move(state)), base::BindOnce(&EnumerateCredentialsResponse::Parse, @@ -504,9 +600,7 @@ void FidoDeviceAuthenticator::OnEnumerateCredentialsDone( if (state.responses.size() < state.rp_count) { RunOperation<CredentialManagementRequest, EnumerateRPsResponse>( CredentialManagementRequest::ForEnumerateRPsGetNext( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview), + GetCredentialManagementRequestVersion(*Options())), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateRPsDone, weak_factory_.GetWeakPtr(), std::move(state)), base::BindOnce(&EnumerateRPsResponse::Parse, @@ -529,24 +623,12 @@ void FidoDeviceAuthenticator::DeleteCredential( RunOperation<CredentialManagementRequest, DeleteCredentialResponse>( CredentialManagementRequest::ForDeleteCredential( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview, - pin_token, credential_id), + GetCredentialManagementRequestVersion(*Options()), pin_token, + credential_id), std::move(callback), base::BindOnce(&DeleteCredentialResponse::Parse), /*string_fixup_predicate=*/nullptr); } -// Helper method for determining correct bio enrollment version. -static BioEnrollmentRequest::Version GetBioEnrollmentRequestVersion( - const AuthenticatorSupportedOptions& options) { - return options.bio_enrollment_availability != - AuthenticatorSupportedOptions::BioEnrollmentAvailability:: - kNotSupported - ? BioEnrollmentRequest::kDefault - : BioEnrollmentRequest::kPreview; -} - void FidoDeviceAuthenticator::GetModality(BioEnrollmentCallback callback) { RunOperation<BioEnrollmentRequest, BioEnrollmentResponse>( BioEnrollmentRequest::ForGetModality( @@ -614,6 +696,22 @@ void FidoDeviceAuthenticator::BioEnrollEnumerate( std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse)); } +base::Optional<base::span<const int32_t>> +FidoDeviceAuthenticator::GetAlgorithms() { + if (device_->supported_protocol() == ProtocolVersion::kU2f) { + static constexpr int32_t kU2fAlgorithms[1] = { + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256)}; + return kU2fAlgorithms; + } + + const base::Optional<AuthenticatorGetInfoResponse>& get_info_response = + device_->device_info(); + if (get_info_response) { + return get_info_response->algorithms; + } + return base::nullopt; +} + void FidoDeviceAuthenticator::Reset(ResetCallback callback) { DCHECK(device_->SupportedProtocolIsInitialized()) << "InitializeAuthenticator() must be called first."; @@ -645,6 +743,13 @@ ProtocolVersion FidoDeviceAuthenticator::SupportedProtocol() const { return device_->supported_protocol(); } +bool FidoDeviceAuthenticator::SupportsHMACSecretExtension() const { + const base::Optional<AuthenticatorGetInfoResponse>& get_info_response = + device_->device_info(); + return get_info_response && get_info_response->extensions && + base::Contains(*get_info_response->extensions, kExtensionHmacSecret); +} + const base::Optional<AuthenticatorSupportedOptions>& FidoDeviceAuthenticator::Options() const { return options_; @@ -695,13 +800,22 @@ void FidoDeviceAuthenticator::GetUvRetries(GetRetriesCallback callback) { base::BindOnce(&pin::RetriesResponse::ParseUvRetries)); } -void FidoDeviceAuthenticator::GetUvToken(GetTokenCallback callback) { - GetEphemeralKey( - base::BindOnce(&FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken, - weak_factory_.GetWeakPtr(), std::move(callback))); +bool FidoDeviceAuthenticator::CanGetUvToken() { + return options_->user_verification_availability == + AuthenticatorSupportedOptions::UserVerificationAvailability:: + kSupportedAndConfigured && + options_->supports_pin_uv_auth_token; +} + +void FidoDeviceAuthenticator::GetUvToken(base::Optional<std::string> rp_id, + GetTokenCallback callback) { + GetEphemeralKey(base::BindOnce( + &FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken, + weak_factory_.GetWeakPtr(), std::move(rp_id), std::move(callback))); } void FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken( + base::Optional<std::string> rp_id, GetTokenCallback callback, CtapDeviceResponseCode status, base::Optional<pin::KeyAgreementResponse> key) { @@ -712,7 +826,7 @@ void FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken( DCHECK(key); - pin::UvTokenRequest request(*key); + pin::UvTokenRequest request(*key, std::move(rp_id)); std::array<uint8_t, 32> shared_key = request.shared_key(); RunOperation<pin::UvTokenRequest, pin::TokenResponse>( std::move(request), std::move(callback), diff --git a/chromium/device/fido/fido_device_authenticator.h b/chromium/device/fido/fido_device_authenticator.h index 032b9301320..f1fb3a075fc 100644 --- a/chromium/device/fido/fido_device_authenticator.h +++ b/chromium/device/fido/fido_device_authenticator.h @@ -33,7 +33,7 @@ class FidoTask; class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator : public FidoAuthenticator { public: - FidoDeviceAuthenticator(std::unique_ptr<FidoDevice> device); + explicit FidoDeviceAuthenticator(std::unique_ptr<FidoDevice> device); ~FidoDeviceAuthenticator() override; // FidoAuthenticator: @@ -45,9 +45,14 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator void GetNextAssertion(GetAssertionCallback callback) override; void GetTouch(base::OnceCallback<void()> callback) override; void GetPinRetries(GetRetriesCallback callback) override; - void GetPINToken(std::string pin, GetTokenCallback callback) override; + void GetPINToken(std::string pin, + const std::vector<pin::Permissions>& permissions, + base::Optional<std::string> rp_id, + GetTokenCallback callback) override; void GetUvRetries(GetRetriesCallback callback) override; - void GetUvToken(GetTokenCallback callback) override; + bool CanGetUvToken() override; + void GetUvToken(base::Optional<std::string> rp_id, + GetTokenCallback callback) override; void SetPIN(const std::string& pin, SetPINCallback callback) override; void ChangePIN(const std::string& old_pin, @@ -87,11 +92,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator std::vector<uint8_t> template_id, BioEnrollmentCallback) override; + base::Optional<base::span<const int32_t>> GetAlgorithms() override; void Reset(ResetCallback callback) override; void Cancel() override; std::string GetId() const override; base::string16 GetDisplayName() const override; ProtocolVersion SupportedProtocol() const override; + bool SupportsHMACSecretExtension() const override; const base::Optional<AuthenticatorSupportedOptions>& Options() const override; base::Optional<FidoTransportProtocol> AuthenticatorTransport() const override; bool IsInPairingMode() const override; @@ -122,8 +129,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator base::Optional<pin::KeyAgreementResponse>)>; void InitializeAuthenticatorDone(base::OnceClosure callback); void GetEphemeralKey(GetEphemeralKeyCallback callback); + void OnHaveEphemeralKey(GetEphemeralKeyCallback callback, + CtapDeviceResponseCode status, + base::Optional<pin::KeyAgreementResponse> key); void OnHaveEphemeralKeyForGetPINToken( std::string pin, + uint8_t permissions, + base::Optional<std::string> rp_id, GetTokenCallback callback, CtapDeviceResponseCode status, base::Optional<pin::KeyAgreementResponse> key); @@ -139,6 +151,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator CtapDeviceResponseCode status, base::Optional<pin::KeyAgreementResponse> key); void OnHaveEphemeralKeyForUvToken( + base::Optional<std::string> rp_id, GetTokenCallback callback, CtapDeviceResponseCode status, base::Optional<pin::KeyAgreementResponse> key); @@ -148,8 +161,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator template <typename... Args> void OperationClearProxy(base::OnceCallback<void(Args...)> callback, Args... args); - template <typename Task, typename Request, typename Response> - void RunTask(Request request, + template <typename Task, typename Response, typename... RequestArgs> + void RunTask(RequestArgs&&... request_args, base::OnceCallback<void(CtapDeviceResponseCode, base::Optional<Response>)> callback); template <typename Request, typename Response> @@ -174,6 +187,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator base::Optional<AuthenticatorSupportedOptions> options_; std::unique_ptr<FidoTask> task_; std::unique_ptr<GenericDeviceOperation> operation_; + base::Optional<pin::KeyAgreementResponse> cached_ephemeral_key_; base::WeakPtrFactory<FidoDeviceAuthenticator> weak_factory_{this}; DISALLOW_COPY_AND_ASSIGN(FidoDeviceAuthenticator); diff --git a/chromium/device/fido/fido_device_discovery.h b/chromium/device/fido/fido_device_discovery.h index 347c665abd0..f935f6fef12 100644 --- a/chromium/device/fido/fido_device_discovery.h +++ b/chromium/device/fido/fido_device_discovery.h @@ -13,7 +13,6 @@ #include <vector> #include "base/component_export.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/strings/string_piece.h" diff --git a/chromium/device/fido/fido_discovery_base.h b/chromium/device/fido/fido_discovery_base.h index 649468150a8..8a29e3a9da5 100644 --- a/chromium/device/fido/fido_discovery_base.h +++ b/chromium/device/fido/fido_discovery_base.h @@ -7,8 +7,10 @@ #include <vector> +#include <ostream> + +#include "base/check.h" #include "base/component_export.h" -#include "base/logging.h" #include "base/macros.h" #include "device/fido/fido_transport_protocol.h" diff --git a/chromium/device/fido/fido_discovery_factory.cc b/chromium/device/fido/fido_discovery_factory.cc index a2e4e418e1f..539b0cb7693 100644 --- a/chromium/device/fido/fido_discovery_factory.cc +++ b/chromium/device/fido/fido_discovery_factory.cc @@ -4,8 +4,8 @@ #include "device/fido/fido_discovery_factory.h" -#include "base/logging.h" #include "base/notreached.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/fido/cable/fido_cable_discovery.h" #include "device/fido/features.h" #include "device/fido/fido_discovery_base.h" @@ -55,7 +55,8 @@ std::unique_ptr<FidoDiscoveryBase> FidoDiscoveryFactory::Create( case FidoTransportProtocol::kBluetoothLowEnergy: return nullptr; case FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy: - if (cable_data_.has_value() || qr_generator_key_.has_value()) { + if (device::BluetoothAdapterFactory::Get()->IsLowEnergySupported() && + (cable_data_.has_value() || qr_generator_key_.has_value())) { return std::make_unique<FidoCableDiscovery>( cable_data_.value_or(std::vector<CableDiscoveryData>()), qr_generator_key_, cable_pairing_callback_); diff --git a/chromium/device/fido/fido_request_handler_base.cc b/chromium/device/fido/fido_request_handler_base.cc index 896cecd966a..a7f687605d5 100644 --- a/chromium/device/fido/fido_request_handler_base.cc +++ b/chromium/device/fido/fido_request_handler_base.cc @@ -13,9 +13,9 @@ #include "base/strings/string_piece.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/time/time.h" -#include "base/timer/elapsed_timer.h" #include "build/build_config.h" #include "components/device_event_log/device_event_log.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/fido/ble_adapter_manager.h" #include "device/fido/fido_authenticator.h" #include "device/fido/fido_discovery_factory.h" @@ -24,23 +24,8 @@ #include "device/fido/win/authenticator.h" #endif -namespace { -// Authenticators that return a response in less than this time are likely to -// have done so without interaction from the user. -static const base::TimeDelta kMinExpectedAuthenticatorResponseTime = - base::TimeDelta::FromMilliseconds(300); -} // namespace - namespace device { -// FidoRequestHandlerBase::AuthenticatorState --------------------------------- - -FidoRequestHandlerBase::AuthenticatorState::AuthenticatorState( - FidoAuthenticator* authenticator) - : authenticator(authenticator) {} - -FidoRequestHandlerBase::AuthenticatorState::~AuthenticatorState() = default; - // FidoRequestHandlerBase::TransportAvailabilityInfo -------------------------- FidoRequestHandlerBase::TransportAvailabilityInfo::TransportAvailabilityInfo() = @@ -91,8 +76,14 @@ void FidoRequestHandlerBase::InitDiscoveries( discoveries_.push_back(std::move(discovery)); } + // Check if the platform supports BLE before trying to get a power manager. + // CaBLE might be in |available_transports| without actual BLE support under + // the virtual environment. + // TODO(nsatragno): Move the BLE power manager logic to CableDiscoveryFactory + // so we don't need this additional check. bool has_ble = false; - if (base::Contains(transport_availability_info_.available_transports, + if (device::BluetoothAdapterFactory::Get()->IsLowEnergySupported() && + base::Contains(transport_availability_info_.available_transports, FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy)) { has_ble = true; base::SequencedTaskRunnerHandle::Get()->PostTask( @@ -176,7 +167,7 @@ void FidoRequestHandlerBase::CancelActiveAuthenticators( DCHECK(!task_it->first.empty()); if (task_it->first != exclude_device_id) { DCHECK(task_it->second); - task_it->second->authenticator->Cancel(); + task_it->second->Cancel(); // Note that the pointer being erased is non-owning. The actual // FidoAuthenticator instance is owned by its discovery (which in turn is @@ -227,20 +218,6 @@ void FidoRequestHandlerBase::Start() { discovery->Start(); } -bool FidoRequestHandlerBase::AuthenticatorMayHaveReturnedImmediately( - const std::string& authenticator_id) { - auto it = active_authenticators_.find(authenticator_id); - if (it == active_authenticators_.end()) - return false; - - if (!it->second->timer) - return true; - - FIDO_LOG(DEBUG) << "Authenticator returned in " - << it->second->timer->Elapsed(); - return it->second->timer->Elapsed() < kMinExpectedAuthenticatorResponseTime; -} - void FidoRequestHandlerBase::AuthenticatorRemoved( FidoDiscoveryBase* discovery, FidoAuthenticator* authenticator) { @@ -249,11 +226,18 @@ void FidoRequestHandlerBase::AuthenticatorRemoved( // ongoing_tasks_.erase() will have no effect for the devices that have been // already removed due to processing error or due to invocation of // CancelOngoingTasks(). - DCHECK(authenticator); - active_authenticators_.erase(authenticator->GetId()); - - if (observer_) + auto authenticator_it = active_authenticators_.find(authenticator->GetId()); + if (authenticator_it == active_authenticators_.end()) { + NOTREACHED(); + FIDO_LOG(ERROR) << "AuthenticatorRemoved() for unknown authenticator " + << authenticator->GetId(); + return; + } + DCHECK_EQ(authenticator_it->second, authenticator); + active_authenticators_.erase(authenticator_it); + if (observer_) { observer_->FidoAuthenticatorRemoved(authenticator->GetId()); + } } void FidoRequestHandlerBase::DiscoveryStarted( @@ -277,9 +261,8 @@ void FidoRequestHandlerBase::AuthenticatorAdded( FidoAuthenticator* authenticator) { DCHECK(!authenticator->GetId().empty()); bool was_inserted; - std::tie(std::ignore, was_inserted) = active_authenticators_.insert( - {authenticator->GetId(), - std::make_unique<AuthenticatorState>(authenticator)}); + std::tie(std::ignore, was_inserted) = + active_authenticators_.insert({authenticator->GetId(), authenticator}); if (!was_inserted) { NOTREACHED(); FIDO_LOG(ERROR) << "Authenticator with duplicate ID " @@ -341,11 +324,10 @@ void FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest( if (authenticator_it == active_authenticators_.end()) { return; } - AuthenticatorState* authenticator_state = authenticator_it->second.get(); - authenticator_state->timer = std::make_unique<base::ElapsedTimer>(); - authenticator_state->authenticator->InitializeAuthenticator(base::BindOnce( - &FidoRequestHandlerBase::DispatchRequest, weak_factory_.GetWeakPtr(), - authenticator_state->authenticator)); + FidoAuthenticator* authenticator = authenticator_it->second; + authenticator->InitializeAuthenticator( + base::BindOnce(&FidoRequestHandlerBase::DispatchRequest, + weak_factory_.GetWeakPtr(), authenticator)); } void FidoRequestHandlerBase::ConstructBleAdapterPowerManager() { @@ -358,4 +340,7 @@ void FidoRequestHandlerBase::StopDiscoveries() { } } +constexpr base::TimeDelta + FidoRequestHandlerBase::kMinExpectedAuthenticatorResponseTime; + } // namespace device diff --git a/chromium/device/fido/fido_request_handler_base.h b/chromium/device/fido/fido_request_handler_base.h index 55b06436450..11c2eaea9ed 100644 --- a/chromium/device/fido/fido_request_handler_base.h +++ b/chromium/device/fido/fido_request_handler_base.h @@ -14,9 +14,9 @@ #include <vector> #include "base/callback.h" +#include "base/check.h" #include "base/component_export.h" #include "base/containers/flat_set.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/optional.h" @@ -25,10 +25,6 @@ #include "device/fido/fido_discovery_base.h" #include "device/fido/fido_transport_protocol.h" -namespace base { -class ElapsedTimer; -} - namespace device { class BleAdapterManager; @@ -52,16 +48,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase enum class RequestType { kMakeCredential, kGetAssertion }; - struct AuthenticatorState { - explicit AuthenticatorState(FidoAuthenticator* authenticator); - ~AuthenticatorState(); - - FidoAuthenticator* authenticator; - std::unique_ptr<base::ElapsedTimer> timer; - }; - using AuthenticatorMap = - std::map<std::string, std::unique_ptr<AuthenticatorState>, std::less<>>; + std::map<std::string, FidoAuthenticator*, std::less<>>; // Encapsulates data required to initiate WebAuthN UX dialog. Once all // components of TransportAvailabilityInfo is set, @@ -240,6 +228,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase void StopDiscoveries(); protected: + // Authenticators that return a response in less than this time are likely to + // have done so without interaction from the user. + static constexpr base::TimeDelta kMinExpectedAuthenticatorResponseTime = + base::TimeDelta::FromMilliseconds(300); + // Subclasses implement this method to dispatch their request onto the given // FidoAuthenticator. The FidoAuthenticator is owned by this // FidoRequestHandler and stored in active_authenticators(). @@ -247,13 +240,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase void Start(); - // Returns |true| if a short enough time has elapsed since the request was - // dispatched that an authenticator may be suspected to have returned a - // response without user interaction. - // Must be called after |DispatchRequest| is called. - bool AuthenticatorMayHaveReturnedImmediately( - const std::string& authenticator_id); - AuthenticatorMap& active_authenticators() { return active_authenticators_; } std::vector<std::unique_ptr<FidoDiscoveryBase>>& discoveries() { return discoveries_; diff --git a/chromium/device/fido/fido_test_data.h b/chromium/device/fido/fido_test_data.h index 840e815983c..71f73e09f30 100644 --- a/chromium/device/fido/fido_test_data.h +++ b/chromium/device/fido/fido_test_data.h @@ -1431,46 +1431,82 @@ constexpr uint8_t kTestMakeCredentialResponseWithIncorrectRpIdHash[] = { // Credential ID to be used in a request to yield the below // kTestGetAssertionResponse. -constexpr uint8_t kTestGetAssertionCredentialId[] = { - 0x9C, 0x06, 0x98, 0x05, 0xA7, 0xE9, 0x0C, 0xED, 0xF9, 0x24, 0xAC, - 0x5A, 0x29, 0x36, 0x95, 0xE0, 0x15, 0x46, 0x95, 0xBF, 0xFF, 0x99, - 0x1A, 0xA5, 0x40, 0xA8, 0x84, 0xAE, 0xF5, 0x42, 0xF3, 0x17, 0x78, - 0x51, 0xBE, 0x8A, 0x15, 0x2D, 0x48, 0x45, 0x2C, 0x0F, 0xE4, 0x67, - 0x29, 0x0C, 0x1B, 0xDA, 0xBE, 0x7C, 0xEB, 0xE5, 0xAD, 0x7A, 0xCA, - 0x6F, 0x76, 0x89, 0x38, 0x83, 0x2E, 0x65, 0x85, 0x1E, +constexpr uint8_t kTestGetAssertionCredentialId[64] = { + 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, }; constexpr uint8_t kTestGetAssertionResponse[] = { - 0x00, 0xA3, 0x01, 0xA2, 0x62, 0x69, 0x64, 0x58, 0x40, 0x9C, 0x06, 0x98, - 0x05, 0xA7, 0xE9, 0x0C, 0xED, 0xF9, 0x24, 0xAC, 0x5A, 0x29, 0x36, 0x95, - 0xE0, 0x15, 0x46, 0x95, 0xBF, 0xFF, 0x99, 0x1A, 0xA5, 0x40, 0xA8, 0x84, - 0xAE, 0xF5, 0x42, 0xF3, 0x17, 0x78, 0x51, 0xBE, 0x8A, 0x15, 0x2D, 0x48, - 0x45, 0x2C, 0x0F, 0xE4, 0x67, 0x29, 0x0C, 0x1B, 0xDA, 0xBE, 0x7C, 0xEB, - 0xE5, 0xAD, 0x7A, 0xCA, 0x6F, 0x76, 0x89, 0x38, 0x83, 0x2E, 0x65, 0x85, - 0x1E, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, - 0x63, 0x2D, 0x6B, 0x65, 0x79, 0x02, 0x58, 0x25, 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, 0x01, 0x00, 0x00, 0x00, 0x5F, 0x03, 0x58, 0x46, + // clang-format off + // See https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion#:~:text=the%20credential%20identifier%20whose%20private%20key + + // success + 0x00, + // map(3) + 0xA3, + // unsigned(1) -- credential + 0x01, + // map(2) + 0xA2, + // text(2) + 0x62, + // "id" + 0x69, 0x64, + // bytes(64) + 0x58, 0x40, + // credential ID (i.e. |kTestGetAssertionCredentialId|) + 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, + // text(4) + 0x64, + // "type" + 0x74, 0x79, 0x70, 0x65, + // text(10) + 0x6A, + // "public-key" + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, + // unsigned(2) -- authData + 0x02, + // bytes(37) + 0x58, 0x25, + // authData + 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, 0x01, 0x00, 0x00, 0x00, + 0x5F, + // unsigned(3) -- signature + 0x03, + // bytes(70) + 0x58, 0x46, + // signature 0x30, 0x44, 0x02, 0x20, 0x62, 0xB8, 0xC4, 0x37, 0xB0, 0xB6, 0xFC, 0x89, 0x37, 0xF6, 0x45, 0xC0, 0x1E, 0x26, 0xCE, 0x0E, 0x26, 0x58, 0x38, 0xFE, 0xC4, 0xA8, 0x74, 0xC5, 0x5D, 0xDD, 0x6D, 0xEC, 0xF0, 0xA0, 0x83, 0xD3, 0x02, 0x20, 0x7C, 0xD4, 0x1C, 0xAF, 0x4F, 0xD8, 0x7F, 0x73, 0xBF, 0x01, 0x25, 0x06, 0x78, 0x11, 0x45, 0x2B, 0x5F, 0xB8, 0x17, 0xA3, 0xFA, 0x73, 0xB2, 0x17, 0x6B, 0xBD, 0x30, 0x36, 0x59, 0xC9, 0xCD, 0x92, + // clang-format on }; // {1: {"id": h'010203', "type": "public-key"}, 2: // h'1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE010000005F', // 3: h'101112', 4: {"id": h'01020304', "name": "..."}} constexpr uint8_t kTestGetAssertionResponseWithTruncatedUTF8[] = { - 0x00, 0xA4, 0x01, 0xA2, 0x62, 0x69, 0x64, 0x58, 0x40, 0x9C, 0x06, 0x98, - 0x05, 0xA7, 0xE9, 0x0C, 0xED, 0xF9, 0x24, 0xAC, 0x5A, 0x29, 0x36, 0x95, - 0xE0, 0x15, 0x46, 0x95, 0xBF, 0xFF, 0x99, 0x1A, 0xA5, 0x40, 0xA8, 0x84, - 0xAE, 0xF5, 0x42, 0xF3, 0x17, 0x78, 0x51, 0xBE, 0x8A, 0x15, 0x2D, 0x48, - 0x45, 0x2C, 0x0F, 0xE4, 0x67, 0x29, 0x0C, 0x1B, 0xDA, 0xBE, 0x7C, 0xEB, - 0xE5, 0xAD, 0x7A, 0xCA, 0x6F, 0x76, 0x89, 0x38, 0x83, 0x2E, 0x65, 0x85, - 0x1E, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, + 0x00, 0xA4, 0x01, 0xA2, 0x62, 0x69, 0x64, 0x58, 0x40, 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, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, 0x02, 0x58, 0x25, 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, @@ -1488,13 +1524,13 @@ constexpr uint8_t kTestGetAssertionResponseWithTruncatedUTF8[] = { // h'1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE010000005F', // 3: h'101112', 4: {"id": h'01020304', "name": "..."}} constexpr uint8_t kTestGetAssertionResponseWithTruncatedAndInvalidUTF8[] = { - 0x00, 0xA4, 0x01, 0xA2, 0x62, 0x69, 0x64, 0x58, 0x40, 0x9C, 0x06, 0x98, - 0x05, 0xA7, 0xE9, 0x0C, 0xED, 0xF9, 0x24, 0xAC, 0x5A, 0x29, 0x36, 0x95, - 0xE0, 0x15, 0x46, 0x95, 0xBF, 0xFF, 0x99, 0x1A, 0xA5, 0x40, 0xA8, 0x84, - 0xAE, 0xF5, 0x42, 0xF3, 0x17, 0x78, 0x51, 0xBE, 0x8A, 0x15, 0x2D, 0x48, - 0x45, 0x2C, 0x0F, 0xE4, 0x67, 0x29, 0x0C, 0x1B, 0xDA, 0xBE, 0x7C, 0xEB, - 0xE5, 0xAD, 0x7A, 0xCA, 0x6F, 0x76, 0x89, 0x38, 0x83, 0x2E, 0x65, 0x85, - 0x1E, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, + 0x00, 0xA4, 0x01, 0xA2, 0x62, 0x69, 0x64, 0x58, 0x40, 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, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, 0x02, 0x58, 0x25, 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, @@ -1523,13 +1559,13 @@ constexpr uint8_t kTestGetAssertionResponseWithEmptyCredential[] = { constexpr uint8_t kTestGetAssertionResponseWithIncorrectRpIdHash[] = { // clang-format off - 0x00, 0xA3, 0x01, 0xA2, 0x62, 0x69, 0x64, 0x58, 0x40, 0x9C, 0x06, 0x98, - 0x05, 0xA7, 0xE9, 0x0C, 0xED, 0xF9, 0x24, 0xAC, 0x5A, 0x29, 0x36, 0x95, - 0xE0, 0x15, 0x46, 0x95, 0xBF, 0xFF, 0x99, 0x1A, 0xA5, 0x40, 0xA8, 0x84, - 0xAE, 0xF5, 0x42, 0xF3, 0x17, 0x78, 0x51, 0xBE, 0x8A, 0x15, 0x2D, 0x48, - 0x45, 0x2C, 0x0F, 0xE4, 0x67, 0x29, 0x0C, 0x1B, 0xDA, 0xBE, 0x7C, 0xEB, - 0xE5, 0xAD, 0x7A, 0xCA, 0x6F, 0x76, 0x89, 0x38, 0x83, 0x2E, 0x65, 0x85, - 0x1E, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, + 0x00, 0xA3, 0x01, 0xA2, 0x62, 0x69, 0x64, 0x58, 0x40, 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, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, 0x02, 0x58, 0x25, // Incorrect relying party ID hash 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, diff --git a/chromium/device/fido/get_assertion_handler_unittest.cc b/chromium/device/fido/get_assertion_handler_unittest.cc index c635a62311a..c516ddc55e0 100644 --- a/chromium/device/fido/get_assertion_handler_unittest.cc +++ b/chromium/device/fido/get_assertion_handler_unittest.cc @@ -57,6 +57,9 @@ class FidoGetAssertionHandlerTest : public ::testing::Test { FidoGetAssertionHandlerTest() { mock_adapter_ = base::MakeRefCounted<::testing::NiceMock<MockBluetoothAdapter>>(); + bluetooth_config_ = + BluetoothAdapterFactory::Get()->InitGlobalValuesForTesting(); + bluetooth_config_->SetLESupported(true); BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_); } @@ -176,6 +179,8 @@ class FidoGetAssertionHandlerTest : public ::testing::Test { FidoTransportProtocol::kInternal, FidoTransportProtocol::kNearFieldCommunication, FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy}; + std::unique_ptr<BluetoothAdapterFactory::GlobalValuesForTesting> + bluetooth_config_; }; TEST_F(FidoGetAssertionHandlerTest, TransportAvailabilityInfo) { @@ -805,7 +810,7 @@ TEST(GetAssertionRequestHandlerWinTest, TestWinUsbDiscovery) { EXPECT_EQ(handler->AuthenticatorsForTesting().size(), 1u); EXPECT_EQ(handler->AuthenticatorsForTesting() .begin() - ->second->authenticator->IsWinNativeApiAuthenticator(), + ->second->IsWinNativeApiAuthenticator(), enable_api); } } diff --git a/chromium/device/fido/get_assertion_request_handler.cc b/chromium/device/fido/get_assertion_request_handler.cc index d231ba7c1a3..a7e9eb84208 100644 --- a/chromium/device/fido/get_assertion_request_handler.cc +++ b/chromium/device/fido/get_assertion_request_handler.cc @@ -13,6 +13,7 @@ #include "base/bind.h" #include "base/metrics/histogram_functions.h" #include "base/stl_util.h" +#include "base/timer/elapsed_timer.h" #include "build/build_config.h" #include "components/cbor/diagnostic_writer.h" #include "components/device_event_log/device_event_log.h" @@ -333,28 +334,20 @@ void GetAssertionRequestHandler::DispatchRequest( } CtapGetAssertionRequest request(request_); - if (authenticator->Options()) { - if (authenticator->Options()->user_verification_availability == - AuthenticatorSupportedOptions::UserVerificationAvailability:: - kSupportedAndConfigured && - request_.user_verification != - UserVerificationRequirement::kDiscouraged) { - if (authenticator->Options()->supports_uv_token) { - FIDO_LOG(DEBUG) << "Getting UV token from " - << authenticator->GetDisplayName(); - authenticator->GetUvToken( - base::BindOnce(&GetAssertionRequestHandler::OnHaveUvToken, - weak_factory_.GetWeakPtr(), authenticator)); - return; - } - request.user_verification = UserVerificationRequirement::kRequired; - } else { - request.user_verification = UserVerificationRequirement::kDiscouraged; - } - if (android_client_data_ext_ && authenticator->Options() && - authenticator->Options()->supports_android_client_data_ext) { - request.android_client_data_ext = *android_client_data_ext_; - } + if (request.user_verification != UserVerificationRequirement::kDiscouraged && + authenticator->CanGetUvToken()) { + FIDO_LOG(DEBUG) << "Getting UV token from " + << authenticator->GetDisplayName(); + authenticator->GetUvToken( + request_.rp_id, + base::BindOnce(&GetAssertionRequestHandler::OnHaveUvToken, + weak_factory_.GetWeakPtr(), authenticator)); + return; + } + + if (android_client_data_ext_ && authenticator->Options() && + authenticator->Options()->supports_android_client_data_ext) { + request.android_client_data_ext = *android_client_data_ext_; } ReportGetAssertionRequestTransport(authenticator); @@ -364,7 +357,8 @@ void GetAssertionRequestHandler::DispatchRequest( authenticator->GetAssertion( std::move(request), base::BindOnce(&GetAssertionRequestHandler::HandleResponse, - weak_factory_.GetWeakPtr(), authenticator)); + weak_factory_.GetWeakPtr(), authenticator, + base::ElapsedTimer())); } void GetAssertionRequestHandler::AuthenticatorAdded( @@ -407,6 +401,7 @@ void GetAssertionRequestHandler::AuthenticatorRemoved( void GetAssertionRequestHandler::HandleResponse( FidoAuthenticator* authenticator, + base::ElapsedTimer request_timer, CtapDeviceResponseCode status, base::Optional<AuthenticatorGetAssertionResponse> response) { DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); @@ -457,9 +452,12 @@ void GetAssertionRequestHandler::HandleResponse( status == CtapDeviceResponseCode::kCtap2ErrOperationDenied) && authenticator->WillNeedPINToGetAssertion(request_, observer()) == PINDisposition::kUsePINForFallback) { - // Some authenticators will return this error immediately without user - // interaction when internal UV is locked. - if (AuthenticatorMayHaveReturnedImmediately(authenticator->GetId())) { + // Authenticators without uvToken support will return this error immediately + // without user interaction when internal UV is locked. + const base::TimeDelta response_time = request_timer.Elapsed(); + if (response_time < kMinExpectedAuthenticatorResponseTime) { + FIDO_LOG(DEBUG) << "Authenticator is probably locked, response_time=" + << response_time; authenticator->GetTouch(base::BindOnce( &GetAssertionRequestHandler::StartPINFallbackForInternalUv, weak_factory_.GetWeakPtr(), authenticator)); @@ -655,7 +653,7 @@ void GetAssertionRequestHandler::OnHavePIN(std::string pin) { state_ = State::kRequestWithPIN; authenticator_->GetPINToken( - std::move(pin), + std::move(pin), {pin::Permissions::kGetAssertion}, request_.rp_id, base::BindOnce(&GetAssertionRequestHandler::OnHavePINToken, weak_factory_.GetWeakPtr())); } @@ -721,6 +719,7 @@ void GetAssertionRequestHandler::OnUvRetriesResponse( } observer()->OnRetryUserVerification(response->retries); authenticator_->GetUvToken( + request_.rp_id, base::BindOnce(&GetAssertionRequestHandler::OnHaveUvToken, weak_factory_.GetWeakPtr(), authenticator_)); } @@ -788,8 +787,6 @@ void GetAssertionRequestHandler::DispatchRequestWithToken( CtapGetAssertionRequest request(request_); request.pin_auth = token.PinAuth(request.client_data_hash); request.pin_protocol = pin::kProtocolVersion; - // Do not do internal UV again. - request.user_verification = UserVerificationRequirement::kDiscouraged; if (android_client_data_ext_ && authenticator_->Options() && authenticator_->Options()->supports_android_client_data_ext) { @@ -801,7 +798,8 @@ void GetAssertionRequestHandler::DispatchRequestWithToken( authenticator_->GetAssertion( std::move(request), base::BindOnce(&GetAssertionRequestHandler::HandleResponse, - weak_factory_.GetWeakPtr(), authenticator_)); + weak_factory_.GetWeakPtr(), authenticator_, + base::ElapsedTimer())); } } // namespace device diff --git a/chromium/device/fido/get_assertion_request_handler.h b/chromium/device/fido/get_assertion_request_handler.h index d9077d5a37c..1b063ce2f48 100644 --- a/chromium/device/fido/get_assertion_request_handler.h +++ b/chromium/device/fido/get_assertion_request_handler.h @@ -19,6 +19,10 @@ #include "device/fido/fido_request_handler_base.h" #include "device/fido/fido_transport_protocol.h" +namespace base { +class ElapsedTimer; +} + namespace device { class FidoAuthenticator; @@ -86,6 +90,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler void HandleResponse( FidoAuthenticator* authenticator, + base::ElapsedTimer request_timer, CtapDeviceResponseCode response_code, base::Optional<AuthenticatorGetAssertionResponse> response); void HandleNextResponse( diff --git a/chromium/device/fido/get_assertion_task.cc b/chromium/device/fido/get_assertion_task.cc index cef806ca853..16d88c8a5a4 100644 --- a/chromium/device/fido/get_assertion_task.cc +++ b/chromium/device/fido/get_assertion_task.cc @@ -61,11 +61,15 @@ void GetAssertionTask::Cancel() { // static bool GetAssertionTask::StringFixupPredicate( const std::vector<const cbor::Value*>& path) { + // This filters out all elements that are not string-keyed, direct children + // of key 0x04, which is the `user` element of a getAssertion response. if (path.size() != 2 || !path[0]->is_unsigned() || path[0]->GetUnsigned() != 4 || !path[1]->is_string()) { return false; } + // Of those string-keyed children, only `name` and `displayName` may have + // truncated UTF-8 in their values. const std::string& user_key = path[1]->GetString(); return user_key == "name" || user_key == "displayName"; } diff --git a/chromium/device/fido/mac/fake_keychain.mm b/chromium/device/fido/mac/fake_keychain.mm index 1230468a89d..7d6892f2579 100644 --- a/chromium/device/fido/mac/fake_keychain.mm +++ b/chromium/device/fido/mac/fake_keychain.mm @@ -6,6 +6,7 @@ #include "device/fido/mac/fake_keychain.h" +#include "base/notreached.h" #include "device/fido/mac/keychain.h" namespace device { diff --git a/chromium/device/fido/mac/make_credential_operation.mm b/chromium/device/fido/mac/make_credential_operation.mm index 71ee5d4fc3f..fc27e4413fa 100644 --- a/chromium/device/fido/mac/make_credential_operation.mm +++ b/chromium/device/fido/mac/make_credential_operation.mm @@ -48,7 +48,7 @@ void MakeCredentialOperation::Run() { auto is_es256 = [](const PublicKeyCredentialParams::CredentialInfo& cred_info) { return cred_info.algorithm == - static_cast<int>(CoseAlgorithmIdentifier::kCoseEs256); + static_cast<int>(CoseAlgorithmIdentifier::kEs256); }; const auto& key_params = request_.public_key_credential_params.public_key_credential_params(); @@ -147,7 +147,7 @@ void MakeCredentialOperation::PromptTouchIdDone(bool success) { AttestationObject( std::move(authenticator_data), std::make_unique<PackedAttestationStatement>( - CoseAlgorithmIdentifier::kCoseEs256, std::move(*signature), + CoseAlgorithmIdentifier::kEs256, std::move(*signature), /*x509_certificates=*/std::vector<std::vector<uint8_t>>()))); std::move(callback_).Run(CtapDeviceResponseCode::kSuccess, std::move(response)); diff --git a/chromium/device/fido/mac/touch_id_context.mm b/chromium/device/fido/mac/touch_id_context.mm index 220354da77b..f9af4eb253a 100644 --- a/chromium/device/fido/mac/touch_id_context.mm +++ b/chromium/device/fido/mac/touch_id_context.mm @@ -129,9 +129,9 @@ bool TouchIdContext::TouchIdAvailableImpl(const AuthenticatorConfig& config) { } base::scoped_nsobject<LAContext> context([[LAContext alloc] init]); - base::scoped_nsobject<NSError> nserr; + NSError* nserr; if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication - error:nserr.InitializeInto()]) { + error:&nserr]) { FIDO_LOG(DEBUG) << "canEvaluatePolicy failed: " << nserr; return false; } diff --git a/chromium/device/fido/mac/util.mm b/chromium/device/fido/mac/util.mm index 624081c4366..57c2e272306 100644 --- a/chromium/device/fido/mac/util.mm +++ b/chromium/device/fido/mac/util.mm @@ -55,7 +55,7 @@ std::unique_ptr<PublicKey> SecKeyRefToECPublicKey(SecKeyRef public_key_ref) base::span<const uint8_t> key_data = base::make_span(CFDataGetBytePtr(data_ref), CFDataGetLength(data_ref)); auto key = P256PublicKey::ParseX962Uncompressed( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), key_data); + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), key_data); if (!key) { LOG(ERROR) << "Unexpected public key format: " << base::HexEncode(key_data.data(), key_data.size()); diff --git a/chromium/device/fido/mac/util_unittest.cc b/chromium/device/fido/mac/util_unittest.cc index 322509b5871..3d6c9d41b50 100644 --- a/chromium/device/fido/mac/util_unittest.cc +++ b/chromium/device/fido/mac/util_unittest.cc @@ -23,7 +23,7 @@ namespace { std::unique_ptr<PublicKey> TestKey() { return P256PublicKey::ParseX962Uncompressed( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), test_data::kX962UncompressedPublicKey); } diff --git a/chromium/device/fido/make_credential_handler_unittest.cc b/chromium/device/fido/make_credential_handler_unittest.cc index ffb1a2814fb..4d6d75e70c5 100644 --- a/chromium/device/fido/make_credential_handler_unittest.cc +++ b/chromium/device/fido/make_credential_handler_unittest.cc @@ -13,6 +13,7 @@ #include "components/cbor/values.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/fido/authenticator_get_info_response.h" #include "device/fido/authenticator_make_credential_response.h" #include "device/fido/authenticator_selection_criteria.h" #include "device/fido/ctap_make_credential_request.h" @@ -331,6 +332,50 @@ MATCHER(IsResidentKeyRequest, "") { return true; } +// Matches a CTAP command that is: +// * A valid make credential request, +// * if |is_uv| is true, +// * with an options map present, +// * and options.uv present and true. +// * if |is_uv_| is false, +// * with an options map not present, +// * or options.uv not present or false. +MATCHER_P(IsUvRequest, is_uv, "") { + if (arg.empty() || + arg[0] != base::strict_cast<uint8_t>( + CtapRequestCommand::kAuthenticatorMakeCredential)) { + *result_listener << "not make credential"; + return false; + } + + base::span<const uint8_t> param_bytes(arg); + param_bytes = param_bytes.subspan(1); + const auto maybe_map = cbor::Reader::Read(param_bytes); + if (!maybe_map || !maybe_map->is_map()) { + *result_listener << "not a map"; + return false; + } + const auto& map = maybe_map->GetMap(); + + const auto options_it = map.find(cbor::Value(7)); + if (options_it == map.end() || !options_it->second.is_map()) { + return is_uv == false; + } + const auto& options = options_it->second.GetMap(); + + const auto uv_it = options.find(cbor::Value("uv")); + if (uv_it == options.end()) { + return is_uv == false; + } + + if (!uv_it->second.is_bool()) { + *result_listener << "'uv' is not a boolean"; + return false; + } + + return uv_it->second.GetBool() == is_uv; +} + ACTION_P(Reply, reply) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, @@ -641,6 +686,34 @@ TEST_F(FidoMakeCredentialHandlerTest, EXPECT_EQ(MakeCredentialStatus::kUserConsentDenied, callback().status()); } +TEST_F(FidoMakeCredentialHandlerTest, + TestCrossPlatformAuthenticatorsForceUVWhenSupported) { + const auto& test_info_response = test_data::kTestAuthenticatorGetInfoResponse; + ASSERT_EQ(ReadCTAPGetInfoResponse(test_info_response) + ->options.user_verification_availability, + AuthenticatorSupportedOptions::UserVerificationAvailability:: + kSupportedAndConfigured); + + auto device = + MockFidoDevice::MakeCtapWithGetInfoExpectation(test_info_response); + device->SetDeviceTransport(FidoTransportProtocol::kUsbHumanInterfaceDevice); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorMakeCredential, + test_data::kTestMakeCredentialResponse, base::TimeDelta(), + IsUvRequest(true)); + + auto request_handler = + CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria( + AuthenticatorAttachment::kAny, /*require_resident_key=*/false, + UserVerificationRequirement::kDiscouraged)); + discovery()->AddDevice(std::move(device)); + discovery()->WaitForCallToStartAndSimulateSuccess(); + + callback().WaitForCallback(); + EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status()); +} + // If a device returns CTAP2_ERR_PIN_AUTH_INVALID, the request should complete // with MakeCredentialStatus::kUserConsentDenied. TEST_F(FidoMakeCredentialHandlerTest, TestRequestWithPinAuthInvalid) { diff --git a/chromium/device/fido/make_credential_request_handler.cc b/chromium/device/fido/make_credential_request_handler.cc index 11cdaccff3d..56564970360 100644 --- a/chromium/device/fido/make_credential_request_handler.cc +++ b/chromium/device/fido/make_credential_request_handler.cc @@ -33,6 +33,15 @@ using MakeCredentialPINDisposition = namespace { +// Permissions requested for PinUvAuthToken. GetAssertion is needed for silent +// probing of credentials. +const std::vector<pin::Permissions> GetMakeCredentialRequestPermissions() { + static const std::vector<pin::Permissions> kMakeCredentialRequestPermissions = + {pin::Permissions::kMakeCredential, pin::Permissions::kGetAssertion, + pin::Permissions::kBioEnrollment}; + return kMakeCredentialRequestPermissions; +} + base::Optional<MakeCredentialStatus> ConvertDeviceResponseCode( CtapDeviceResponseCode device_response_code) { switch (device_response_code) { @@ -121,6 +130,31 @@ MakeCredentialStatus IsCandidateAuthenticatorPostTouch( return MakeCredentialStatus::kAuthenticatorMissingUserVerification; } + base::Optional<base::span<const int32_t>> supported_algorithms( + authenticator->GetAlgorithms()); + if (supported_algorithms) { + // Substitution of defaults should have happened by this point. + DCHECK(!request.public_key_credential_params.public_key_credential_params() + .empty()); + + bool at_least_one_common_algorithm = false; + for (const auto& algo : + request.public_key_credential_params.public_key_credential_params()) { + if (algo.type != CredentialType::kPublicKey) { + continue; + } + + if (base::Contains(*supported_algorithms, algo.algorithm)) { + at_least_one_common_algorithm = true; + break; + } + } + + if (!at_least_one_common_algorithm) { + return MakeCredentialStatus::kNoCommonAlgorithms; + } + } + return MakeCredentialStatus::kSuccess; } @@ -398,39 +432,28 @@ void MakeCredentialRequestHandler::DispatchRequest( } CtapMakeCredentialRequest request(request_); - if (authenticator->Options()) { - // If the authenticator has UV configured then UV will be required in - // order to create a credential (as specified by CTAP 2.0), even if - // user-verification is "discouraged". However, if the request is U2F-only - // then that doesn't apply and UV must be set to discouraged so that the - // request can be translated to U2F. Platform authenticators are exempted - // from this UV enforcement. - if (authenticator->Options()->user_verification_availability == - AuthenticatorSupportedOptions::UserVerificationAvailability:: - kSupportedAndConfigured && - !request_.is_u2f_only && - authenticator->AuthenticatorTransport() != - FidoTransportProtocol::kInternal) { - if (authenticator->Options()->supports_uv_token) { - authenticator->GetUvToken( - base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken, - weak_factory_.GetWeakPtr(), authenticator)); - return; - } - request.user_verification = UserVerificationRequirement::kRequired; - } else { - request.user_verification = UserVerificationRequirement::kDiscouraged; - } + if (authenticator->Options()) { SpecializeRequestForAuthenticator(&request, authenticator); } + if (!request_.is_u2f_only && + request.user_verification != UserVerificationRequirement::kDiscouraged && + authenticator->CanGetUvToken()) { + authenticator->GetUvToken( + request_.rp.id, + base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken, + weak_factory_.GetWeakPtr(), authenticator)); + return; + } + ReportMakeCredentialRequestTransport(authenticator); authenticator->MakeCredential( std::move(request), base::BindOnce(&MakeCredentialRequestHandler::HandleResponse, - weak_factory_.GetWeakPtr(), authenticator)); + weak_factory_.GetWeakPtr(), authenticator, + base::ElapsedTimer())); } void MakeCredentialRequestHandler::AuthenticatorRemoved( @@ -454,6 +477,7 @@ void MakeCredentialRequestHandler::AuthenticatorRemoved( void MakeCredentialRequestHandler::HandleResponse( FidoAuthenticator* authenticator, + base::ElapsedTimer request_timer, CtapDeviceResponseCode status, base::Optional<AuthenticatorMakeCredentialResponse> response) { DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); @@ -501,9 +525,12 @@ void MakeCredentialRequestHandler::HandleResponse( status == CtapDeviceResponseCode::kCtap2ErrPinRequired) && authenticator->WillNeedPINToMakeCredential(request_, observer()) == MakeCredentialPINDisposition::kUsePINForFallback) { - // Some authenticators will return this error immediately without user - // interaction when internal UV is locked. - if (AuthenticatorMayHaveReturnedImmediately(authenticator->GetId())) { + // Authenticators without uvToken support will return this error immediately + // without user interaction when internal UV is locked. + const base::TimeDelta response_time = request_timer.Elapsed(); + if (response_time < kMinExpectedAuthenticatorResponseTime) { + FIDO_LOG(DEBUG) << "Authenticator is probably locked, response_time=" + << response_time; authenticator->GetTouch(base::BindOnce( &MakeCredentialRequestHandler::StartPINFallbackForInternalUv, weak_factory_.GetWeakPtr(), authenticator)); @@ -634,7 +661,7 @@ void MakeCredentialRequestHandler::OnHavePIN(std::string pin) { if (state_ == State::kWaitingForPIN) { state_ = State::kRequestWithPIN; authenticator_->GetPINToken( - std::move(pin), + std::move(pin), GetMakeCredentialRequestPermissions(), request_.rp.id, base::BindOnce(&MakeCredentialRequestHandler::OnHavePINToken, weak_factory_.GetWeakPtr())); return; @@ -690,7 +717,7 @@ void MakeCredentialRequestHandler::OnHaveSetPIN( // get a PIN token. state_ = State::kRequestWithPIN; authenticator_->GetPINToken( - std::move(pin), + std::move(pin), GetMakeCredentialRequestPermissions(), request_.rp.id, base::BindOnce(&MakeCredentialRequestHandler::OnHavePINToken, weak_factory_.GetWeakPtr())); } @@ -813,6 +840,7 @@ void MakeCredentialRequestHandler::OnUvRetriesResponse( } observer()->OnRetryUserVerification(response->retries); authenticator_->GetUvToken( + request_.rp.id, base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken, weak_factory_.GetWeakPtr(), authenticator_)); } @@ -876,8 +904,6 @@ void MakeCredentialRequestHandler::DispatchRequestWithToken( CtapMakeCredentialRequest request(request_); request.pin_auth = token.PinAuth(request.client_data_hash); request.pin_protocol = pin::kProtocolVersion; - // Do not do internal UV again. - request.user_verification = UserVerificationRequirement::kDiscouraged; SpecializeRequestForAuthenticator(&request, authenticator_); ReportMakeCredentialRequestTransport(authenticator_); @@ -885,7 +911,8 @@ void MakeCredentialRequestHandler::DispatchRequestWithToken( authenticator_->MakeCredential( std::move(request), base::BindOnce(&MakeCredentialRequestHandler::HandleResponse, - weak_factory_.GetWeakPtr(), authenticator_)); + weak_factory_.GetWeakPtr(), authenticator_, + base::ElapsedTimer())); } void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator( @@ -902,6 +929,10 @@ void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator( authenticator->Options()->supports_android_client_data_ext) { request->android_client_data_ext = *options_.android_client_data_ext; } + + if (request->hmac_secret && !authenticator->SupportsHMACSecretExtension()) { + request->hmac_secret = false; + } } } // namespace device diff --git a/chromium/device/fido/make_credential_request_handler.h b/chromium/device/fido/make_credential_request_handler.h index 08499623700..48fa11b1fdd 100644 --- a/chromium/device/fido/make_credential_request_handler.h +++ b/chromium/device/fido/make_credential_request_handler.h @@ -26,6 +26,10 @@ #include "device/fido/fido_transport_protocol.h" #include "device/fido/pin.h" +namespace base { +class ElapsedTimer; +} + namespace device { class FidoAuthenticator; @@ -51,6 +55,7 @@ enum class MakeCredentialStatus { // there's no UI support for collecting a PIN. This could // be clearer. kAuthenticatorMissingUserVerification, + kNoCommonAlgorithms, kStorageFull, kWinInvalidStateError, kWinNotAllowedError, @@ -121,6 +126,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler void HandleResponse( FidoAuthenticator* authenticator, + base::ElapsedTimer request_timer, CtapDeviceResponseCode response_code, base::Optional<AuthenticatorMakeCredentialResponse> response); void CollectPINThenSendRequest(FidoAuthenticator* authenticator); diff --git a/chromium/device/fido/make_credential_task.cc b/chromium/device/fido/make_credential_task.cc index 4cb8ca45cde..04ac74c937c 100644 --- a/chromium/device/fido/make_credential_task.cc +++ b/chromium/device/fido/make_credential_task.cc @@ -28,6 +28,10 @@ namespace { bool CtapDeviceShouldUseU2fBecauseClientPinIsSet( const FidoDevice* device, const CtapMakeCredentialRequest& request) { + if (!IsConvertibleToU2fRegisterCommand(request)) { + return false; + } + DCHECK_EQ(device->supported_protocol(), ProtocolVersion::kCtap2); // Don't use U2F for requests that require UV or PIN which U2F doesn't // support. Note that |pin_auth| may also be set by GetTouchRequest(), but we @@ -79,7 +83,7 @@ CtapMakeCredentialRequest MakeCredentialTask::GetTouchRequest( std::move(user), PublicKeyCredentialParams( {{CredentialType::kPublicKey, - base::strict_cast<int>(CoseAlgorithmIdentifier::kCoseEs256)}})); + base::strict_cast<int>(CoseAlgorithmIdentifier::kEs256)}})); // If a device supports CTAP2 and has PIN support then setting an empty // pinAuth should trigger just a touch[1]. Our U2F code also understands diff --git a/chromium/device/fido/make_credential_task_unittest.cc b/chromium/device/fido/make_credential_task_unittest.cc index 95a5cf2d940..a4e7049f7e4 100644 --- a/chromium/device/fido/make_credential_task_unittest.cc +++ b/chromium/device/fido/make_credential_task_unittest.cc @@ -41,7 +41,7 @@ using TestMakeCredentialTaskCallback = class FidoMakeCredentialTaskTest : public testing::Test { public: - FidoMakeCredentialTaskTest() {} + FidoMakeCredentialTaskTest() = default; std::unique_ptr<MakeCredentialTask> CreateMakeCredentialTask( FidoDevice* device) { @@ -117,7 +117,8 @@ TEST_F(FidoMakeCredentialTaskTest, FallbackToU2fRegisterSuccess) { TEST_F(FidoMakeCredentialTaskTest, DefaultToU2fWhenClientPinSet) { AuthenticatorGetInfoResponse device_info( - {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, kTestDeviceAaguid); + {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, + {Ctap2Version::kCtap2_0}, kTestDeviceAaguid); AuthenticatorSupportedOptions options; options.client_pin_availability = AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet; @@ -138,7 +139,8 @@ TEST_F(FidoMakeCredentialTaskTest, DefaultToU2fWhenClientPinSet) { TEST_F(FidoMakeCredentialTaskTest, EnforceClientPinWhenUserVerificationSet) { AuthenticatorGetInfoResponse device_info( - {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, kTestDeviceAaguid); + {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, + {Ctap2Version::kCtap2_0}, kTestDeviceAaguid); AuthenticatorSupportedOptions options; options.client_pin_availability = AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet; diff --git a/chromium/device/fido/mock_fido_device.cc b/chromium/device/fido/mock_fido_device.cc index f74015a7600..d5562e29b37 100644 --- a/chromium/device/fido/mock_fido_device.cc +++ b/chromium/device/fido/mock_fido_device.cc @@ -86,12 +86,12 @@ std::vector<uint8_t> MockFidoDevice::EncodeCBORRequest( return request_bytes; } -// Matcher to compare the fist byte of the incoming requests. +// Matcher to compare the first byte of the incoming requests. MATCHER_P(IsCtap2Command, expected_command, "") { return !arg.empty() && arg[0] == base::strict_cast<uint8_t>(expected_command); } -MockFidoDevice::MockFidoDevice() {} +MockFidoDevice::MockFidoDevice() = default; MockFidoDevice::MockFidoDevice( ProtocolVersion protocol_version, base::Optional<AuthenticatorGetInfoResponse> device_info) @@ -138,14 +138,17 @@ void MockFidoDevice::StubGetId() { void MockFidoDevice::ExpectCtap2CommandAndRespondWith( CtapRequestCommand command, base::Optional<base::span<const uint8_t>> response, - base::TimeDelta delay) { + base::TimeDelta delay, + testing::Matcher<base::span<const uint8_t>> request_matcher) { auto data = fido_parsing_utils::MaterializeOrNull(response); auto send_response = [ data(std::move(data)), delay ](DeviceCallback & cb) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::BindOnce(std::move(cb), std::move(data)), delay); }; - EXPECT_CALL(*this, DeviceTransactPtr(IsCtap2Command(command), ::testing::_)) + EXPECT_CALL(*this, + DeviceTransactPtr(AllOf(IsCtap2Command(command), request_matcher), + ::testing::_)) .WillOnce(::testing::DoAll( ::testing::WithArg<1>(::testing::Invoke(send_response)), ::testing::Return(0))); diff --git a/chromium/device/fido/mock_fido_device.h b/chromium/device/fido/mock_fido_device.h index 1cb93d8b1db..90fa9956ed0 100644 --- a/chromium/device/fido/mock_fido_device.h +++ b/chromium/device/fido/mock_fido_device.h @@ -85,7 +85,9 @@ class MockFidoDevice : public ::testing::StrictMock<FidoDevice> { void ExpectCtap2CommandAndRespondWith( CtapRequestCommand command, base::Optional<base::span<const uint8_t>> response, - base::TimeDelta delay = base::TimeDelta()); + base::TimeDelta delay = base::TimeDelta(), + testing::Matcher<base::span<const uint8_t>> request_matcher = + testing::A<base::span<const uint8_t>>()); void ExpectCtap2CommandAndRespondWithError( CtapRequestCommand command, CtapDeviceResponseCode response_code, diff --git a/chromium/device/fido/p256_public_key.cc b/chromium/device/fido/p256_public_key.cc index 2bc521cb226..a6e0a7ffef9 100644 --- a/chromium/device/fido/p256_public_key.cc +++ b/chromium/device/fido/p256_public_key.cc @@ -8,6 +8,7 @@ #include "components/cbor/writer.h" #include "components/device_event_log/device_event_log.h" +#include "device/fido/cbor_extract.h" #include "device/fido/fido_constants.h" #include "device/fido/public_key.h" #include "third_party/boringssl/src/include/openssl/bn.h" @@ -17,6 +18,11 @@ #include "third_party/boringssl/src/include/openssl/mem.h" #include "third_party/boringssl/src/include/openssl/obj.h" +using device::cbor_extract::IntKey; +using device::cbor_extract::Is; +using device::cbor_extract::StepOrByte; +using device::cbor_extract::Stop; + namespace device { // kFieldElementLength is the size of a P-256 field element. The field is @@ -72,39 +78,43 @@ std::unique_ptr<PublicKey> P256PublicKey::ExtractFromCOSEKey( int32_t algorithm, base::span<const uint8_t> cbor_bytes, const cbor::Value::MapValue& map) { - cbor::Value::MapValue::const_iterator it = - map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kKty))); - if (it == map.end() || !it->second.is_integer() || - it->second.GetInteger() != static_cast<int64_t>(CoseKeyTypes::kEC2)) { - return nullptr; - } - - it = map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticCurve))); - if (it == map.end() || !it->second.is_integer() || - it->second.GetInteger() != static_cast<int64_t>(CoseCurves::kP256)) { - return nullptr; - } - - cbor::Value::MapValue::const_iterator it_x = - map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticX))); - cbor::Value::MapValue::const_iterator it_y = - map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticY))); - if (it_x == map.end() || !it_x->second.is_bytestring() || it_y == map.end() || - !it_y->second.is_bytestring()) { + struct COSEKey { + const int64_t* kty; + const int64_t* crv; + const std::vector<uint8_t>* x; + const std::vector<uint8_t>* y; + } cose_key; + + static constexpr cbor_extract::StepOrByte<COSEKey> kSteps[] = { + // clang-format off + ELEMENT(Is::kRequired, COSEKey, kty), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kKty)), + + ELEMENT(Is::kRequired, COSEKey, crv), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticCurve)), + + ELEMENT(Is::kRequired, COSEKey, x), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticX)), + + ELEMENT(Is::kRequired, COSEKey, y), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticY)), + + Stop<COSEKey>(), + // clang-format on + }; + + if (!cbor_extract::Extract<COSEKey>(&cose_key, kSteps, map) || + *cose_key.kty != static_cast<int64_t>(CoseKeyTypes::kEC2) || + *cose_key.crv != static_cast<int64_t>(CoseCurves::kP256) || + cose_key.x->size() != kFieldElementLength || + cose_key.y->size() != kFieldElementLength) { return nullptr; } - const std::vector<uint8_t>& x(it_x->second.GetBytestring()); - const std::vector<uint8_t>& y(it_y->second.GetBytestring()); - - if (x.size() != kFieldElementLength || y.size() != kFieldElementLength) { - FIDO_LOG(ERROR) << "Incorrect length for P-256 COSE key coordinates"; - } - bssl::UniquePtr<BIGNUM> x_bn(BN_new()); bssl::UniquePtr<BIGNUM> y_bn(BN_new()); - if (!BN_bin2bn(x.data(), x.size(), x_bn.get()) || - !BN_bin2bn(y.data(), y.size(), y_bn.get())) { + if (!BN_bin2bn(cose_key.x->data(), cose_key.x->size(), x_bn.get()) || + !BN_bin2bn(cose_key.y->data(), cose_key.y->size(), y_bn.get())) { return nullptr; } diff --git a/chromium/device/fido/p256_public_key.h b/chromium/device/fido/p256_public_key.h index 4eeccb704d6..f9d5bec7324 100644 --- a/chromium/device/fido/p256_public_key.h +++ b/chromium/device/fido/p256_public_key.h @@ -15,7 +15,7 @@ namespace device { -class PublicKey; +struct PublicKey; struct COMPONENT_EXPORT(DEVICE_FIDO) P256PublicKey { static std::unique_ptr<PublicKey> ExtractFromU2fRegistrationResponse( diff --git a/chromium/device/fido/pin.cc b/chromium/device/fido/pin.cc index 6480e111017..ee93c4135ec 100644 --- a/chromium/device/fido/pin.cc +++ b/chromium/device/fido/pin.cc @@ -508,19 +508,65 @@ AsCTAPRequestValuePair(const PinTokenRequest& request) { }); } -UvTokenRequest::UvTokenRequest(const KeyAgreementResponse& peer_key) - : TokenRequest(peer_key) {} +PinTokenWithPermissionsRequest::PinTokenWithPermissionsRequest( + const std::string& pin, + const KeyAgreementResponse& peer_key, + const uint8_t permissions, + const base::Optional<std::string> rp_id) + : PinTokenRequest(pin, peer_key), + permissions_(permissions), + rp_id_(rp_id) {} + +// static +std::pair<CtapRequestCommand, base::Optional<cbor::Value>> +AsCTAPRequestValuePair(const PinTokenWithPermissionsRequest& request) { + uint8_t encrypted_pin[sizeof(request.pin_hash_)]; + Encrypt(request.shared_key_.data(), request.pin_hash_, encrypted_pin); + + return EncodePINCommand( + Subcommand::kGetPinUvAuthTokenUsingPinWithPermissions, + [&request, encrypted_pin](cbor::Value::MapValue* map) { + map->emplace(static_cast<int>(RequestKey::kKeyAgreement), + std::move(request.cose_key_)); + map->emplace( + static_cast<int>(RequestKey::kPINHashEnc), + base::span<const uint8_t>(encrypted_pin, sizeof(encrypted_pin))); + map->emplace(static_cast<int>(RequestKey::kPermissions), + std::move(request.permissions_)); + if (request.rp_id_) { + map->emplace(static_cast<int>(RequestKey::kPermissionsRPID), + *request.rp_id_); + } + }); +} + +PinTokenWithPermissionsRequest::~PinTokenWithPermissionsRequest() = default; + +PinTokenWithPermissionsRequest::PinTokenWithPermissionsRequest( + PinTokenWithPermissionsRequest&& other) = default; + +UvTokenRequest::UvTokenRequest(const KeyAgreementResponse& peer_key, + base::Optional<std::string> rp_id) + : TokenRequest(peer_key), rp_id_(rp_id) {} UvTokenRequest::~UvTokenRequest() = default; UvTokenRequest::UvTokenRequest(UvTokenRequest&& other) = default; +// static std::pair<CtapRequestCommand, base::Optional<cbor::Value>> AsCTAPRequestValuePair(const UvTokenRequest& request) { return EncodePINCommand( Subcommand::kGetUvToken, [&request](cbor::Value::MapValue* map) { map->emplace(static_cast<int>(RequestKey::kKeyAgreement), std::move(request.cose_key_)); + map->emplace(static_cast<int>(RequestKey::kPermissions), + static_cast<uint8_t>(Permissions::kMakeCredential) | + static_cast<uint8_t>(Permissions::kGetAssertion)); + if (request.rp_id_) { + map->emplace(static_cast<int>(RequestKey::kPermissionsRPID), + *request.rp_id_); + } }); } diff --git a/chromium/device/fido/pin.h b/chromium/device/fido/pin.h index da7a00faa1d..88d16f09205 100644 --- a/chromium/device/fido/pin.h +++ b/chromium/device/fido/pin.h @@ -24,6 +24,16 @@ namespace device { namespace pin { +// Permission list flags. See +// https://drafts.fidoalliance.org/fido-2/stable-links-to-latest/fido-client-to-authenticator-protocol.html#permissions +enum class Permissions : uint8_t { + kMakeCredential = 0x01, + kGetAssertion = 0x02, + kCredentialManagement = 0x04, + kBioEnrollment = 0x08, + kPlatformConfiguration = 0x10, +}; + // kProtocolVersion is the version of the PIN protocol that this code // implements. constexpr int kProtocolVersion = 1; @@ -166,19 +176,42 @@ class PinTokenRequest : public TokenRequest { friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>> AsCTAPRequestValuePair(const PinTokenRequest&); - private: + protected: uint8_t pin_hash_[16]; }; +class PinTokenWithPermissionsRequest : public PinTokenRequest { + public: + PinTokenWithPermissionsRequest(const std::string& pin, + const KeyAgreementResponse& peer_key, + const uint8_t permissions, + const base::Optional<std::string> rp_id); + PinTokenWithPermissionsRequest(PinTokenWithPermissionsRequest&&); + PinTokenWithPermissionsRequest(const PinTokenWithPermissionsRequest&) = + delete; + ~PinTokenWithPermissionsRequest() override; + + friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>> + AsCTAPRequestValuePair(const PinTokenWithPermissionsRequest&); + + private: + uint8_t permissions_; + base::Optional<std::string> rp_id_; +}; + class UvTokenRequest : public TokenRequest { public: - explicit UvTokenRequest(const KeyAgreementResponse& peer_key); + UvTokenRequest(const KeyAgreementResponse& peer_key, + base::Optional<std::string> rp_id); UvTokenRequest(UvTokenRequest&&); UvTokenRequest(const UvTokenRequest&) = delete; virtual ~UvTokenRequest(); friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>> AsCTAPRequestValuePair(const UvTokenRequest&); + + private: + base::Optional<std::string> rp_id_; }; // TokenResponse represents the response to a pin-token request. In order to diff --git a/chromium/device/fido/pin_internal.h b/chromium/device/fido/pin_internal.h index 7c8e2e11e22..9124cb1c8aa 100644 --- a/chromium/device/fido/pin_internal.h +++ b/chromium/device/fido/pin_internal.h @@ -30,17 +30,23 @@ enum class Subcommand : uint8_t { kGetPINToken = 0x05, kGetUvToken = 0x06, kGetUvRetries = 0x07, + kSetMinPINLength = 0x08, + kGetPinUvAuthTokenUsingPinWithPermissions = 0x09, }; // RequestKey enumerates the keys in the top-level CBOR map for all PIN // commands. enum class RequestKey : int { - kProtocol = 1, - kSubcommand = 2, - kKeyAgreement = 3, - kPINAuth = 4, - kNewPINEnc = 5, - kPINHashEnc = 6, + kProtocol = 0x01, + kSubcommand = 0x02, + kKeyAgreement = 0x03, + kPINAuth = 0x04, + kNewPINEnc = 0x05, + kPINHashEnc = 0x06, + kMinPINLength = 0x07, + kMinPINLengthRPIDs = 0x08, + kPermissions = 0x09, + kPermissionsRPID = 0x0A, }; // ResponseKey enumerates the keys in the top-level CBOR map for all PIN diff --git a/chromium/device/fido/public_key.cc b/chromium/device/fido/public_key.cc index 39c639725b0..88c6f581027 100644 --- a/chromium/device/fido/public_key.cc +++ b/chromium/device/fido/public_key.cc @@ -11,25 +11,13 @@ namespace device { -PublicKey::~PublicKey() = default; - -PublicKey::PublicKey(int32_t algorithm, - base::span<const uint8_t> cbor_bytes, - base::Optional<std::vector<uint8_t>> der_bytes) - : algorithm_(algorithm), - cbor_bytes_(fido_parsing_utils::Materialize(cbor_bytes)), - der_bytes_(std::move(der_bytes)) {} - -int32_t PublicKey::algorithm() const { - return algorithm_; -} +PublicKey::PublicKey(int32_t in_algorithm, + base::span<const uint8_t> in_cose_key_bytes, + base::Optional<std::vector<uint8_t>> in_der_bytes) + : algorithm(in_algorithm), + cose_key_bytes(fido_parsing_utils::Materialize(in_cose_key_bytes)), + der_bytes(std::move(in_der_bytes)) {} -const std::vector<uint8_t>& PublicKey::cose_key_bytes() const { - return cbor_bytes_; -} - -const base::Optional<std::vector<uint8_t>>& PublicKey::der_bytes() const { - return der_bytes_; -} +PublicKey::~PublicKey() = default; } // namespace device diff --git a/chromium/device/fido/public_key.h b/chromium/device/fido/public_key.h index c3e51ab7269..f9e97012984 100644 --- a/chromium/device/fido/public_key.h +++ b/chromium/device/fido/public_key.h @@ -6,41 +6,36 @@ #define DEVICE_FIDO_PUBLIC_KEY_H_ #include <stdint.h> -#include <string> #include <vector> #include "base/component_export.h" #include "base/containers/span.h" #include "base/macros.h" +#include "base/optional.h" namespace device { // https://www.w3.org/TR/webauthn/#credentialpublickey -class COMPONENT_EXPORT(DEVICE_FIDO) PublicKey { - public: +struct COMPONENT_EXPORT(DEVICE_FIDO) PublicKey { PublicKey(int32_t algorithm, base::span<const uint8_t> cbor_bytes, base::Optional<std::vector<uint8_t>> der_bytes); - virtual ~PublicKey(); + ~PublicKey(); - // algorithm returns the COSE algorithm identifier for this public key. - int32_t algorithm() const; + // algorithm contains the COSE algorithm identifier for this public key. + const int32_t algorithm; - // The credential public key as a COSE_Key map as defined in Section 7 - // of https://tools.ietf.org/html/rfc8152. - const std::vector<uint8_t>& cose_key_bytes() const; + // cose_key_bytes contains the credential public key as a COSE_Key map as + // defined in Section 7 of https://tools.ietf.org/html/rfc8152. + const std::vector<uint8_t> cose_key_bytes; - // der_bytes returns an ASN.1, DER, SubjectPublicKeyInfo describing this + // der_bytes contains an ASN.1, DER, SubjectPublicKeyInfo describing this // public key, if possible. (WebAuthn can negotiate the use of unknown // public-key algorithms so not all public keys can be transformed into SPKI // form.) - const base::Optional<std::vector<uint8_t>>& der_bytes() const; + const base::Optional<std::vector<uint8_t>> der_bytes; private: - const int32_t algorithm_; - std::vector<uint8_t> cbor_bytes_; - base::Optional<std::vector<uint8_t>> der_bytes_; - DISALLOW_COPY_AND_ASSIGN(PublicKey); }; diff --git a/chromium/device/fido/public_key_credential_params.h b/chromium/device/fido/public_key_credential_params.h index e866c9398ec..c93ca32e61b 100644 --- a/chromium/device/fido/public_key_credential_params.h +++ b/chromium/device/fido/public_key_credential_params.h @@ -25,8 +25,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) PublicKeyCredentialParams { public: struct COMPONENT_EXPORT(DEVICE_FIDO) CredentialInfo { bool operator==(const CredentialInfo& other) const; + CredentialType type = CredentialType::kPublicKey; - int algorithm = base::strict_cast<int>(CoseAlgorithmIdentifier::kCoseEs256); + int32_t algorithm = + base::strict_cast<int32_t>(CoseAlgorithmIdentifier::kEs256); }; static base::Optional<PublicKeyCredentialParams> CreateFromCBORValue( diff --git a/chromium/device/fido/rsa_public_key.cc b/chromium/device/fido/rsa_public_key.cc index e4abd88f606..be9af40234d 100644 --- a/chromium/device/fido/rsa_public_key.cc +++ b/chromium/device/fido/rsa_public_key.cc @@ -7,6 +7,7 @@ #include <utility> #include "components/cbor/writer.h" +#include "device/fido/cbor_extract.h" #include "device/fido/fido_constants.h" #include "third_party/boringssl/src/include/openssl/bn.h" #include "third_party/boringssl/src/include/openssl/bytestring.h" @@ -15,6 +16,11 @@ #include "third_party/boringssl/src/include/openssl/obj.h" #include "third_party/boringssl/src/include/openssl/rsa.h" +using device::cbor_extract::IntKey; +using device::cbor_extract::Is; +using device::cbor_extract::StepOrByte; +using device::cbor_extract::Stop; + namespace device { // static @@ -23,30 +29,36 @@ std::unique_ptr<PublicKey> RSAPublicKey::ExtractFromCOSEKey( base::span<const uint8_t> cbor_bytes, const cbor::Value::MapValue& map) { // See https://tools.ietf.org/html/rfc8230#section-4 - cbor::Value::MapValue::const_iterator it = - map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kKty))); - if (it == map.end() || !it->second.is_integer() || - it->second.GetInteger() != static_cast<int64_t>(CoseKeyTypes::kRSA)) { - return nullptr; - } + struct COSEKey { + const int64_t* kty; + const std::vector<uint8_t>* n; + const std::vector<uint8_t>* e; + } cose_key; + + static constexpr cbor_extract::StepOrByte<COSEKey> kSteps[] = { + // clang-format off + ELEMENT(Is::kRequired, COSEKey, kty), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kKty)), - cbor::Value::MapValue::const_iterator it_n = - map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kRSAModulus))); - cbor::Value::MapValue::const_iterator it_e = map.find( - cbor::Value(static_cast<int64_t>(CoseKeyKey::kRSAPublicExponent))); + ELEMENT(Is::kRequired, COSEKey, n), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kRSAModulus)), - if (it_n == map.end() || !it_n->second.is_bytestring() || it_e == map.end() || - !it_e->second.is_bytestring()) { + ELEMENT(Is::kRequired, COSEKey, e), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kRSAPublicExponent)), + + Stop<COSEKey>(), + // clang-format on + }; + + if (!cbor_extract::Extract<COSEKey>(&cose_key, kSteps, map) || + *cose_key.kty != static_cast<int64_t>(CoseKeyTypes::kRSA)) { return nullptr; } - const std::vector<uint8_t>& n(it_n->second.GetBytestring()); - const std::vector<uint8_t>& e(it_e->second.GetBytestring()); - bssl::UniquePtr<BIGNUM> n_bn(BN_new()); bssl::UniquePtr<BIGNUM> e_bn(BN_new()); - if (!BN_bin2bn(n.data(), n.size(), n_bn.get()) || - !BN_bin2bn(e.data(), e.size(), e_bn.get())) { + if (!BN_bin2bn(cose_key.n->data(), cose_key.n->size(), n_bn.get()) || + !BN_bin2bn(cose_key.e->data(), cose_key.e->size(), e_bn.get())) { return nullptr; } diff --git a/chromium/device/fido/u2f_command_constructor.cc b/chromium/device/fido/u2f_command_constructor.cc index e54cf880ff0..53a3cf4fbce 100644 --- a/chromium/device/fido/u2f_command_constructor.cc +++ b/chromium/device/fido/u2f_command_constructor.cc @@ -25,7 +25,7 @@ bool IsConvertibleToU2fRegisterCommand( public_key_credential_info.begin(), public_key_credential_info.end(), [](const auto& credential_info) { return credential_info.algorithm == - base::strict_cast<int>(CoseAlgorithmIdentifier::kCoseEs256); + base::strict_cast<int>(CoseAlgorithmIdentifier::kEs256); }); } diff --git a/chromium/device/fido/virtual_ctap2_device.cc b/chromium/device/fido/virtual_ctap2_device.cc index 6cea9e49edd..6a82f0679df 100644 --- a/chromium/device/fido/virtual_ctap2_device.cc +++ b/chromium/device/fido/virtual_ctap2_device.cc @@ -6,6 +6,7 @@ #include <algorithm> #include <array> +#include <memory> #include <string> #include <utility> @@ -54,6 +55,17 @@ constexpr std::array<uint8_t, kAaguidLength> kDeviceAaguid = { {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}}; +constexpr uint8_t kSupportedPermissionsMask = + static_cast<uint8_t>(pin::Permissions::kMakeCredential) | + static_cast<uint8_t>(pin::Permissions::kGetAssertion) | + static_cast<uint8_t>(pin::Permissions::kCredentialManagement) | + static_cast<uint8_t>(pin::Permissions::kBioEnrollment); + +struct PinUvAuthTokenPermissions { + uint8_t permissions; + base::Optional<std::string> rp_id; +}; + std::vector<uint8_t> ConstructResponse(CtapDeviceResponseCode response_code, base::span<const uint8_t> data) { std::vector<uint8_t> response{base::strict_cast<uint8_t>(response_code)}; @@ -61,6 +73,45 @@ std::vector<uint8_t> ConstructResponse(CtapDeviceResponseCode response_code, return response; } +// Returns true if the |permissions| parameter requires an explicit permissions +// RPID. +bool PermissionsRequireRPID(uint8_t permissions) { + return permissions & + static_cast<uint8_t>(pin::Permissions::kMakeCredential) || + permissions & static_cast<uint8_t>(pin::Permissions::kGetAssertion); +} + +CtapDeviceResponseCode ExtractPermissions( + const cbor::Value::MapValue& request_map, + PinUvAuthTokenPermissions& out_permissions) { + const auto permissions_it = request_map.find( + cbor::Value(static_cast<int>(pin::RequestKey::kPermissions))); + if (permissions_it == request_map.end() || + !permissions_it->second.is_unsigned()) { + return CtapDeviceResponseCode::kCtap2ErrMissingParameter; + } + out_permissions.permissions = permissions_it->second.GetUnsigned(); + if (out_permissions.permissions == 0) { + return CtapDeviceResponseCode::kCtap1ErrInvalidParameter; + } + + DCHECK((out_permissions.permissions & ~kSupportedPermissionsMask) == 0); + + const auto permissions_rpid_it = request_map.find( + cbor::Value(static_cast<int>(pin::RequestKey::kPermissionsRPID))); + if (permissions_rpid_it == request_map.end() && + PermissionsRequireRPID(out_permissions.permissions)) { + return CtapDeviceResponseCode::kCtap2ErrMissingParameter; + } + if (permissions_rpid_it != request_map.end()) { + if (!permissions_rpid_it->second.is_string()) { + return CtapDeviceResponseCode::kCtap2ErrMissingParameter; + } + out_permissions.rp_id = permissions_rpid_it->second.GetString(); + } + return CtapDeviceResponseCode::kSuccess; +} + void ReturnCtap2Response( FidoDevice::DeviceCallback cb, CtapDeviceResponseCode response_code, @@ -384,13 +435,8 @@ VirtualCtap2Device::Config& VirtualCtap2Device::Config::operator=( const Config&) = default; VirtualCtap2Device::Config::~Config() = default; -VirtualCtap2Device::VirtualCtap2Device() : VirtualFidoDevice() { - device_info_ = - AuthenticatorGetInfoResponse({ProtocolVersion::kCtap2}, kDeviceAaguid); - device_info_->algorithms = { - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseRs256), - }; +VirtualCtap2Device::VirtualCtap2Device() { + Init({ProtocolVersion::kCtap2}); } VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, @@ -399,10 +445,9 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, std::vector<ProtocolVersion> versions = {ProtocolVersion::kCtap2}; if (config.u2f_support) { versions.emplace_back(ProtocolVersion::kU2f); - u2f_device_.reset(new VirtualU2fDevice(NewReferenceToState())); + u2f_device_ = std::make_unique<VirtualU2fDevice>(NewReferenceToState()); } - device_info_ = - AuthenticatorGetInfoResponse(std::move(versions), kDeviceAaguid); + Init(std::move(versions)); AuthenticatorSupportedOptions options; bool options_updated = false; @@ -427,9 +472,12 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, options.user_verification_availability = AuthenticatorSupportedOptions:: UserVerificationAvailability::kSupportedButNotConfigured; } - options.supports_uv_token = config.uv_token_support; } + options.supports_pin_uv_auth_token = config.pin_uv_auth_token_support; + DCHECK(!options.supports_pin_uv_auth_token || + base::Contains(config.ctap2_versions, Ctap2Version::kCtap2_1)); + if (config.resident_key_support) { options_updated = true; options.supports_resident_key = true; @@ -438,6 +486,7 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, if (config.credential_management_support) { options_updated = true; options.supports_credential_management = true; + options.supports_credential_management_preview = true; } if (config.bio_enrollment_support) { @@ -483,14 +532,22 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, device_info_->options = std::move(options); } + std::vector<std::string> extensions; + if (config.cred_protect_support) { - device_info_->extensions.emplace( - {std::string(device::kExtensionCredProtect)}); + extensions.emplace_back(device::kExtensionCredProtect); + } + + if (config.hmac_secret_support) { + extensions.emplace_back(device::kExtensionHmacSecret); } if (config.support_android_client_data_extension) { - device_info_->extensions.emplace( - {std::string(device::kExtensionAndroidClientData)}); + extensions.emplace_back(device::kExtensionAndroidClientData); + } + + if (!extensions.empty()) { + device_info_->extensions.emplace(std::move(extensions)); } if (config.max_credential_count_in_list > 0) { @@ -501,6 +558,11 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, if (config.max_credential_id_length > 0) { device_info_->max_credential_id_length = config.max_credential_id_length; } + + if (config.support_invalid_for_testing_algorithm) { + device_info_->algorithms.push_back( + static_cast<int32_t>(CoseAlgorithmIdentifier::kInvalidForTesting)); + } } VirtualCtap2Device::~VirtualCtap2Device() = default; @@ -570,6 +632,7 @@ FidoDevice::CancelToken VirtualCtap2Device::DeviceTransact( break; } case CtapRequestCommand::kAuthenticatorCredentialManagement: + case CtapRequestCommand::kAuthenticatorCredentialManagementPreview: response_code = OnCredentialManagement(request_bytes, &response_data); break; case CtapRequestCommand::kAuthenticatorBioEnrollment: @@ -590,10 +653,21 @@ base::WeakPtr<FidoDevice> VirtualCtap2Device::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } +void VirtualCtap2Device::Init(std::vector<ProtocolVersion> versions) { + device_info_ = AuthenticatorGetInfoResponse( + std::move(versions), config_.ctap2_versions, kDeviceAaguid); + device_info_->algorithms = { + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEdDSA), + static_cast<int32_t>(CoseAlgorithmIdentifier::kRs256), + }; +} + base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::CheckUserVerification( bool is_make_credential, const AuthenticatorSupportedOptions& options, + const std::string& rp_id, const base::Optional<std::vector<uint8_t>>& pin_auth, const base::Optional<uint8_t>& pin_protocol, base::span<const uint8_t> pin_token, @@ -647,9 +721,11 @@ VirtualCtap2Device::CheckUserVerification( return CtapDeviceResponseCode::kCtap2ErrInvalidOption; } - // Step 4. + // "If authenticator is protected by some form of user verification:" bool uv = false; if (can_do_uv) { + // "If the request is passed with "uv" option, use built-in user + // verification method and verify the user." if (user_verification == UserVerificationRequirement::kRequired) { if (options.user_verification_availability == AuthenticatorSupportedOptions::UserVerificationAvailability:: @@ -669,11 +745,37 @@ VirtualCtap2Device::CheckUserVerification( } } + // "If pinUvAuthParam parameter is present and pinUvAuthProtocol is 1". if (pin_auth && (options.client_pin_availability == AuthenticatorSupportedOptions::ClientPinAvailability:: kSupportedAndPinSet || - options.supports_uv_token)) { + options.supports_pin_uv_auth_token)) { DCHECK(pin_protocol && *pin_protocol == 1); + + // "Verify that the pinUvAuthToken has the {mc,ga} permission, if not, + // return CTAP2_ERR_PIN_AUTH_INVALID." + auto permission = is_make_credential ? pin::Permissions::kMakeCredential + : pin::Permissions::kGetAssertion; + if (!(mutable_state()->pin_uv_token_permissions & + static_cast<uint8_t>(permission))) { + return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid; + } + + // "If the pinUvAuthToken has a permissions RPID associated and it + // does not match the RPID in this request, return + // CTAP2_ERR_PIN_AUTH_INVALID." + if (mutable_state()->pin_uv_token_rpid && + mutable_state()->pin_uv_token_rpid != rp_id) { + return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid; + } + + // "If the pinUvAuthToken does not have a permissions RPID associated, + // associate the request RPID as permissions RPID." + if (!mutable_state()->pin_uv_token_rpid) { + mutable_state()->pin_uv_token_rpid = rp_id; + } + + // Verify pinUvAuthParam. if (CheckPINToken(pin_token, *pin_auth, client_data_hash)) { uv = true; } else { @@ -712,7 +814,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( bool user_verified; const base::Optional<CtapDeviceResponseCode> uv_error = CheckUserVerification( - true /* is makeCredential */, options, request.pin_auth, + true /* is makeCredential */, options, request.rp.id, request.pin_auth, request.pin_protocol, mutable_state()->pin_token, request.client_data_hash, request.user_verification, &user_verified); if (uv_error != CtapDeviceResponseCode::kSuccess) { @@ -756,11 +858,20 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( switch (param.algorithm) { default: continue; - case static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256): - private_key = FreshP256Key(); + case static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256): + private_key = PrivateKey::FreshP256Key(); + break; + case static_cast<int32_t>(CoseAlgorithmIdentifier::kRs256): + private_key = PrivateKey::FreshRSAKey(); break; - case static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseRs256): - private_key = FreshRSAKey(); + case static_cast<int32_t>(CoseAlgorithmIdentifier::kEdDSA): + private_key = PrivateKey::FreshEd25519Key(); + break; + case static_cast<int32_t>(CoseAlgorithmIdentifier::kInvalidForTesting): + if (!config_.support_invalid_for_testing_algorithm) { + continue; + } + private_key = PrivateKey::FreshInvalidForTestingKey(); break; } break; @@ -785,11 +896,18 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( } // Our key handles are simple hashes of the public key. - const auto key_handle = crypto::SHA256Hash(public_key->cose_key_bytes()); + const auto key_handle = crypto::SHA256Hash(public_key->cose_key_bytes); base::Optional<cbor::Value> extensions; cbor::Value::MapValue extensions_map; if (request.hmac_secret) { + if (!config_.hmac_secret_support) { + // Should not have been sent. Authenticators will normally ignore unknown + // extensions but Chromium should not make this mistake. + DLOG(ERROR) + << "Rejecting makeCredential due to unexpected hmac_secret extension"; + return base::nullopt; + } extensions_map.emplace(cbor::Value(kExtensionHmacSecret), cbor::Value(true)); } @@ -816,8 +934,8 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( extensions = cbor::Value(std::move(extensions_map)); } - auto authenticator_data = ConstructAuthenticatorData( - rp_id_hash, user_verified, 01ul, + AuthenticatorData authenticator_data( + rp_id_hash, /*user_present=*/true, user_verified, 01ul, ConstructAttestedCredentialData(key_handle, std::move(public_key)), std::move(extensions)); @@ -941,7 +1059,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion( bool user_verified; const base::Optional<CtapDeviceResponseCode> uv_error = CheckUserVerification( - false /* not makeCredential */, options, request.pin_auth, + false /* not makeCredential */, options, request.rp_id, request.pin_auth, request.pin_protocol, mutable_state()->pin_token, request.client_data_hash, request.user_verification, &user_verified); if (uv_error != CtapDeviceResponseCode::kSuccess) { @@ -1058,9 +1176,9 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion( registration.second->private_key->GetPublicKey())); } - auto authenticator_data = ConstructAuthenticatorData( - rp_id_hash, user_verified, registration.second->counter, - std::move(opt_attested_cred_data), + AuthenticatorData authenticator_data( + rp_id_hash, /*user_present=*/true, user_verified, + registration.second->counter, std::move(opt_attested_cred_data), extensions ? base::make_optional(extensions->Clone()) : base::nullopt); base::Optional<std::string> opt_android_client_data_json; @@ -1158,7 +1276,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( std::vector<uint8_t>* response) { if (device_info_->options.client_pin_availability == AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported && - !config_.uv_token_support) { + !config_.pin_uv_auth_token_support) { return CtapDeviceResponseCode::kCtap1ErrInvalidCommand; } @@ -1287,7 +1405,15 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( break; } - case static_cast<int>(device::pin::Subcommand::kGetPINToken): { + case static_cast<int>(device::pin::Subcommand::kGetPINToken): + case static_cast<int>( + device::pin::Subcommand::kGetPinUvAuthTokenUsingPinWithPermissions): { + if (subcommand == + static_cast<int>(device::pin::Subcommand:: + kGetPinUvAuthTokenUsingPinWithPermissions) && + !config_.pin_uv_auth_token_support) { + return CtapDeviceResponseCode::kCtap1ErrInvalidCommand; + } const auto encrypted_pin_hash = GetPINBytestring(request_map, pin::RequestKey::kPINHashEnc); const auto peer_key = @@ -1298,6 +1424,31 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( return CtapDeviceResponseCode::kCtap2ErrMissingParameter; } + PinUvAuthTokenPermissions permissions; + if (subcommand == + static_cast<int>(device::pin::Subcommand::kGetPINToken)) { + if (request_map.find(cbor::Value(static_cast<int>( + pin::RequestKey::kPermissions))) != request_map.end() || + request_map.find(cbor::Value(static_cast<int>( + pin::RequestKey::kPermissionsRPID))) != request_map.end()) { + return CtapDeviceResponseCode::kCtap1ErrInvalidParameter; + } + // Set default PinUvAuthToken permissions. + permissions.permissions = + static_cast<uint8_t>(pin::Permissions::kMakeCredential) | + static_cast<uint8_t>(pin::Permissions::kGetAssertion); + } else { + DCHECK_EQ( + subcommand, + static_cast<int>(device::pin::Subcommand:: + kGetPinUvAuthTokenUsingPinWithPermissions)); + CtapDeviceResponseCode response = + ExtractPermissions(request_map, permissions); + if (response != CtapDeviceResponseCode::kSuccess) { + return response; + } + } + uint8_t shared_key[SHA256_DIGEST_LENGTH]; if (!mutable_state()->ecdh_key) { // kGetKeyAgreement should have been called first. @@ -1313,6 +1464,11 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( return err; }; + mutable_state()->pin_retries = kMaxPinRetries; + + mutable_state()->pin_uv_token_permissions = permissions.permissions; + mutable_state()->pin_uv_token_rpid = permissions.rp_id; + response_map.emplace( static_cast<int>(pin::ResponseKey::kPINToken), GenerateAndEncryptToken(shared_key, mutable_state()->pin_token)); @@ -1326,6 +1482,13 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( return CtapDeviceResponseCode::kCtap2ErrMissingParameter; } + PinUvAuthTokenPermissions permissions; + CtapDeviceResponseCode response = + ExtractPermissions(request_map, permissions); + if (response != CtapDeviceResponseCode::kSuccess) { + return response; + } + if (device_info_->options.user_verification_availability == AuthenticatorSupportedOptions::UserVerificationAvailability:: kSupportedButNotConfigured) { @@ -1357,6 +1520,8 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( mutable_state()->pin_retries = kMaxPinRetries; mutable_state()->uv_retries = kMaxUvRetries; + mutable_state()->pin_uv_token_permissions = permissions.permissions; + mutable_state()->pin_uv_token_rpid = permissions.rp_id; response_map.emplace( static_cast<int>(pin::ResponseKey::kPINToken), @@ -1652,6 +1817,8 @@ CtapDeviceResponseCode VirtualCtap2Device::OnBioEnrollment( using SubCmd = BioEnrollmentSubCommand; switch (*cmd) { + // TODO(crbug.com/1090415): some of these commands should be checking + // PinUvAuthToken. case SubCmd::kGetFingerprintSensorInfo: response_map.emplace( static_cast<int>(BioEnrollmentResponseKey::kModality), @@ -1831,7 +1998,7 @@ void VirtualCtap2Device::InitPendingRegistrations( registration.first))); base::Optional<cbor::Value> cose_key = cbor::Reader::Read( - registration.second.private_key->GetPublicKey()->cose_key_bytes()); + registration.second.private_key->GetPublicKey()->cose_key_bytes); response_map.emplace( static_cast<int>(CredentialManagementResponseKey::kPublicKey), cose_key->GetMap()); @@ -1874,34 +2041,4 @@ AttestedCredentialData VirtualCtap2Device::ConstructAttestedCredentialData( fido_parsing_utils::Materialize(key_handle), std::move(public_key)); } - -AuthenticatorData VirtualCtap2Device::ConstructAuthenticatorData( - base::span<const uint8_t, kRpIdHashLength> rp_id_hash, - bool user_verified, - uint32_t current_signature_count, - base::Optional<AttestedCredentialData> attested_credential_data, - base::Optional<cbor::Value> extensions) { - uint8_t flag = - base::strict_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence); - if (user_verified) { - flag |= base::strict_cast<uint8_t>( - AuthenticatorData::Flag::kTestOfUserVerification); - } - if (attested_credential_data) - flag |= base::strict_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); - if (extensions) { - flag |= base::strict_cast<uint8_t>( - AuthenticatorData::Flag::kExtensionDataIncluded); - } - - std::array<uint8_t, kSignCounterLength> signature_counter; - signature_counter[0] = (current_signature_count >> 24) & 0xff; - signature_counter[1] = (current_signature_count >> 16) & 0xff; - signature_counter[2] = (current_signature_count >> 8) & 0xff; - signature_counter[3] = (current_signature_count)&0xff; - - return AuthenticatorData(rp_id_hash, flag, signature_counter, - std::move(attested_credential_data), - std::move(extensions)); -} } // namespace device diff --git a/chromium/device/fido/virtual_ctap2_device.h b/chromium/device/fido/virtual_ctap2_device.h index f2ab064f05e..7f05fc3e443 100644 --- a/chromium/device/fido/virtual_ctap2_device.h +++ b/chromium/device/fido/virtual_ctap2_device.h @@ -37,14 +37,14 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device Config& operator=(const Config&); ~Config(); + base::flat_set<Ctap2Version> ctap2_versions = {Ctap2Version::kCtap2_0}; // u2f_support, if true, makes this device a dual-protocol (i.e. CTAP2 and // U2F) device. bool u2f_support = false; bool pin_support = false; bool is_platform_authenticator = false; bool internal_uv_support = false; - // Ignored if |internal_uv_support| is false. - bool uv_token_support = false; + bool pin_uv_auth_token_support = false; bool resident_key_support = false; bool credential_management_support = false; bool bio_enrollment_support = false; @@ -52,6 +52,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device uint8_t bio_enrollment_capacity = 10; uint8_t bio_enrollment_samples_required = 4; bool cred_protect_support = false; + bool hmac_secret_support = false; // force_cred_protect, if set and if |cred_protect_support| is true, is a // credProtect level that will be forced for all registrations. This @@ -131,6 +132,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device // unhashed client data for the authenticator to assemble and hash instead // of using the regular, already hashed value. bool send_unsolicited_android_client_data_extension = false; + + // support_invalid_for_testing_algorithm causes the + // |CoseAlgorithmIdentifier::kInvalidForTesting| public-key algorithm to be + // advertised and supported to aid testing of unknown public-key types. + bool support_invalid_for_testing_algorithm = false; }; VirtualCtap2Device(); @@ -144,11 +150,15 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device base::WeakPtr<FidoDevice> GetWeakPtr() override; private: + // Init performs initialization that's common across the constructors. + void Init(std::vector<ProtocolVersion> versions); + // CheckUserVerification implements the first, common steps of // makeCredential and getAssertion from the CTAP2 spec. base::Optional<CtapDeviceResponseCode> CheckUserVerification( bool is_make_credential, const AuthenticatorSupportedOptions& options, + const std::string& rp_id, const base::Optional<std::vector<uint8_t>>& pin_auth, const base::Optional<uint8_t>& pin_protocol, base::span<const uint8_t> pin_token, @@ -181,12 +191,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device AttestedCredentialData ConstructAttestedCredentialData( base::span<const uint8_t> key_handle, std::unique_ptr<PublicKey> public_key); - AuthenticatorData ConstructAuthenticatorData( - base::span<const uint8_t, kRpIdHashLength> rp_id_hash, - bool user_verified, - uint32_t current_signature_count, - base::Optional<AttestedCredentialData> attested_credential_data, - base::Optional<cbor::Value> extensions); std::unique_ptr<VirtualU2fDevice> u2f_device_; diff --git a/chromium/device/fido/virtual_ctap2_device_unittest.cc b/chromium/device/fido/virtual_ctap2_device_unittest.cc index 494a8fd39b9..c8b27854e78 100644 --- a/chromium/device/fido/virtual_ctap2_device_unittest.cc +++ b/chromium/device/fido/virtual_ctap2_device_unittest.cc @@ -17,6 +17,7 @@ #include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" #include "device/fido/test_callback_receiver.h" +#include "net/cert/asn1_util.h" #include "net/cert/x509_certificate.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -186,6 +187,16 @@ TEST_F(VirtualCtap2DeviceTest, AttestationCertificateIsValid) { base::Time now = base::Time::Now(); EXPECT_LT(cert->valid_start(), now); EXPECT_GT(cert->valid_expiry(), now); + + bool present; + bool critical; + base::StringPiece contents; + ASSERT_TRUE(net::asn1::ExtractExtensionFromDERCert( + net::x509_util::CryptoBufferAsStringPiece(cert->cert_buffer()), + base::StringPiece("\x55\x1d\x13"), &present, &critical, &contents)); + EXPECT_TRUE(present); + EXPECT_TRUE(critical); + EXPECT_EQ(base::StringPiece("\x30\x03\x01\x01\x00", 5), contents); } } // namespace device diff --git a/chromium/device/fido/virtual_fido_device.cc b/chromium/device/fido/virtual_fido_device.cc index 6f4dcad4bb7..f01e9dc0f90 100644 --- a/chromium/device/fido/virtual_fido_device.cc +++ b/chromium/device/fido/virtual_fido_device.cc @@ -9,7 +9,9 @@ #include <utility> #include "base/bind.h" +#include "base/logging.h" #include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" #include "components/cbor/values.h" #include "components/cbor/writer.h" #include "crypto/ec_private_key.h" @@ -50,7 +52,7 @@ constexpr uint8_t kAttestationKey[]{ // CBBFunctionToVector converts a BoringSSL function that writes to a CBB to one // that returns a std::vector. Invoke for a function, f, with: -// CBBFunctionToVector<decltype(f), f>(args, to, f); +// CBBFunctionToVector<decltype(&f), f>(args, to, f); template <typename F, F function, typename... Args> std::vector<uint8_t> CBBFunctionToVector(Args&&... args) { uint8_t* der = nullptr; @@ -101,7 +103,7 @@ class EVPBackedPrivateKey : public VirtualFidoDevice::PrivateKey { } std::vector<uint8_t> GetPKCS8PrivateKey() const override { - return CBBFunctionToVector<decltype(EVP_marshal_private_key), + return CBBFunctionToVector<decltype(&EVP_marshal_private_key), EVP_marshal_private_key>(pkey_.get()); } @@ -121,7 +123,7 @@ class P256PrivateKey : public EVPBackedPrivateKey { std::vector<uint8_t> GetX962PublicKey() const override { const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey_.get()); - return CBBFunctionToVector<decltype(EC_POINT_point2cbb), + return CBBFunctionToVector<decltype(&EC_POINT_point2cbb), EC_POINT_point2cbb>( EC_KEY_get0_group(ec_key), EC_KEY_get0_public_key(ec_key), POINT_CONVERSION_UNCOMPRESSED, /*ctx=*/nullptr); @@ -129,7 +131,7 @@ class P256PrivateKey : public EVPBackedPrivateKey { std::unique_ptr<PublicKey> GetPublicKey() const override { return P256PublicKey::ParseX962Uncompressed( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), GetX962PublicKey()); } @@ -159,7 +161,7 @@ class RSAPrivateKey : public EVPBackedPrivateKey { cbor::Value::MapValue map; map.emplace(static_cast<int64_t>(CoseKeyKey::kAlg), - static_cast<int64_t>(CoseAlgorithmIdentifier::kCoseRs256)); + static_cast<int64_t>(CoseAlgorithmIdentifier::kRs256)); map.emplace(static_cast<int64_t>(CoseKeyKey::kKty), static_cast<int64_t>(CoseKeyTypes::kRSA)); map.emplace(static_cast<int64_t>(CoseKeyKey::kRSAModulus), @@ -171,11 +173,11 @@ class RSAPrivateKey : public EVPBackedPrivateKey { cbor::Writer::Write(cbor::Value(std::move(map)))); std::vector<uint8_t> der_bytes( - CBBFunctionToVector<decltype(EVP_marshal_public_key), + CBBFunctionToVector<decltype(&EVP_marshal_public_key), EVP_marshal_public_key>(pkey_.get())); return std::make_unique<PublicKey>( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseRs256), *cbor_bytes, + static_cast<int32_t>(CoseAlgorithmIdentifier::kRs256), *cbor_bytes, std::move(der_bytes)); } @@ -185,6 +187,77 @@ class RSAPrivateKey : public EVPBackedPrivateKey { } }; +class Ed25519PrivateKey : public EVPBackedPrivateKey { + public: + Ed25519PrivateKey() + : EVPBackedPrivateKey(EVP_PKEY_ED25519, ConfigureKeyGen) {} + + explicit Ed25519PrivateKey(bssl::UniquePtr<EVP_PKEY> pkey) + : EVPBackedPrivateKey(std::move(pkey)) {} + + std::unique_ptr<PublicKey> GetPublicKey() const override { + uint8_t public_key[32]; + size_t public_key_len = sizeof(public_key); + CHECK( + EVP_PKEY_get_raw_public_key(pkey_.get(), public_key, &public_key_len) && + public_key_len == sizeof(public_key)); + + cbor::Value::MapValue map; + map.emplace(static_cast<int64_t>(CoseKeyKey::kAlg), + static_cast<int64_t>(CoseAlgorithmIdentifier::kEdDSA)); + map.emplace(static_cast<int64_t>(CoseKeyKey::kKty), + static_cast<int64_t>(CoseKeyTypes::kOKP)); + map.emplace(static_cast<int64_t>(CoseKeyKey::kEllipticCurve), + static_cast<int64_t>(CoseCurves::kEd25519)); + map.emplace(static_cast<int64_t>(CoseKeyKey::kEllipticX), + base::span<const uint8_t>(public_key, sizeof(public_key))); + + base::Optional<std::vector<uint8_t>> cbor_bytes( + cbor::Writer::Write(cbor::Value(std::move(map)))); + + std::vector<uint8_t> der_bytes( + CBBFunctionToVector<decltype(&EVP_marshal_public_key), + EVP_marshal_public_key>(pkey_.get())); + + return std::make_unique<PublicKey>( + static_cast<int32_t>(CoseAlgorithmIdentifier::kRs256), *cbor_bytes, + std::move(der_bytes)); + } + + private: + static int ConfigureKeyGen(EVP_PKEY_CTX* ctx) { return 1; } +}; + +class InvalidForTestingPrivateKey : public VirtualFidoDevice::PrivateKey { + public: + InvalidForTestingPrivateKey() = default; + + std::vector<uint8_t> Sign(base::span<const uint8_t> message) override { + return {'s', 'i', 'g'}; + } + + std::vector<uint8_t> GetPKCS8PrivateKey() const override { + CHECK(false); + return {}; + } + + std::unique_ptr<PublicKey> GetPublicKey() const override { + cbor::Value::MapValue map; + map.emplace( + static_cast<int64_t>(CoseKeyKey::kAlg), + static_cast<int64_t>(CoseAlgorithmIdentifier::kInvalidForTesting)); + map.emplace(static_cast<int64_t>(CoseKeyKey::kKty), + static_cast<int64_t>(CoseKeyTypes::kInvalidForTesting)); + + base::Optional<std::vector<uint8_t>> cbor_bytes( + cbor::Writer::Write(cbor::Value(std::move(map)))); + + return std::make_unique<PublicKey>( + static_cast<int32_t>(CoseAlgorithmIdentifier::kInvalidForTesting), + *cbor_bytes, base::nullopt); + } +}; + } // namespace // VirtualFidoDevice::PrivateKey ---------------------------------------------- @@ -222,11 +295,39 @@ VirtualFidoDevice::PrivateKey::FromPKCS8( case EVP_PKEY_RSA: return std::unique_ptr<PrivateKey>(new RSAPrivateKey(std::move(pkey))); + case EVP_PKEY_ED25519: + return std::unique_ptr<PrivateKey>( + new Ed25519PrivateKey(std::move(pkey))); + default: return base::nullopt; } } +// static +std::unique_ptr<VirtualFidoDevice::PrivateKey> +VirtualFidoDevice::PrivateKey::FreshP256Key() { + return std::make_unique<P256PrivateKey>(); +} + +// static +std::unique_ptr<VirtualFidoDevice::PrivateKey> +VirtualFidoDevice::PrivateKey::FreshRSAKey() { + return std::make_unique<RSAPrivateKey>(); +} + +// static +std::unique_ptr<VirtualFidoDevice::PrivateKey> +VirtualFidoDevice::PrivateKey::FreshEd25519Key() { + return std::make_unique<Ed25519PrivateKey>(); +} + +// static +std::unique_ptr<VirtualFidoDevice::PrivateKey> +VirtualFidoDevice::PrivateKey::FreshInvalidForTestingKey() { + return std::make_unique<InvalidForTestingPrivateKey>(); +} + // VirtualFidoDevice::RegistrationData ---------------------------------------- VirtualFidoDevice::RegistrationData::RegistrationData() = default; @@ -242,8 +343,9 @@ VirtualFidoDevice::RegistrationData::RegistrationData(RegistrationData&& data) = default; VirtualFidoDevice::RegistrationData::~RegistrationData() = default; -VirtualFidoDevice::RegistrationData& VirtualFidoDevice::RegistrationData:: -operator=(RegistrationData&& other) = default; +VirtualFidoDevice::RegistrationData& +VirtualFidoDevice::RegistrationData::operator=(RegistrationData&& other) = + default; // VirtualFidoDevice::State --------------------------------------------------- @@ -258,7 +360,7 @@ bool VirtualFidoDevice::State::InjectRegistration( auto application_parameter = fido_parsing_utils::CreateSHA256Hash(relying_party_id); - RegistrationData registration(FreshP256Key(), + RegistrationData registration(PrivateKey::FreshP256Key(), std::move(application_parameter), 0 /* signature counter */); @@ -304,7 +406,7 @@ bool VirtualFidoDevice::State::InjectResidentKey( device::PublicKeyCredentialUserEntity user) { return InjectResidentKey(std::move(credential_id), std::move(rp), std::move(user), /*signature_counter=*/0, - FreshP256Key()); + PrivateKey::FreshP256Key()); } bool VirtualFidoDevice::State::InjectResidentKey( @@ -336,18 +438,6 @@ std::vector<uint8_t> VirtualFidoDevice::GetAttestationKey() { return fido_parsing_utils::Materialize(kAttestationKey); } -// static -std::unique_ptr<VirtualFidoDevice::PrivateKey> -VirtualFidoDevice::FreshP256Key() { - return std::make_unique<P256PrivateKey>(); -} - -// static -std::unique_ptr<VirtualFidoDevice::PrivateKey> -VirtualFidoDevice::FreshRSAKey() { - return std::unique_ptr<PrivateKey>(new RSAPrivateKey); -} - bool VirtualFidoDevice::Sign(crypto::ECPrivateKey* private_key, base::span<const uint8_t> sign_buffer, std::vector<uint8_t>* signature) { @@ -387,8 +477,21 @@ VirtualFidoDevice::GenerateAttestationCertificate( 8 - transport_bit - 1, // trailing bits unused 0b10000000 >> transport_bit, // transport }; + + // https://www.w3.org/TR/webauthn/#packed-attestation-cert-requirements + // The Basic Constraints extension MUST have the CA component set to false. + static constexpr uint8_t kBasicContraintsOID[] = {0x55, 0x1d, 0x13}; + static constexpr uint8_t kBasicContraintsContents[] = { + 0x30, // SEQUENCE + 0x03, // three bytes long + 0x01, // BOOLEAN + 0x01, // one byte long + 0x00, // false + }; + const std::vector<net::x509_util::Extension> extensions = { - {kTransportTypesOID, false /* not critical */, kTransportTypesContents}, + {kTransportTypesOID, /*critical=*/false, kTransportTypesContents}, + {kBasicContraintsOID, /*critical=*/true, kBasicContraintsContents}, }; // https://w3c.github.io/webauthn/#sctn-packed-attestation-cert-requirements @@ -474,7 +577,9 @@ FidoTransportProtocol VirtualFidoDevice::DeviceTransport() const { // static std::string VirtualFidoDevice::MakeVirtualFidoDeviceId() { - return "VirtualFidoDevice-" + base::RandBytesAsString(32); + uint8_t rand_bytes[32]; + base::RandBytes(rand_bytes, sizeof(rand_bytes)); + return "VirtualFidoDevice-" + base::HexEncode(rand_bytes); } } // namespace device diff --git a/chromium/device/fido/virtual_fido_device.h b/chromium/device/fido/virtual_fido_device.h index ba6348e742a..e39677127ef 100644 --- a/chromium/device/fido/virtual_fido_device.h +++ b/chromium/device/fido/virtual_fido_device.h @@ -32,7 +32,7 @@ class ECPrivateKey; namespace device { -class PublicKey; +struct PublicKey; constexpr size_t kMaxPinRetries = 8; @@ -50,6 +50,20 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { static base::Optional<std::unique_ptr<PrivateKey>> FromPKCS8( base::span<const uint8_t> pkcs8_private_key); + // FreshP256Key returns a randomly generated P-256 PrivateKey. + static std::unique_ptr<PrivateKey> FreshP256Key(); + + // FreshRSAKey returns a randomly generated RSA PrivateKey. + static std::unique_ptr<PrivateKey> FreshRSAKey(); + + // FreshEd25519Key returns a randomly generated Ed25519 PrivateKey. + static std::unique_ptr<PrivateKey> FreshEd25519Key(); + + // FreshInvalidForTestingKey returns a dummy |PrivateKey| with a special + // algorithm number that is used to test that unknown public keys are + // handled correctly. + static std::unique_ptr<PrivateKey> FreshInvalidForTestingKey(); + virtual ~PrivateKey(); // Sign returns a signature over |message|. @@ -151,6 +165,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { // The random PIN token that is returned as a placeholder for the PIN // itself. uint8_t pin_token[32]; + // The permissions parameter for |pin_token|. + uint8_t pin_uv_token_permissions = 0; + // The permissions RPID for |pin_token|. + base::Optional<std::string> pin_uv_token_rpid; // Number of internal UV retries remaining. int uv_retries = kMaxUvRetries; @@ -258,8 +276,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { scoped_refptr<State> NewReferenceToState() const { return state_; } - static std::unique_ptr<PrivateKey> FreshP256Key(); - static std::unique_ptr<PrivateKey> FreshRSAKey(); static bool Sign(crypto::ECPrivateKey* private_key, base::span<const uint8_t> sign_buffer, std::vector<uint8_t>* signature); diff --git a/chromium/device/fido/virtual_u2f_device.cc b/chromium/device/fido/virtual_u2f_device.cc index a0567217c8d..b8a77cdc3a2 100644 --- a/chromium/device/fido/virtual_u2f_device.cc +++ b/chromium/device/fido/virtual_u2f_device.cc @@ -143,7 +143,7 @@ base::Optional<std::vector<uint8_t>> VirtualU2fDevice::DoRegister( // Create key to register. // Note: Non-deterministic, you need to mock this out if you rely on // deterministic behavior. - std::unique_ptr<PrivateKey> private_key(FreshP256Key()); + std::unique_ptr<PrivateKey> private_key(PrivateKey::FreshP256Key()); const std::vector<uint8_t> x962 = private_key->GetX962PublicKey(); // Our key handles are simple hashes of the public key. diff --git a/chromium/device/fido/win/authenticator.cc b/chromium/device/fido/win/authenticator.cc index 6595896329a..29901515f03 100644 --- a/chromium/device/fido/win/authenticator.cc +++ b/chromium/device/fido/win/authenticator.cc @@ -188,6 +188,10 @@ bool WinWebAuthnApiAuthenticator::SupportsCredProtectExtension() const { return win_api_->Version() >= WEBAUTHN_API_VERSION_2; } +bool WinWebAuthnApiAuthenticator::SupportsHMACSecretExtension() const { + return true; +} + const base::Optional<AuthenticatorSupportedOptions>& WinWebAuthnApiAuthenticator::Options() const { // The request can potentially be fulfilled by any device that Windows diff --git a/chromium/device/fido/win/authenticator.h b/chromium/device/fido/win/authenticator.h index fd66a029c4e..fb3d59f14dd 100644 --- a/chromium/device/fido/win/authenticator.h +++ b/chromium/device/fido/win/authenticator.h @@ -63,6 +63,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) WinWebAuthnApiAuthenticator // SupportsCredProtectExtension returns whether the native API supports the // credProtect CTAP extension. bool SupportsCredProtectExtension() const override; + bool SupportsHMACSecretExtension() const override; const base::Optional<AuthenticatorSupportedOptions>& Options() const override; base::Optional<FidoTransportProtocol> AuthenticatorTransport() const override; bool IsWinNativeApiAuthenticator() const override; diff --git a/chromium/device/fido/win/fake_webauthn_api.cc b/chromium/device/fido/win/fake_webauthn_api.cc index 049ab511a58..3ec8e1ac95a 100644 --- a/chromium/device/fido/win/fake_webauthn_api.cc +++ b/chromium/device/fido/win/fake_webauthn_api.cc @@ -5,16 +5,65 @@ #include "device/fido/win/fake_webauthn_api.h" #include "base/check.h" +#include "base/containers/span.h" #include "base/notreached.h" #include "base/optional.h" +#include "base/stl_util.h" #include "base/strings/string16.h" +#include "base/strings/string_piece_forward.h" +#include "base/strings/utf_string_conversions.h" +#include "components/cbor/values.h" +#include "crypto/sha2.h" +#include "device/fido/attested_credential_data.h" +#include "device/fido/authenticator_data.h" #include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" namespace device { +struct FakeWinWebAuthnApi::WebAuthnAssertionEx { + std::vector<uint8_t> credential_id; + std::vector<uint8_t> authenticator_data; + std::vector<uint8_t> signature; + WEBAUTHN_ASSERTION assertion; +}; + FakeWinWebAuthnApi::FakeWinWebAuthnApi() = default; -FakeWinWebAuthnApi::~FakeWinWebAuthnApi() = default; +FakeWinWebAuthnApi::~FakeWinWebAuthnApi() { + // Ensure callers free unmanaged pointers returned by the real Windows API. + DCHECK(returned_attestations_.empty()); + DCHECK(returned_assertions_.empty()); +} + +bool FakeWinWebAuthnApi::InjectNonDiscoverableCredential( + base::span<const uint8_t> credential_id, + const std::string& rp_id) { + bool was_inserted; + std::tie(std::ignore, was_inserted) = registrations_.insert( + {fido_parsing_utils::Materialize(credential_id), + RegistrationData(VirtualFidoDevice::PrivateKey::FreshP256Key(), + fido_parsing_utils::CreateSHA256Hash(rp_id), + /*counter=*/0)}); + return was_inserted; +} + +bool FakeWinWebAuthnApi::InjectDiscoverableCredential( + base::span<const uint8_t> credential_id, + device::PublicKeyCredentialRpEntity rp, + device::PublicKeyCredentialUserEntity user) { + RegistrationData registration(VirtualFidoDevice::PrivateKey::FreshP256Key(), + fido_parsing_utils::CreateSHA256Hash(rp.id), + /*counter=*/0); + registration.is_resident = true; + registration.user = std::move(user); + registration.rp = std::move(rp); + + bool was_inserted; + std::tie(std::ignore, was_inserted) = + registrations_.insert({fido_parsing_utils::Materialize(credential_id), + std::move(registration)}); + return was_inserted; +} bool FakeWinWebAuthnApi::IsAvailable() const { return is_available_; @@ -34,9 +83,15 @@ HRESULT FakeWinWebAuthnApi::AuthenticatorMakeCredential( PCWEBAUTHN_CLIENT_DATA client_data, PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS options, PWEBAUTHN_CREDENTIAL_ATTESTATION* credential_attestation_ptr) { + // TODO(martinkr): Implement to create a credential in |registrations_|. DCHECK(is_available_); - *credential_attestation_ptr = &attestation_; - return result_; + if (result_override_ != S_OK) { + return result_override_; + } + + returned_attestations_.push_back(FakeAttestation()); + *credential_attestation_ptr = &returned_attestations_.back(); + return S_OK; } HRESULT FakeWinWebAuthnApi::AuthenticatorGetAssertion( @@ -45,9 +100,104 @@ HRESULT FakeWinWebAuthnApi::AuthenticatorGetAssertion( PCWEBAUTHN_CLIENT_DATA client_data, PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS options, PWEBAUTHN_ASSERTION* assertion_ptr) { + // TODO(martinkr): support AppID extension DCHECK(is_available_); - *assertion_ptr = &assertion_; - return result_; + + if (result_override_ != S_OK) { + return result_override_; + } + + const auto rp_id_hash = + fido_parsing_utils::CreateSHA256Hash(base::UTF16ToUTF8(rp_id)); + + RegistrationData* registration = nullptr; + base::span<const uint8_t> credential_id; + PCWEBAUTHN_CREDENTIAL_LIST allow_credentials = options->pAllowCredentialList; + + // Find a matching resident credential if allow list is empty. Windows + // provides its own account selector, so only one credential gets returned. + // Pretend the user selected the first match. + if (allow_credentials->cCredentials == 0) { + for (auto& registration_pair : registrations_) { + if (!registration_pair.second.is_resident || + registration_pair.second.application_parameter != rp_id_hash) { + continue; + } + credential_id = registration_pair.first; + registration = ®istration_pair.second; + break; + } + } + + for (size_t i = 0; i < allow_credentials->cCredentials; i++) { + PWEBAUTHN_CREDENTIAL_EX credential = allow_credentials->ppCredentials[i]; + base::span<const uint8_t> allow_credential_id(credential->pbId, + credential->cbId); + auto it = registrations_.find(allow_credential_id); + if (it == registrations_.end() || + it->second.application_parameter != rp_id_hash) { + continue; + } + credential_id = it->first; + registration = &it->second; + break; + } + + if (!registration) { + return NTE_NOT_FOUND; + } + DCHECK(!credential_id.empty()); + + WebAuthnAssertionEx result; + result.credential_id = fido_parsing_utils::Materialize(credential_id); + result.authenticator_data = + AuthenticatorData( + registration->application_parameter, + /*user_present=*/true, + /*user_verified=*/options->dwUserVerificationRequirement != + WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED, + registration->counter++, + /*attested_credential_data=*/base::nullopt, + /*extensions=*/base::nullopt) + .SerializeToByteArray(); + + // Create the assertion signature. + std::vector<uint8_t> sign_data; + fido_parsing_utils::Append(&sign_data, result.authenticator_data); + fido_parsing_utils::Append( + &sign_data, crypto::SHA256Hash({client_data->pbClientDataJSON, + client_data->cbClientDataJSON})); + result.signature = + registration->private_key->Sign({sign_data.data(), sign_data.size()}); + + // Fill in the WEBAUTHN_ASSERTION struct returned to the caller. + result.assertion = {}; + result.assertion.dwVersion = 1; + result.assertion.cbAuthenticatorData = result.authenticator_data.size(); + result.assertion.pbAuthenticatorData = reinterpret_cast<PBYTE>( + const_cast<uint8_t*>(result.authenticator_data.data())); + + result.assertion.cbSignature = result.signature.size(); + result.assertion.pbSignature = result.signature.data(); + result.assertion.Credential = {}; + result.assertion.Credential.dwVersion = 1; + result.assertion.Credential.cbId = result.credential_id.size(); + result.assertion.Credential.pbId = result.credential_id.data(); + result.assertion.Credential.pwszCredentialType = + WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY; + // TODO(martinkr): Return a user entity for requests with empty allow lists. + // (Though the CTAP2.0 spec allows that to be omitted if only a single + // credential matched.) + result.assertion.pbUserId = nullptr; + result.assertion.cbUserId = 0; + + // The real API hands out results in naked pointers and asks callers + // to call FreeAssertion() when they're done. We maintain ownership + // of the pointees in |returned_assertions_|. + returned_assertions_.push_back(std::move(result)); + *assertion_ptr = &returned_assertions_.back().assertion; + + return S_OK; } HRESULT FakeWinWebAuthnApi::CancelCurrentOperation(GUID* cancellation_id) { @@ -82,9 +232,28 @@ PCWSTR FakeWinWebAuthnApi::GetErrorName(HRESULT hr) { } void FakeWinWebAuthnApi::FreeCredentialAttestation( - PWEBAUTHN_CREDENTIAL_ATTESTATION) {} + PWEBAUTHN_CREDENTIAL_ATTESTATION credential_attestation) { + for (auto it = returned_attestations_.begin(); + it != returned_attestations_.end(); ++it) { + if (credential_attestation != &*it) { + continue; + } + returned_attestations_.erase(it); + return; + } + NOTREACHED(); +} -void FakeWinWebAuthnApi::FreeAssertion(PWEBAUTHN_ASSERTION pWebAuthNAssertion) { +void FakeWinWebAuthnApi::FreeAssertion(PWEBAUTHN_ASSERTION assertion) { + for (auto it = returned_assertions_.begin(); it != returned_assertions_.end(); + ++it) { + if (assertion != &it->assertion) { + continue; + } + returned_assertions_.erase(it); + return; + } + NOTREACHED(); } int FakeWinWebAuthnApi::Version() { @@ -110,29 +279,4 @@ WEBAUTHN_CREDENTIAL_ATTESTATION FakeWinWebAuthnApi::FakeAttestation() { return attestation; } -// static -WEBAUTHN_ASSERTION FakeWinWebAuthnApi::FakeAssertion() { - WEBAUTHN_CREDENTIAL credential = {}; - // No constant macro available because 1 is the current version - credential.dwVersion = 1; - credential.cbId = sizeof(test_data::kCredentialId); - credential.pbId = - reinterpret_cast<PBYTE>(const_cast<uint8_t*>(test_data::kCredentialId)); - credential.pwszCredentialType = L"public-key"; - - WEBAUTHN_ASSERTION assertion = {}; - // No constant macro available because 1 is the current version - assertion.dwVersion = 1; - assertion.cbAuthenticatorData = sizeof(test_data::kTestSignAuthenticatorData); - assertion.pbAuthenticatorData = reinterpret_cast<PBYTE>( - const_cast<uint8_t*>(test_data::kTestSignAuthenticatorData)); - assertion.cbSignature = sizeof(test_data::kCtap2GetAssertionSignature); - assertion.pbSignature = reinterpret_cast<PBYTE>( - const_cast<uint8_t*>(test_data::kCtap2GetAssertionSignature)); - assertion.Credential = credential; - assertion.pbUserId = nullptr; - assertion.cbUserId = 0; - return assertion; -} - } // namespace device diff --git a/chromium/device/fido/win/fake_webauthn_api.h b/chromium/device/fido/win/fake_webauthn_api.h index 88a901df9f8..0e405c84a77 100644 --- a/chromium/device/fido/win/fake_webauthn_api.h +++ b/chromium/device/fido/win/fake_webauthn_api.h @@ -5,23 +5,55 @@ #ifndef DEVICE_FIDO_WIN_FAKE_WEBAUTHN_API_H_ #define DEVICE_FIDO_WIN_FAKE_WEBAUTHN_API_H_ +#include <stdint.h> +#include <map> +#include <memory> +#include <vector> + #include "base/component_export.h" #include "device/fido/public_key_credential_descriptor.h" #include "device/fido/public_key_credential_rp_entity.h" #include "device/fido/public_key_credential_user_entity.h" +#include "device/fido/virtual_fido_device.h" #include "device/fido/win/webauthn_api.h" namespace device { +// FakeWinWebAuthnApi is a test fake to use instead of the real Windows WebAuthn +// API implemented by webauthn.dll. +// +// The fake supports injecting discoverable and non-discoverable credentials +// that can be challenged via AuthenticatorGetAssertion(). +// AuthenticatorMakeCredential() returns a mock response and does not actually +// create a credential. +// +// Tests can inject a FakeWinWebAuthnApi via VirtualFidoDeviceFactory. class COMPONENT_EXPORT(DEVICE_FIDO) FakeWinWebAuthnApi : public WinWebAuthnApi { public: + using RegistrationData = VirtualFidoDevice::RegistrationData; + FakeWinWebAuthnApi(); ~FakeWinWebAuthnApi() override; + // Injects a non-discoverable credential that can be challenged with + // AuthenticatorGetAssertion(). + bool InjectNonDiscoverableCredential(base::span<const uint8_t> credential_id, + const std::string& relying_party_id); + + // Injects a discoverable credential that can be challenged with + // AuthenticatorGetAssertion(). + bool InjectDiscoverableCredential(base::span<const uint8_t> credential_id, + device::PublicKeyCredentialRpEntity rp, + device::PublicKeyCredentialUserEntity user); + // Inject the return value for WinWebAuthnApi::IsAvailable(). void set_available(bool available) { is_available_ = available; } - void set_hresult(HRESULT result) { result_ = result; } + // Injects an HRESULT to return from AuthenticatorMakeCredential() and + // AuthenticatorGetAssertion(). If set to anything other than |S_OK|, + // AuthenticatorGetAssertion() will immediately terminate the request with + // that value and not return a WEBAUTHN_ASSERTION. + void set_hresult(HRESULT result) { result_override_ = result; } // Inject the return value for // WinWebAuthnApi::IsUserverifyingPlatformAuthenticatorAvailable(). @@ -54,15 +86,24 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FakeWinWebAuthnApi : public WinWebAuthnApi { int Version() override; private: + struct WebAuthnAssertionEx; + static WEBAUTHN_CREDENTIAL_ATTESTATION FakeAttestation(); - static WEBAUTHN_ASSERTION FakeAssertion(); bool is_available_ = true; bool is_uvpaa_ = false; int version_ = WEBAUTHN_API_VERSION_2; - WEBAUTHN_CREDENTIAL_ATTESTATION attestation_ = FakeAttestation(); - WEBAUTHN_ASSERTION assertion_ = FakeAssertion(); - HRESULT result_ = S_OK; + HRESULT result_override_ = S_OK; + + // Owns the attestations returned by AuthenticatorMakeCredential(). + std::vector<WEBAUTHN_CREDENTIAL_ATTESTATION> returned_attestations_; + + // Owns assertions returned by AuthenticatorGetAssertion(). + std::vector<WebAuthnAssertionEx> returned_assertions_; + + std:: + map<std::vector<uint8_t>, RegistrationData, fido_parsing_utils::RangeLess> + registrations_; }; } // namespace device diff --git a/chromium/device/fido/win/webauthn_api.cc b/chromium/device/fido/win/webauthn_api.cc index 5f17312cde0..4706be4ec8c 100644 --- a/chromium/device/fido/win/webauthn_api.cc +++ b/chromium/device/fido/win/webauthn_api.cc @@ -295,13 +295,6 @@ AuthenticatorMakeCredentialBlocking(WinWebAuthnApi* webauthn_api, }; WEBAUTHN_CREDENTIAL_ATTESTATION* credential_attestation = nullptr; - std::unique_ptr<WEBAUTHN_CREDENTIAL_ATTESTATION, - std::function<void(PWEBAUTHN_CREDENTIAL_ATTESTATION)>> - credential_attestation_deleter( - credential_attestation, - [webauthn_api](PWEBAUTHN_CREDENTIAL_ATTESTATION ptr) { - webauthn_api->FreeCredentialAttestation(ptr); - }); FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorMakeCredential(" << "rp=" << rp_info << ", user=" << user_info @@ -312,6 +305,14 @@ AuthenticatorMakeCredentialBlocking(WinWebAuthnApi* webauthn_api, HRESULT hresult = webauthn_api->AuthenticatorMakeCredential( h_wnd, &rp_info, &user_info, &cose_credential_parameters, &client_data, &options, &credential_attestation); + std::unique_ptr<WEBAUTHN_CREDENTIAL_ATTESTATION, + std::function<void(PWEBAUTHN_CREDENTIAL_ATTESTATION)>> + credential_attestation_deleter( + credential_attestation, + [webauthn_api](PWEBAUTHN_CREDENTIAL_ATTESTATION ptr) { + webauthn_api->FreeCredentialAttestation(ptr); + }); + if (hresult != S_OK) { FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorMakeCredential()=" << webauthn_api->GetErrorName(hresult); @@ -403,16 +404,17 @@ AuthenticatorGetAssertionBlocking(WinWebAuthnApi* webauthn_api, }; WEBAUTHN_ASSERTION* assertion = nullptr; - std::unique_ptr<WEBAUTHN_ASSERTION, std::function<void(PWEBAUTHN_ASSERTION)>> - assertion_deleter(assertion, [webauthn_api](PWEBAUTHN_ASSERTION ptr) { - webauthn_api->FreeAssertion(ptr); - }); FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorGetAssertion(" << "rp_id=\"" << rp_id16 << "\", client_data=" << client_data << ", options=" << options << ")"; HRESULT hresult = webauthn_api->AuthenticatorGetAssertion( h_wnd, base::as_wcstr(rp_id16), &client_data, &options, &assertion); + std::unique_ptr<WEBAUTHN_ASSERTION, std::function<void(PWEBAUTHN_ASSERTION)>> + assertion_deleter(assertion, [webauthn_api](PWEBAUTHN_ASSERTION ptr) { + webauthn_api->FreeAssertion(ptr); + }); + if (hresult != S_OK) { FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorGetAssertion()=" << webauthn_api->GetErrorName(hresult); |