Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "components/gcm_driver/crypto/gcm_key_store.h" | 5 #include "components/gcm_driver/crypto/gcm_key_store.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include <utility> | 9 #include <utility> |
| 10 | 10 |
| 11 #include "base/bind_helpers.h" | |
| 12 #include "base/callback.h" | |
| 11 #include "base/logging.h" | 13 #include "base/logging.h" |
| 12 #include "base/metrics/histogram_macros.h" | 14 #include "base/metrics/histogram_macros.h" |
| 13 #include "components/gcm_driver/crypto/p256_key_util.h" | 15 #include "components/gcm_driver/crypto/p256_key_util.h" |
| 14 #include "components/leveldb_proto/proto_database_impl.h" | 16 #include "components/leveldb_proto/proto_database_impl.h" |
| 15 #include "crypto/random.h" | 17 #include "crypto/random.h" |
| 16 | 18 |
| 17 namespace gcm { | 19 namespace gcm { |
| 18 | 20 |
| 21 namespace { | |
| 22 | |
| 23 std::string DatabaseKey(const std::string& app_id, | |
| 24 const std::string& instance_id_authorized_entity) { | |
| 25 DCHECK_EQ(std::string::npos, app_id.find(',')); | |
| 26 DCHECK_EQ(std::string::npos, instance_id_authorized_entity.find(',')); | |
| 27 DCHECK_NE("*", instance_id_authorized_entity) << "Don't store wildcards"; | |
| 28 return instance_id_authorized_entity.empty() | |
| 29 ? app_id // No comma, for compatibility with existing keys. | |
| 30 : app_id + ',' + instance_id_authorized_entity; | |
| 31 } | |
| 32 | |
| 33 } // namespace | |
| 34 | |
| 19 // Statistics are logged to UMA with this string as part of histogram name. They | 35 // Statistics are logged to UMA with this string as part of histogram name. They |
| 20 // can all be found under LevelDB.*.GCMKeyStore. Changing this needs to | 36 // can all be found under LevelDB.*.GCMKeyStore. Changing this needs to |
| 21 // synchronize with histograms.xml, AND will also become incompatible with older | 37 // synchronize with histograms.xml, AND will also become incompatible with older |
| 22 // browsers still reporting the previous values. | 38 // browsers still reporting the previous values. |
| 23 const char kDatabaseUMAClientName[] = "GCMKeyStore"; | 39 const char kDatabaseUMAClientName[] = "GCMKeyStore"; |
|
Peter Beverloo
2016/05/09 14:10:10
Please move this (and kAuthSecretBytes) to the top
johnme
2016/05/09 18:15:55
Done.
| |
| 24 | 40 |
| 25 // Number of cryptographically secure random bytes to generate as a key pair's | 41 // Number of cryptographically secure random bytes to generate as a key pair's |
| 26 // authentication secret. Must be at least 16 bytes. | 42 // authentication secret. Must be at least 16 bytes. |
| 27 const size_t kAuthSecretBytes = 16; | 43 const size_t kAuthSecretBytes = 16; |
| 28 | 44 |
| 29 enum class GCMKeyStore::State { | 45 enum class GCMKeyStore::State { |
| 30 UNINITIALIZED, | 46 UNINITIALIZED, |
| 31 INITIALIZING, | 47 INITIALIZING, |
| 32 INITIALIZED, | 48 INITIALIZED, |
| 33 FAILED | 49 FAILED |
| 34 }; | 50 }; |
| 35 | 51 |
| 36 GCMKeyStore::GCMKeyStore( | 52 GCMKeyStore::GCMKeyStore( |
| 37 const base::FilePath& key_store_path, | 53 const base::FilePath& key_store_path, |
| 38 const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner) | 54 const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner) |
| 39 : key_store_path_(key_store_path), | 55 : key_store_path_(key_store_path), |
| 40 blocking_task_runner_(blocking_task_runner), | 56 blocking_task_runner_(blocking_task_runner), |
| 41 state_(State::UNINITIALIZED), | 57 state_(State::UNINITIALIZED), |
| 42 weak_factory_(this) { | 58 weak_factory_(this) { |
| 43 DCHECK(blocking_task_runner); | 59 DCHECK(blocking_task_runner); |
| 44 } | 60 } |
| 45 | 61 |
| 46 GCMKeyStore::~GCMKeyStore() {} | 62 GCMKeyStore::~GCMKeyStore() {} |
| 47 | 63 |
| 48 void GCMKeyStore::GetKeys(const std::string& app_id, | 64 void GCMKeyStore::GetKeys(const std::string& app_id, |
| 65 const std::string& instance_id_authorized_entity, | |
| 66 bool fallback_to_empty_authorized_entity, | |
| 49 const KeysCallback& callback) { | 67 const KeysCallback& callback) { |
| 50 LazyInitialize(base::Bind(&GCMKeyStore::GetKeysAfterInitialize, | 68 LazyInitialize(base::Bind(&GCMKeyStore::GetKeysAfterInitialize, |
| 51 weak_factory_.GetWeakPtr(), app_id, callback)); | 69 weak_factory_.GetWeakPtr(), app_id, |
| 70 instance_id_authorized_entity, | |
| 71 fallback_to_empty_authorized_entity, callback)); | |
| 52 } | 72 } |
| 53 | 73 |
| 54 void GCMKeyStore::GetKeysAfterInitialize(const std::string& app_id, | 74 void GCMKeyStore::GetKeysAfterInitialize( |
| 55 const KeysCallback& callback) { | 75 const std::string& app_id, |
| 76 const std::string& instance_id_authorized_entity, | |
| 77 bool fallback_to_empty_authorized_entity, | |
| 78 const KeysCallback& callback) { | |
| 56 DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED); | 79 DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED); |
| 57 const auto& iter = key_pairs_.find(app_id); | 80 bool success = false; |
| 58 | 81 |
| 59 const bool success = state_ == State::INITIALIZED && iter != key_pairs_.end(); | 82 if (state_ == State::INITIALIZED) { |
| 60 UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.GetKeySuccessRate", success); | 83 auto outer_iter = key_data_.find(app_id); |
| 61 | 84 if (outer_iter != key_data_.end()) { |
| 62 if (!success) { | 85 const auto& inner_map = outer_iter->second; |
| 63 callback.Run(KeyPair(), std::string() /* auth_secret */); | 86 auto inner_iter = inner_map.find(instance_id_authorized_entity); |
| 64 return; | 87 if (fallback_to_empty_authorized_entity && inner_iter == inner_map.end()) |
| 88 inner_iter = inner_map.find(std::string()); | |
| 89 if (inner_iter != inner_map.end()) { | |
| 90 const KeyPairAndAuthSecret& key_and_auth = inner_iter->second; | |
| 91 callback.Run(key_and_auth.first, key_and_auth.second); | |
| 92 success = true; | |
| 93 } | |
| 94 } | |
| 65 } | 95 } |
| 66 | 96 |
| 67 const auto& auth_secret_iter = auth_secrets_.find(app_id); | 97 UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.GetKeySuccessRate", success); |
| 68 DCHECK(auth_secret_iter != auth_secrets_.end()); | 98 if (!success) |
| 69 | 99 callback.Run(KeyPair(), std::string() /* auth_secret */); |
| 70 callback.Run(iter->second, auth_secret_iter->second); | |
| 71 } | 100 } |
| 72 | 101 |
| 73 void GCMKeyStore::CreateKeys(const std::string& app_id, | 102 void GCMKeyStore::CreateKeys(const std::string& app_id, |
| 103 const std::string& instance_id_authorized_entity, | |
| 74 const KeysCallback& callback) { | 104 const KeysCallback& callback) { |
| 75 LazyInitialize(base::Bind(&GCMKeyStore::CreateKeysAfterInitialize, | 105 LazyInitialize(base::Bind(&GCMKeyStore::CreateKeysAfterInitialize, |
| 76 weak_factory_.GetWeakPtr(), app_id, callback)); | 106 weak_factory_.GetWeakPtr(), app_id, |
| 107 instance_id_authorized_entity, callback)); | |
| 77 } | 108 } |
| 78 | 109 |
| 79 void GCMKeyStore::CreateKeysAfterInitialize(const std::string& app_id, | 110 void GCMKeyStore::CreateKeysAfterInitialize( |
| 80 const KeysCallback& callback) { | 111 const std::string& app_id, |
| 112 const std::string& instance_id_authorized_entity, | |
| 113 const KeysCallback& callback) { | |
| 81 DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED); | 114 DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED); |
| 82 if (state_ != State::INITIALIZED) { | 115 if (state_ != State::INITIALIZED) { |
| 83 callback.Run(KeyPair(), std::string() /* auth_secret */); | 116 callback.Run(KeyPair(), std::string() /* auth_secret */); |
| 84 return; | 117 return; |
| 85 } | 118 } |
| 86 | 119 |
| 87 // Only allow creating new keys if no keys currently exist. | 120 // Only allow creating new keys if no keys currently exist. Multiple Instance |
| 88 DCHECK_EQ(0u, key_pairs_.count(app_id)); | 121 // ID tokens can share an app_id (with different authorized entities), but |
| 122 // Instance ID tokens cannot share an app_id with a legacy GCM registration. | |
| 123 // This invariant is necessary for the fallback_to_empty_authorized_entity | |
| 124 // mode of GetKey (needed by GCMEncryptionProvider::DecryptMessage, which | |
| 125 // can't distinguish Instance ID tokens from legacy GCM registrations). | |
| 126 DCHECK(!key_data_.count(app_id) || | |
| 127 (!instance_id_authorized_entity.empty() && | |
| 128 !key_data_[app_id].count(instance_id_authorized_entity) && | |
| 129 !key_data_[app_id].count(std::string()))) | |
| 130 << "Instance ID tokens cannot share an app_id with a legacy GCM " | |
| 131 "registration"; | |
| 89 | 132 |
| 90 std::string private_key, public_key_x509, public_key; | 133 std::string private_key, public_key_x509, public_key; |
| 91 if (!CreateP256KeyPair(&private_key, &public_key_x509, &public_key)) { | 134 if (!CreateP256KeyPair(&private_key, &public_key_x509, &public_key)) { |
| 92 NOTREACHED() << "Unable to initialize a P-256 key pair."; | 135 NOTREACHED() << "Unable to initialize a P-256 key pair."; |
| 93 | 136 |
| 94 callback.Run(KeyPair(), std::string() /* auth_secret */); | 137 callback.Run(KeyPair(), std::string() /* auth_secret */); |
| 95 return; | 138 return; |
| 96 } | 139 } |
| 97 | 140 |
| 98 std::string auth_secret; | 141 std::string auth_secret; |
| 99 | 142 |
| 100 // Create the authentication secret, which has to be a cryptographically | 143 // Create the authentication secret, which has to be a cryptographically |
| 101 // secure random number of at least 128 bits (16 bytes). | 144 // secure random number of at least 128 bits (16 bytes). |
| 102 crypto::RandBytes(base::WriteInto(&auth_secret, kAuthSecretBytes + 1), | 145 crypto::RandBytes(base::WriteInto(&auth_secret, kAuthSecretBytes + 1), |
| 103 kAuthSecretBytes); | 146 kAuthSecretBytes); |
| 104 | 147 |
| 105 // Store the keys in a new EncryptionData object. | 148 // Store the keys in a new EncryptionData object. |
| 106 EncryptionData encryption_data; | 149 EncryptionData encryption_data; |
| 107 encryption_data.set_app_id(app_id); | 150 encryption_data.set_app_id(app_id); |
| 151 if (!instance_id_authorized_entity.empty()) { | |
| 152 encryption_data.set_instance_id_authorized_entity( | |
| 153 instance_id_authorized_entity); | |
| 154 } | |
| 108 encryption_data.set_auth_secret(auth_secret); | 155 encryption_data.set_auth_secret(auth_secret); |
| 109 | 156 |
| 110 KeyPair* pair = encryption_data.add_keys(); | 157 KeyPair* pair = encryption_data.add_keys(); |
| 111 pair->set_type(KeyPair::ECDH_P256); | 158 pair->set_type(KeyPair::ECDH_P256); |
| 112 pair->set_private_key(private_key); | 159 pair->set_private_key(private_key); |
| 113 pair->set_public_key_x509(public_key_x509); | 160 pair->set_public_key_x509(public_key_x509); |
| 114 pair->set_public_key(public_key); | 161 pair->set_public_key(public_key); |
| 115 | 162 |
| 116 // Write them immediately to our cache, so subsequent calls to | 163 // Write them immediately to our cache, so subsequent calls to |
| 117 // {Get/Create/Remove}Keys can see them. | 164 // {Get/Create/Remove}Keys can see them. |
| 118 key_pairs_[app_id] = *pair; | 165 key_data_[app_id][instance_id_authorized_entity] = {*pair, auth_secret}; |
| 119 auth_secrets_[app_id] = auth_secret; | |
| 120 | 166 |
| 121 using EntryVectorType = | 167 using EntryVectorType = |
| 122 leveldb_proto::ProtoDatabase<EncryptionData>::KeyEntryVector; | 168 leveldb_proto::ProtoDatabase<EncryptionData>::KeyEntryVector; |
| 123 | 169 |
| 124 std::unique_ptr<EntryVectorType> entries_to_save(new EntryVectorType()); | 170 std::unique_ptr<EntryVectorType> entries_to_save(new EntryVectorType()); |
| 125 std::unique_ptr<std::vector<std::string>> keys_to_remove( | 171 std::unique_ptr<std::vector<std::string>> keys_to_remove( |
| 126 new std::vector<std::string>()); | 172 new std::vector<std::string>()); |
| 127 | 173 |
| 128 entries_to_save->push_back(std::make_pair(app_id, encryption_data)); | 174 entries_to_save->push_back(std::make_pair( |
| 175 DatabaseKey(app_id, instance_id_authorized_entity), encryption_data)); | |
| 129 | 176 |
| 130 database_->UpdateEntries( | 177 database_->UpdateEntries( |
| 131 std::move(entries_to_save), std::move(keys_to_remove), | 178 std::move(entries_to_save), std::move(keys_to_remove), |
| 132 base::Bind(&GCMKeyStore::DidStoreKeys, weak_factory_.GetWeakPtr(), *pair, | 179 base::Bind(&GCMKeyStore::DidStoreKeys, weak_factory_.GetWeakPtr(), *pair, |
| 133 auth_secret, callback)); | 180 auth_secret, callback)); |
| 134 } | 181 } |
| 135 | 182 |
| 136 void GCMKeyStore::DidStoreKeys(const KeyPair& pair, | 183 void GCMKeyStore::DidStoreKeys(const KeyPair& pair, |
| 137 const std::string& auth_secret, | 184 const std::string& auth_secret, |
| 138 const KeysCallback& callback, | 185 const KeysCallback& callback, |
| 139 bool success) { | 186 bool success) { |
| 140 UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.CreateKeySuccessRate", success); | 187 UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.CreateKeySuccessRate", success); |
| 141 | 188 |
| 142 if (!success) { | 189 if (!success) { |
| 143 LOG(ERROR) << "Unable to store the created key in the GCM Key Store."; | 190 LOG(ERROR) << "Unable to store the created key in the GCM Key Store."; |
| 144 | 191 |
| 145 // Our cache is now inconsistent. Reject requests until restarted. | 192 // Our cache is now inconsistent. Reject requests until restarted. |
| 146 state_ = State::FAILED; | 193 state_ = State::FAILED; |
| 147 | 194 |
| 148 callback.Run(KeyPair(), std::string() /* auth_secret */); | 195 callback.Run(KeyPair(), std::string() /* auth_secret */); |
| 149 return; | 196 return; |
| 150 } | 197 } |
| 151 | 198 |
| 152 callback.Run(pair, auth_secret); | 199 callback.Run(pair, auth_secret); |
| 153 } | 200 } |
| 154 | 201 |
| 155 void GCMKeyStore::RemoveKeys(const std::string& app_id, | 202 void GCMKeyStore::RemoveKeys(const std::string& app_id, |
| 203 const std::string& instance_id_authorized_entity, | |
| 156 const base::Closure& callback) { | 204 const base::Closure& callback) { |
| 157 LazyInitialize(base::Bind(&GCMKeyStore::RemoveKeysAfterInitialize, | 205 LazyInitialize(base::Bind(&GCMKeyStore::RemoveKeysAfterInitialize, |
| 158 weak_factory_.GetWeakPtr(), app_id, callback)); | 206 weak_factory_.GetWeakPtr(), app_id, |
| 207 instance_id_authorized_entity, callback)); | |
| 159 } | 208 } |
| 160 | 209 |
| 161 void GCMKeyStore::RemoveKeysAfterInitialize(const std::string& app_id, | 210 void GCMKeyStore::RemoveKeysAfterInitialize( |
| 162 const base::Closure& callback) { | 211 const std::string& app_id, |
| 212 const std::string& instance_id_authorized_entity, | |
| 213 const base::Closure& callback) { | |
| 163 DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED); | 214 DCHECK(state_ == State::INITIALIZED || state_ == State::FAILED); |
| 164 const auto iter = key_pairs_.find(app_id); | 215 |
| 165 if (iter == key_pairs_.end() || state_ != State::INITIALIZED) { | 216 const auto& outer_iter = key_data_.find(app_id); |
| 217 if (outer_iter == key_data_.end() || state_ != State::INITIALIZED) { | |
| 166 callback.Run(); | 218 callback.Run(); |
| 167 return; | 219 return; |
| 168 } | 220 } |
| 169 | 221 |
| 170 // Clear them immediately from our cache, so subsequent calls to | |
| 171 // {Get/Create/Remove}Keys don't see them. | |
| 172 key_pairs_.erase(app_id); | |
| 173 auth_secrets_.erase(app_id); | |
| 174 | |
| 175 using EntryVectorType = | 222 using EntryVectorType = |
| 176 leveldb_proto::ProtoDatabase<EncryptionData>::KeyEntryVector; | 223 leveldb_proto::ProtoDatabase<EncryptionData>::KeyEntryVector; |
| 177 | 224 |
| 178 std::unique_ptr<EntryVectorType> entries_to_save(new EntryVectorType()); | 225 std::unique_ptr<EntryVectorType> entries_to_save(new EntryVectorType()); |
| 179 std::unique_ptr<std::vector<std::string>> keys_to_remove( | 226 std::unique_ptr<std::vector<std::string>> keys_to_remove( |
| 180 new std::vector<std::string>(1, app_id)); | 227 new std::vector<std::string>()); |
| 228 | |
| 229 bool had_keys = false; | |
| 230 auto& inner_map = outer_iter->second; | |
| 231 for (auto it = inner_map.begin(); it != inner_map.end();) { | |
| 232 // Wildcard "*" matches all non-empty authorized entities (not legacy GCM). | |
| 233 if (instance_id_authorized_entity == "*" | |
| 234 ? !it->first.empty() | |
| 235 : it->first == instance_id_authorized_entity) { | |
| 236 had_keys = true; | |
| 237 | |
| 238 keys_to_remove->push_back(DatabaseKey(app_id, it->first)); | |
| 239 | |
| 240 // Clear keys immediately from our cache, so subsequent calls to | |
| 241 // {Get/Create/Remove}Keys don't see them. | |
| 242 it = inner_map.erase(it); | |
| 243 } else { | |
| 244 ++it; | |
| 245 } | |
| 246 } | |
| 247 if (!had_keys) { | |
| 248 callback.Run(); | |
| 249 return; | |
| 250 } | |
| 251 if (inner_map.empty()) | |
| 252 key_data_.erase(app_id); | |
| 181 | 253 |
| 182 database_->UpdateEntries(std::move(entries_to_save), | 254 database_->UpdateEntries(std::move(entries_to_save), |
| 183 std::move(keys_to_remove), | 255 std::move(keys_to_remove), |
| 184 base::Bind(&GCMKeyStore::DidRemoveKeys, | 256 base::Bind(&GCMKeyStore::DidRemoveKeys, |
| 185 weak_factory_.GetWeakPtr(), callback)); | 257 weak_factory_.GetWeakPtr(), callback)); |
| 186 } | 258 } |
| 187 | 259 |
| 188 void GCMKeyStore::DidRemoveKeys(const base::Closure& callback, bool success) { | 260 void GCMKeyStore::DidRemoveKeys(const base::Closure& callback, bool success) { |
| 189 UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.RemoveKeySuccessRate", success); | 261 UMA_HISTOGRAM_BOOLEAN("GCM.Crypto.RemoveKeySuccessRate", success); |
| 190 | 262 |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 240 DVLOG(1) << "Unable to load entries into the GCM Key Store."; | 312 DVLOG(1) << "Unable to load entries into the GCM Key Store."; |
| 241 state_ = State::FAILED; | 313 state_ = State::FAILED; |
| 242 | 314 |
| 243 delayed_task_controller_.SetReady(); | 315 delayed_task_controller_.SetReady(); |
| 244 return; | 316 return; |
| 245 } | 317 } |
| 246 | 318 |
| 247 for (const EncryptionData& entry : *entries) { | 319 for (const EncryptionData& entry : *entries) { |
| 248 DCHECK_EQ(1, entry.keys_size()); | 320 DCHECK_EQ(1, entry.keys_size()); |
| 249 | 321 |
| 250 key_pairs_[entry.app_id()] = entry.keys(0); | 322 std::string instance_id_authorized_entity; |
| 251 auth_secrets_[entry.app_id()] = entry.auth_secret(); | 323 if (entry.has_instance_id_authorized_entity()) |
| 324 instance_id_authorized_entity = entry.instance_id_authorized_entity(); | |
| 325 key_data_[entry.app_id()][instance_id_authorized_entity] = { | |
| 326 entry.keys(0), entry.auth_secret()}; | |
| 252 } | 327 } |
| 253 | 328 |
| 254 state_ = State::INITIALIZED; | 329 state_ = State::INITIALIZED; |
| 255 | 330 |
| 256 delayed_task_controller_.SetReady(); | 331 delayed_task_controller_.SetReady(); |
| 257 } | 332 } |
| 258 | 333 |
| 259 } // namespace gcm | 334 } // namespace gcm |
| OLD | NEW |