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