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/file_util.h"
9 #include "base/json/json_reader.h"
10 #include "base/strings/string_util.h"
11 #include "base/values.h"
12 #include "crypto/signature_verifier.h"
13 #include "extensions/common/extension.h"
15 using base::DictionaryValue;
16 using base::ListValue;
21 // Note: this structure is an ASN.1 which encodes the algorithm used with its
22 // parameters. The signature algorithm is "RSA256" aka "RSASSA-PKCS-v1_5 using
23 // SHA-256 hash algorithm". This is defined in PKCS #1 (RFC 3447).
24 // It is encoding: { OID sha256WithRSAEncryption PARAMETERS NULL }
25 const uint8 kSignatureAlgorithm[15] = {0x30, 0x0d, 0x06, 0x09, 0x2a,
26 0x86, 0x48, 0x86, 0xf7, 0x0d,
27 0x01, 0x01, 0x0b, 0x05, 0x00};
29 const char kBlockSizeKey[] = "block_size";
30 const char kContentHashesKey[] = "content_hashes";
31 const char kDescriptionKey[] = "description";
32 const char kFilesKey[] = "files";
33 const char kFormatKey[] = "format";
34 const char kHashBlockSizeKey[] = "hash_block_size";
35 const char kHeaderKidKey[] = "header.kid";
36 const char kItemIdKey[] = "item_id";
37 const char kItemVersionKey[] = "item_version";
38 const char kPathKey[] = "path";
39 const char kPayloadKey[] = "payload";
40 const char kProtectedKey[] = "protected";
41 const char kRootHashKey[] = "root_hash";
42 const char kSignatureKey[] = "signature";
43 const char kSignaturesKey[] = "signatures";
44 const char kSignedContentKey[] = "signed_content";
45 const char kTreeHashPerFile[] = "treehash per file";
46 const char kTreeHash[] = "treehash";
47 const char kWebstoreKId[] = "webstore";
49 // Helper function to iterate over a list of dictionaries, returning the
50 // dictionary that has |key| -> |value| in it, if any, or NULL.
51 DictionaryValue* FindDictionaryWithValue(const ListValue* list,
54 for (ListValue::const_iterator i = list->begin(); i != list->end(); ++i) {
55 if (!(*i)->IsType(Value::TYPE_DICTIONARY))
57 DictionaryValue* dictionary = static_cast<DictionaryValue*>(*i);
58 std::string found_value;
59 if (dictionary->GetString(key, &found_value) && found_value == value)
67 namespace extensions {
70 bool VerifiedContents::FixupBase64Encoding(std::string* input) {
71 for (std::string::iterator i = input->begin(); i != input->end(); ++i) {
77 switch (input->size() % 4) {
92 VerifiedContents::VerifiedContents(const uint8* public_key, int public_key_size)
93 : public_key_(public_key),
94 public_key_size_(public_key_size),
95 valid_signature_(false), // Guilty until proven innocent.
99 VerifiedContents::~VerifiedContents() {
102 // The format of the payload json is:
104 // "item_id": "<extension id>",
105 // "item_version": "<extension version>",
106 // "content_hashes": [
108 // "block_size": 4096,
109 // "hash_block_size": 4096,
110 // "format": "treehash",
113 // "path": "foo/bar",
114 // "root_hash": "<base64url encoded bytes>"
121 bool VerifiedContents::InitFrom(const base::FilePath& path,
122 bool ignore_invalid_signature) {
124 if (!GetPayload(path, &payload, ignore_invalid_signature))
127 scoped_ptr<base::Value> value(base::JSONReader::Read(payload));
128 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY))
130 DictionaryValue* dictionary = static_cast<DictionaryValue*>(value.get());
133 if (!dictionary->GetString(kItemIdKey, &item_id) ||
134 !Extension::IdIsValid(item_id))
136 extension_id_ = item_id;
138 std::string version_string;
139 if (!dictionary->GetString(kItemVersionKey, &version_string))
141 version_ = base::Version(version_string);
142 if (!version_.IsValid())
145 ListValue* hashes_list = NULL;
146 if (!dictionary->GetList(kContentHashesKey, &hashes_list))
149 for (size_t i = 0; i < hashes_list->GetSize(); i++) {
150 DictionaryValue* hashes = NULL;
151 if (!hashes_list->GetDictionary(i, &hashes))
154 if (!hashes->GetString(kFormatKey, &format) || format != kTreeHash)
158 int hash_block_size = 0;
159 if (!hashes->GetInteger(kBlockSizeKey, &block_size) ||
160 !hashes->GetInteger(kHashBlockSizeKey, &hash_block_size))
162 block_size_ = block_size;
164 // We don't support using a different block_size and hash_block_size at
166 if (block_size_ != hash_block_size)
169 ListValue* files = NULL;
170 if (!hashes->GetList(kFilesKey, &files))
173 for (size_t j = 0; j < files->GetSize(); j++) {
174 DictionaryValue* data = NULL;
175 if (!files->GetDictionary(j, &data))
177 std::string file_path_string;
178 std::string encoded_root_hash;
179 std::string root_hash;
180 if (!data->GetString(kPathKey, &file_path_string) ||
181 !base::IsStringUTF8(file_path_string) ||
182 !data->GetString(kRootHashKey, &encoded_root_hash) ||
183 !FixupBase64Encoding(&encoded_root_hash) ||
184 !base::Base64Decode(encoded_root_hash, &root_hash))
186 base::FilePath file_path =
187 base::FilePath::FromUTF8Unsafe(file_path_string);
188 root_hashes_[file_path] = std::string();
189 root_hashes_[file_path].swap(root_hash);
197 const std::string* VerifiedContents::GetTreeHashRoot(
198 const base::FilePath& relative_path) {
199 std::map<base::FilePath, std::string>::const_iterator i =
200 root_hashes_.find(relative_path);
201 if (i == root_hashes_.end())
206 // We're loosely following the "JSON Web Signature" draft spec for signing
209 // http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26
211 // The idea is that you have some JSON that you want to sign, so you
212 // base64-encode that and put it as the "payload" field in a containing
213 // dictionary. There might be signatures of it done with multiple
214 // algorithms/parameters, so the payload is followed by a list of one or more
215 // signature sections. Each signature section specifies the
216 // algorithm/parameters in a JSON object which is base64url encoded into one
217 // string and put into a "protected" field in the signature. Then the encoded
218 // "payload" and "protected" strings are concatenated with a "." in between
219 // them and those bytes are signed and the resulting signature is base64url
220 // encoded and placed in the "signature" field. To allow for extensibility, we
221 // wrap this, so we can include additional kinds of payloads in the future. E.g.
224 // "description": "treehash per file",
225 // "signed_content": {
226 // "payload": "<base64url encoded JSON to sign>",
229 // "protected": "<base64url encoded JSON with algorithm/parameters>",
231 // <object with metadata about this signature, eg a key identifier>
234 // "<base64url encoded signature over payload || . || protected>"
236 // ... <zero or more additional signatures> ...
241 // There might be both a signature generated with a webstore private key and a
242 // signature generated with the extension's private key - for now we only
243 // verify the webstore one (since the id is in the payload, so we can trust
244 // that it is for a given extension), but in the future we may validate using
245 // the extension's key too (eg for non-webstore hosted extensions such as
246 // enterprise installs).
247 bool VerifiedContents::GetPayload(const base::FilePath& path,
248 std::string* payload,
249 bool ignore_invalid_signature) {
250 std::string contents;
251 if (!base::ReadFileToString(path, &contents))
253 scoped_ptr<base::Value> value(base::JSONReader::Read(contents));
254 if (!value.get() || !value->IsType(Value::TYPE_LIST))
256 ListValue* top_list = static_cast<ListValue*>(value.get());
258 // Find the "treehash per file" signed content, e.g.
261 // "description": "treehash per file",
262 // "signed_content": {
263 // "signatures": [ ... ],
268 DictionaryValue* dictionary =
269 FindDictionaryWithValue(top_list, kDescriptionKey, kTreeHashPerFile);
270 DictionaryValue* signed_content = NULL;
272 !dictionary->GetDictionaryWithoutPathExpansion(kSignedContentKey,
277 ListValue* signatures = NULL;
278 if (!signed_content->GetList(kSignaturesKey, &signatures))
281 DictionaryValue* signature_dict =
282 FindDictionaryWithValue(signatures, kHeaderKidKey, kWebstoreKId);
286 std::string protected_value;
287 std::string encoded_signature;
288 std::string decoded_signature;
289 if (!signature_dict->GetString(kProtectedKey, &protected_value) ||
290 !signature_dict->GetString(kSignatureKey, &encoded_signature) ||
291 !FixupBase64Encoding(&encoded_signature) ||
292 !base::Base64Decode(encoded_signature, &decoded_signature))
295 std::string encoded_payload;
296 if (!signed_content->GetString(kPayloadKey, &encoded_payload))
300 VerifySignature(protected_value, encoded_payload, decoded_signature);
301 if (!valid_signature_ && !ignore_invalid_signature)
304 if (!FixupBase64Encoding(&encoded_payload) ||
305 !base::Base64Decode(encoded_payload, payload))
311 bool VerifiedContents::VerifySignature(const std::string& protected_value,
312 const std::string& payload,
313 const std::string& signature_bytes) {
314 crypto::SignatureVerifier signature_verifier;
315 if (!signature_verifier.VerifyInit(
317 sizeof(kSignatureAlgorithm),
318 reinterpret_cast<const uint8*>(signature_bytes.data()),
319 signature_bytes.size(),
322 VLOG(1) << "Could not verify signature - VerifyInit failure";
326 signature_verifier.VerifyUpdate(
327 reinterpret_cast<const uint8*>(protected_value.data()),
328 protected_value.size());
330 std::string dot(".");
331 signature_verifier.VerifyUpdate(reinterpret_cast<const uint8*>(dot.data()),
334 signature_verifier.VerifyUpdate(
335 reinterpret_cast<const uint8*>(payload.data()), payload.size());
337 if (!signature_verifier.VerifyFinal()) {
338 VLOG(1) << "Could not verify signature - VerifyFinal failure";
344 } // namespace extensions