blob: ced66e069738bf5aac4ab9c965f789132b3bda2e [file] [log] [blame]
// 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 "chromeos/services/device_sync/cryptauth_key_bundle.h"
#include "base/base64.h"
#include "base/no_destructor.h"
#include "base/stl_util.h"
#include "chromeos/components/multidevice/logging/logging.h"
namespace chromeos {
namespace device_sync {
namespace {
// The special string used in SyncSingleKeyRequest::key_name, which is
// understood by CryptAuth to be the user's (non-rotated) key pair. If the user
// already enrolled with CryptAuth v1, this string will correspond to the
// existing key pair returned by
// CryptAuthEnrollmentManager::GetUser{Public,Private}Key().
const char kUserKeyPairName[] = "PublicKey";
const char kBundleNameDictKey[] = "name";
const char kKeyListDictKey[] = "keys";
const char kKeyDirectiveDictKey[] = "key_directive";
base::Optional<cryptauthv2::KeyDirective> KeyDirectiveFromPrefString(
const std::string& encoded_serialized_key_directive) {
std::string decoded_serialized_key_directive;
base::Base64Decode(encoded_serialized_key_directive,
&decoded_serialized_key_directive);
cryptauthv2::KeyDirective key_directive;
if (!key_directive.ParseFromString(decoded_serialized_key_directive)) {
PA_LOG(ERROR) << "Error parsing KeyDirective from pref string";
return base::nullopt;
}
return key_directive;
}
std::string KeyDirectiveToPrefString(
const cryptauthv2::KeyDirective& key_directive) {
std::string encoded_serialized_key_directive;
base::Base64Encode(key_directive.SerializeAsString(),
&encoded_serialized_key_directive);
return encoded_serialized_key_directive;
}
} // namespace
// static
const base::flat_set<CryptAuthKeyBundle::Name>& CryptAuthKeyBundle::AllNames() {
static const base::NoDestructor<base::flat_set<CryptAuthKeyBundle::Name>>
name_list({CryptAuthKeyBundle::Name::kUserKeyPair});
return *name_list;
}
// static
std::string CryptAuthKeyBundle::KeyBundleNameEnumToString(
CryptAuthKeyBundle::Name name) {
switch (name) {
case CryptAuthKeyBundle::Name::kUserKeyPair:
return kUserKeyPairName;
}
}
// static
base::Optional<CryptAuthKeyBundle::Name>
CryptAuthKeyBundle::KeyBundleNameStringToEnum(const std::string& name) {
if (name == kUserKeyPairName)
return CryptAuthKeyBundle::Name::kUserKeyPair;
return base::nullopt;
}
// static
base::Optional<CryptAuthKeyBundle> CryptAuthKeyBundle::FromDictionary(
const base::Value& dict) {
if (!dict.is_dict())
return base::nullopt;
const std::string* name_string = dict.FindStringKey(kBundleNameDictKey);
if (!name_string)
return base::nullopt;
base::Optional<CryptAuthKeyBundle::Name> name =
KeyBundleNameStringToEnum(*name_string);
if (!name)
return base::nullopt;
CryptAuthKeyBundle bundle(*name);
const base::Value* keys = dict.FindKey(kKeyListDictKey);
if (!keys || !keys->is_list())
return base::nullopt;
bool active_key_exists = false;
for (const base::Value& key_dict : keys->GetList()) {
base::Optional<CryptAuthKey> key = CryptAuthKey::FromDictionary(key_dict);
if (!key)
return base::nullopt;
// Return nullopt if there are multiple active keys.
if (key->status() == CryptAuthKey::Status::kActive) {
if (active_key_exists)
return base::nullopt;
active_key_exists = true;
}
// Return nullopt if duplicate handles exist.
if (base::ContainsKey(bundle.handle_to_key_map(), key->handle()))
return base::nullopt;
bundle.AddKey(*key);
}
const std::string* encoded_serialized_key_directive =
dict.FindStringKey(kKeyDirectiveDictKey);
if (encoded_serialized_key_directive) {
base::Optional<cryptauthv2::KeyDirective> key_directive =
KeyDirectiveFromPrefString(*encoded_serialized_key_directive);
if (!key_directive)
return base::nullopt;
bundle.set_key_directive(*key_directive);
}
return bundle;
}
CryptAuthKeyBundle::CryptAuthKeyBundle(Name name) : name_(name) {}
CryptAuthKeyBundle::CryptAuthKeyBundle(const CryptAuthKeyBundle&) = default;
CryptAuthKeyBundle::~CryptAuthKeyBundle() = default;
const CryptAuthKey* CryptAuthKeyBundle::GetActiveKey() const {
const auto& it = std::find_if(
handle_to_key_map_.begin(), handle_to_key_map_.end(),
[](const std::pair<std::string, CryptAuthKey>& handle_key_pair) -> bool {
return handle_key_pair.second.status() == CryptAuthKey::Status::kActive;
});
if (it == handle_to_key_map_.end())
return nullptr;
return &it->second;
}
void CryptAuthKeyBundle::AddKey(const CryptAuthKey& key) {
if (key.status() == CryptAuthKey::Status::kActive)
DeactivateKeys();
handle_to_key_map_.insert_or_assign(key.handle(), key);
}
void CryptAuthKeyBundle::SetActiveKey(const std::string& handle) {
auto it = handle_to_key_map_.find(handle);
DCHECK(it != handle_to_key_map_.end());
DeactivateKeys();
it->second.set_status(CryptAuthKey::Status::kActive);
}
void CryptAuthKeyBundle::DeleteKey(const std::string& handle) {
DCHECK(base::ContainsKey(handle_to_key_map_, handle));
handle_to_key_map_.erase(handle);
}
void CryptAuthKeyBundle::DeactivateKeys() {
for (auto& handle_key_pair : handle_to_key_map_)
handle_key_pair.second.set_status(CryptAuthKey::Status::kInactive);
}
base::Value CryptAuthKeyBundle::AsDictionary() const {
base::Value dict(base::Value::Type::DICTIONARY);
dict.SetKey(kBundleNameDictKey,
base::Value(KeyBundleNameEnumToString(name_)));
std::vector<base::Value> keys;
std::transform(
handle_to_key_map_.begin(), handle_to_key_map_.end(),
std::back_inserter(keys),
[](const std::pair<std::string, CryptAuthKey>& handle_key_pair) {
if (handle_key_pair.second.IsSymmetricKey())
return handle_key_pair.second.AsSymmetricKeyDictionary();
DCHECK(handle_key_pair.second.IsAsymmetricKey());
return handle_key_pair.second.AsAsymmetricKeyDictionary();
});
dict.SetKey(kKeyListDictKey, base::Value(keys));
if (key_directive_) {
dict.SetKey(kKeyDirectiveDictKey,
base::Value(KeyDirectiveToPrefString(*key_directive_)));
}
return dict;
}
bool CryptAuthKeyBundle::operator==(const CryptAuthKeyBundle& other) const {
return name_ == other.name_ &&
handle_to_key_map_ == other.handle_to_key_map_ &&
key_directive_.has_value() == other.key_directive_.has_value() &&
(!key_directive_ || key_directive_->SerializeAsString() ==
other.key_directive_->SerializeAsString());
}
bool CryptAuthKeyBundle::operator!=(const CryptAuthKeyBundle& other) const {
return !(*this == other);
}
} // namespace device_sync
} // namespace chromeos