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