blob: 78850c6ba9c917feb19a619c666a5772518542e4 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/trusted_vault/trusted_vault_connection_impl.h"
#include <string>
#include <utility>
#include "base/base64url.h"
#include "base/containers/span.h"
#include "base/files/important_file_writer.h"
#include "base/time/time.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/trusted_vault/download_keys_response_handler.h"
#include "components/trusted_vault/proto/vault.pb.h"
#include "components/trusted_vault/proto_string_bytes_conversion.h"
#include "components/trusted_vault/securebox.h"
#include "components/trusted_vault/trusted_vault_access_token_fetcher.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "components/trusted_vault/trusted_vault_crypto.h"
#include "components/trusted_vault/trusted_vault_request.h"
#include "components/trusted_vault/trusted_vault_server_constants.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace trusted_vault {
namespace {
// Returns security domain epoch if valid (>0) and nullopt otherwise.
absl::optional<int> GetLastKeyVersionFromJoinSecurityDomainsResponse(
const trusted_vault_pb::JoinSecurityDomainsResponse response) {
if (response.security_domain().current_epoch() > 0) {
return response.security_domain().current_epoch();
}
return absl::nullopt;
}
// Returns security domain epoch if input is a valid response for already exists
// error case and nullopt otherwise.
absl::optional<int> GetLastKeyVersionFromAlreadyExistsResponse(
const std::string& response_body) {
trusted_vault_pb::RPCStatus rpc_status;
rpc_status.ParseFromString(response_body);
for (const trusted_vault_pb::Proto3Any& status_detail :
rpc_status.details()) {
if (status_detail.type_url() != kJoinSecurityDomainsErrorDetailTypeURL) {
continue;
}
trusted_vault_pb::JoinSecurityDomainsErrorDetail error_detail;
error_detail.ParseFromString(status_detail.value());
return GetLastKeyVersionFromJoinSecurityDomainsResponse(
error_detail.already_exists_response());
}
return absl::nullopt;
}
std::vector<TrustedVaultKeyAndVersion> GetTrustedVaultKeysWithVersions(
const std::vector<std::vector<uint8_t>>& trusted_vault_keys,
int last_trusted_vault_key_version) {
const int first_key_version = last_trusted_vault_key_version -
static_cast<int>(trusted_vault_keys.size()) + 1;
std::vector<TrustedVaultKeyAndVersion> result;
for (size_t i = 0; i < trusted_vault_keys.size(); ++i) {
result.emplace_back(trusted_vault_keys[i], first_key_version + i);
}
return result;
}
trusted_vault_pb::SharedMemberKey CreateSharedMemberKey(
const TrustedVaultKeyAndVersion& trusted_vault_key_and_version,
const SecureBoxPublicKey& public_key) {
trusted_vault_pb::SharedMemberKey shared_member_key;
shared_member_key.set_epoch(trusted_vault_key_and_version.version);
const std::vector<uint8_t>& trusted_vault_key =
trusted_vault_key_and_version.key;
AssignBytesToProtoString(
ComputeTrustedVaultWrappedKey(public_key, trusted_vault_key),
shared_member_key.mutable_wrapped_key());
AssignBytesToProtoString(ComputeMemberProof(public_key, trusted_vault_key),
shared_member_key.mutable_member_proof());
return shared_member_key;
}
trusted_vault_pb::SecurityDomainMember CreateSecurityDomainMember(
const SecureBoxPublicKey& public_key,
AuthenticationFactorType authentication_factor_type) {
trusted_vault_pb::SecurityDomainMember member;
std::string public_key_string;
AssignBytesToProtoString(public_key.ExportToBytes(), &public_key_string);
std::string encoded_public_key;
base::Base64UrlEncode(public_key_string,
base::Base64UrlEncodePolicy::OMIT_PADDING,
&encoded_public_key);
member.set_name(kSecurityDomainMemberNamePrefix + encoded_public_key);
// Note: |public_key_string| using here is intentional, encoding is required
// only to compute member name.
member.set_public_key(public_key_string);
switch (authentication_factor_type) {
case AuthenticationFactorType::kPhysicalDevice:
member.set_member_type(
trusted_vault_pb::SecurityDomainMember::MEMBER_TYPE_PHYSICAL_DEVICE);
break;
case AuthenticationFactorType::kUnspecified:
member.set_member_type(
trusted_vault_pb::SecurityDomainMember::MEMBER_TYPE_UNSPECIFIED);
break;
}
return member;
}
trusted_vault_pb::JoinSecurityDomainsRequest CreateJoinSecurityDomainsRequest(
const std::vector<std::vector<uint8_t>>& trusted_vault_keys,
int last_trusted_vault_key_version,
const SecureBoxPublicKey& public_key,
AuthenticationFactorType authentication_factor_type,
absl::optional<int> authentication_factor_type_hint) {
trusted_vault_pb::JoinSecurityDomainsRequest request;
request.mutable_security_domain()->set_name(kSyncSecurityDomainName);
*request.mutable_security_domain_member() =
CreateSecurityDomainMember(public_key, authentication_factor_type);
for (const TrustedVaultKeyAndVersion& trusted_vault_key_and_version :
GetTrustedVaultKeysWithVersions(trusted_vault_keys,
last_trusted_vault_key_version)) {
*request.add_shared_member_key() =
CreateSharedMemberKey(trusted_vault_key_and_version, public_key);
}
if (authentication_factor_type_hint.has_value()) {
request.set_member_type_hint(authentication_factor_type_hint.value());
}
return request;
}
void RunRegisterAuthenticationFactorCallback(
TrustedVaultConnection::RegisterAuthenticationFactorCallback callback,
TrustedVaultRegistrationStatus status,
int last_key_version) {
std::move(callback).Run(status);
}
void RunRegisterDeviceWithoutKeysCallback(
TrustedVaultConnection::RegisterDeviceWithoutKeysCallback callback,
TrustedVaultRegistrationStatus status,
int last_key_version) {
std::move(callback).Run(
status, TrustedVaultKeyAndVersion{GetConstantTrustedVaultKey(),
last_key_version});
}
void ProcessJoinSecurityDomainsResponse(
TrustedVaultConnectionImpl::JoinSecurityDomainsCallback callback,
TrustedVaultRequest::HttpStatus http_status,
const std::string& response_body) {
switch (http_status) {
case TrustedVaultRequest::HttpStatus::kSuccess:
case TrustedVaultRequest::HttpStatus::kConflict:
break;
case TrustedVaultRequest::HttpStatus::kNetworkError:
std::move(callback).Run(TrustedVaultRegistrationStatus::kNetworkError,
/*last_key_version=*/0);
return;
case TrustedVaultRequest::HttpStatus::kOtherError:
std::move(callback).Run(TrustedVaultRegistrationStatus::kOtherError,
/*last_key_version=*/0);
return;
case TrustedVaultRequest::HttpStatus::kTransientAccessTokenFetchError:
std::move(callback).Run(
TrustedVaultRegistrationStatus::kTransientAccessTokenFetchError,
/*last_key_version=*/0);
return;
case TrustedVaultRequest::HttpStatus::kPersistentAccessTokenFetchError:
std::move(callback).Run(
TrustedVaultRegistrationStatus::kPersistentAccessTokenFetchError,
/*last_key_version=*/0);
return;
case TrustedVaultRequest::HttpStatus::
kPrimaryAccountChangeAccessTokenFetchError:
std::move(callback).Run(TrustedVaultRegistrationStatus::
kPrimaryAccountChangeAccessTokenFetchError,
/*last_key_version=*/0);
return;
case TrustedVaultRequest::HttpStatus::kNotFound:
case TrustedVaultRequest::HttpStatus::kBadRequest:
// Local trusted vault keys are outdated.
std::move(callback).Run(
TrustedVaultRegistrationStatus::kLocalDataObsolete,
/*last_key_version=*/0);
return;
}
absl::optional<int> last_key_version;
if (http_status == TrustedVaultRequest::HttpStatus::kConflict) {
last_key_version =
GetLastKeyVersionFromAlreadyExistsResponse(response_body);
} else {
trusted_vault_pb::JoinSecurityDomainsResponse response;
response.ParseFromString(response_body);
last_key_version =
GetLastKeyVersionFromJoinSecurityDomainsResponse(response);
}
if (!last_key_version.has_value()) {
std::move(callback).Run(TrustedVaultRegistrationStatus::kOtherError,
/*last_key_version=*/0);
return;
}
std::move(callback).Run(
http_status == TrustedVaultRequest::HttpStatus::kConflict
? TrustedVaultRegistrationStatus::kAlreadyRegistered
: TrustedVaultRegistrationStatus::kSuccess,
*last_key_version);
}
void ProcessDownloadKeysResponse(
std::unique_ptr<DownloadKeysResponseHandler> response_handler,
TrustedVaultConnection::DownloadNewKeysCallback callback,
TrustedVaultRequest::HttpStatus http_status,
const std::string& response_body) {
DownloadKeysResponseHandler::ProcessedResponse processed_response =
response_handler->ProcessResponse(http_status, response_body);
std::move(callback).Run(processed_response.status,
processed_response.new_keys,
processed_response.last_key_version);
}
void ProcessDownloadIsRecoverabilityDegradedResponse(
TrustedVaultConnection::IsRecoverabilityDegradedCallback callback,
TrustedVaultRequest::HttpStatus http_status,
const std::string& response_body) {
// TODO(crbug.com/1201659): consider special handling when security domain
// doesn't exist.
switch (http_status) {
case TrustedVaultRequest::HttpStatus::kSuccess:
break;
case TrustedVaultRequest::HttpStatus::kNetworkError:
case TrustedVaultRequest::HttpStatus::kOtherError:
case TrustedVaultRequest::HttpStatus::kNotFound:
case TrustedVaultRequest::HttpStatus::kBadRequest:
case TrustedVaultRequest::HttpStatus::kConflict:
case TrustedVaultRequest::HttpStatus::kTransientAccessTokenFetchError:
case TrustedVaultRequest::HttpStatus::kPersistentAccessTokenFetchError:
case TrustedVaultRequest::HttpStatus::
kPrimaryAccountChangeAccessTokenFetchError:
std::move(callback).Run(TrustedVaultRecoverabilityStatus::kError);
return;
}
trusted_vault_pb::SecurityDomain security_domain;
if (!security_domain.ParseFromString(response_body) ||
!security_domain.security_domain_details().has_sync_details()) {
std::move(callback).Run(TrustedVaultRecoverabilityStatus::kError);
return;
}
TrustedVaultRecoverabilityStatus status =
TrustedVaultRecoverabilityStatus::kNotDegraded;
if (security_domain.security_domain_details()
.sync_details()
.degraded_recoverability()) {
status = TrustedVaultRecoverabilityStatus::kDegraded;
}
std::move(callback).Run(status);
}
TrustedVaultURLFetchReasonForUMA
GetURLFetchReasonForUMAForJoinSecurityDomainsRequest(
AuthenticationFactorType authentication_factor_type) {
switch (authentication_factor_type) {
case AuthenticationFactorType::kPhysicalDevice:
return TrustedVaultURLFetchReasonForUMA::kRegisterDevice;
case AuthenticationFactorType::kUnspecified:
return TrustedVaultURLFetchReasonForUMA::
kRegisterUnspecifiedAuthenticationFactor;
}
NOTREACHED();
return TrustedVaultURLFetchReasonForUMA::kUnspecified;
}
} // namespace
TrustedVaultConnectionImpl::TrustedVaultConnectionImpl(
const GURL& trusted_vault_service_url,
std::unique_ptr<network::PendingSharedURLLoaderFactory>
pending_url_loader_factory,
std::unique_ptr<TrustedVaultAccessTokenFetcher> access_token_fetcher)
: pending_url_loader_factory_(std::move(pending_url_loader_factory)),
access_token_fetcher_(std::move(access_token_fetcher)),
trusted_vault_service_url_(trusted_vault_service_url) {
DCHECK(trusted_vault_service_url_.is_valid());
}
TrustedVaultConnectionImpl::~TrustedVaultConnectionImpl() = default;
std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::RegisterAuthenticationFactor(
const CoreAccountInfo& account_info,
const std::vector<std::vector<uint8_t>>& trusted_vault_keys,
int last_trusted_vault_key_version,
const SecureBoxPublicKey& authentication_factor_public_key,
AuthenticationFactorType authentication_factor_type,
absl::optional<int> authentication_factor_type_hint,
RegisterAuthenticationFactorCallback callback) {
return SendJoinSecurityDomainsRequest(
account_info, trusted_vault_keys, last_trusted_vault_key_version,
authentication_factor_public_key, authentication_factor_type,
authentication_factor_type_hint,
base::BindOnce(&RunRegisterAuthenticationFactorCallback,
std::move(callback)));
}
std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::RegisterDeviceWithoutKeys(
const CoreAccountInfo& account_info,
const SecureBoxPublicKey& device_public_key,
RegisterDeviceWithoutKeysCallback callback) {
return SendJoinSecurityDomainsRequest(
account_info, /*trusted_vault_keys=*/{GetConstantTrustedVaultKey()},
/*last_trusted_vault_key_version=*/kUnknownConstantKeyVersion,
device_public_key, AuthenticationFactorType::kPhysicalDevice,
/*authentication_factor_type_hint=*/absl::nullopt,
base::BindOnce(&RunRegisterDeviceWithoutKeysCallback,
std::move(callback)));
}
std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::DownloadNewKeys(
const CoreAccountInfo& account_info,
const TrustedVaultKeyAndVersion& last_trusted_vault_key_and_version,
std::unique_ptr<SecureBoxKeyPair> device_key_pair,
DownloadNewKeysCallback callback) {
// TODO(crbug.com/1413179): consider retries for keys downloading after
// initial failure returned to the upper layers.
auto request = std::make_unique<TrustedVaultRequest>(
account_info.account_id, TrustedVaultRequest::HttpMethod::kGet,
GURL(trusted_vault_service_url_.spec() +
GetGetSecurityDomainMemberURLPathAndQuery(
device_key_pair->public_key().ExportToBytes())),
/*serialized_request_proto=*/absl::nullopt,
/*max_retry_duration=*/base::Seconds(0), GetOrCreateURLLoaderFactory(),
access_token_fetcher_->Clone(),
TrustedVaultURLFetchReasonForUMA::kDownloadKeys);
request->FetchAccessTokenAndSendRequest(base::BindOnce(
&ProcessDownloadKeysResponse,
/*response_processor=*/
std::make_unique<DownloadKeysResponseHandler>(
last_trusted_vault_key_and_version, std::move(device_key_pair)),
std::move(callback)));
return request;
}
std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::DownloadIsRecoverabilityDegraded(
const CoreAccountInfo& account_info,
IsRecoverabilityDegradedCallback callback) {
auto request = std::make_unique<TrustedVaultRequest>(
account_info.account_id, TrustedVaultRequest::HttpMethod::kGet,
GURL(trusted_vault_service_url_.spec() +
kGetSecurityDomainURLPathAndQuery),
/*serialized_request_proto=*/absl::nullopt,
/*max_retry_duration=*/base::Seconds(0), GetOrCreateURLLoaderFactory(),
access_token_fetcher_->Clone(),
TrustedVaultURLFetchReasonForUMA::kDownloadIsRecoverabilityDegraded);
request->FetchAccessTokenAndSendRequest(base::BindOnce(
&ProcessDownloadIsRecoverabilityDegradedResponse, std::move(callback)));
return request;
}
std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::SendJoinSecurityDomainsRequest(
const CoreAccountInfo& account_info,
const std::vector<std::vector<uint8_t>>& trusted_vault_keys,
int last_trusted_vault_key_version,
const SecureBoxPublicKey& authentication_factor_public_key,
AuthenticationFactorType authentication_factor_type,
absl::optional<int> authentication_factor_type_hint,
JoinSecurityDomainsCallback callback) {
auto request = std::make_unique<TrustedVaultRequest>(
account_info.account_id, TrustedVaultRequest::HttpMethod::kPost,
GURL(trusted_vault_service_url_.spec() + kJoinSecurityDomainsURLPath),
/*serialized_request_proto=*/
CreateJoinSecurityDomainsRequest(
trusted_vault_keys, last_trusted_vault_key_version,
authentication_factor_public_key, authentication_factor_type,
authentication_factor_type_hint)
.SerializeAsString(),
kMaxJoinSecurityDomainRetryDuration, GetOrCreateURLLoaderFactory(),
access_token_fetcher_->Clone(),
GetURLFetchReasonForUMAForJoinSecurityDomainsRequest(
authentication_factor_type));
request->FetchAccessTokenAndSendRequest(
base::BindOnce(&ProcessJoinSecurityDomainsResponse, std::move(callback)));
return request;
}
scoped_refptr<network::SharedURLLoaderFactory>
TrustedVaultConnectionImpl::GetOrCreateURLLoaderFactory() {
if (!url_loader_factory_) {
url_loader_factory_ = network::SharedURLLoaderFactory::Create(
std::move(pending_url_loader_factory_));
}
return url_loader_factory_;
}
} // namespace trusted_vault