Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(510)

Side by Side Diff: extensions/browser/verified_contents.cc

Issue 278593005: Extension content verification: new class for parsing data from store (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix windows compile in unit tests file Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
5 #include "extensions/browser/verified_contents.h"
6
7 #include "base/base64.h"
8 #include "base/file_util.h"
9 #include "base/json/json_reader.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/values.h"
13 #include "crypto/signature_verifier.h"
14 #include "extensions/common/extension.h"
15
16 using base::DictionaryValue;
17 using base::ListValue;
18 using base::Value;
19
20 namespace {
21
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};
29
30 const char kBlockSizeKey[] = "block_size";
31 const char kContentHashesKey[] = "content_hashes";
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 kTreeHash[] = "treehash";
45 const char kWebstoreKId[] = "webstore";
46
47 // This function fixes up a string in base64url encoding to be in standard
48 // base64.
49 //
50 // The JSON signing spec we're following uses "base64url" encoding (RFC 4648
51 // section 5 without padding). The slight differences from regular base64
52 // encoding are:
53 // 1. uses '_' instead of '/'
54 // 2. uses '-' instead of '+'
55 // 3. omits trailing '=' padding
56 bool FixupBase64Encoding(std::string* input) {
57 for (std::string::iterator i = input->begin(); i != input->end(); ++i) {
58 if (*i == '-')
59 *i = '+';
60 else if (*i == '_')
61 *i = '/';
62 }
63 switch (input->size() % 4) {
64 case 0:
65 break;
66 case 2:
67 input->append("==");
68 break;
69 case 3:
70 input->append("=");
71 break;
72 default:
73 return false;
74 }
75 return true;
76 }
77
78 } // namespace
79
80 namespace extensions {
81
82 VerifiedContents::VerifiedContents(const uint8* public_key, int public_key_size)
83 : public_key_(public_key),
84 public_key_size_(public_key_size),
85 valid_signature_(false), // Guilty until proven innocent.
86 block_size_(0) {
87 }
88
89 VerifiedContents::~VerifiedContents() {
90 }
91
92 // The format of the payload json is:
93 // {
94 // "content_hashes": [
95 // {
96 // "block_size": 4096,
97 // "hash_block_size": 4096,
98 // "format": "treehash",
99 // "files": [
100 // {
101 // "path": "foo/bar",
102 // "root_hash": "<hex encoded bytes>"
103 // },
104 // ...
105 // ]
106 // }
107 // ]
108 // }
109 bool VerifiedContents::InitFrom(const base::FilePath& path,
110 bool ignore_invalid_signature) {
111 std::string payload;
112 if (!GetPayload(path, &payload, ignore_invalid_signature))
113 return false;
114
115 scoped_ptr<base::Value> value(base::JSONReader::Read(payload));
116 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY))
117 return false;
118 DictionaryValue* dictionary = static_cast<DictionaryValue*>(value.get());
119
120 std::string item_id;
121 if (!dictionary->GetString(kItemIdKey, &item_id) ||
122 !Extension::IdIsValid(item_id))
123 return false;
124 extension_id_ = item_id;
125
126 std::string version_string;
127 if (!dictionary->GetString(kItemVersionKey, &version_string))
128 return false;
129 version_ = base::Version(version_string);
130 if (!version_.IsValid())
131 return false;
132
133 ListValue* hashes_list = NULL;
134 if (!dictionary->GetList(kContentHashesKey, &hashes_list))
135 return false;
136
137 for (size_t i = 0; i < hashes_list->GetSize(); i++) {
138 DictionaryValue* hashes = NULL;
139 if (!hashes_list->GetDictionary(i, &hashes))
140 return false;
141 std::string format;
142 if (!hashes->GetString(kFormatKey, &format) || format != kTreeHash)
143 continue;
144
145 int block_size = 0;
146 int hash_block_size = 0;
147 if (!hashes->GetInteger(kBlockSizeKey, &block_size) ||
148 !hashes->GetInteger(kHashBlockSizeKey, &hash_block_size))
149 return false;
150 block_size_ = block_size;
151
152 // We don't support using a different block_size and hash_block_size at
153 // the moment.
154 if (block_size_ != hash_block_size)
155 return false;
156
157 ListValue* files = NULL;
158 if (!hashes->GetList(kFilesKey, &files))
159 return false;
160
161 for (size_t j = 0; j < files->GetSize(); j++) {
162 DictionaryValue* data = NULL;
163 if (!files->GetDictionary(j, &data))
164 return false;
165 std::string file_path_string;
166 std::string encoded_root_hash;
167 std::vector<uint8> root_hash;
168 if (!data->GetString(kPathKey, &file_path_string) ||
169 !base::IsStringUTF8(file_path_string) ||
170 !data->GetString(kRootHashKey, &encoded_root_hash) ||
171 !base::HexStringToBytes(encoded_root_hash, &root_hash))
172 return false;
173 base::FilePath file_path =
174 base::FilePath::FromUTF8Unsafe(file_path_string);
175 root_hashes_[file_path] = std::string();
176 root_hashes_[file_path].append(root_hash.begin(), root_hash.end());
177 }
178
179 break;
180 }
181 return true;
182 }
183
184 const std::string* VerifiedContents::GetTreeHashRoot(
185 const base::FilePath& relative_path) {
186 std::map<base::FilePath, std::string>::const_iterator i =
187 root_hashes_.find(relative_path);
188 if (i == root_hashes_.end())
189 return NULL;
190 return &i->second;
191 }
192
193 // We're loosely following the "JSON Web Signature" draft spec for signing
194 // a JSON payload:
195 //
196 // http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26
197 //
198 // The idea is that you have some JSON that you want to sign, so you
199 // base64-encode that and put it as the "payload" field in a containing
200 // dictionary. There might be signatures of it done with multiple
201 // alrogirhtms/parameters, so the payload is followed by a list of one or more
202 // signature sections. Each signature section specifies the
203 // algorithm/parameters in a JSON object which is base64url encoded into one
204 // string and put into a "protected" field in the signature. Then the encoded
205 // "payload" and "protected" strings are concatenated with a "." in between
206 // them and those bytes are signed and the resulting signature is base64url
207 // encoded and placed in the "signature" field. E.g.
208 // {
209 // "payload": "<base64url encoded JSON to sign>",
210 // "signatures": [
211 // {
212 // "protected": "<base64url encoded JSON with algorithm/parameters>",
213 // "header": {
214 // <object with metadata about this signature, eg a key identifier>
215 // }
216 // "signature":
217 // "<base64url encoded signature done over payload || . || protected>"
218 // },
219 // ... <zero or more additional signatures> ...
220 // ]
221 // }
222 //
223 // There might be both a signature generated with a webstore private key and a
224 // signature generated with the extension's private key - for now we only
225 // verify the webstore one (since the id is in the payload, so we can trust
226 // that it is for a given extension), but in the future we may validate using
227 // the extension's key too (eg for non-webstore hosted extensions such as
228 // enterprise installs).
229 bool VerifiedContents::GetPayload(const base::FilePath& path,
230 std::string* payload,
231 bool ignore_invalid_signature) {
232 std::string contents;
233 if (!base::ReadFileToString(path, &contents))
234 return false;
235 scoped_ptr<base::Value> value(base::JSONReader::Read(contents));
236 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY))
237 return false;
238 DictionaryValue* dictionary = static_cast<DictionaryValue*>(value.get());
239
240 ListValue* signatures = NULL;
241 if (!dictionary->GetList(kSignaturesKey, &signatures))
242 return false;
243
244 std::string protected_value;
245 std::string decoded_signature;
246 for (size_t i = 0; i < signatures->GetSize(); i++) {
247 DictionaryValue* signature_dict = NULL;
248 if (!signatures->GetDictionary(i, &signature_dict))
249 return false;
250 std::string kid;
251 if (!signature_dict->GetString(kHeaderKidKey, &kid))
252 continue;
253 if (kid == std::string(kWebstoreKId)) {
254 std::string encoded_signature;
255 if (!signature_dict->GetString(kProtectedKey, &protected_value) ||
256 !signature_dict->GetString(kSignatureKey, &encoded_signature) ||
257 !FixupBase64Encoding(&encoded_signature) ||
258 !base::Base64Decode(encoded_signature, &decoded_signature))
259 return false;
260 break;
261 }
262 }
263 std::string encoded_payload;
264 if (!dictionary->GetString(kPayloadKey, &encoded_payload))
265 return false;
266
267 valid_signature_ =
268 VerifySignature(protected_value, encoded_payload, decoded_signature);
269 if (!valid_signature_ && !ignore_invalid_signature)
270 return false;
271
272 if (!FixupBase64Encoding(&encoded_payload) ||
273 !base::Base64Decode(encoded_payload, payload))
274 return false;
275
276 return true;
277 }
278
279 bool VerifiedContents::VerifySignature(const std::string& protected_value,
280 const std::string& payload,
281 const std::string& signature_bytes) {
282 crypto::SignatureVerifier signature_verifier;
283 if (!signature_verifier.VerifyInit(
284 kSignatureAlgorithm,
285 sizeof(kSignatureAlgorithm),
286 reinterpret_cast<const uint8*>(signature_bytes.data()),
287 signature_bytes.size(),
288 public_key_,
289 public_key_size_)) {
290 VLOG(1) << "Could not verify signature - VerifyInit failure";
291 return false;
292 }
293
294 signature_verifier.VerifyUpdate(
295 reinterpret_cast<const uint8*>(protected_value.data()),
296 protected_value.size());
297
298 std::string dot(".");
299 signature_verifier.VerifyUpdate(reinterpret_cast<const uint8*>(dot.data()),
300 dot.size());
301
302 signature_verifier.VerifyUpdate(
303 reinterpret_cast<const uint8*>(payload.data()), payload.size());
304
305 if (!signature_verifier.VerifyFinal()) {
306 VLOG(1) << "Could not verify signature - VerifyFinal failure";
307 return false;
308 }
309 return true;
310 }
311
312 } // namespace extensions
OLDNEW
« no previous file with comments | « extensions/browser/verified_contents.h ('k') | extensions/browser/verified_contents_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698