Upstream version 10.38.220.0
[platform/framework/web/crosswalk.git] / src / extensions / browser / verified_contents.cc
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_util.h"
11 #include "base/values.h"
12 #include "crypto/signature_verifier.h"
13 #include "extensions/common/extension.h"
14
15 using base::DictionaryValue;
16 using base::ListValue;
17 using base::Value;
18
19 namespace {
20
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};
28
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";
48
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,
52                                          std::string key,
53                                          std::string value) {
54   for (ListValue::const_iterator i = list->begin(); i != list->end(); ++i) {
55     if (!(*i)->IsType(Value::TYPE_DICTIONARY))
56       continue;
57     DictionaryValue* dictionary = static_cast<DictionaryValue*>(*i);
58     std::string found_value;
59     if (dictionary->GetString(key, &found_value) && found_value == value)
60       return dictionary;
61   }
62   return NULL;
63 }
64
65 }  // namespace
66
67 namespace extensions {
68
69 // static
70 bool VerifiedContents::FixupBase64Encoding(std::string* input) {
71   for (std::string::iterator i = input->begin(); i != input->end(); ++i) {
72     if (*i == '-')
73       *i = '+';
74     else if (*i == '_')
75       *i = '/';
76   }
77   switch (input->size() % 4) {
78     case 0:
79       break;
80     case 2:
81       input->append("==");
82       break;
83     case 3:
84       input->append("=");
85       break;
86     default:
87       return false;
88   }
89   return true;
90 }
91
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.
96       block_size_(0) {
97 }
98
99 VerifiedContents::~VerifiedContents() {
100 }
101
102 // The format of the payload json is:
103 // {
104 //   "item_id": "<extension id>",
105 //   "item_version": "<extension version>",
106 //   "content_hashes": [
107 //     {
108 //       "block_size": 4096,
109 //       "hash_block_size": 4096,
110 //       "format": "treehash",
111 //       "files": [
112 //         {
113 //           "path": "foo/bar",
114 //           "root_hash": "<base64url encoded bytes>"
115 //         },
116 //         ...
117 //       ]
118 //     }
119 //   ]
120 // }
121 bool VerifiedContents::InitFrom(const base::FilePath& path,
122                                 bool ignore_invalid_signature) {
123   std::string payload;
124   if (!GetPayload(path, &payload, ignore_invalid_signature))
125     return false;
126
127   scoped_ptr<base::Value> value(base::JSONReader::Read(payload));
128   if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY))
129     return false;
130   DictionaryValue* dictionary = static_cast<DictionaryValue*>(value.get());
131
132   std::string item_id;
133   if (!dictionary->GetString(kItemIdKey, &item_id) ||
134       !Extension::IdIsValid(item_id))
135     return false;
136   extension_id_ = item_id;
137
138   std::string version_string;
139   if (!dictionary->GetString(kItemVersionKey, &version_string))
140     return false;
141   version_ = base::Version(version_string);
142   if (!version_.IsValid())
143     return false;
144
145   ListValue* hashes_list = NULL;
146   if (!dictionary->GetList(kContentHashesKey, &hashes_list))
147     return false;
148
149   for (size_t i = 0; i < hashes_list->GetSize(); i++) {
150     DictionaryValue* hashes = NULL;
151     if (!hashes_list->GetDictionary(i, &hashes))
152       return false;
153     std::string format;
154     if (!hashes->GetString(kFormatKey, &format) || format != kTreeHash)
155       continue;
156
157     int block_size = 0;
158     int hash_block_size = 0;
159     if (!hashes->GetInteger(kBlockSizeKey, &block_size) ||
160         !hashes->GetInteger(kHashBlockSizeKey, &hash_block_size))
161       return false;
162     block_size_ = block_size;
163
164     // We don't support using a different block_size and hash_block_size at
165     // the moment.
166     if (block_size_ != hash_block_size)
167       return false;
168
169     ListValue* files = NULL;
170     if (!hashes->GetList(kFilesKey, &files))
171       return false;
172
173     for (size_t j = 0; j < files->GetSize(); j++) {
174       DictionaryValue* data = NULL;
175       if (!files->GetDictionary(j, &data))
176         return false;
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))
185         return false;
186       base::FilePath file_path =
187           base::FilePath::FromUTF8Unsafe(file_path_string);
188       RootHashes::iterator i = root_hashes_.insert(std::make_pair(
189           base::StringToLowerASCII(file_path.value()), std::string()));
190       i->second.swap(root_hash);
191     }
192
193     break;
194   }
195   return true;
196 }
197
198 bool VerifiedContents::HasTreeHashRoot(
199     const base::FilePath& relative_path) const {
200   base::FilePath::StringType path = base::StringToLowerASCII(
201       relative_path.NormalizePathSeparatorsTo('/').value());
202   return root_hashes_.find(path) != root_hashes_.end();
203 }
204
205 bool VerifiedContents::TreeHashRootEquals(const base::FilePath& relative_path,
206                                           const std::string& expected) const {
207   base::FilePath::StringType path = base::StringToLowerASCII(
208       relative_path.NormalizePathSeparatorsTo('/').value());
209   for (RootHashes::const_iterator i = root_hashes_.find(path);
210        i != root_hashes_.end();
211        ++i) {
212     if (expected == i->second)
213       return true;
214   }
215   return false;
216 }
217
218 // We're loosely following the "JSON Web Signature" draft spec for signing
219 // a JSON payload:
220 //
221 //   http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26
222 //
223 // The idea is that you have some JSON that you want to sign, so you
224 // base64-encode that and put it as the "payload" field in a containing
225 // dictionary. There might be signatures of it done with multiple
226 // algorithms/parameters, so the payload is followed by a list of one or more
227 // signature sections. Each signature section specifies the
228 // algorithm/parameters in a JSON object which is base64url encoded into one
229 // string and put into a "protected" field in the signature. Then the encoded
230 // "payload" and "protected" strings are concatenated with a "." in between
231 // them and those bytes are signed and the resulting signature is base64url
232 // encoded and placed in the "signature" field. To allow for extensibility, we
233 // wrap this, so we can include additional kinds of payloads in the future. E.g.
234 // [
235 //   {
236 //     "description": "treehash per file",
237 //     "signed_content": {
238 //       "payload": "<base64url encoded JSON to sign>",
239 //       "signatures": [
240 //         {
241 //           "protected": "<base64url encoded JSON with algorithm/parameters>",
242 //           "header": {
243 //             <object with metadata about this signature, eg a key identifier>
244 //           }
245 //           "signature":
246 //              "<base64url encoded signature over payload || . || protected>"
247 //         },
248 //         ... <zero or more additional signatures> ...
249 //       ]
250 //     }
251 //   }
252 // ]
253 // There might be both a signature generated with a webstore private key and a
254 // signature generated with the extension's private key - for now we only
255 // verify the webstore one (since the id is in the payload, so we can trust
256 // that it is for a given extension), but in the future we may validate using
257 // the extension's key too (eg for non-webstore hosted extensions such as
258 // enterprise installs).
259 bool VerifiedContents::GetPayload(const base::FilePath& path,
260                                   std::string* payload,
261                                   bool ignore_invalid_signature) {
262   std::string contents;
263   if (!base::ReadFileToString(path, &contents))
264     return false;
265   scoped_ptr<base::Value> value(base::JSONReader::Read(contents));
266   if (!value.get() || !value->IsType(Value::TYPE_LIST))
267     return false;
268   ListValue* top_list = static_cast<ListValue*>(value.get());
269
270   // Find the "treehash per file" signed content, e.g.
271   // [
272   //   {
273   //     "description": "treehash per file",
274   //     "signed_content": {
275   //       "signatures": [ ... ],
276   //       "payload": "..."
277   //     }
278   //   }
279   // ]
280   DictionaryValue* dictionary =
281       FindDictionaryWithValue(top_list, kDescriptionKey, kTreeHashPerFile);
282   DictionaryValue* signed_content = NULL;
283   if (!dictionary ||
284       !dictionary->GetDictionaryWithoutPathExpansion(kSignedContentKey,
285                                                      &signed_content)) {
286     return false;
287   }
288
289   ListValue* signatures = NULL;
290   if (!signed_content->GetList(kSignaturesKey, &signatures))
291     return false;
292
293   DictionaryValue* signature_dict =
294       FindDictionaryWithValue(signatures, kHeaderKidKey, kWebstoreKId);
295   if (!signature_dict)
296     return false;
297
298   std::string protected_value;
299   std::string encoded_signature;
300   std::string decoded_signature;
301   if (!signature_dict->GetString(kProtectedKey, &protected_value) ||
302       !signature_dict->GetString(kSignatureKey, &encoded_signature) ||
303       !FixupBase64Encoding(&encoded_signature) ||
304       !base::Base64Decode(encoded_signature, &decoded_signature))
305     return false;
306
307   std::string encoded_payload;
308   if (!signed_content->GetString(kPayloadKey, &encoded_payload))
309     return false;
310
311   valid_signature_ =
312       VerifySignature(protected_value, encoded_payload, decoded_signature);
313   if (!valid_signature_ && !ignore_invalid_signature)
314     return false;
315
316   if (!FixupBase64Encoding(&encoded_payload) ||
317       !base::Base64Decode(encoded_payload, payload))
318     return false;
319
320   return true;
321 }
322
323 bool VerifiedContents::VerifySignature(const std::string& protected_value,
324                                        const std::string& payload,
325                                        const std::string& signature_bytes) {
326   crypto::SignatureVerifier signature_verifier;
327   if (!signature_verifier.VerifyInit(
328           kSignatureAlgorithm,
329           sizeof(kSignatureAlgorithm),
330           reinterpret_cast<const uint8*>(signature_bytes.data()),
331           signature_bytes.size(),
332           public_key_,
333           public_key_size_)) {
334     VLOG(1) << "Could not verify signature - VerifyInit failure";
335     return false;
336   }
337
338   signature_verifier.VerifyUpdate(
339       reinterpret_cast<const uint8*>(protected_value.data()),
340       protected_value.size());
341
342   std::string dot(".");
343   signature_verifier.VerifyUpdate(reinterpret_cast<const uint8*>(dot.data()),
344                                   dot.size());
345
346   signature_verifier.VerifyUpdate(
347       reinterpret_cast<const uint8*>(payload.data()), payload.size());
348
349   if (!signature_verifier.VerifyFinal()) {
350     VLOG(1) << "Could not verify signature - VerifyFinal failure";
351     return false;
352   }
353   return true;
354 }
355
356 }  // namespace extensions