blob: 88188a2810d7cd630be01e6363f618ed28428156 [file] [log] [blame]
Darin Petkov85d02b72011-05-17 13:25:51 -07001// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
Andrew de los Reyes0c440052010-08-20 11:25:54 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "update_engine/payload_signer.h"
6
Darin Petkovb039d502010-12-03 09:08:04 -08007#include <base/logging.h>
Chris Masoned903c3b2011-05-12 15:35:46 -07008#include <base/string_split.h>
Darin Petkovb039d502010-12-03 09:08:04 -08009#include <base/string_util.h>
10#include <openssl/pem.h>
11
Darin Petkov9574f7e2011-01-13 10:48:12 -080012#include "update_engine/delta_diff_generator.h"
13#include "update_engine/delta_performer.h"
Andrew de los Reyes932bc4c2010-08-23 18:14:09 -070014#include "update_engine/omaha_hash_calculator.h"
Andrew de los Reyes0c440052010-08-20 11:25:54 -070015#include "update_engine/subprocess.h"
16#include "update_engine/update_metadata.pb.h"
17#include "update_engine/utils.h"
18
19using std::string;
20using std::vector;
21
22namespace chromeos_update_engine {
23
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -070024const uint32_t kSignatureMessageOriginalVersion = 1;
25const uint32_t kSignatureMessageCurrentVersion = 1;
Andrew de los Reyes0c440052010-08-20 11:25:54 -070026
Darin Petkov9574f7e2011-01-13 10:48:12 -080027namespace {
Andrew de los Reyesbdfaaf02011-03-30 10:35:12 -070028
Han Shen2643cb72012-06-26 14:45:33 -070029const unsigned char kRSA2048SHA256Padding[] = {
Andrew de los Reyesbdfaaf02011-03-30 10:35:12 -070030 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
31 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
32 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
33 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
34 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
35 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
36 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
37 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
38 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
39 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
40 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
41 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
42 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
43 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
44 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
45 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x31, 0x30,
46 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
47 0x00, 0x04, 0x20
48};
49
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -070050// Given raw |signatures|, packs them into a protobuf and serializes it into a
Darin Petkov9574f7e2011-01-13 10:48:12 -080051// binary blob. Returns true on success, false otherwise.
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -070052bool ConvertSignatureToProtobufBlob(const vector<vector<char> >& signatures,
Darin Petkov9574f7e2011-01-13 10:48:12 -080053 vector<char>* out_signature_blob) {
54 // Pack it into a protobuf
55 Signatures out_message;
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -070056 uint32_t version = kSignatureMessageOriginalVersion;
57 LOG_IF(WARNING, kSignatureMessageCurrentVersion -
58 kSignatureMessageOriginalVersion + 1 < signatures.size())
59 << "You may want to support clients in the rage ["
60 << kSignatureMessageOriginalVersion << ", "
61 << kSignatureMessageCurrentVersion << "] inclusive, but you only "
62 << "provided " << signatures.size() << " signatures.";
63 for (vector<vector<char> >::const_iterator it = signatures.begin(),
64 e = signatures.end(); it != e; ++it) {
65 const vector<char>& signature = *it;
66 Signatures_Signature* sig_message = out_message.add_signatures();
67 sig_message->set_version(version++);
68 sig_message->set_data(signature.data(), signature.size());
69 }
Darin Petkov9574f7e2011-01-13 10:48:12 -080070
71 // Serialize protobuf
72 string serialized;
73 TEST_AND_RETURN_FALSE(out_message.AppendToString(&serialized));
74 out_signature_blob->insert(out_signature_blob->end(),
75 serialized.begin(),
76 serialized.end());
77 LOG(INFO) << "Signature blob size: " << out_signature_blob->size();
78 return true;
79}
80
Darin Petkovadb3cef2011-01-13 16:16:08 -080081bool LoadPayload(const string& payload_path,
82 vector<char>* out_payload,
83 DeltaArchiveManifest* out_manifest,
84 uint64_t* out_metadata_size) {
85 vector<char> payload;
86 // Loads the payload and parses the manifest.
87 TEST_AND_RETURN_FALSE(utils::ReadFile(payload_path, &payload));
88 LOG(INFO) << "Payload size: " << payload.size();
89 TEST_AND_RETURN_FALSE(DeltaPerformer::ParsePayloadMetadata(
90 payload, out_manifest, out_metadata_size) ==
91 DeltaPerformer::kMetadataParseSuccess);
92 LOG(INFO) << "Metadata size: " << *out_metadata_size;
93 out_payload->swap(payload);
94 return true;
95}
96
Darin Petkov9574f7e2011-01-13 10:48:12 -080097// Given an unsigned payload under |payload_path| and the |signature_blob_size|
98// generates an updated payload that includes a dummy signature op in its
99// manifest. Returns true on success, false otherwise.
Darin Petkovadb3cef2011-01-13 16:16:08 -0800100bool AddSignatureOpToPayload(const string& payload_path,
Darin Petkov9574f7e2011-01-13 10:48:12 -0800101 int signature_blob_size,
102 vector<char>* out_payload) {
103 const int kProtobufOffset = 20;
104 const int kProtobufSizeOffset = 12;
105
Darin Petkovadb3cef2011-01-13 16:16:08 -0800106 // Loads the payload.
Darin Petkov9574f7e2011-01-13 10:48:12 -0800107 vector<char> payload;
Darin Petkov9574f7e2011-01-13 10:48:12 -0800108 DeltaArchiveManifest manifest;
Darin Petkovadb3cef2011-01-13 16:16:08 -0800109 uint64_t metadata_size;
110 TEST_AND_RETURN_FALSE(LoadPayload(
111 payload_path, &payload, &manifest, &metadata_size));
Darin Petkov9574f7e2011-01-13 10:48:12 -0800112 TEST_AND_RETURN_FALSE(!manifest.has_signatures_offset() &&
113 !manifest.has_signatures_size());
114
115 // Updates the manifest to include the signature operation.
116 DeltaDiffGenerator::AddSignatureOp(payload.size() - metadata_size,
117 signature_blob_size,
118 &manifest);
119
120 // Updates the payload to include the new manifest.
121 string serialized_manifest;
122 TEST_AND_RETURN_FALSE(manifest.AppendToString(&serialized_manifest));
123 LOG(INFO) << "Updated protobuf size: " << serialized_manifest.size();
124 payload.erase(payload.begin() + kProtobufOffset,
125 payload.begin() + metadata_size);
126 payload.insert(payload.begin() + kProtobufOffset,
127 serialized_manifest.begin(),
128 serialized_manifest.end());
129
130 // Updates the protobuf size.
131 uint64_t size_be = htobe64(serialized_manifest.size());
132 memcpy(&payload[kProtobufSizeOffset], &size_be, sizeof(size_be));
133 LOG(INFO) << "Updated payload size: " << payload.size();
134 out_payload->swap(payload);
135 return true;
136}
137} // namespace {}
138
139bool PayloadSigner::SignHash(const vector<char>& hash,
140 const string& private_key_path,
141 vector<char>* out_signature) {
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700142 string sig_path;
143 TEST_AND_RETURN_FALSE(
144 utils::MakeTempFile("/tmp/signature.XXXXXX", &sig_path, NULL));
145 ScopedPathUnlinker sig_path_unlinker(sig_path);
Andrew de los Reyes932bc4c2010-08-23 18:14:09 -0700146
147 string hash_path;
148 TEST_AND_RETURN_FALSE(
149 utils::MakeTempFile("/tmp/hash.XXXXXX", &hash_path, NULL));
150 ScopedPathUnlinker hash_path_unlinker(hash_path);
Andrew de los Reyesbdfaaf02011-03-30 10:35:12 -0700151 // We expect unpadded SHA256 hash coming in
152 TEST_AND_RETURN_FALSE(hash.size() == 32);
153 vector<char> padded_hash(hash);
154 PadRSA2048SHA256Hash(&padded_hash);
Andrew de los Reyes932bc4c2010-08-23 18:14:09 -0700155 TEST_AND_RETURN_FALSE(utils::WriteFile(hash_path.c_str(),
Andrew de los Reyesbdfaaf02011-03-30 10:35:12 -0700156 padded_hash.data(),
157 padded_hash.size()));
Darin Petkovd22cb292010-09-29 10:02:29 -0700158
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700159 // This runs on the server, so it's okay to cop out and call openssl
160 // executable rather than properly use the library
161 vector<string> cmd;
Mike Frysinger2149be42012-03-12 19:23:47 -0400162 base::SplitString("openssl rsautl -raw -sign -inkey x -in x -out x",
Chris Masoned903c3b2011-05-12 15:35:46 -0700163 ' ',
164 &cmd);
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700165 cmd[cmd.size() - 5] = private_key_path;
Andrew de los Reyes932bc4c2010-08-23 18:14:09 -0700166 cmd[cmd.size() - 3] = hash_path;
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700167 cmd[cmd.size() - 1] = sig_path;
Darin Petkovd22cb292010-09-29 10:02:29 -0700168
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700169 int return_code = 0;
Darin Petkov85d02b72011-05-17 13:25:51 -0700170 TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &return_code, NULL));
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700171 TEST_AND_RETURN_FALSE(return_code == 0);
Darin Petkovd22cb292010-09-29 10:02:29 -0700172
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700173 vector<char> signature;
174 TEST_AND_RETURN_FALSE(utils::ReadFile(sig_path, &signature));
Darin Petkov9574f7e2011-01-13 10:48:12 -0800175 out_signature->swap(signature);
176 return true;
177}
Darin Petkovd22cb292010-09-29 10:02:29 -0700178
Darin Petkov9574f7e2011-01-13 10:48:12 -0800179bool PayloadSigner::SignPayload(const string& unsigned_payload_path,
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700180 const vector<string>& private_key_paths,
Darin Petkov9574f7e2011-01-13 10:48:12 -0800181 vector<char>* out_signature_blob) {
182 vector<char> hash_data;
183 TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfFile(
184 unsigned_payload_path, -1, &hash_data) ==
185 utils::FileSize(unsigned_payload_path));
Darin Petkovd22cb292010-09-29 10:02:29 -0700186
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700187 vector<vector<char> > signatures;
188 for (vector<string>::const_iterator it = private_key_paths.begin(),
189 e = private_key_paths.end(); it != e; ++it) {
190 vector<char> signature;
191 TEST_AND_RETURN_FALSE(SignHash(hash_data, *it, &signature));
192 signatures.push_back(signature);
193 }
194 TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
Darin Petkov9574f7e2011-01-13 10:48:12 -0800195 out_signature_blob));
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700196 return true;
197}
198
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700199bool PayloadSigner::SignatureBlobLength(const vector<string>& private_key_paths,
200 uint64_t* out_length) {
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700201 DCHECK(out_length);
Darin Petkovd22cb292010-09-29 10:02:29 -0700202
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700203 string x_path;
204 TEST_AND_RETURN_FALSE(
205 utils::MakeTempFile("/tmp/signed_data.XXXXXX", &x_path, NULL));
206 ScopedPathUnlinker x_path_unlinker(x_path);
207 TEST_AND_RETURN_FALSE(utils::WriteFile(x_path.c_str(), "x", 1));
208
209 vector<char> sig_blob;
210 TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(x_path,
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700211 private_key_paths,
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700212 &sig_blob));
213 *out_length = sig_blob.size();
214 return true;
215}
216
Darin Petkovd7061ab2010-10-06 14:37:09 -0700217bool PayloadSigner::VerifySignature(const std::vector<char>& signature_blob,
218 const std::string& public_key_path,
219 std::vector<char>* out_hash_data) {
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700220 return VerifySignatureVersion(signature_blob, public_key_path,
221 kSignatureMessageCurrentVersion, out_hash_data);
222}
223
224bool PayloadSigner::VerifySignatureVersion(
225 const std::vector<char>& signature_blob,
226 const std::string& public_key_path,
227 uint32_t client_version,
228 std::vector<char>* out_hash_data) {
Darin Petkovd7061ab2010-10-06 14:37:09 -0700229 TEST_AND_RETURN_FALSE(!public_key_path.empty());
230
231 Signatures signatures;
232 TEST_AND_RETURN_FALSE(signatures.ParseFromArray(&signature_blob[0],
233 signature_blob.size()));
234
235 // Finds a signature that matches the current version.
236 int sig_index = 0;
237 for (; sig_index < signatures.signatures_size(); sig_index++) {
238 const Signatures_Signature& signature = signatures.signatures(sig_index);
239 if (signature.has_version() &&
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700240 signature.version() == client_version) {
Darin Petkovd7061ab2010-10-06 14:37:09 -0700241 break;
242 }
243 }
244 TEST_AND_RETURN_FALSE(sig_index < signatures.signatures_size());
245
246 const Signatures_Signature& signature = signatures.signatures(sig_index);
Darin Petkovb039d502010-12-03 09:08:04 -0800247 const string& sig_data = signature.data();
Darin Petkovd7061ab2010-10-06 14:37:09 -0700248
Darin Petkovb039d502010-12-03 09:08:04 -0800249 // The code below executes the equivalent of:
250 //
251 // openssl rsautl -verify -pubin -inkey |public_key_path|
252 // -in |sig_data| -out |out_hash_data|
Darin Petkovd7061ab2010-10-06 14:37:09 -0700253
Darin Petkovb039d502010-12-03 09:08:04 -0800254 // Loads the public key.
255 FILE* fpubkey = fopen(public_key_path.c_str(), "rb");
256 TEST_AND_RETURN_FALSE(fpubkey != NULL);
257 char dummy_password[] = { ' ', 0 }; // Ensure no password is read from stdin.
258 RSA* rsa = PEM_read_RSA_PUBKEY(fpubkey, NULL, NULL, dummy_password);
259 fclose(fpubkey);
260 TEST_AND_RETURN_FALSE(rsa != NULL);
261 unsigned int keysize = RSA_size(rsa);
262 if (sig_data.size() > 2 * keysize) {
263 LOG(ERROR) << "Signature size is too big for public key size.";
264 RSA_free(rsa);
265 return false;
266 }
Darin Petkovd7061ab2010-10-06 14:37:09 -0700267
Darin Petkovb039d502010-12-03 09:08:04 -0800268 // Decrypts the signature.
269 vector<char> hash_data(keysize);
270 int decrypt_size = RSA_public_decrypt(
271 sig_data.size(),
272 reinterpret_cast<const unsigned char*>(sig_data.data()),
273 reinterpret_cast<unsigned char*>(hash_data.data()),
274 rsa,
Andrew de los Reyesbdfaaf02011-03-30 10:35:12 -0700275 RSA_NO_PADDING);
Darin Petkovb039d502010-12-03 09:08:04 -0800276 RSA_free(rsa);
277 TEST_AND_RETURN_FALSE(decrypt_size > 0 &&
278 decrypt_size <= static_cast<int>(hash_data.size()));
279 hash_data.resize(decrypt_size);
280 out_hash_data->swap(hash_data);
Darin Petkovd7061ab2010-10-06 14:37:09 -0700281 return true;
282}
283
Darin Petkovadb3cef2011-01-13 16:16:08 -0800284bool PayloadSigner::VerifySignedPayload(const std::string& payload_path,
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700285 const std::string& public_key_path,
286 uint32_t client_key_check_version) {
Darin Petkovadb3cef2011-01-13 16:16:08 -0800287 vector<char> payload;
288 DeltaArchiveManifest manifest;
289 uint64_t metadata_size;
290 TEST_AND_RETURN_FALSE(LoadPayload(
291 payload_path, &payload, &manifest, &metadata_size));
292 TEST_AND_RETURN_FALSE(manifest.has_signatures_offset() &&
293 manifest.has_signatures_size());
294 CHECK_EQ(payload.size(),
295 metadata_size + manifest.signatures_offset() +
296 manifest.signatures_size());
297 vector<char> signature_blob(
298 payload.begin() + metadata_size + manifest.signatures_offset(),
299 payload.end());
300 vector<char> signed_hash;
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700301 TEST_AND_RETURN_FALSE(VerifySignatureVersion(
302 signature_blob, public_key_path, client_key_check_version, &signed_hash));
Darin Petkovadb3cef2011-01-13 16:16:08 -0800303 TEST_AND_RETURN_FALSE(!signed_hash.empty());
304 vector<char> hash;
305 TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(
306 payload.data(), metadata_size + manifest.signatures_offset(), &hash));
Andrew de los Reyesbdfaaf02011-03-30 10:35:12 -0700307 PadRSA2048SHA256Hash(&hash);
Darin Petkovadb3cef2011-01-13 16:16:08 -0800308 TEST_AND_RETURN_FALSE(hash == signed_hash);
309 return true;
310}
311
Darin Petkov9574f7e2011-01-13 10:48:12 -0800312bool PayloadSigner::HashPayloadForSigning(const std::string& payload_path,
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700313 const vector<int>& signature_sizes,
Darin Petkov9574f7e2011-01-13 10:48:12 -0800314 vector<char>* out_hash_data) {
315 // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory.
316
317 // Loads the payload and adds the signature op to it.
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700318 vector<vector<char> > signatures;
319 for (vector<int>::const_iterator it = signature_sizes.begin(),
320 e = signature_sizes.end(); it != e; ++it) {
321 vector<char> signature(*it, 0);
322 signatures.push_back(signature);
323 }
Darin Petkov9574f7e2011-01-13 10:48:12 -0800324 vector<char> signature_blob;
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700325 TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
Darin Petkov9574f7e2011-01-13 10:48:12 -0800326 &signature_blob));
327 vector<char> payload;
328 TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path,
329 signature_blob.size(),
330 &payload));
331 // Calculates the hash on the updated payload. Note that the payload includes
332 // the signature op but doesn't include the signature blob at the end.
333 TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfData(payload,
334 out_hash_data));
335 return true;
336}
337
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700338bool PayloadSigner::AddSignatureToPayload(
339 const string& payload_path,
340 const vector<vector<char> >& signatures,
341 const string& signed_payload_path) {
Darin Petkov9574f7e2011-01-13 10:48:12 -0800342 // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory.
343
344 // Loads the payload and adds the signature op to it.
345 vector<char> signature_blob;
Andrew de los Reyesc24e3f32011-08-30 15:45:20 -0700346 TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
Darin Petkov9574f7e2011-01-13 10:48:12 -0800347 &signature_blob));
348 vector<char> payload;
349 TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path,
350 signature_blob.size(),
351 &payload));
352 // Appends the signature blob to the end of the payload and writes the new
353 // payload.
354 payload.insert(payload.end(), signature_blob.begin(), signature_blob.end());
355 LOG(INFO) << "Signed payload size: " << payload.size();
356 TEST_AND_RETURN_FALSE(utils::WriteFile(signed_payload_path.c_str(),
357 payload.data(),
358 payload.size()));
359 return true;
360}
361
Andrew de los Reyesbdfaaf02011-03-30 10:35:12 -0700362bool PayloadSigner::PadRSA2048SHA256Hash(std::vector<char>* hash) {
363 TEST_AND_RETURN_FALSE(hash->size() == 32);
364 hash->insert(hash->begin(),
Han Shen2643cb72012-06-26 14:45:33 -0700365 reinterpret_cast<const char*>(kRSA2048SHA256Padding),
366 reinterpret_cast<const char*>(kRSA2048SHA256Padding +
367 sizeof(kRSA2048SHA256Padding)));
Andrew de los Reyesbdfaaf02011-03-30 10:35:12 -0700368 TEST_AND_RETURN_FALSE(hash->size() == 256);
369 return true;
370}
371
Andrew de los Reyes0c440052010-08-20 11:25:54 -0700372} // namespace chromeos_update_engine