OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/cloud_policy_validator.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/bind_helpers.h" | |
9 #include "base/message_loop.h" | |
10 #include "base/stl_util.h" | |
11 #include "chrome/browser/policy/cloud_policy_constants.h" | |
12 #include "chrome/browser/policy/proto/chrome_device_policy.pb.h" | |
13 #include "chrome/browser/policy/proto/cloud_policy.pb.h" | |
14 #include "chrome/browser/policy/proto/device_management_backend.pb.h" | |
15 #include "content/public/browser/browser_thread.h" | |
16 #include "crypto/signature_verifier.h" | |
17 #include "google_apis/gaia/gaia_auth_util.h" | |
18 | |
19 namespace em = enterprise_management; | |
20 | |
21 namespace policy { | |
22 | |
23 namespace { | |
24 | |
25 // Grace interval for policy timestamp checks, in seconds. | |
26 const int kTimestampGraceIntervalSeconds = 60; | |
27 | |
28 // DER-encoded ASN.1 object identifier for the SHA1-RSA signature algorithm. | |
29 const uint8 kSignatureAlgorithm[] = { | |
30 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, | |
31 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00 | |
32 }; | |
33 | |
34 } // namespace | |
35 | |
36 CloudPolicyValidatorBase::~CloudPolicyValidatorBase() {} | |
37 | |
38 void CloudPolicyValidatorBase::ValidateTimestamp(base::Time not_before, | |
39 base::Time now, | |
40 bool allow_missing_timestamp) { | |
41 // Timestamp should be from the past. We allow for a 1-minute grace interval | |
42 // to cover clock drift. | |
43 validation_flags_ |= VALIDATE_TIMESTAMP; | |
44 timestamp_not_before_ = | |
45 (not_before - base::Time::UnixEpoch()).InMilliseconds(); | |
46 timestamp_not_after_ = | |
47 ((now + base::TimeDelta::FromSeconds(kTimestampGraceIntervalSeconds)) - | |
48 base::Time::UnixEpoch()).InMillisecondsRoundedUp(); | |
49 allow_missing_timestamp_ = allow_missing_timestamp; | |
50 } | |
51 | |
52 void CloudPolicyValidatorBase::ValidateUsername( | |
53 const std::string& expected_user) { | |
54 validation_flags_ |= VALIDATE_USERNAME; | |
55 user_ = gaia::CanonicalizeEmail(expected_user); | |
56 } | |
57 | |
58 void CloudPolicyValidatorBase::ValidateDomain( | |
59 const std::string& expected_domain) { | |
60 validation_flags_ |= VALIDATE_DOMAIN; | |
61 domain_ = gaia::CanonicalizeDomain(expected_domain); | |
62 } | |
63 | |
64 void CloudPolicyValidatorBase::ValidateDMToken(const std::string& token) { | |
65 validation_flags_ |= VALIDATE_TOKEN; | |
66 token_ = token; | |
67 } | |
68 | |
69 void CloudPolicyValidatorBase::ValidatePolicyType( | |
70 const std::string& policy_type) { | |
71 validation_flags_ |= VALIDATE_POLICY_TYPE; | |
72 policy_type_ = policy_type; | |
73 } | |
74 | |
75 void CloudPolicyValidatorBase::ValidatePayload() { | |
76 validation_flags_ |= VALIDATE_PAYLOAD; | |
77 } | |
78 | |
79 void CloudPolicyValidatorBase::ValidateSignature(const std::vector<uint8>& key, | |
80 bool allow_key_rotation) { | |
81 validation_flags_ |= VALIDATE_SIGNATURE; | |
82 key_ = std::string(reinterpret_cast<const char*>(vector_as_array(&key)), | |
83 key.size()); | |
84 allow_key_rotation_ = allow_key_rotation; | |
85 } | |
86 | |
87 void CloudPolicyValidatorBase::ValidateInitialKey() { | |
88 validation_flags_ |= VALIDATE_INITIAL_KEY; | |
89 } | |
90 | |
91 void CloudPolicyValidatorBase::ValidateAgainstCurrentPolicy( | |
92 const em::PolicyData* policy_data, | |
93 bool allow_missing_timestamp) { | |
94 base::Time last_policy_timestamp; | |
95 std::string expected_dm_token; | |
96 if (policy_data) { | |
97 last_policy_timestamp = | |
98 base::Time::UnixEpoch() + | |
99 base::TimeDelta::FromMilliseconds(policy_data->timestamp()); | |
100 expected_dm_token = policy_data->request_token(); | |
101 } | |
102 ValidateTimestamp(last_policy_timestamp, base::Time::NowFromSystemTime(), | |
103 allow_missing_timestamp); | |
104 if (!expected_dm_token.empty()) | |
105 ValidateDMToken(expected_dm_token); | |
106 } | |
107 | |
108 CloudPolicyValidatorBase::CloudPolicyValidatorBase( | |
109 scoped_ptr<em::PolicyFetchResponse> policy_response, | |
110 google::protobuf::MessageLite* payload) | |
111 : status_(VALIDATION_OK), | |
112 policy_(policy_response.Pass()), | |
113 payload_(payload), | |
114 validation_flags_(0), | |
115 timestamp_not_before_(0), | |
116 timestamp_not_after_(0), | |
117 allow_missing_timestamp_(false), | |
118 allow_key_rotation_(false) {} | |
119 | |
120 // static | |
121 void CloudPolicyValidatorBase::PerformValidation( | |
122 scoped_ptr<CloudPolicyValidatorBase> self, | |
123 scoped_refptr<base::MessageLoopProxy> message_loop, | |
124 const base::Closure& completion_callback) { | |
125 // Run the validation activities on this thread. | |
126 self->RunValidation(); | |
127 | |
128 // Report completion on |message_loop|. | |
129 message_loop->PostTask( | |
130 FROM_HERE, | |
131 base::Bind(&CloudPolicyValidatorBase::ReportCompletion, | |
132 base::Passed(&self), | |
133 completion_callback)); | |
134 } | |
135 | |
136 // static | |
137 void CloudPolicyValidatorBase::ReportCompletion( | |
138 scoped_ptr<CloudPolicyValidatorBase> self, | |
139 const base::Closure& completion_callback) { | |
140 completion_callback.Run(); | |
141 } | |
142 | |
143 void CloudPolicyValidatorBase::RunValidation() { | |
144 policy_data_.reset(new em::PolicyData()); | |
145 RunChecks(); | |
146 } | |
147 | |
148 void CloudPolicyValidatorBase::RunChecks() { | |
149 status_ = VALIDATION_OK; | |
150 if ((policy_->has_error_code() && policy_->error_code() != 200) || | |
151 (policy_->has_error_message() && !policy_->error_message().empty())) { | |
152 LOG(ERROR) << "Error in policy blob." | |
153 << " code: " << policy_->error_code() | |
154 << " message: " << policy_->error_message(); | |
155 status_ = VALIDATION_ERROR_CODE_PRESENT; | |
156 return; | |
157 } | |
158 | |
159 // Parse policy data. | |
160 if (!policy_data_->ParseFromString(policy_->policy_data()) || | |
161 !policy_data_->IsInitialized()) { | |
162 LOG(ERROR) << "Failed to parse policy response"; | |
163 status_ = VALIDATION_PAYLOAD_PARSE_ERROR; | |
164 return; | |
165 } | |
166 | |
167 // Table of checks we run. These are sorted by descending severity of the | |
168 // error, s.t. the most severe check will determine the validation status. | |
169 static const struct { | |
170 int flag; | |
171 Status (CloudPolicyValidatorBase::* checkFunction)(); | |
172 } kCheckFunctions[] = { | |
173 { VALIDATE_SIGNATURE, &CloudPolicyValidatorBase::CheckSignature }, | |
174 { VALIDATE_INITIAL_KEY, &CloudPolicyValidatorBase::CheckInitialKey }, | |
175 { VALIDATE_POLICY_TYPE, &CloudPolicyValidatorBase::CheckPolicyType }, | |
176 { VALIDATE_TOKEN, &CloudPolicyValidatorBase::CheckToken }, | |
177 { VALIDATE_USERNAME, &CloudPolicyValidatorBase::CheckUsername }, | |
178 { VALIDATE_DOMAIN, &CloudPolicyValidatorBase::CheckDomain }, | |
179 { VALIDATE_TIMESTAMP, &CloudPolicyValidatorBase::CheckTimestamp }, | |
180 { VALIDATE_PAYLOAD, &CloudPolicyValidatorBase::CheckPayload }, | |
181 }; | |
182 | |
183 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kCheckFunctions); ++i) { | |
184 if (validation_flags_ & kCheckFunctions[i].flag) { | |
185 status_ = (this->*(kCheckFunctions[i].checkFunction))(); | |
186 if (status_ != VALIDATION_OK) | |
187 break; | |
188 } | |
189 } | |
190 } | |
191 | |
192 CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckSignature() { | |
193 const std::string* signature_key = &key_; | |
194 if (policy_->has_new_public_key() && allow_key_rotation_) { | |
195 signature_key = &policy_->new_public_key(); | |
196 if (!policy_->has_new_public_key_signature() || | |
197 !VerifySignature(policy_->new_public_key(), key_, | |
198 policy_->new_public_key_signature())) { | |
199 LOG(ERROR) << "New public key signature verification failed"; | |
200 return VALIDATION_BAD_SIGNATURE; | |
201 } | |
202 } | |
203 | |
204 if (!policy_->has_policy_data_signature() || | |
205 !VerifySignature(policy_->policy_data(), *signature_key, | |
206 policy_->policy_data_signature())) { | |
207 LOG(ERROR) << "Policy signature validation failed"; | |
208 return VALIDATION_BAD_SIGNATURE; | |
209 } | |
210 | |
211 return VALIDATION_OK; | |
212 } | |
213 | |
214 CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckInitialKey() { | |
215 if (!policy_->has_new_public_key() || | |
216 !policy_->has_policy_data_signature() || | |
217 !VerifySignature(policy_->policy_data(), policy_->new_public_key(), | |
218 policy_->policy_data_signature())) { | |
219 LOG(ERROR) << "Initial policy signature validation failed"; | |
220 return VALIDATION_BAD_SIGNATURE; | |
221 } | |
222 | |
223 return VALIDATION_OK; | |
224 } | |
225 | |
226 CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckPolicyType() { | |
227 if (!policy_data_->has_policy_type() || | |
228 policy_data_->policy_type() != policy_type_) { | |
229 LOG(ERROR) << "Wrong policy type " << policy_data_->policy_type(); | |
230 return VALIDATION_WRONG_POLICY_TYPE; | |
231 } | |
232 | |
233 return VALIDATION_OK; | |
234 } | |
235 | |
236 CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckTimestamp() { | |
237 if (!policy_data_->has_timestamp()) { | |
238 if (allow_missing_timestamp_) { | |
239 return VALIDATION_OK; | |
240 } else { | |
241 LOG(ERROR) << "Policy timestamp missing"; | |
242 return VALIDATION_BAD_TIMESTAMP; | |
243 } | |
244 } | |
245 | |
246 if (policy_data_->timestamp() < timestamp_not_before_) { | |
247 LOG(ERROR) << "Policy too old: " << policy_data_->timestamp(); | |
248 return VALIDATION_BAD_TIMESTAMP; | |
249 } | |
250 if (policy_data_->timestamp() > timestamp_not_after_) { | |
251 LOG(ERROR) << "Policy from the future: " << policy_data_->timestamp(); | |
252 return VALIDATION_BAD_TIMESTAMP; | |
253 } | |
254 | |
255 return VALIDATION_OK; | |
256 } | |
257 | |
258 CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckToken() { | |
259 if (!policy_data_->has_request_token() || | |
260 policy_data_->request_token() != token_) { | |
261 LOG(ERROR) << "Invalid DM token " << policy_data_->request_token(); | |
262 return VALIDATION_WRONG_TOKEN; | |
263 } | |
264 | |
265 return VALIDATION_OK; | |
266 } | |
267 | |
268 CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckUsername() { | |
269 if (!policy_data_->has_username()) { | |
270 LOG(ERROR) << "Policy is missing user name"; | |
271 return VALIDATION_BAD_USERNAME; | |
272 } | |
273 | |
274 std::string policy_username = | |
275 gaia::CanonicalizeEmail(gaia::SanitizeEmail(policy_data_->username())); | |
276 | |
277 if (user_ != policy_username) { | |
278 LOG(ERROR) << "Invalid user name " << policy_data_->username(); | |
279 return VALIDATION_BAD_USERNAME; | |
280 } | |
281 | |
282 return VALIDATION_OK; | |
283 } | |
284 | |
285 | |
286 CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckDomain() { | |
287 if (!policy_data_->has_username()) { | |
288 LOG(ERROR) << "Policy is missing user name"; | |
289 return VALIDATION_BAD_USERNAME; | |
290 } | |
291 | |
292 std::string policy_domain = | |
293 gaia::ExtractDomainName( | |
294 gaia::CanonicalizeEmail( | |
295 gaia::SanitizeEmail(policy_data_->username()))); | |
296 | |
297 if (domain_ != policy_domain) { | |
298 LOG(ERROR) << "Invalid user name " << policy_data_->username(); | |
299 return VALIDATION_BAD_USERNAME; | |
300 } | |
301 | |
302 return VALIDATION_OK; | |
303 } | |
304 | |
305 CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckPayload() { | |
306 if (!policy_data_->has_policy_value() || | |
307 !payload_->ParseFromString(policy_data_->policy_value()) || | |
308 !payload_->IsInitialized()) { | |
309 LOG(ERROR) << "Failed to decode policy payload protobuf"; | |
310 return VALIDATION_POLICY_PARSE_ERROR; | |
311 } | |
312 | |
313 return VALIDATION_OK; | |
314 } | |
315 | |
316 // static | |
317 bool CloudPolicyValidatorBase::VerifySignature(const std::string& data, | |
318 const std::string& key, | |
319 const std::string& signature) { | |
320 crypto::SignatureVerifier verifier; | |
321 | |
322 if (!verifier.VerifyInit(kSignatureAlgorithm, sizeof(kSignatureAlgorithm), | |
323 reinterpret_cast<const uint8*>(signature.c_str()), | |
324 signature.size(), | |
325 reinterpret_cast<const uint8*>(key.c_str()), | |
326 key.size())) { | |
327 return false; | |
328 } | |
329 verifier.VerifyUpdate(reinterpret_cast<const uint8*>(data.c_str()), | |
330 data.size()); | |
331 return verifier.VerifyFinal(); | |
332 } | |
333 | |
334 template<typename PayloadProto> | |
335 CloudPolicyValidator<PayloadProto>::~CloudPolicyValidator() {} | |
336 | |
337 template<typename PayloadProto> | |
338 CloudPolicyValidator<PayloadProto>* CloudPolicyValidator<PayloadProto>::Create( | |
339 scoped_ptr<em::PolicyFetchResponse> policy_response) { | |
340 return new CloudPolicyValidator( | |
341 policy_response.Pass(), | |
342 scoped_ptr<PayloadProto>(new PayloadProto())); | |
343 } | |
344 | |
345 template<typename PayloadProto> | |
346 CloudPolicyValidator<PayloadProto>::CloudPolicyValidator( | |
347 scoped_ptr<em::PolicyFetchResponse> policy_response, | |
348 scoped_ptr<PayloadProto> payload) | |
349 : CloudPolicyValidatorBase(policy_response.Pass(), payload.get()), | |
350 payload_(payload.Pass()) {} | |
351 | |
352 template<typename PayloadProto> | |
353 void CloudPolicyValidator<PayloadProto>::StartValidation( | |
354 const CompletionCallback& completion_callback) { | |
355 content::BrowserThread::PostTask( | |
356 content::BrowserThread::FILE, FROM_HERE, | |
357 base::Bind(&CloudPolicyValidatorBase::PerformValidation, | |
358 base::Passed(scoped_ptr<CloudPolicyValidatorBase>(this)), | |
359 MessageLoop::current()->message_loop_proxy(), | |
360 base::Bind(completion_callback, this))); | |
361 } | |
362 | |
363 template class CloudPolicyValidator<em::ChromeDeviceSettingsProto>; | |
364 template class CloudPolicyValidator<em::CloudPolicySettings>; | |
365 | |
366 } // namespace policy | |
OLD | NEW |