| // Copyright 2015 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/gcm_driver/crypto/gcm_key_store.h" | 
 |  | 
 | #include <stddef.h> | 
 |  | 
 | #include <utility> | 
 |  | 
 | #include "base/containers/span.h" | 
 | #include "base/functional/bind.h" | 
 | #include "base/functional/callback.h" | 
 | #include "base/logging.h" | 
 | #include "base/metrics/histogram_macros.h" | 
 | #include "base/strings/string_view_util.h" | 
 | #include "base/task/sequenced_task_runner.h" | 
 | #include "components/gcm_driver/crypto/p256_key_util.h" | 
 | #include "components/leveldb_proto/public/proto_database_provider.h" | 
 | #include "components/leveldb_proto/public/shared_proto_database_client_list.h" | 
 | #include "crypto/evp.h" | 
 | #include "crypto/keypair.h" | 
 | #include "crypto/openssl_util.h" | 
 | #include "crypto/random.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/pkcs8.h" | 
 |  | 
 | namespace gcm { | 
 |  | 
 | namespace { | 
 |  | 
 | using EntryVectorType = | 
 |     leveldb_proto::ProtoDatabase<EncryptionData>::KeyEntryVector; | 
 |  | 
 | // Number of cryptographically secure random bytes to generate as a key pair's | 
 | // authentication secret. Must be at least 16 bytes. | 
 | const size_t kAuthSecretBytes = 16; | 
 |  | 
 | std::string DatabaseKey(const std::string& app_id, | 
 |                         const std::string& authorized_entity) { | 
 |   DCHECK_EQ(std::string::npos, app_id.find(',')); | 
 |   DCHECK_EQ(std::string::npos, authorized_entity.find(',')); | 
 |   DCHECK_NE("*", authorized_entity) << "Wildcards require special handling"; | 
 |   return authorized_entity.empty() | 
 |              ? app_id  // No comma, for compatibility with existing keys. | 
 |              : app_id + ',' + authorized_entity; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | enum class GCMKeyStore::State { | 
 |    UNINITIALIZED, | 
 |    INITIALIZING, | 
 |    INITIALIZED, | 
 |    FAILED | 
 | }; | 
 |  | 
 | GCMKeyStore::GCMKeyStore( | 
 |     const base::FilePath& key_store_path, | 
 |     const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner) | 
 |     : key_store_path_(key_store_path), | 
 |       blocking_task_runner_(blocking_task_runner), | 
 |       state_(State::UNINITIALIZED) { | 
 |   DCHECK(blocking_task_runner); | 
 | } | 
 |  | 
 | GCMKeyStore::~GCMKeyStore() = default; | 
 |  | 
 | void GCMKeyStore::GetKeys(const std::string& app_id, | 
 |                           const std::string& authorized_entity, | 
 |                           bool fallback_to_empty_authorized_entity, | 
 |                           KeysCallback callback) { | 
 |   LazyInitialize( | 
 |       base::BindOnce(&GCMKeyStore::GetKeysAfterInitialize, | 
 |                      weak_factory_.GetWeakPtr(), app_id, authorized_entity, | 
 |                      fallback_to_empty_authorized_entity, std::move(callback))); | 
 | } | 
 |  | 
 | void GCMKeyStore::GetKeysAfterInitialize( | 
 |     const std::string& app_id, | 
 |     const std::string& authorized_entity, | 
 |     bool fallback_to_empty_authorized_entity, | 
 |     KeysCallback callback) { | 
 |   DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED); | 
 |   bool success = false; | 
 |  | 
 |   if (state_ == State::INITIALIZED) { | 
 |     auto outer_iter = key_data_.find(app_id); | 
 |     if (outer_iter != key_data_.end()) { | 
 |       const auto& inner_map = outer_iter->second; | 
 |       auto inner_iter = inner_map.find(authorized_entity); | 
 |       if (fallback_to_empty_authorized_entity && inner_iter == inner_map.end()) | 
 |         inner_iter = inner_map.find(std::string()); | 
 |       if (inner_iter != inner_map.end()) { | 
 |         const auto& map_entry = inner_iter->second; | 
 |         std::move(callback).Run(map_entry.first, map_entry.second); | 
 |         success = true; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   if (!success) | 
 |     std::move(callback).Run(std::nullopt /* key */, | 
 |                             std::string() /* auth_secret */); | 
 | } | 
 |  | 
 | void GCMKeyStore::CreateKeys(const std::string& app_id, | 
 |                              const std::string& authorized_entity, | 
 |                              KeysCallback callback) { | 
 |   LazyInitialize(base::BindOnce(&GCMKeyStore::CreateKeysAfterInitialize, | 
 |                                 weak_factory_.GetWeakPtr(), app_id, | 
 |                                 authorized_entity, std::move(callback))); | 
 | } | 
 |  | 
 | void GCMKeyStore::CreateKeysAfterInitialize( | 
 |     const std::string& app_id, | 
 |     const std::string& authorized_entity, | 
 |     KeysCallback callback) { | 
 |   DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED); | 
 |   if (state_ != State::INITIALIZED) { | 
 |     std::move(callback).Run(std::nullopt /* key */, | 
 |                             std::string() /* auth_secret */); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Only allow creating new keys if no keys currently exist. Multiple Instance | 
 |   // ID tokens can share an app_id (with different authorized entities), but | 
 |   // InstanceID tokens can't share an app_id with a non-InstanceID registration. | 
 |   // This invariant is necessary for the fallback_to_empty_authorized_entity | 
 |   // mode of GetKey (needed by GCMEncryptionProvider::DecryptMessage, which | 
 |   // can't distinguish Instance ID tokens from non-InstanceID registrations). | 
 |   DCHECK(!key_data_.count(app_id) || | 
 |          (!authorized_entity.empty() && | 
 |           !key_data_[app_id].count(authorized_entity) && | 
 |           !key_data_[app_id].count(std::string()))) | 
 |       << "Instance ID tokens cannot share an app_id with a non-InstanceID GCM " | 
 |          "registration"; | 
 |  | 
 |   auto key = crypto::keypair::PrivateKey::GenerateEcP256(); | 
 |  | 
 |   // Create the authentication secret, which has to be a cryptographically | 
 |   // secure random number of at least 128 bits (16 bytes). | 
 |   std::string auth_secret(kAuthSecretBytes, '\0'); | 
 |   crypto::RandBytes(base::as_writable_byte_span(auth_secret)); | 
 |  | 
 |   // Store the keys in a new EncryptionData object. | 
 |   EncryptionData encryption_data; | 
 |   encryption_data.set_app_id(app_id); | 
 |   if (!authorized_entity.empty()) | 
 |     encryption_data.set_authorized_entity(authorized_entity); | 
 |   encryption_data.set_auth_secret(auth_secret); | 
 |  | 
 |   encryption_data.set_private_key(base::as_string_view(key.ToPrivateKeyInfo())); | 
 |  | 
 |   // Write them immediately to our cache, so subsequent calls to | 
 |   // {Get/Create/Remove}Keys can see them. | 
 |   key_data_[app_id].emplace(authorized_entity, | 
 |                             std::make_pair(key, auth_secret)); | 
 |  | 
 |   std::unique_ptr<EntryVectorType> entries_to_save(new EntryVectorType()); | 
 |   std::unique_ptr<std::vector<std::string>> keys_to_remove( | 
 |       new std::vector<std::string>()); | 
 |  | 
 |   entries_to_save->push_back( | 
 |       std::make_pair(DatabaseKey(app_id, authorized_entity), encryption_data)); | 
 |  | 
 |   database_->UpdateEntries( | 
 |       std::move(entries_to_save), std::move(keys_to_remove), | 
 |       base::BindOnce(&GCMKeyStore::DidStoreKeys, weak_factory_.GetWeakPtr(), | 
 |                      key, auth_secret, std::move(callback))); | 
 | } | 
 |  | 
 | void GCMKeyStore::DidStoreKeys(crypto::keypair::PrivateKey key, | 
 |                                const std::string& auth_secret, | 
 |                                KeysCallback callback, | 
 |                                bool success) { | 
 |   if (!success) { | 
 |     DVLOG(1) << "Unable to store the created key in the GCM Key Store."; | 
 |  | 
 |     // Our cache is now inconsistent. Reject requests until restarted. | 
 |     state_ = State::FAILED; | 
 |  | 
 |     std::move(callback).Run(std::nullopt /* key */, | 
 |                             std::string() /* auth_secret */); | 
 |     return; | 
 |   } | 
 |  | 
 |   std::move(callback).Run(key, auth_secret); | 
 | } | 
 |  | 
 | void GCMKeyStore::RemoveKeys(const std::string& app_id, | 
 |                              const std::string& authorized_entity, | 
 |                              base::OnceClosure callback) { | 
 |   LazyInitialize(base::BindOnce(&GCMKeyStore::RemoveKeysAfterInitialize, | 
 |                                 weak_factory_.GetWeakPtr(), app_id, | 
 |                                 authorized_entity, std::move(callback))); | 
 | } | 
 |  | 
 | void GCMKeyStore::RemoveKeysAfterInitialize( | 
 |     const std::string& app_id, | 
 |     const std::string& authorized_entity, | 
 |     base::OnceClosure callback) { | 
 |   DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED); | 
 |  | 
 |   const auto& outer_iter = key_data_.find(app_id); | 
 |   if (outer_iter == key_data_.end() || state_ != State::INITIALIZED) { | 
 |     std::move(callback).Run(); | 
 |     return; | 
 |   } | 
 |  | 
 |   std::unique_ptr<EntryVectorType> entries_to_save(new EntryVectorType()); | 
 |   std::unique_ptr<std::vector<std::string>> keys_to_remove( | 
 |       new std::vector<std::string>()); | 
 |  | 
 |   bool had_keys = false; | 
 |   auto& inner_map = outer_iter->second; | 
 |   for (auto it = inner_map.begin(); it != inner_map.end();) { | 
 |     // Wildcard "*" matches all non-empty authorized entities (InstanceID only). | 
 |     if (authorized_entity == "*" ? !it->first.empty() | 
 |                                  : it->first == authorized_entity) { | 
 |       had_keys = true; | 
 |  | 
 |       keys_to_remove->push_back(DatabaseKey(app_id, it->first)); | 
 |  | 
 |       // Clear keys immediately from our cache, so subsequent calls to | 
 |       // {Get/Create/Remove}Keys don't see them. | 
 |       it = inner_map.erase(it); | 
 |     } else { | 
 |       ++it; | 
 |     } | 
 |   } | 
 |   if (!had_keys) { | 
 |     std::move(callback).Run(); | 
 |     return; | 
 |   } | 
 |   if (inner_map.empty()) | 
 |     key_data_.erase(app_id); | 
 |  | 
 |   database_->UpdateEntries( | 
 |       std::move(entries_to_save), std::move(keys_to_remove), | 
 |       base::BindOnce(&GCMKeyStore::DidRemoveKeys, weak_factory_.GetWeakPtr(), | 
 |                      std::move(callback))); | 
 | } | 
 |  | 
 | void GCMKeyStore::DidRemoveKeys(base::OnceClosure callback, bool success) { | 
 |   if (!success) { | 
 |     DVLOG(1) << "Unable to delete a key from the GCM Key Store."; | 
 |  | 
 |     // Our cache is now inconsistent. Reject requests until restarted. | 
 |     state_ = State::FAILED; | 
 |   } | 
 |  | 
 |   std::move(callback).Run(); | 
 | } | 
 |  | 
 | void GCMKeyStore::DidUpgradeDatabase(bool success) { | 
 |   if (!success) { | 
 |     DVLOG(1) << "Unable to upgrade the GCM Key Store database."; | 
 |     // Our cache is now inconsistent. Reject requests until restarted. | 
 |     state_ = State::FAILED; | 
 |     delayed_task_controller_.SetReady(); | 
 |     return; | 
 |   } | 
 |  | 
 |   database_->LoadEntries( | 
 |       base::BindOnce(&GCMKeyStore::DidLoadKeys, weak_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void GCMKeyStore::LazyInitialize(base::OnceClosure done_closure) { | 
 |   if (delayed_task_controller_.CanRunTaskWithoutDelay()) { | 
 |     std::move(done_closure).Run(); | 
 |     return; | 
 |   } | 
 |  | 
 |   delayed_task_controller_.AddTask(std::move(done_closure)); | 
 |   if (state_ == State::INITIALIZING) | 
 |     return; | 
 |  | 
 |   state_ = State::INITIALIZING; | 
 |  | 
 |   database_ = leveldb_proto::ProtoDatabaseProvider::GetUniqueDB<EncryptionData>( | 
 |       leveldb_proto::ProtoDbType::GCM_KEY_STORE, key_store_path_, | 
 |       blocking_task_runner_); | 
 |  | 
 |   database_->Init( | 
 |       leveldb_proto::CreateSimpleOptions(), | 
 |       base::BindOnce(&GCMKeyStore::DidInitialize, weak_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void GCMKeyStore::DidInitialize(leveldb_proto::Enums::InitStatus status) { | 
 |   bool success = status == leveldb_proto::Enums::kOK; | 
 |   if (!success) { | 
 |     DVLOG(1) << "Unable to initialize the GCM Key Store."; | 
 |     state_ = State::FAILED; | 
 |  | 
 |     delayed_task_controller_.SetReady(); | 
 |     return; | 
 |   } | 
 |  | 
 |   database_->LoadEntries( | 
 |       base::BindOnce(&GCMKeyStore::DidLoadKeys, weak_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void GCMKeyStore::UpgradeDatabase( | 
 |     std::unique_ptr<std::vector<EncryptionData>> entries) { | 
 |   std::unique_ptr<EntryVectorType> entries_to_save = | 
 |       std::make_unique<EntryVectorType>(); | 
 |   std::unique_ptr<std::vector<std::string>> keys_to_remove = | 
 |       std::make_unique<std::vector<std::string>>(); | 
 |  | 
 |   // Loop over entries, create list of database entries to overwrite. | 
 |   for (EncryptionData& entry : *entries) { | 
 |     if (!entry.keys_size()) | 
 |       continue; | 
 |     std::string decrypted_private_key; | 
 |     if (!DecryptPrivateKey(entry.keys(0).private_key(), | 
 |                            &decrypted_private_key)) { | 
 |       DVLOG(1) << "Unable to decrypt private key: " | 
 |                << entry.keys(0).private_key(); | 
 |       state_ = State::FAILED; | 
 |       delayed_task_controller_.SetReady(); | 
 |       return; | 
 |     } | 
 |  | 
 |     entry.set_private_key(decrypted_private_key); | 
 |     entry.clear_keys(); | 
 |     entries_to_save->push_back(std::make_pair( | 
 |         DatabaseKey(entry.app_id(), entry.authorized_entity()), entry)); | 
 |   } | 
 |  | 
 |   database_->UpdateEntries(std::move(entries_to_save), | 
 |                            std::move(keys_to_remove), | 
 |                            base::BindOnce(&GCMKeyStore::DidUpgradeDatabase, | 
 |                                           weak_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void GCMKeyStore::DidLoadKeys( | 
 |     bool success, | 
 |     std::unique_ptr<std::vector<EncryptionData>> entries) { | 
 |   if (!success) { | 
 |     DVLOG(1) << "Unable to load entries into the GCM Key Store."; | 
 |     state_ = State::FAILED; | 
 |  | 
 |     delayed_task_controller_.SetReady(); | 
 |     return; | 
 |   } | 
 |  | 
 |   for (const EncryptionData& entry : *entries) { | 
 |     std::string authorized_entity; | 
 |     if (entry.has_authorized_entity()) | 
 |       authorized_entity = entry.authorized_entity(); | 
 |  | 
 |     // The old format of EncryptionData has a KeyPair in it. Previously | 
 |     // we used to cache the key pair and auth secret in key_data_. | 
 |     // The new code adds the pair {PrivateKey, auth_secret} to | 
 |     // key_data_ instead. | 
 |     if (entry.keys_size()) { | 
 |       if (state_ == State::FAILED) | 
 |         return; | 
 |  | 
 |       // Old format of EncryptionData. Upgrade database so there are no such | 
 |       // entries. We'll reload keys from the database once this is done. | 
 |       UpgradeDatabase(std::move(entries)); | 
 |       return; | 
 |     } | 
 |  | 
 |     if (entry.private_key().empty()) { | 
 |       continue; | 
 |     } | 
 |     auto key = crypto::keypair::PrivateKey::FromPrivateKeyInfo( | 
 |         base::as_byte_span(entry.private_key())); | 
 |     if (!key || !key->IsEc()) { | 
 |       continue; | 
 |     } | 
 |     key_data_[entry.app_id()].emplace( | 
 |         authorized_entity, std::make_pair(*key, entry.auth_secret())); | 
 |   } | 
 |  | 
 |   state_ = State::INITIALIZED; | 
 |  | 
 |   delayed_task_controller_.SetReady(); | 
 | } | 
 |  | 
 | bool GCMKeyStore::DecryptPrivateKey(const std::string& to_decrypt_str, | 
 |                                     std::string* decrypted) { | 
 |   auto to_decrypt = base::as_byte_span(to_decrypt_str); | 
 |  | 
 |   crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); | 
 |   CBS cbs; | 
 |   CBS_init(&cbs, to_decrypt.data(), to_decrypt.size()); | 
 |   bssl::UniquePtr<EVP_PKEY> pkey( | 
 |       PKCS8_parse_encrypted_private_key(&cbs, "", 0)); | 
 |  | 
 |   // Hack for reading keys generated by an older version of the OpenSSL code. | 
 |   // Some implementations encode the empty password as "\0\0" (passwords are | 
 |   // normally encoded in big-endian UCS-2 with a NUL terminator) and some | 
 |   // encode as the empty string. PKCS8_parse_encrypted_private_key | 
 |   // distinguishes the two by whether the password is nullptr. | 
 |   if (!pkey) { | 
 |     CBS_init(&cbs, to_decrypt.data(), to_decrypt.size()); | 
 |     pkey.reset(PKCS8_parse_encrypted_private_key(&cbs, nullptr, 0)); | 
 |   } | 
 |  | 
 |   if (!pkey || CBS_len(&cbs) != 0 || EVP_PKEY_id(pkey.get()) != EVP_PKEY_EC) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   auto decrypted_vec = crypto::evp::PrivateKeyToBytes(pkey.get()); | 
 |   decrypted->assign(decrypted_vec.begin(), decrypted_vec.end()); | 
 |   return true; | 
 | } | 
 |  | 
 | }  // namespace gcm |