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
;
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
,
55 for (ListValue::const_iterator i
= list
->begin(); i
!= list
->end(); ++i
) {
56 if (!(*i
)->IsType(Value::TYPE_DICTIONARY
))
58 DictionaryValue
* dictionary
= static_cast<DictionaryValue
*>(*i
);
59 std::string found_value
;
60 if (dictionary
->GetString(key
, &found_value
) && found_value
== value
)
68 namespace extensions
{
71 bool VerifiedContents::FixupBase64Encoding(std::string
* input
) {
72 for (std::string::iterator i
= input
->begin(); i
!= input
->end(); ++i
) {
78 switch (input
->size() % 4) {
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.
100 VerifiedContents::~VerifiedContents() {
103 // The format of the payload json is:
105 // "item_id": "<extension id>",
106 // "item_version": "<extension version>",
107 // "content_hashes": [
109 // "block_size": 4096,
110 // "hash_block_size": 4096,
111 // "format": "treehash",
114 // "path": "foo/bar",
115 // "root_hash": "<base64url encoded bytes>"
122 bool VerifiedContents::InitFrom(const base::FilePath
& path
,
123 bool ignore_invalid_signature
) {
125 if (!GetPayload(path
, &payload
, ignore_invalid_signature
))
128 scoped_ptr
<base::Value
> value(base::JSONReader::Read(payload
));
129 if (!value
.get() || !value
->IsType(Value::TYPE_DICTIONARY
))
131 DictionaryValue
* dictionary
= static_cast<DictionaryValue
*>(value
.get());
134 if (!dictionary
->GetString(kItemIdKey
, &item_id
) ||
135 !crx_file::id_util::IdIsValid(item_id
))
137 extension_id_
= item_id
;
139 std::string version_string
;
140 if (!dictionary
->GetString(kItemVersionKey
, &version_string
))
142 version_
= base::Version(version_string
);
143 if (!version_
.IsValid())
146 ListValue
* hashes_list
= NULL
;
147 if (!dictionary
->GetList(kContentHashesKey
, &hashes_list
))
150 for (size_t i
= 0; i
< hashes_list
->GetSize(); i
++) {
151 DictionaryValue
* hashes
= NULL
;
152 if (!hashes_list
->GetDictionary(i
, &hashes
))
155 if (!hashes
->GetString(kFormatKey
, &format
) || format
!= kTreeHash
)
159 int hash_block_size
= 0;
160 if (!hashes
->GetInteger(kBlockSizeKey
, &block_size
) ||
161 !hashes
->GetInteger(kHashBlockSizeKey
, &hash_block_size
))
163 block_size_
= block_size
;
165 // We don't support using a different block_size and hash_block_size at
167 if (block_size_
!= hash_block_size
)
170 ListValue
* files
= NULL
;
171 if (!hashes
->GetList(kFilesKey
, &files
))
174 for (size_t j
= 0; j
< files
->GetSize(); j
++) {
175 DictionaryValue
* data
= NULL
;
176 if (!files
->GetDictionary(j
, &data
))
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
))
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
);
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();
213 if (expected
== i
->second
)
219 // We're loosely following the "JSON Web Signature" draft spec for signing
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.
237 // "description": "treehash per file",
238 // "signed_content": {
239 // "payload": "<base64url encoded JSON to sign>",
242 // "protected": "<base64url encoded JSON with algorithm/parameters>",
244 // <object with metadata about this signature, eg a key identifier>
247 // "<base64url encoded signature over payload || . || protected>"
249 // ... <zero or more additional signatures> ...
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
))
266 scoped_ptr
<base::Value
> value(base::JSONReader::Read(contents
));
267 if (!value
.get() || !value
->IsType(Value::TYPE_LIST
))
269 ListValue
* top_list
= static_cast<ListValue
*>(value
.get());
271 // Find the "treehash per file" signed content, e.g.
274 // "description": "treehash per file",
275 // "signed_content": {
276 // "signatures": [ ... ],
281 DictionaryValue
* dictionary
=
282 FindDictionaryWithValue(top_list
, kDescriptionKey
, kTreeHashPerFile
);
283 DictionaryValue
* signed_content
= NULL
;
285 !dictionary
->GetDictionaryWithoutPathExpansion(kSignedContentKey
,
290 ListValue
* signatures
= NULL
;
291 if (!signed_content
->GetList(kSignaturesKey
, &signatures
))
294 DictionaryValue
* signature_dict
=
295 FindDictionaryWithValue(signatures
, kHeaderKidKey
, kWebstoreKId
);
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
))
308 std::string encoded_payload
;
309 if (!signed_content
->GetString(kPayloadKey
, &encoded_payload
))
313 VerifySignature(protected_value
, encoded_payload
, decoded_signature
);
314 if (!valid_signature_
&& !ignore_invalid_signature
)
317 if (!FixupBase64Encoding(&encoded_payload
) ||
318 !base::Base64Decode(encoded_payload
, payload
))
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(
330 sizeof(kSignatureAlgorithm
),
331 reinterpret_cast<const uint8
*>(signature_bytes
.data()),
332 signature_bytes
.size(),
335 VLOG(1) << "Could not verify signature - VerifyInit failure";
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()),
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";
357 } // namespace extensions