// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/services/device_sync/cryptauth_metadata_syncer_impl.h"
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/services/device_sync/async_execution_time_metrics_logger.h"
#include "chromeos/ash/services/device_sync/cryptauth_client.h"
#include "chromeos/ash/services/device_sync/cryptauth_ecies_encryptor_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_key_creator_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_task_metrics_logger.h"
#include "chromeos/ash/services/device_sync/pref_names.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_logging.h"
#include "chromeos/ash/services/device_sync/value_string_encoding.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
namespace ash {
namespace device_sync {
namespace {
const char kUnsetPrefValue[] = "[Unset pref value]";
const cryptauthv2::KeyType kGroupKeyType = cryptauthv2::KeyType::P256;
// Timeout values for asynchronous operations.
// TODO(https://crbug.com/933656): Use async execution time metrics to tune
// these timeout values. For now, set these timeouts to the max execution time
// recorded by the metrics.
constexpr base::TimeDelta kWaitingForGroupKeyCreationTimeout =
kMaxAsyncExecutionTime;
constexpr base::TimeDelta kWaitingForLocalDeviceMetadataEncryptionTimeout =
kMaxAsyncExecutionTime;
constexpr base::TimeDelta kWaitingForFirstSyncMetadataResponseTimeout =
kMaxAsyncExecutionTime;
constexpr base::TimeDelta kWaitingForSecondSyncMetadataResponseTimeout =
kMaxAsyncExecutionTime;
CryptAuthDeviceSyncResult::ResultCode
SyncMetadataNetworkRequestErrorToResultCode(NetworkRequestError error) {
switch (error) {
case NetworkRequestError::kOffline:
return CryptAuthDeviceSyncResult::ResultCode::
kErrorSyncMetadataApiCallOffline;
case NetworkRequestError::kEndpointNotFound:
return CryptAuthDeviceSyncResult::ResultCode::
kErrorSyncMetadataApiCallEndpointNotFound;
case NetworkRequestError::kAuthenticationError:
return CryptAuthDeviceSyncResult::ResultCode::
kErrorSyncMetadataApiCallAuthenticationError;
case NetworkRequestError::kBadRequest:
return CryptAuthDeviceSyncResult::ResultCode::
kErrorSyncMetadataApiCallBadRequest;
case NetworkRequestError::kResponseMalformed:
return CryptAuthDeviceSyncResult::ResultCode::
kErrorSyncMetadataApiCallResponseMalformed;
case NetworkRequestError::kInternalServerError:
return CryptAuthDeviceSyncResult::ResultCode::
kErrorSyncMetadataApiCallInternalServerError;
case NetworkRequestError::kUnknown:
return CryptAuthDeviceSyncResult::ResultCode::
kErrorSyncMetadataApiCallUnknownError;
}
}
void RecordGroupKeyCreationMetrics(const base::TimeDelta& execution_time,
CryptAuthAsyncTaskResult result) {
LogAsyncExecutionTimeMetric(
"CryptAuth.DeviceSyncV2.MetadataSyncer.ExecutionTime.GroupKeyCreation",
execution_time);
LogCryptAuthAsyncTaskSuccessMetric(
"CryptAuth.DeviceSyncV2.MetadataSyncer.AsyncTaskResult.GroupKeyCreation",
result);
}
void RecordLocalDeviceMetadataEncryptionMetrics(
const base::TimeDelta& execution_time,
CryptAuthAsyncTaskResult result) {
LogAsyncExecutionTimeMetric(
"CryptAuth.DeviceSyncV2.MetadataSyncer.ExecutionTime."
"LocalDeviceMetadataEncryption",
execution_time);
LogCryptAuthAsyncTaskSuccessMetric(
"CryptAuth.DeviceSyncV2.MetadataSyncer.AsyncTaskResult."
"LocalDeviceMetadataEncryption",
result);
}
void RecordFirstSyncMetadataMetrics(const base::TimeDelta& execution_time,
CryptAuthApiCallResult result) {
LogAsyncExecutionTimeMetric(
"CryptAuth.DeviceSyncV2.MetadataSyncer.ExecutionTime.FirstSyncMetadata",
execution_time);
LogCryptAuthApiCallSuccessMetric(
"CryptAuth.DeviceSyncV2.MetadataSyncer.ApiCallResult.FirstSyncMetadata",
result);
}
void RecordSecondSyncMetadataMetrics(const base::TimeDelta& execution_time,
CryptAuthApiCallResult result) {
LogAsyncExecutionTimeMetric(
"CryptAuth.DeviceSyncV2.MetadataSyncer.ExecutionTime.SecondSyncMetadata",
execution_time);
LogCryptAuthApiCallSuccessMetric(
"CryptAuth.DeviceSyncV2.MetadataSyncer.ApiCallResult.SecondSyncMetadata",
result);
}
} // namespace
// static
CryptAuthMetadataSyncerImpl::Factory*
CryptAuthMetadataSyncerImpl::Factory::test_factory_ = nullptr;
// static
std::unique_ptr<CryptAuthMetadataSyncer>
CryptAuthMetadataSyncerImpl::Factory::Create(
CryptAuthClientFactory* client_factory,
PrefService* pref_service,
std::unique_ptr<base::OneShotTimer> timer) {
if (test_factory_) {
return test_factory_->CreateInstance(client_factory, pref_service,
std::move(timer));
}
return base::WrapUnique(new CryptAuthMetadataSyncerImpl(
client_factory, pref_service, std::move(timer)));
}
// static
void CryptAuthMetadataSyncerImpl::Factory::SetFactoryForTesting(
Factory* test_factory) {
test_factory_ = test_factory;
}
CryptAuthMetadataSyncerImpl::Factory::~Factory() = default;
// static
void CryptAuthMetadataSyncerImpl::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterStringPref(
prefs::kCryptAuthLastSyncedEncryptedLocalDeviceMetadata, kUnsetPrefValue);
registry->RegisterStringPref(
prefs::kCryptAuthLastSyncedUnencryptedLocalDeviceMetadata,
kUnsetPrefValue);
registry->RegisterStringPref(prefs::kCryptAuthLastSyncedGroupPublicKey,
kUnsetPrefValue);
}
// static
std::optional<base::TimeDelta> CryptAuthMetadataSyncerImpl::GetTimeoutForState(
State state) {
switch (state) {
case State::kWaitingForGroupKeyCreation:
return kWaitingForGroupKeyCreationTimeout;
case State::kWaitingForLocalDeviceMetadataEncryption:
return kWaitingForLocalDeviceMetadataEncryptionTimeout;
case State::kWaitingForFirstSyncMetadataResponse:
return kWaitingForFirstSyncMetadataResponseTimeout;
case State::kWaitingForSecondSyncMetadataResponse:
return kWaitingForSecondSyncMetadataResponseTimeout;
default:
// Signifies that there should not be a timeout.
return std::nullopt;
}
}
// static
std::optional<CryptAuthDeviceSyncResult::ResultCode>
CryptAuthMetadataSyncerImpl::ResultCodeErrorFromTimeoutDuringState(
State state) {
switch (state) {
case State::kWaitingForGroupKeyCreation:
return CryptAuthDeviceSyncResult::ResultCode::
kErrorTimeoutWaitingForGroupKeyCreation;
case State::kWaitingForLocalDeviceMetadataEncryption:
return CryptAuthDeviceSyncResult::ResultCode::
kErrorTimeoutWaitingForLocalDeviceMetadataEncryption;
case State::kWaitingForFirstSyncMetadataResponse:
return CryptAuthDeviceSyncResult::ResultCode::
kErrorTimeoutWaitingForFirstSyncMetadataResponse;
case State::kWaitingForSecondSyncMetadataResponse:
return CryptAuthDeviceSyncResult::ResultCode::
kErrorTimeoutWaitingForSecondSyncMetadataResponse;
default:
return std::nullopt;
}
}
CryptAuthMetadataSyncerImpl::CryptAuthMetadataSyncerImpl(
CryptAuthClientFactory* client_factory,
PrefService* pref_service,
std::unique_ptr<base::OneShotTimer> timer)
: client_factory_(client_factory),
pref_service_(pref_service),
timer_(std::move(timer)) {
DCHECK(client_factory);
DCHECK(pref_service);
}
CryptAuthMetadataSyncerImpl::~CryptAuthMetadataSyncerImpl() = default;
void CryptAuthMetadataSyncerImpl::OnAttemptStarted(
const cryptauthv2::RequestContext& request_context,
const cryptauthv2::BetterTogetherDeviceMetadata& local_device_metadata,
const CryptAuthKey* initial_group_key) {
DCHECK_EQ(State::kNotStarted, state_);
request_context_ = request_context;
local_device_metadata_ = local_device_metadata;
initial_group_key_ = initial_group_key;
AttemptNextStep();
}
void CryptAuthMetadataSyncerImpl::SetState(State state) {
timer_->Stop();
PA_LOG(INFO) << "Transitioning from " << state_ << " to " << state;
state_ = state;
last_state_change_timestamp_ = base::TimeTicks::Now();
std::optional<base::TimeDelta> timeout_for_state = GetTimeoutForState(state);
if (!timeout_for_state)
return;
timer_->Start(FROM_HERE, *timeout_for_state,
base::BindOnce(&CryptAuthMetadataSyncerImpl::OnTimeout,
base::Unretained(this)));
}
void CryptAuthMetadataSyncerImpl::OnTimeout() {
// If there's a timeout specified, there should be a corresponding error code.
std::optional<CryptAuthDeviceSyncResult::ResultCode> error_code =
ResultCodeErrorFromTimeoutDuringState(state_);
DCHECK(error_code);
base::TimeDelta execution_time =
base::TimeTicks::Now() - last_state_change_timestamp_;
switch (state_) {
case State::kWaitingForGroupKeyCreation:
RecordGroupKeyCreationMetrics(execution_time,
CryptAuthAsyncTaskResult::kTimeout);
break;
case State::kWaitingForLocalDeviceMetadataEncryption:
RecordLocalDeviceMetadataEncryptionMetrics(
execution_time, CryptAuthAsyncTaskResult::kTimeout);
break;
case State::kWaitingForFirstSyncMetadataResponse:
RecordFirstSyncMetadataMetrics(execution_time,
CryptAuthApiCallResult::kTimeout);
break;
case State::kWaitingForSecondSyncMetadataResponse:
RecordSecondSyncMetadataMetrics(execution_time,
CryptAuthApiCallResult::kTimeout);
break;
default:
NOTREACHED_IN_MIGRATION();
}
FinishAttempt(*error_code);
}
const CryptAuthKey* CryptAuthMetadataSyncerImpl::GetGroupKey() {
if (new_group_key_)
return new_group_key_.get();
return initial_group_key_;
}
CryptAuthMetadataSyncerImpl::GroupPublicKeyState
CryptAuthMetadataSyncerImpl::GetGroupPublicKeyState() {
const CryptAuthKey* group_key = GetGroupKey();
if (!group_key)
return GroupPublicKeyState::kNewKeyNeedsToBeCreated;
if (!sync_metadata_response_)
return GroupPublicKeyState::kKeyExistsButNotConfirmedWithCryptAuth;
if (sync_metadata_response_->group_public_key().empty())
return GroupPublicKeyState::kNewKeyNeedsToBeCreated;
if (group_key->public_key() != sync_metadata_response_->group_public_key())
return GroupPublicKeyState::kNewKeyReceivedFromCryptAuth;
return GroupPublicKeyState::kEstablished;
}
void CryptAuthMetadataSyncerImpl::AttemptNextStep() {
switch (state_) {
// Start the flow.
case State::kNotStarted: {
GroupPublicKeyState group_public_key_state = GetGroupPublicKeyState();
PA_LOG(VERBOSE) << "Group public key state: " << group_public_key_state;
switch (group_public_key_state) {
case GroupPublicKeyState::kNewKeyNeedsToBeCreated:
CreateGroupKey();
return;
case GroupPublicKeyState::kKeyExistsButNotConfirmedWithCryptAuth:
EncryptLocalDeviceMetadata();
return;
default:
NOTREACHED_IN_MIGRATION();
return;
}
}
// After group key creation, encrypt the local device metadata.
case State::kWaitingForGroupKeyCreation:
EncryptLocalDeviceMetadata();
return;
// After local device metadata is encrypted, start constructing the
// SyncMetadata call.
case State::kWaitingForLocalDeviceMetadataEncryption:
MakeSyncMetadataCall();
return;
// After receiving the first SyncMetadata response, take further action
// based on the state of the group public key.
case State::kWaitingForFirstSyncMetadataResponse: {
GroupPublicKeyState group_public_key_state = GetGroupPublicKeyState();
PA_LOG(VERBOSE) << "Group public key state: " << group_public_key_state;
switch (group_public_key_state) {
case GroupPublicKeyState::kNewKeyNeedsToBeCreated:
CreateGroupKey();
return;
case GroupPublicKeyState::kNewKeyReceivedFromCryptAuth:
new_group_key_ = std::make_unique<CryptAuthKey>(
sync_metadata_response_->group_public_key(),
std::string() /* private_key */, CryptAuthKey::Status::kActive,
kGroupKeyType);
EncryptLocalDeviceMetadata();
return;
case GroupPublicKeyState::kEstablished:
FilterMetadataAndFinishAttempt();
return;
default:
NOTREACHED_IN_MIGRATION();
return;
}
}
// After receiving the second SyncMetadata response, process the metadata
// and finish. Note: In the v2 DeviceSync protocol, no more than two
// SyncMetadata requests should be necessary to establish the group public
// key.
case State::kWaitingForSecondSyncMetadataResponse: {
GroupPublicKeyState group_public_key_state = GetGroupPublicKeyState();
PA_LOG(VERBOSE) << "Group public key state: " << group_public_key_state;
switch (group_public_key_state) {
case GroupPublicKeyState::kEstablished:
FilterMetadataAndFinishAttempt();
return;
default:
FinishAttempt(CryptAuthDeviceSyncResult::ResultCode::
kErrorEstablishingGroupPublicKey);
return;
}
}
// Each CryptAuthMetadataSyncer object can only be used once.
case State::kFinished:
NOTREACHED_IN_MIGRATION();
return;
}
}
bool CryptAuthMetadataSyncerImpl::
ShouldUseCachedEncryptedLocalDeviceMetadata() {
std::optional<std::string> last_synced_unencrypted_metadata =
util::DecodeFromString(pref_service_->GetString(
prefs::kCryptAuthLastSyncedUnencryptedLocalDeviceMetadata));
std::optional<std::string> last_synced_group_public_key =
util::DecodeFromString(
pref_service_->GetString(prefs::kCryptAuthLastSyncedGroupPublicKey));
std::optional<std::string> last_synced_encrypted_metadata =
util::DecodeFromString(pref_service_->GetString(
prefs::kCryptAuthLastSyncedEncryptedLocalDeviceMetadata));
// Persisted values are not encoded properly.
if (!last_synced_unencrypted_metadata || !last_synced_group_public_key ||
!last_synced_encrypted_metadata) {
return false;
}
// Prefs should be all set or all unset.
DCHECK_EQ(last_synced_unencrypted_metadata == kUnsetPrefValue,
last_synced_group_public_key == kUnsetPrefValue);
DCHECK_EQ(last_synced_unencrypted_metadata == kUnsetPrefValue,
last_synced_encrypted_metadata == kUnsetPrefValue);
if (last_synced_unencrypted_metadata == kUnsetPrefValue)
return false;
return last_synced_unencrypted_metadata ==
local_device_metadata_.SerializeAsString() &&
last_synced_group_public_key == GetGroupKey()->public_key();
}
void CryptAuthMetadataSyncerImpl::EncryptLocalDeviceMetadata() {
SetState(State::kWaitingForLocalDeviceMetadataEncryption);
if (ShouldUseCachedEncryptedLocalDeviceMetadata()) {
OnLocalDeviceMetadataEncrypted(
*util::DecodeFromString(pref_service_->GetString(
prefs::kCryptAuthLastSyncedEncryptedLocalDeviceMetadata)));
return;
}
encryptor_ = CryptAuthEciesEncryptorImpl::Factory::Create();
encryptor_->Encrypt(
local_device_metadata_.SerializeAsString(), GetGroupKey()->public_key(),
base::BindOnce(
&CryptAuthMetadataSyncerImpl::OnLocalDeviceMetadataEncrypted,
base::Unretained(this)));
}
void CryptAuthMetadataSyncerImpl::OnLocalDeviceMetadataEncrypted(
const std::optional<std::string>& encrypted_metadata) {
DCHECK_EQ(State::kWaitingForLocalDeviceMetadataEncryption, state_);
bool success = encrypted_metadata.has_value();
RecordLocalDeviceMetadataEncryptionMetrics(
base::TimeTicks::Now() - last_state_change_timestamp_,
success ? CryptAuthAsyncTaskResult::kSuccess
: CryptAuthAsyncTaskResult::kError);
if (!success) {
FinishAttempt(
CryptAuthDeviceSyncResult::ResultCode::kErrorEncryptingDeviceMetadata);
return;
}
encrypted_local_device_metadata_ = encrypted_metadata;
AttemptNextStep();
}
void CryptAuthMetadataSyncerImpl::CreateGroupKey() {
SetState(State::kWaitingForGroupKeyCreation);
key_creator_ = CryptAuthKeyCreatorImpl::Factory::Create();
key_creator_->CreateKeys(
{{CryptAuthKeyBundle::Name::kDeviceSyncBetterTogetherGroupKey,
CryptAuthKeyCreator::CreateKeyData(CryptAuthKey::Status::kActive,
kGroupKeyType)}},
std::nullopt /* server_ephemeral_dh */,
base::BindOnce(&CryptAuthMetadataSyncerImpl::OnGroupKeyCreated,
base::Unretained(this)));
}
void CryptAuthMetadataSyncerImpl::OnGroupKeyCreated(
const base::flat_map<CryptAuthKeyBundle::Name, std::optional<CryptAuthKey>>&
new_keys,
const std::optional<CryptAuthKey>& client_ephemeral_dh) {
DCHECK_EQ(State::kWaitingForGroupKeyCreation, state_);
const auto it = new_keys.find(
CryptAuthKeyBundle::Name::kDeviceSyncBetterTogetherGroupKey);
DCHECK(it != new_keys.end());
bool success = it->second.has_value();
RecordGroupKeyCreationMetrics(
base::TimeTicks::Now() - last_state_change_timestamp_,
success ? CryptAuthAsyncTaskResult::kSuccess
: CryptAuthAsyncTaskResult::kError);
if (!success) {
FinishAttempt(
CryptAuthDeviceSyncResult::ResultCode::kErrorCreatingGroupKey);
return;
}
new_group_key_ = std::make_unique<CryptAuthKey>(*it->second);
AttemptNextStep();
}
void CryptAuthMetadataSyncerImpl::MakeSyncMetadataCall() {
DCHECK(encrypted_local_device_metadata_);
const CryptAuthKey* group_key = GetGroupKey();
DCHECK(group_key);
cryptauthv2::SyncMetadataRequest request;
request.mutable_context()->CopyFrom(request_context_);
request.set_group_public_key(group_key->public_key());
request.set_encrypted_metadata(*encrypted_local_device_metadata_);
request.set_need_group_private_key(group_key->private_key().empty());
++num_sync_metadata_calls_;
switch (num_sync_metadata_calls_) {
case 1:
SetState(State::kWaitingForFirstSyncMetadataResponse);
break;
case 2:
SetState(State::kWaitingForSecondSyncMetadataResponse);
break;
default:
// AttemptNextStep() ensures that no more than two calls are made.
NOTREACHED_IN_MIGRATION();
return;
}
cryptauth_client_ = client_factory_->CreateInstance();
cryptauth_client_->SyncMetadata(
request,
base::BindOnce(&CryptAuthMetadataSyncerImpl::OnSyncMetadataSuccess,
base::Unretained(this)),
base::BindOnce(&CryptAuthMetadataSyncerImpl::OnSyncMetadataFailure,
base::Unretained(this)));
}
void CryptAuthMetadataSyncerImpl::OnSyncMetadataSuccess(
const cryptauthv2::SyncMetadataResponse& response) {
base::TimeDelta execution_time =
base::TimeTicks::Now() - last_state_change_timestamp_;
if (state_ == State::kWaitingForFirstSyncMetadataResponse)
RecordFirstSyncMetadataMetrics(execution_time,
CryptAuthApiCallResult::kSuccess);
else if (state_ == State::kWaitingForSecondSyncMetadataResponse)
RecordSecondSyncMetadataMetrics(execution_time,
CryptAuthApiCallResult::kSuccess);
else
NOTREACHED_IN_MIGRATION();
PA_LOG(VERBOSE) << "SyncMetadata response:\n" << response;
// Cache encrypted and unencrypted local device metadata, along with the group
// public key used to encrypt the data, that was successfully sent in the
// SyncMetadata request. Note: the cached group public key might not match
// the key returned in the respone.
pref_service_->SetString(
prefs::kCryptAuthLastSyncedUnencryptedLocalDeviceMetadata,
util::EncodeAsString(local_device_metadata_.SerializeAsString()));
pref_service_->SetString(prefs::kCryptAuthLastSyncedGroupPublicKey,
util::EncodeAsString(GetGroupKey()->public_key()));
pref_service_->SetString(
prefs::kCryptAuthLastSyncedEncryptedLocalDeviceMetadata,
util::EncodeAsString(*encrypted_local_device_metadata_));
sync_metadata_response_ = response;
AttemptNextStep();
}
void CryptAuthMetadataSyncerImpl::OnSyncMetadataFailure(
NetworkRequestError error) {
base::TimeDelta execution_time =
base::TimeTicks::Now() - last_state_change_timestamp_;
if (state_ == State::kWaitingForFirstSyncMetadataResponse)
RecordFirstSyncMetadataMetrics(
execution_time, CryptAuthApiCallResultFromNetworkRequestError(error));
else if (state_ == State::kWaitingForSecondSyncMetadataResponse)
RecordSecondSyncMetadataMetrics(
execution_time, CryptAuthApiCallResultFromNetworkRequestError(error));
else
NOTREACHED_IN_MIGRATION();
FinishAttempt(SyncMetadataNetworkRequestErrorToResultCode(error));
}
void CryptAuthMetadataSyncerImpl::FilterMetadataAndFinishAttempt() {
DCHECK_EQ(GroupPublicKeyState::kEstablished, GetGroupPublicKeyState());
DCHECK(sync_metadata_response_);
// At minimum, the local device's metadata should be present in the
// SyncMetadataResponse.
if (sync_metadata_response_->encrypted_metadata().empty()) {
FinishAttempt(
CryptAuthDeviceSyncResult::ResultCode::kErrorNoMetadataInResponse);
return;
}
bool did_non_fatal_error_occur = false;
for (const cryptauthv2::DeviceMetadataPacket& metadata :
sync_metadata_response_->encrypted_metadata()) {
bool is_device_metadata_packet_valid =
!metadata.device_id().empty() && !metadata.device_name().empty() &&
!metadata.device_public_key().empty();
base::UmaHistogramBoolean(
"CryptAuth.DeviceSyncV2.MetadataSyncer.IsDeviceMetadataPacketValid",
is_device_metadata_packet_valid);
if (!is_device_metadata_packet_valid) {
PA_LOG(ERROR) << "Invalid DeviceMetadataPacket: device_id = "
<< metadata.device_id() << ", device_public_key = "
<< util::EncodeAsString(metadata.device_public_key())
<< ", device_name empty? "
<< (metadata.device_name().empty() ? "yes" : "no") << ".";
did_non_fatal_error_occur = true;
continue;
}
bool is_duplicate_id =
base::Contains(id_to_device_metadata_packet_map_, metadata.device_id());
base::UmaHistogramBoolean(
"CryptAuth.DeviceSyncV2.MetadataSyncer.IsDuplicateDeviceId",
is_duplicate_id);
if (is_duplicate_id) {
PA_LOG(ERROR) << "Duplicate device IDs (" << metadata.device_id()
<< ") in SyncMetadata response.";
did_non_fatal_error_occur = true;
continue;
}
id_to_device_metadata_packet_map_[metadata.device_id()] = metadata;
}
// Finish attempt if DeviceMetadataPackets were sent but none were valid.
if (id_to_device_metadata_packet_map_.empty()) {
FinishAttempt(CryptAuthDeviceSyncResult::ResultCode::
kErrorAllResponseMetadataInvalid);
return;
}
// We require that the local device's metadata is returned in the response.
if (!base::Contains(id_to_device_metadata_packet_map_,
request_context_.device_id())) {
PA_LOG(ERROR) << "Metadata for local device (Instance ID: "
<< request_context_.device_id()
<< ") not in SyncMetadata response.";
FinishAttempt(CryptAuthDeviceSyncResult::ResultCode::
kErrorNoLocalDeviceMetadataInResponse);
return;
}
CryptAuthDeviceSyncResult::ResultCode result_code =
did_non_fatal_error_occur
? CryptAuthDeviceSyncResult::ResultCode::kFinishedWithNonFatalErrors
: CryptAuthDeviceSyncResult::ResultCode::kSuccess;
FinishAttempt(result_code);
}
void CryptAuthMetadataSyncerImpl::FinishAttempt(
CryptAuthDeviceSyncResult::ResultCode result_code) {
cryptauth_client_.reset();
key_creator_.reset();
encryptor_.reset();
std::optional<cryptauthv2::ClientDirective> new_client_directive;
std::optional<cryptauthv2::EncryptedGroupPrivateKey>
encrypted_group_private_key;
if (sync_metadata_response_) {
if (sync_metadata_response_->has_client_directive())
new_client_directive = sync_metadata_response_->client_directive();
if (sync_metadata_response_->has_encrypted_group_private_key()) {
encrypted_group_private_key =
sync_metadata_response_->encrypted_group_private_key();
}
}
SetState(State::kFinished);
OnAttemptFinished(id_to_device_metadata_packet_map_,
std::move(new_group_key_), encrypted_group_private_key,
new_client_directive, result_code);
}
std::ostream& operator<<(std::ostream& stream,
const CryptAuthMetadataSyncerImpl::State& state) {
switch (state) {
case CryptAuthMetadataSyncerImpl::State::kNotStarted:
stream << "[MetadataSyncer state: Not started]";
break;
case CryptAuthMetadataSyncerImpl::State::kWaitingForGroupKeyCreation:
stream << "[MetadataSyncer state: Waiting for group key pair creation]";
break;
case CryptAuthMetadataSyncerImpl::State::
kWaitingForLocalDeviceMetadataEncryption:
stream << "[MetadataSyncer state: Waiting for local device metadata "
<< "encryption]";
break;
case CryptAuthMetadataSyncerImpl::State::
kWaitingForFirstSyncMetadataResponse:
stream << "[MetadataSyncer state: Waiting for first SyncMetadata "
<< "response]";
break;
case CryptAuthMetadataSyncerImpl::State::
kWaitingForSecondSyncMetadataResponse:
stream << "[MetadataSyncer state: Waiting for second SyncMetadata "
<< "response]";
break;
case CryptAuthMetadataSyncerImpl::State::kFinished:
stream << "[MetadataSyncer state: Finished]";
break;
}
return stream;
}
std::ostream& operator<<(
std::ostream& stream,
const CryptAuthMetadataSyncerImpl::GroupPublicKeyState& key_state) {
switch (key_state) {
case CryptAuthMetadataSyncerImpl::GroupPublicKeyState::kUndetermined:
stream << "[Undetermined]";
break;
case CryptAuthMetadataSyncerImpl::GroupPublicKeyState::
kKeyExistsButNotConfirmedWithCryptAuth:
stream << "[Key exists but not confirmed with CryptAuth]";
break;
case CryptAuthMetadataSyncerImpl::GroupPublicKeyState::
kNewKeyNeedsToBeCreated:
stream << "[New key needs to be created]";
break;
case CryptAuthMetadataSyncerImpl::GroupPublicKeyState::
kNewKeyReceivedFromCryptAuth:
stream << "[New key received from CryptAuth]";
break;
case CryptAuthMetadataSyncerImpl::GroupPublicKeyState::kEstablished:
stream << "[Established]";
break;
}
return stream;
}
} // namespace device_sync
} // namespace ash