OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/policy/cros_user_policy_cache.h" | |
6 | |
7 #include <string> | |
8 #include <vector> | |
9 | |
10 #include "base/bind.h" | |
11 #include "base/callback.h" | |
12 #include "base/file_path.h" | |
13 #include "base/file_util.h" | |
14 #include "base/path_service.h" | |
15 #include "chrome/browser/chromeos/cros/login_library.h" | |
16 #include "chrome/browser/policy/proto/cloud_policy.pb.h" | |
17 #include "chrome/browser/policy/proto/device_management_backend.pb.h" | |
18 #include "chrome/browser/policy/proto/device_management_local.pb.h" | |
19 #include "chrome/common/chrome_paths.h" | |
20 #include "chrome/common/extensions/extension_constants.h" | |
21 #include "content/browser/browser_thread.h" | |
22 #include "crypto/signature_verifier.h" | |
23 | |
24 using extension_misc::kSignatureAlgorithm; | |
25 | |
26 static const FilePath::CharType kUserPolicyKeyFile[] = FILE_PATH_LITERAL("key"); | |
27 | |
28 namespace policy { | |
29 | |
30 // Decodes a CloudPolicySettings object into two maps with mandatory and | |
31 // recommended settings, respectively. The implementation is generated code | |
32 // in policy/cloud_policy_generated.cc. | |
33 void DecodePolicy(const em::CloudPolicySettings& policy, | |
34 PolicyMap* mandatory, PolicyMap* recommended); | |
35 | |
36 // Manages the policy key and implements signature checking on the key. | |
37 class CrosUserPolicyCache::PolicyKey | |
38 : public base::RefCountedThreadSafe<PolicyKey> { | |
39 public: | |
40 typedef base::Callback<void(bool)> StatusCallback; | |
41 | |
42 explicit PolicyKey(const FilePath& key_file); | |
43 | |
44 // Loads the key asynchronously on the file thread. | |
45 void LoadKey(); | |
46 | |
47 // Checks whether |signature| is a valid over |data|. The status will be | |
48 // reported through |callback| once the check is complete. If the |reload_key| | |
49 // flag is set, the key is first reloaded from the file. | |
50 void CheckSignature(const std::string& data, | |
51 const std::string& signature, | |
52 bool reload_key, | |
53 const StatusCallback& callback); | |
54 | |
55 private: | |
56 // Reports the result of a signature check. Must be called on the UI thread. | |
57 void ReportResult(const StatusCallback& callback, bool result); | |
58 | |
59 // Read the public key from |key_file_|. Must be called on the file thread. | |
60 void ReadKeyFromFile(); | |
61 | |
62 // Runs the actual signature checking. Must be called on the file thread. | |
63 void CheckSignatureOnFileThread(const std::string& data, | |
64 const std::string& signature, | |
65 bool reload_key, | |
66 const StatusCallback& callback); | |
67 | |
68 // The data members should only be accessed from the file thread. | |
69 const FilePath key_file_; | |
70 std::vector<uint8> public_key_; | |
71 | |
72 DISALLOW_COPY_AND_ASSIGN(PolicyKey); | |
73 }; | |
74 | |
75 CrosUserPolicyCache::PolicyKey::PolicyKey(const FilePath& key_file) | |
76 : key_file_(key_file) {} | |
77 | |
78 void CrosUserPolicyCache::PolicyKey::LoadKey() { | |
79 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | |
80 NewRunnableMethod(this, &PolicyKey::ReadKeyFromFile)); | |
81 } | |
82 | |
83 void CrosUserPolicyCache::PolicyKey::CheckSignature( | |
84 const std::string& data, | |
85 const std::string& signature, | |
86 bool reload_key, | |
87 const StatusCallback& callback) { | |
88 BrowserThread::PostTask( | |
89 BrowserThread::FILE, FROM_HERE, | |
90 NewRunnableMethod(this, &PolicyKey::CheckSignatureOnFileThread, | |
91 data, signature, reload_key, callback)); | |
92 } | |
93 | |
94 void CrosUserPolicyCache::PolicyKey::CheckSignatureOnFileThread( | |
95 const std::string& data, | |
96 const std::string& signature, | |
97 bool reload_key, | |
98 const StatusCallback& callback) { | |
99 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
100 bool status = false; | |
101 | |
102 // Re-read the key if necessary. | |
103 if (public_key_.empty() || reload_key) | |
104 ReadKeyFromFile(); | |
105 | |
106 if (!public_key_.empty()) { | |
107 crypto::SignatureVerifier verifier; | |
108 if (verifier.VerifyInit(kSignatureAlgorithm, sizeof(kSignatureAlgorithm), | |
109 reinterpret_cast<const uint8*>(&signature[0]), | |
110 signature.size(), | |
111 &public_key_[0], public_key_.size())) { | |
112 verifier.VerifyUpdate(reinterpret_cast<const uint8*>(data.c_str()), | |
113 data.length()); | |
114 status = verifier.VerifyFinal(); | |
115 } | |
116 } | |
117 | |
118 BrowserThread::PostTask(BrowserThread::UI, | |
119 FROM_HERE, | |
120 NewRunnableMethod(this, &PolicyKey::ReportResult, | |
121 callback, status)); | |
122 } | |
123 | |
124 void CrosUserPolicyCache::PolicyKey::ReportResult( | |
125 const StatusCallback& callback, | |
126 bool result) { | |
127 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
128 callback.Run(result); | |
129 } | |
130 | |
131 void CrosUserPolicyCache::PolicyKey::ReadKeyFromFile() { | |
132 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
133 | |
134 public_key_.clear(); | |
135 if (!file_util::PathExists(key_file_)) | |
136 return; | |
137 | |
138 int64 size = -1; | |
139 if (!file_util::GetFileSize(key_file_, &size)) { | |
140 LOG(ERROR) << "Failed to get file size for " << key_file_.value(); | |
141 return; | |
142 } | |
143 | |
144 const int64 kMaxSize = 1 * 1024 * 1024; // 1M should be sufficient. | |
145 if (size < 0 || size > kMaxSize) { | |
146 LOG(ERROR) << "Bogus policy key file size " << size; | |
147 return; | |
148 } | |
149 | |
150 std::vector<uint8> key_data(size); | |
151 if (!file_util::ReadFile(key_file_, | |
152 reinterpret_cast<char*>(&key_data[0]), | |
153 key_data.size())) { | |
154 LOG(ERROR) << "Failed to read " << key_file_.value(); | |
155 return; | |
156 } | |
157 | |
158 public_key_.swap(key_data); | |
159 } | |
160 | |
161 // Takes care of sending a new policy blob to session manager and reports back | |
162 // the status through a callback. | |
163 class CrosUserPolicyCache::StorePolicyOperation { | |
164 public: | |
165 typedef base::Callback<void(bool)> StatusCallback; | |
166 | |
167 // Creates and exectues an operation. | |
168 static StorePolicyOperation* Run(const em::PolicyFetchResponse& policy, | |
169 chromeos::LoginLibrary* login_library, | |
170 const StatusCallback& callback); | |
171 | |
172 // Cancels the operation, making sure that any pending callbacks get killed. | |
gfeher
2011/07/21 23:37:15
This doesn't really cancel the operation, just kil
Mattias Nissler (ping if slow)
2011/07/22 11:29:48
Done.
| |
173 void Cancel(); | |
174 | |
175 const em::PolicyFetchResponse& policy() { return policy_; } | |
176 | |
177 private: | |
178 StorePolicyOperation(const em::PolicyFetchResponse& policy, | |
179 const StatusCallback& callback); | |
180 | |
181 // StorePolicyOperation manages its own lifetime. | |
182 ~StorePolicyOperation() {} | |
183 | |
184 // A callback function suitable for passing to login_library. | |
185 static void StorePolicyCallback(void* delegate, bool result); | |
186 | |
187 em::PolicyFetchResponse policy_; | |
188 StatusCallback callback_; | |
189 | |
190 DISALLOW_COPY_AND_ASSIGN(StorePolicyOperation); | |
191 }; | |
192 | |
193 // static | |
194 CrosUserPolicyCache::StorePolicyOperation* | |
195 CrosUserPolicyCache::StorePolicyOperation::Run( | |
196 const em::PolicyFetchResponse& policy, | |
197 chromeos::LoginLibrary* login_library, | |
198 const StatusCallback& callback) { | |
199 StorePolicyOperation* op = new StorePolicyOperation(policy, callback); | |
200 std::string serialized; | |
201 if (!policy.SerializeToString(&serialized)) { | |
202 LOG(ERROR) << "Failed to serialize policy protobuf!"; | |
203 callback.Run(false); | |
204 delete op; | |
205 return NULL; | |
206 } | |
207 login_library->RequestStoreUserPolicy(serialized, StorePolicyCallback, op); | |
208 return op; | |
209 } | |
210 | |
211 void CrosUserPolicyCache::StorePolicyOperation::Cancel() { | |
212 callback_.Reset(); | |
213 } | |
214 | |
215 CrosUserPolicyCache::StorePolicyOperation::StorePolicyOperation( | |
216 const em::PolicyFetchResponse& policy, | |
217 const StatusCallback& callback) | |
218 : policy_(policy), | |
219 callback_(callback) {} | |
220 | |
221 // static | |
222 void CrosUserPolicyCache::StorePolicyOperation::StorePolicyCallback( | |
223 void* delegate, | |
224 bool result) { | |
225 StorePolicyOperation* op(static_cast<StorePolicyOperation*>(delegate)); | |
226 if (!op->callback_.is_null()) | |
227 op->callback_.Run(result); | |
228 delete op; | |
229 } | |
230 | |
231 class CrosUserPolicyCache::RetrievePolicyOperation { | |
232 public: | |
233 typedef base::Callback<void(bool, const em::PolicyFetchResponse&)> | |
234 ResultCallback; | |
235 | |
236 // Creates and executes an operation. | |
237 static RetrievePolicyOperation* Run(PolicyKey* key, | |
238 chromeos::LoginLibrary* login_library, | |
239 bool reload_key, | |
240 const ResultCallback& callback); | |
241 | |
242 // Cancels the operation, disengaging all pending callbacks. | |
gfeher
2011/07/21 23:37:15
Same here.
Mattias Nissler (ping if slow)
2011/07/22 11:29:48
Done.
| |
243 void Cancel(); | |
244 | |
245 private: | |
246 RetrievePolicyOperation(PolicyKey* key, | |
247 bool reload_key, | |
248 const ResultCallback& callback); | |
249 | |
250 // RetrievePolicyOperation manages its own lifetime. | |
251 ~RetrievePolicyOperation() {} | |
252 | |
253 // Decodes the policy data and triggers a signature check. | |
254 void OnPolicyRetrieved(const char* data, unsigned int size); | |
255 | |
256 // Handles the signature verification callback and reports the result. | |
257 void OnSignatureChecked(bool result); | |
258 | |
259 // Finishes the operation, makes the |callback_|, and deletes |this|. | |
260 void Finish(bool status); | |
261 | |
262 // A callback function suitable for passing to login_library. | |
263 static void RetrievePolicyCallback(void* delegate, | |
264 const char* data, | |
265 unsigned int size); | |
266 scoped_refptr<PolicyKey> key_; | |
267 bool reload_key_; | |
268 ResultCallback callback_; | |
269 em::PolicyFetchResponse policy_; | |
270 | |
271 DISALLOW_COPY_AND_ASSIGN(RetrievePolicyOperation); | |
272 }; | |
273 | |
274 // static | |
275 CrosUserPolicyCache::RetrievePolicyOperation* | |
276 CrosUserPolicyCache::RetrievePolicyOperation::Run( | |
277 PolicyKey* key, | |
278 chromeos::LoginLibrary* login_library, | |
279 bool reload_key, | |
280 const ResultCallback& callback) { | |
281 RetrievePolicyOperation* op = | |
282 new RetrievePolicyOperation(key, reload_key, callback); | |
283 login_library->RequestRetrieveUserPolicy(RetrievePolicyCallback, op); | |
284 return op; | |
285 } | |
286 | |
287 void CrosUserPolicyCache::RetrievePolicyOperation::Cancel() { | |
288 callback_.Reset(); | |
289 } | |
290 | |
291 CrosUserPolicyCache::RetrievePolicyOperation::RetrievePolicyOperation( | |
292 PolicyKey* key, | |
293 bool reload_key, | |
294 const ResultCallback& callback) | |
295 : key_(key), | |
296 reload_key_(reload_key), | |
297 callback_(callback) {} | |
298 | |
299 void CrosUserPolicyCache::RetrievePolicyOperation::OnPolicyRetrieved( | |
300 const char* data, | |
301 unsigned int size) { | |
302 if (!policy_.ParseFromArray(data, size) || | |
303 !policy_.has_policy_data() || | |
304 !policy_.has_policy_data_signature()) { | |
305 LOG(ERROR) << "Failed to decode policy"; | |
306 Finish(false); | |
307 return; | |
308 } | |
309 key_->CheckSignature(policy_.policy_data(), | |
310 policy_.policy_data_signature(), | |
311 reload_key_, | |
312 base::Bind(&RetrievePolicyOperation::OnSignatureChecked, | |
313 base::Unretained(this))); | |
314 } | |
315 | |
316 void CrosUserPolicyCache::RetrievePolicyOperation::OnSignatureChecked( | |
317 bool result) { | |
318 if (!result) | |
319 LOG(ERROR) << "User policy signature check failed."; | |
320 Finish(result); | |
321 } | |
322 | |
323 void CrosUserPolicyCache::RetrievePolicyOperation::Finish(bool status) { | |
324 if (!callback_.is_null()) | |
325 callback_.Run(status, policy_); | |
326 delete this; | |
327 } | |
328 | |
329 // static | |
330 void CrosUserPolicyCache::RetrievePolicyOperation::RetrievePolicyCallback( | |
331 void *delegate, | |
332 const char* data, | |
333 unsigned int size) { | |
334 static_cast<RetrievePolicyOperation*>(delegate)->OnPolicyRetrieved(data, | |
335 size); | |
336 } | |
337 | |
338 CrosUserPolicyCache::CrosUserPolicyCache( | |
339 chromeos::LoginLibrary* login_library, | |
340 CloudPolicyDataStore* data_store, | |
341 const FilePath& legacy_token_cache_file, | |
342 const FilePath& legacy_policy_cache_file) | |
343 : login_library_(login_library), | |
344 data_store_(data_store), | |
345 policy_cache_loaded_(false), | |
346 store_operation_(NULL), | |
347 retrieve_operation_(NULL), | |
348 legacy_cache_dir_(legacy_token_cache_file.DirName()), | |
349 ALLOW_THIS_IN_INITIALIZER_LIST( | |
350 legacy_token_cache_delegate_factory_(this)), | |
351 ALLOW_THIS_IN_INITIALIZER_LIST( | |
352 legacy_policy_cache_delegate_factory_(this)) { | |
353 DCHECK_EQ(legacy_token_cache_file.DirName().value(), | |
354 legacy_policy_cache_file.DirName().value()); | |
355 | |
356 FilePath key_file; | |
357 if (PathService::Get(chrome::DIR_USER_POLICY, &key_file)) | |
358 key_file.Append(kUserPolicyKeyFile); | |
359 else | |
360 NOTREACHED(); | |
361 key_ = new PolicyKey(key_file); | |
362 | |
363 legacy_token_loader_ = | |
364 new UserPolicyTokenLoader( | |
365 legacy_token_cache_delegate_factory_.GetWeakPtr(), | |
366 legacy_token_cache_file); | |
367 legacy_policy_cache_ = | |
368 new UserPolicyDiskCache( | |
369 legacy_policy_cache_delegate_factory_.GetWeakPtr(), | |
370 legacy_policy_cache_file); | |
371 } | |
372 | |
373 CrosUserPolicyCache::~CrosUserPolicyCache() { | |
374 CancelStore(); | |
375 CancelRetrieve(); | |
376 } | |
377 | |
378 void CrosUserPolicyCache::Load() { | |
379 key_->LoadKey(); | |
380 retrieve_operation_ = | |
381 RetrievePolicyOperation::Run( | |
382 key_, | |
383 login_library_, | |
384 false, | |
385 base::Bind(&CrosUserPolicyCache::OnPolicyLoadDone, | |
386 base::Unretained(this))); | |
387 } | |
388 | |
389 void CrosUserPolicyCache::SetPolicy(const em::PolicyFetchResponse& policy) { | |
390 CancelStore(); | |
391 set_last_policy_refresh_time(base::Time::NowFromSystemTime()); | |
392 store_operation_ = | |
393 StorePolicyOperation::Run(policy, | |
394 login_library_, | |
395 base::Bind(&CrosUserPolicyCache::OnPolicyStored, | |
396 base::Unretained(this))); | |
397 } | |
398 | |
399 void CrosUserPolicyCache::SetUnmanaged() { | |
400 base::Time now(base::Time::NowFromSystemTime()); | |
401 SetUnmanagedInternal(now); | |
402 | |
403 // Construct a policy blob with unmanaged state. | |
404 em::PolicyData policy_data; | |
405 policy_data.set_policy_type(data_store_->policy_type()); | |
406 policy_data.set_timestamp((now - base::Time::UnixEpoch()).InMilliseconds()); | |
gfeher
2011/07/21 23:37:15
I don't think it makes sense to stuff that into th
Mattias Nissler (ping if slow)
2011/07/22 11:29:48
Makes sense :) I'll keep it in mind when doing mor
| |
407 policy_data.set_state(em::PolicyData::UNMANAGED); | |
408 | |
409 em::PolicyFetchResponse policy; | |
410 if (!policy_data.SerializeToString(policy.mutable_policy_data())) { | |
411 LOG(ERROR) << "Failed to serialize policy_data"; | |
412 return; | |
413 } | |
414 | |
415 SetPolicy(policy); | |
416 } | |
417 | |
418 bool CrosUserPolicyCache::IsReady() { | |
419 return policy_cache_loaded_; | |
420 } | |
421 | |
422 bool CrosUserPolicyCache::DecodePolicyData(const em::PolicyData& policy_data, | |
423 PolicyMap* mandatory, | |
424 PolicyMap* recommended) { | |
425 em::CloudPolicySettings policy; | |
426 if (!policy.ParseFromString(policy_data.policy_value())) { | |
427 LOG(WARNING) << "Failed to parse CloudPolicySettings protobuf."; | |
428 return false; | |
429 } | |
430 DecodePolicy(policy, mandatory, recommended); | |
431 return true; | |
432 } | |
433 | |
434 void CrosUserPolicyCache::OnTokenLoaded(const std::string& token, | |
435 const std::string& device_id) { | |
436 data_store_->set_device_id(device_id); | |
437 data_store_->SetDeviceToken(token, true); | |
438 } | |
439 | |
440 void CrosUserPolicyCache::OnDiskCacheLoaded( | |
441 const em::CachedCloudPolicyResponse& policy) { | |
442 policy_cache_loaded_ = true; | |
443 if (policy.unmanaged()) { | |
444 SetUnmanagedInternal(base::Time::FromTimeT(policy.timestamp())); | |
445 } else if (policy.has_cloud_policy()) { | |
446 base::Time timestamp; | |
447 if (SetPolicyInternal(policy.cloud_policy(), ×tamp, true)) | |
448 set_last_policy_refresh_time(timestamp); | |
449 } | |
450 } | |
451 | |
452 void CrosUserPolicyCache::OnPolicyStored(bool result) { | |
453 DCHECK(store_operation_); | |
454 if (result) { | |
455 // Policy is stored successfully, reload from session_manager and apply. | |
456 // This helps us making sure we only use policy that session_manager has | |
457 // checked and confirmed to be good. | |
458 CancelRetrieve(); | |
459 retrieve_operation_ = | |
460 RetrievePolicyOperation::Run( | |
461 key_, | |
462 login_library_, | |
463 store_operation_->policy().has_new_public_key(), | |
464 base::Bind(&CrosUserPolicyCache::OnPolicyReloadDone, | |
465 base::Unretained(this))); | |
466 | |
467 // Now that we have installed the new policy blob, we can make sure that the | |
468 // old cache directory is removed. | |
469 if (!legacy_cache_dir_.empty()) { | |
470 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | |
471 NewRunnableFunction(&RemoveLegacyCacheDir, | |
472 legacy_cache_dir_)); | |
473 } | |
474 } else { | |
475 InformNotifier(CloudPolicySubsystem::LOCAL_ERROR, | |
476 CloudPolicySubsystem::POLICY_LOCAL_ERROR); | |
477 } | |
478 CancelStore(); | |
479 } | |
480 | |
481 void CrosUserPolicyCache::OnPolicyLoadDone( | |
482 bool result, | |
483 const em::PolicyFetchResponse& policy) { | |
484 DCHECK(retrieve_operation_); | |
485 CancelRetrieve(); | |
486 if (!result) { | |
487 // No policy present. Try to load the legacy token and policy cache. | |
488 legacy_token_loader_->Load(); | |
489 legacy_policy_cache_->Load(); | |
490 return; | |
491 } | |
492 | |
493 policy_cache_loaded_ = true; | |
494 | |
495 // We have new-style policy, no need to clean up. | |
496 legacy_cache_dir_.clear(); | |
497 | |
498 em::PolicyData policy_data; | |
499 if (!policy_data.ParseFromString(policy.policy_data())) { | |
500 LOG(WARNING) << "Failed to parse PolicyData protobuf."; | |
501 InformNotifier(CloudPolicySubsystem::LOCAL_ERROR, | |
502 CloudPolicySubsystem::POLICY_LOCAL_ERROR); | |
503 data_store_->SetDeviceToken(std::string(), true); | |
504 return; | |
505 } | |
506 if (policy_data.request_token().empty() || | |
507 policy_data.username().empty() || | |
508 policy_data.device_id().empty()) { | |
509 LOG(WARNING) << "Policy protobuf is missing credentials"; | |
510 InformNotifier(CloudPolicySubsystem::LOCAL_ERROR, | |
511 CloudPolicySubsystem::POLICY_LOCAL_ERROR); | |
512 data_store_->SetDeviceToken(std::string(), true); | |
513 return; | |
514 } | |
515 data_store_->set_device_id(policy_data.device_id()); | |
516 data_store_->SetDeviceToken(policy_data.request_token(), true); | |
517 if (SetPolicyInternal(policy, NULL, true)) | |
518 set_last_policy_refresh_time(base::Time::NowFromSystemTime()); | |
519 } | |
520 | |
521 void CrosUserPolicyCache::OnPolicyReloadDone( | |
522 bool result, | |
523 const em::PolicyFetchResponse& policy) { | |
524 DCHECK(retrieve_operation_); | |
525 policy_cache_loaded_ = true; | |
526 CancelRetrieve(); | |
527 if (result) { | |
528 if (SetPolicyInternal(policy, NULL, false)) | |
529 set_last_policy_refresh_time(base::Time::NowFromSystemTime()); | |
530 } else { | |
531 InformNotifier(CloudPolicySubsystem::LOCAL_ERROR, | |
532 CloudPolicySubsystem::POLICY_LOCAL_ERROR); | |
533 } | |
534 } | |
535 | |
536 void CrosUserPolicyCache::CancelStore() { | |
537 if (store_operation_) { | |
538 store_operation_->Cancel(); | |
539 store_operation_ = NULL; | |
540 } | |
541 } | |
542 | |
543 void CrosUserPolicyCache::CancelRetrieve() { | |
544 if (retrieve_operation_) { | |
545 retrieve_operation_->Cancel(); | |
546 retrieve_operation_ = NULL; | |
547 } | |
548 } | |
549 | |
550 // static | |
551 void CrosUserPolicyCache::RemoveLegacyCacheDir(const FilePath& dir) { | |
552 if (!file_util::Delete(dir, true)) | |
553 PLOG(ERROR) << "Failed to remove " << dir.value(); | |
554 } | |
555 | |
556 } // namespace policy | |
OLD | NEW |