Add ICU message format support
[chromium-blink-merge.git] / extensions / browser / verified_contents.cc
blobd9ff75c2abd5a70a9a80b8f8936f61a6b9bc43ca
1 // Copyright 2014 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.
5 #include "extensions/browser/verified_contents.h"
7 #include "base/base64.h"
8 #include "base/files/file_util.h"
9 #include "base/json/json_reader.h"
10 #include "base/strings/string_util.h"
11 #include "base/values.h"
12 #include "components/crx_file/id_util.h"
13 #include "crypto/signature_verifier.h"
14 #include "extensions/common/extension.h"
16 using base::DictionaryValue;
17 using base::ListValue;
18 using base::Value;
20 namespace {
22 // Note: this structure is an ASN.1 which encodes the algorithm used with its
23 // parameters. The signature algorithm is "RSA256" aka "RSASSA-PKCS-v1_5 using
24 // SHA-256 hash algorithm". This is defined in PKCS #1 (RFC 3447).
25 // It is encoding: { OID sha256WithRSAEncryption PARAMETERS NULL }
26 const uint8 kSignatureAlgorithm[15] = {0x30, 0x0d, 0x06, 0x09, 0x2a,
27 0x86, 0x48, 0x86, 0xf7, 0x0d,
28 0x01, 0x01, 0x0b, 0x05, 0x00};
30 const char kBlockSizeKey[] = "block_size";
31 const char kContentHashesKey[] = "content_hashes";
32 const char kDescriptionKey[] = "description";
33 const char kFilesKey[] = "files";
34 const char kFormatKey[] = "format";
35 const char kHashBlockSizeKey[] = "hash_block_size";
36 const char kHeaderKidKey[] = "header.kid";
37 const char kItemIdKey[] = "item_id";
38 const char kItemVersionKey[] = "item_version";
39 const char kPathKey[] = "path";
40 const char kPayloadKey[] = "payload";
41 const char kProtectedKey[] = "protected";
42 const char kRootHashKey[] = "root_hash";
43 const char kSignatureKey[] = "signature";
44 const char kSignaturesKey[] = "signatures";
45 const char kSignedContentKey[] = "signed_content";
46 const char kTreeHashPerFile[] = "treehash per file";
47 const char kTreeHash[] = "treehash";
48 const char kWebstoreKId[] = "webstore";
50 // Helper function to iterate over a list of dictionaries, returning the
51 // dictionary that has |key| -> |value| in it, if any, or NULL.
52 DictionaryValue* FindDictionaryWithValue(const ListValue* list,
53 std::string key,
54 std::string value) {
55 for (ListValue::const_iterator i = list->begin(); i != list->end(); ++i) {
56 if (!(*i)->IsType(Value::TYPE_DICTIONARY))
57 continue;
58 DictionaryValue* dictionary = static_cast<DictionaryValue*>(*i);
59 std::string found_value;
60 if (dictionary->GetString(key, &found_value) && found_value == value)
61 return dictionary;
63 return NULL;
66 } // namespace
68 namespace extensions {
70 // static
71 bool VerifiedContents::FixupBase64Encoding(std::string* input) {
72 for (std::string::iterator i = input->begin(); i != input->end(); ++i) {
73 if (*i == '-')
74 *i = '+';
75 else if (*i == '_')
76 *i = '/';
78 switch (input->size() % 4) {
79 case 0:
80 break;
81 case 2:
82 input->append("==");
83 break;
84 case 3:
85 input->append("=");
86 break;
87 default:
88 return false;
90 return true;
93 VerifiedContents::VerifiedContents(const uint8* public_key, int public_key_size)
94 : public_key_(public_key),
95 public_key_size_(public_key_size),
96 valid_signature_(false), // Guilty until proven innocent.
97 block_size_(0) {
100 VerifiedContents::~VerifiedContents() {
103 // The format of the payload json is:
104 // {
105 // "item_id": "<extension id>",
106 // "item_version": "<extension version>",
107 // "content_hashes": [
108 // {
109 // "block_size": 4096,
110 // "hash_block_size": 4096,
111 // "format": "treehash",
112 // "files": [
113 // {
114 // "path": "foo/bar",
115 // "root_hash": "<base64url encoded bytes>"
116 // },
117 // ...
118 // ]
119 // }
120 // ]
121 // }
122 bool VerifiedContents::InitFrom(const base::FilePath& path,
123 bool ignore_invalid_signature) {
124 std::string payload;
125 if (!GetPayload(path, &payload, ignore_invalid_signature))
126 return false;
128 scoped_ptr<base::Value> value(base::JSONReader::Read(payload));
129 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY))
130 return false;
131 DictionaryValue* dictionary = static_cast<DictionaryValue*>(value.get());
133 std::string item_id;
134 if (!dictionary->GetString(kItemIdKey, &item_id) ||
135 !crx_file::id_util::IdIsValid(item_id))
136 return false;
137 extension_id_ = item_id;
139 std::string version_string;
140 if (!dictionary->GetString(kItemVersionKey, &version_string))
141 return false;
142 version_ = base::Version(version_string);
143 if (!version_.IsValid())
144 return false;
146 ListValue* hashes_list = NULL;
147 if (!dictionary->GetList(kContentHashesKey, &hashes_list))
148 return false;
150 for (size_t i = 0; i < hashes_list->GetSize(); i++) {
151 DictionaryValue* hashes = NULL;
152 if (!hashes_list->GetDictionary(i, &hashes))
153 return false;
154 std::string format;
155 if (!hashes->GetString(kFormatKey, &format) || format != kTreeHash)
156 continue;
158 int block_size = 0;
159 int hash_block_size = 0;
160 if (!hashes->GetInteger(kBlockSizeKey, &block_size) ||
161 !hashes->GetInteger(kHashBlockSizeKey, &hash_block_size))
162 return false;
163 block_size_ = block_size;
165 // We don't support using a different block_size and hash_block_size at
166 // the moment.
167 if (block_size_ != hash_block_size)
168 return false;
170 ListValue* files = NULL;
171 if (!hashes->GetList(kFilesKey, &files))
172 return false;
174 for (size_t j = 0; j < files->GetSize(); j++) {
175 DictionaryValue* data = NULL;
176 if (!files->GetDictionary(j, &data))
177 return false;
178 std::string file_path_string;
179 std::string encoded_root_hash;
180 std::string root_hash;
181 if (!data->GetString(kPathKey, &file_path_string) ||
182 !base::IsStringUTF8(file_path_string) ||
183 !data->GetString(kRootHashKey, &encoded_root_hash) ||
184 !FixupBase64Encoding(&encoded_root_hash) ||
185 !base::Base64Decode(encoded_root_hash, &root_hash))
186 return false;
187 base::FilePath file_path =
188 base::FilePath::FromUTF8Unsafe(file_path_string);
189 RootHashes::iterator i = root_hashes_.insert(std::make_pair(
190 base::StringToLowerASCII(file_path.value()), std::string()));
191 i->second.swap(root_hash);
194 break;
196 return true;
199 bool VerifiedContents::HasTreeHashRoot(
200 const base::FilePath& relative_path) const {
201 base::FilePath::StringType path = base::StringToLowerASCII(
202 relative_path.NormalizePathSeparatorsTo('/').value());
203 return root_hashes_.find(path) != root_hashes_.end();
206 bool VerifiedContents::TreeHashRootEquals(const base::FilePath& relative_path,
207 const std::string& expected) const {
208 base::FilePath::StringType path = base::StringToLowerASCII(
209 relative_path.NormalizePathSeparatorsTo('/').value());
210 for (RootHashes::const_iterator i = root_hashes_.find(path);
211 i != root_hashes_.end();
212 ++i) {
213 if (expected == i->second)
214 return true;
216 return false;
219 // We're loosely following the "JSON Web Signature" draft spec for signing
220 // a JSON payload:
222 // http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26
224 // The idea is that you have some JSON that you want to sign, so you
225 // base64-encode that and put it as the "payload" field in a containing
226 // dictionary. There might be signatures of it done with multiple
227 // algorithms/parameters, so the payload is followed by a list of one or more
228 // signature sections. Each signature section specifies the
229 // algorithm/parameters in a JSON object which is base64url encoded into one
230 // string and put into a "protected" field in the signature. Then the encoded
231 // "payload" and "protected" strings are concatenated with a "." in between
232 // them and those bytes are signed and the resulting signature is base64url
233 // encoded and placed in the "signature" field. To allow for extensibility, we
234 // wrap this, so we can include additional kinds of payloads in the future. E.g.
235 // [
236 // {
237 // "description": "treehash per file",
238 // "signed_content": {
239 // "payload": "<base64url encoded JSON to sign>",
240 // "signatures": [
241 // {
242 // "protected": "<base64url encoded JSON with algorithm/parameters>",
243 // "header": {
244 // <object with metadata about this signature, eg a key identifier>
245 // }
246 // "signature":
247 // "<base64url encoded signature over payload || . || protected>"
248 // },
249 // ... <zero or more additional signatures> ...
250 // ]
251 // }
252 // }
253 // ]
254 // There might be both a signature generated with a webstore private key and a
255 // signature generated with the extension's private key - for now we only
256 // verify the webstore one (since the id is in the payload, so we can trust
257 // that it is for a given extension), but in the future we may validate using
258 // the extension's key too (eg for non-webstore hosted extensions such as
259 // enterprise installs).
260 bool VerifiedContents::GetPayload(const base::FilePath& path,
261 std::string* payload,
262 bool ignore_invalid_signature) {
263 std::string contents;
264 if (!base::ReadFileToString(path, &contents))
265 return false;
266 scoped_ptr<base::Value> value(base::JSONReader::Read(contents));
267 if (!value.get() || !value->IsType(Value::TYPE_LIST))
268 return false;
269 ListValue* top_list = static_cast<ListValue*>(value.get());
271 // Find the "treehash per file" signed content, e.g.
272 // [
273 // {
274 // "description": "treehash per file",
275 // "signed_content": {
276 // "signatures": [ ... ],
277 // "payload": "..."
278 // }
279 // }
280 // ]
281 DictionaryValue* dictionary =
282 FindDictionaryWithValue(top_list, kDescriptionKey, kTreeHashPerFile);
283 DictionaryValue* signed_content = NULL;
284 if (!dictionary ||
285 !dictionary->GetDictionaryWithoutPathExpansion(kSignedContentKey,
286 &signed_content)) {
287 return false;
290 ListValue* signatures = NULL;
291 if (!signed_content->GetList(kSignaturesKey, &signatures))
292 return false;
294 DictionaryValue* signature_dict =
295 FindDictionaryWithValue(signatures, kHeaderKidKey, kWebstoreKId);
296 if (!signature_dict)
297 return false;
299 std::string protected_value;
300 std::string encoded_signature;
301 std::string decoded_signature;
302 if (!signature_dict->GetString(kProtectedKey, &protected_value) ||
303 !signature_dict->GetString(kSignatureKey, &encoded_signature) ||
304 !FixupBase64Encoding(&encoded_signature) ||
305 !base::Base64Decode(encoded_signature, &decoded_signature))
306 return false;
308 std::string encoded_payload;
309 if (!signed_content->GetString(kPayloadKey, &encoded_payload))
310 return false;
312 valid_signature_ =
313 VerifySignature(protected_value, encoded_payload, decoded_signature);
314 if (!valid_signature_ && !ignore_invalid_signature)
315 return false;
317 if (!FixupBase64Encoding(&encoded_payload) ||
318 !base::Base64Decode(encoded_payload, payload))
319 return false;
321 return true;
324 bool VerifiedContents::VerifySignature(const std::string& protected_value,
325 const std::string& payload,
326 const std::string& signature_bytes) {
327 crypto::SignatureVerifier signature_verifier;
328 if (!signature_verifier.VerifyInit(
329 kSignatureAlgorithm,
330 sizeof(kSignatureAlgorithm),
331 reinterpret_cast<const uint8*>(signature_bytes.data()),
332 signature_bytes.size(),
333 public_key_,
334 public_key_size_)) {
335 VLOG(1) << "Could not verify signature - VerifyInit failure";
336 return false;
339 signature_verifier.VerifyUpdate(
340 reinterpret_cast<const uint8*>(protected_value.data()),
341 protected_value.size());
343 std::string dot(".");
344 signature_verifier.VerifyUpdate(reinterpret_cast<const uint8*>(dot.data()),
345 dot.size());
347 signature_verifier.VerifyUpdate(
348 reinterpret_cast<const uint8*>(payload.data()), payload.size());
350 if (!signature_verifier.VerifyFinal()) {
351 VLOG(1) << "Could not verify signature - VerifyFinal failure";
352 return false;
354 return true;
357 } // namespace extensions