// Copyright 2019 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/credential_management.h" #include "base/check_op.h" #include "components/cbor/reader.h" #include "components/cbor/values.h" #include "components/cbor/writer.h" #include "device/fido/fido_parsing_utils.h" #include "device/fido/pin.h" #include "third_party/boringssl/src/include/openssl/hmac.h" #include "third_party/boringssl/src/include/openssl/sha.h" namespace device { CredentialManagementRequest::CredentialManagementRequest( Version version_, CredentialManagementSubCommand subcommand_, absl::optional params_) : version(version_), subcommand(subcommand_), params(std::move(params_)) {} CredentialManagementRequest::CredentialManagementRequest( CredentialManagementRequest&&) = default; CredentialManagementRequest& CredentialManagementRequest::operator=( CredentialManagementRequest&&) = default; CredentialManagementRequest::~CredentialManagementRequest() = default; // static CredentialManagementRequest CredentialManagementRequest::ForGetCredsMetadata( Version version, const pin::TokenResponse& token) { CredentialManagementRequest request( version, CredentialManagementSubCommand::kGetCredsMetadata, /*params=*/absl::nullopt); std::tie(request.pin_protocol, request.pin_auth) = token.PinAuth({{static_cast( CredentialManagementSubCommand::kGetCredsMetadata)}}); return request; } // static CredentialManagementRequest CredentialManagementRequest::ForEnumerateRPsBegin( Version version, const pin::TokenResponse& token) { CredentialManagementRequest request( version, CredentialManagementSubCommand::kEnumerateRPsBegin, /*params=*/absl::nullopt); std::tie(request.pin_protocol, request.pin_auth) = token.PinAuth({{static_cast( CredentialManagementSubCommand::kEnumerateRPsBegin)}}); return request; } // static CredentialManagementRequest CredentialManagementRequest::ForEnumerateRPsGetNext( Version version) { return CredentialManagementRequest( version, CredentialManagementSubCommand::kEnumerateRPsGetNextRP, /*params=*/absl::nullopt); } // static CredentialManagementRequest CredentialManagementRequest::ForEnumerateCredentialsBegin( Version version, const pin::TokenResponse& token, std::array rp_id_hash) { cbor::Value::MapValue params_map; params_map.emplace( static_cast(CredentialManagementRequestParamKey::kRPIDHash), std::move(rp_id_hash)); std::vector pin_auth_bytes = *cbor::Writer::Write(cbor::Value(params_map)); CredentialManagementRequest request( version, CredentialManagementSubCommand::kEnumerateCredentialsBegin, std::move(params_map)); pin_auth_bytes.insert( pin_auth_bytes.begin(), static_cast( CredentialManagementSubCommand::kEnumerateCredentialsBegin)); std::tie(request.pin_protocol, request.pin_auth) = token.PinAuth(pin_auth_bytes); return request; } // static CredentialManagementRequest CredentialManagementRequest::ForEnumerateCredentialsGetNext(Version version) { return CredentialManagementRequest( version, CredentialManagementSubCommand::kEnumerateCredentialsGetNextCredential, /*params=*/absl::nullopt); } // static CredentialManagementRequest CredentialManagementRequest::ForDeleteCredential( Version version, const pin::TokenResponse& token, const PublicKeyCredentialDescriptor& credential_id) { cbor::Value::MapValue params_map; params_map.emplace( static_cast(CredentialManagementRequestParamKey::kCredentialID), AsCBOR(credential_id)); std::vector pin_auth_bytes = *cbor::Writer::Write(cbor::Value(params_map)); CredentialManagementRequest request( version, CredentialManagementSubCommand::kDeleteCredential, std::move(params_map)); pin_auth_bytes.insert( pin_auth_bytes.begin(), static_cast(CredentialManagementSubCommand::kDeleteCredential)); std::tie(request.pin_protocol, request.pin_auth) = token.PinAuth(pin_auth_bytes); return request; } // static CredentialManagementRequest CredentialManagementRequest::ForUpdateUserInformation( Version version, const pin::TokenResponse& token, const PublicKeyCredentialDescriptor& credential_id, const PublicKeyCredentialUserEntity& updated_user) { cbor::Value::MapValue params_map; params_map.emplace( static_cast(CredentialManagementRequestParamKey::kCredentialID), AsCBOR(credential_id)); params_map.emplace( static_cast(CredentialManagementRequestParamKey::kUser), AsCBOR(updated_user)); std::vector pin_auth_bytes = *cbor::Writer::Write(cbor::Value(params_map)); CredentialManagementRequest request( version, CredentialManagementSubCommand::kUpdateUserInformation, std::move(params_map)); pin_auth_bytes.insert( pin_auth_bytes.begin(), static_cast( CredentialManagementSubCommand::kUpdateUserInformation)); std::tie(request.pin_protocol, request.pin_auth) = token.PinAuth(pin_auth_bytes); return request; } // static absl::optional CredentialsMetadataResponse::Parse( const absl::optional& cbor_response) { CredentialsMetadataResponse response; if (!cbor_response || !cbor_response->is_map()) { return absl::nullopt; } const cbor::Value::MapValue& response_map = cbor_response->GetMap(); auto it = response_map.find(cbor::Value(static_cast( CredentialManagementResponseKey::kExistingResidentCredentialsCount))); if (it == response_map.end() || !it->second.is_unsigned()) { return absl::nullopt; } const int64_t existing_count = it->second.GetUnsigned(); if (existing_count > std::numeric_limits::max()) { return absl::nullopt; } response.num_existing_credentials = static_cast(existing_count); it = response_map.find(cbor::Value( static_cast(CredentialManagementResponseKey:: kMaxPossibleRemainingResidentCredentialsCount))); if (it == response_map.end() || !it->second.is_unsigned()) { return absl::nullopt; } const int64_t remaining_count = it->second.GetUnsigned(); if (remaining_count > std::numeric_limits::max()) { return absl::nullopt; } response.num_estimated_remaining_credentials = static_cast(remaining_count); return response; } // static absl::optional EnumerateRPsResponse::Parse( bool expect_rp_count, const absl::optional& cbor_response) { if (!cbor_response) { // Some authenticators send an empty response if there are no RPs (though // the spec doesn't say that). return EnumerateRPsResponse(absl::nullopt, absl::nullopt, 0); } if (!cbor_response->is_map() || cbor_response->GetMap().empty()) { return absl::nullopt; } const cbor::Value::MapValue& response_map = cbor_response->GetMap(); size_t rp_count = 0; auto it = response_map.find(cbor::Value( static_cast(CredentialManagementResponseKey::kTotalRPs))); if (!expect_rp_count && it != response_map.end()) { return absl::nullopt; } if (expect_rp_count) { if (it == response_map.end() || !it->second.is_unsigned() || it->second.GetUnsigned() > std::numeric_limits::max()) { return absl::nullopt; } rp_count = static_cast(it->second.GetUnsigned()); if (rp_count == 0) { if (response_map.size() != 1) { return absl::nullopt; } return EnumerateRPsResponse(absl::nullopt, absl::nullopt, 0); } } it = response_map.find( cbor::Value(static_cast(CredentialManagementResponseKey::kRP))); if (it == response_map.end()) { return absl::nullopt; } auto opt_rp = PublicKeyCredentialRpEntity::CreateFromCBORValue(it->second); if (!opt_rp) { return absl::nullopt; } it = response_map.find(cbor::Value( static_cast(CredentialManagementResponseKey::kRPIDHash))); if (it == response_map.end() || !it->second.is_bytestring()) { return absl::nullopt; } const std::vector& rp_id_hash_bytes = it->second.GetBytestring(); if (rp_id_hash_bytes.size() != kRpIdHashLength) { return absl::nullopt; } std::array rp_id_hash; std::copy_n(rp_id_hash_bytes.begin(), kRpIdHashLength, rp_id_hash.begin()); return EnumerateRPsResponse(std::move(*opt_rp), std::move(rp_id_hash), rp_count); } // static bool EnumerateRPsResponse::StringFixupPredicate( const std::vector& path) { // In the rp field (0x04), "name" may be truncated. if (path.size() != 2 || !path[0]->is_unsigned() || path[0]->GetUnsigned() != static_cast(CredentialManagementResponseKey::kRP) || !path[1]->is_string()) { return false; } return path[1]->GetString() == "name"; } EnumerateRPsResponse::EnumerateRPsResponse(EnumerateRPsResponse&&) = default; EnumerateRPsResponse& EnumerateRPsResponse::operator=(EnumerateRPsResponse&&) = default; EnumerateRPsResponse::~EnumerateRPsResponse() = default; EnumerateRPsResponse::EnumerateRPsResponse( absl::optional rp_, absl::optional> rp_id_hash_, size_t rp_count_) : rp(std::move(rp_)), rp_id_hash(std::move(rp_id_hash_)), rp_count(rp_count_) {} // static absl::optional EnumerateCredentialsResponse::Parse( bool expect_credential_count, const absl::optional& cbor_response) { if (!cbor_response || !cbor_response->is_map()) { // Note that some authenticators may send an empty response if they don't // have a credential for a given RP ID hash (though the spec doesn't say // that). However, that case should not be reached from // CredentialManagementHandler. return absl::nullopt; } const cbor::Value::MapValue& response_map = cbor_response->GetMap(); auto it = response_map.find( cbor::Value(static_cast(CredentialManagementResponseKey::kUser))); if (it == response_map.end()) { return absl::nullopt; } auto opt_user = PublicKeyCredentialUserEntity::CreateFromCBORValue(it->second); if (!opt_user) { return absl::nullopt; } it = response_map.find(cbor::Value( static_cast(CredentialManagementResponseKey::kCredentialID))); if (it == response_map.end()) { return absl::nullopt; } auto opt_credential_id = PublicKeyCredentialDescriptor::CreateFromCBORValue(it->second); if (!opt_credential_id) { return absl::nullopt; } // Ignore the public key's value. it = response_map.find(cbor::Value( static_cast(CredentialManagementResponseKey::kPublicKey))); if (it == response_map.end() || !it->second.is_map()) { return absl::nullopt; } size_t credential_count = 0; if (!expect_credential_count) { if (response_map.find(cbor::Value(static_cast( CredentialManagementResponseKey::kTotalCredentials))) != response_map.end()) { return absl::nullopt; } } else { it = response_map.find(cbor::Value( static_cast(CredentialManagementResponseKey::kTotalCredentials))); if (it == response_map.end() || !it->second.is_unsigned() || it->second.GetUnsigned() == 0 || it->second.GetUnsigned() > std::numeric_limits::max()) { return absl::nullopt; } credential_count = static_cast(it->second.GetUnsigned()); } return EnumerateCredentialsResponse( std::move(*opt_user), std::move(*opt_credential_id), credential_count); } // static bool EnumerateCredentialsResponse::StringFixupPredicate( const std::vector& path) { // In the user field (0x06), "name" or "displayName" may be truncated. if (path.size() != 2 || !path[0]->is_unsigned() || path[0]->GetUnsigned() != static_cast(CredentialManagementResponseKey::kUser) || !path[1]->is_string()) { return false; } const std::string& user_key = path[1]->GetString(); return user_key == "name" || user_key == "displayName"; } EnumerateCredentialsResponse::EnumerateCredentialsResponse( EnumerateCredentialsResponse&&) = default; EnumerateCredentialsResponse& EnumerateCredentialsResponse::operator=( EnumerateCredentialsResponse&&) = default; EnumerateCredentialsResponse::~EnumerateCredentialsResponse() = default; EnumerateCredentialsResponse::EnumerateCredentialsResponse( PublicKeyCredentialUserEntity user_, PublicKeyCredentialDescriptor credential_id_, size_t credential_count_) : user(std::move(user_)), credential_id(std::move(credential_id_)), credential_count(credential_count_) {} AggregatedEnumerateCredentialsResponse::AggregatedEnumerateCredentialsResponse( PublicKeyCredentialRpEntity rp_) : rp(std::move(rp_)), credentials() {} AggregatedEnumerateCredentialsResponse::AggregatedEnumerateCredentialsResponse( AggregatedEnumerateCredentialsResponse&&) = default; AggregatedEnumerateCredentialsResponse& AggregatedEnumerateCredentialsResponse::operator=( AggregatedEnumerateCredentialsResponse&&) = default; AggregatedEnumerateCredentialsResponse:: ~AggregatedEnumerateCredentialsResponse() = default; std::pair> AsCTAPRequestValuePair(const CredentialManagementRequest& request) { cbor::Value::MapValue request_map; request_map.emplace( static_cast(CredentialManagementRequestKey::kSubCommand), static_cast(request.subcommand)); if (request.params) { request_map.emplace( static_cast(CredentialManagementRequestKey::kSubCommandParams), *request.params); } DCHECK_EQ(request.pin_protocol.has_value(), request.pin_auth.has_value()); if (request.pin_auth) { request_map.emplace( static_cast(CredentialManagementRequestKey::kPinProtocol), static_cast(*request.pin_protocol)); request_map.emplace( static_cast(CredentialManagementRequestKey::kPinAuth), *request.pin_auth); } return {request.version == CredentialManagementRequest::kPreview ? CtapRequestCommand::kAuthenticatorCredentialManagementPreview : CtapRequestCommand::kAuthenticatorCredentialManagement, cbor::Value(std::move(request_map))}; } } // namespace device