105436638acc7c010349a69c3395f1a57c642dc62Ying Wang// Copyright 2014 The Chromium Authors. All rights reserved.
205436638acc7c010349a69c3395f1a57c642dc62Ying Wang// Use of this source code is governed by a BSD-style license that can be
305436638acc7c010349a69c3395f1a57c642dc62Ying Wang// found in the LICENSE file.
405436638acc7c010349a69c3395f1a57c642dc62Ying Wang
505436638acc7c010349a69c3395f1a57c642dc62Ying Wang#include "extensions/browser/verified_contents.h"
605436638acc7c010349a69c3395f1a57c642dc62Ying Wang
705436638acc7c010349a69c3395f1a57c642dc62Ying Wang#include "base/base64.h"
805436638acc7c010349a69c3395f1a57c642dc62Ying Wang#include "base/files/file_util.h"
905436638acc7c010349a69c3395f1a57c642dc62Ying Wang#include "base/json/json_reader.h"
1005436638acc7c010349a69c3395f1a57c642dc62Ying Wang#include "base/strings/string_util.h"
1105436638acc7c010349a69c3395f1a57c642dc62Ying Wang#include "base/values.h"
1205436638acc7c010349a69c3395f1a57c642dc62Ying Wang#include "components/crx_file/id_util.h"
1305436638acc7c010349a69c3395f1a57c642dc62Ying Wang#include "crypto/signature_verifier.h"
1405436638acc7c010349a69c3395f1a57c642dc62Ying Wang#include "extensions/common/extension.h"
1505436638acc7c010349a69c3395f1a57c642dc62Ying Wang
1605436638acc7c010349a69c3395f1a57c642dc62Ying Wangusing base::DictionaryValue;
1705436638acc7c010349a69c3395f1a57c642dc62Ying Wangusing base::ListValue;
1805436638acc7c010349a69c3395f1a57c642dc62Ying Wangusing base::Value;
1905436638acc7c010349a69c3395f1a57c642dc62Ying Wang
2005436638acc7c010349a69c3395f1a57c642dc62Ying Wangnamespace {
2105436638acc7c010349a69c3395f1a57c642dc62Ying Wang
2205436638acc7c010349a69c3395f1a57c642dc62Ying Wang// Note: this structure is an ASN.1 which encodes the algorithm used with its
2305436638acc7c010349a69c3395f1a57c642dc62Ying Wang// parameters.  The signature algorithm is "RSA256" aka "RSASSA-PKCS-v1_5 using
2405436638acc7c010349a69c3395f1a57c642dc62Ying Wang// SHA-256 hash algorithm". This is defined in PKCS #1 (RFC 3447).
2505436638acc7c010349a69c3395f1a57c642dc62Ying Wang// It is encoding: { OID sha256WithRSAEncryption      PARAMETERS NULL }
2605436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst uint8 kSignatureAlgorithm[15] = {0x30, 0x0d, 0x06, 0x09, 0x2a,
2705436638acc7c010349a69c3395f1a57c642dc62Ying Wang                                       0x86, 0x48, 0x86, 0xf7, 0x0d,
2805436638acc7c010349a69c3395f1a57c642dc62Ying Wang                                       0x01, 0x01, 0x0b, 0x05, 0x00};
2905436638acc7c010349a69c3395f1a57c642dc62Ying Wang
3005436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kBlockSizeKey[] = "block_size";
3105436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kContentHashesKey[] = "content_hashes";
3205436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kDescriptionKey[] = "description";
3305436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kFilesKey[] = "files";
3405436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kFormatKey[] = "format";
3505436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kHashBlockSizeKey[] = "hash_block_size";
3605436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kHeaderKidKey[] = "header.kid";
3705436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kItemIdKey[] = "item_id";
3805436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kItemVersionKey[] = "item_version";
3905436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kPathKey[] = "path";
4005436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kPayloadKey[] = "payload";
4105436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kProtectedKey[] = "protected";
4205436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kRootHashKey[] = "root_hash";
4305436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kSignatureKey[] = "signature";
4405436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kSignaturesKey[] = "signatures";
4505436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kSignedContentKey[] = "signed_content";
4605436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kTreeHashPerFile[] = "treehash per file";
4705436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kTreeHash[] = "treehash";
4805436638acc7c010349a69c3395f1a57c642dc62Ying Wangconst char kWebstoreKId[] = "webstore";
4905436638acc7c010349a69c3395f1a57c642dc62Ying Wang
5005436638acc7c010349a69c3395f1a57c642dc62Ying Wang// Helper function to iterate over a list of dictionaries, returning the
5105436638acc7c010349a69c3395f1a57c642dc62Ying Wang// dictionary that has |key| -> |value| in it, if any, or NULL.
5205436638acc7c010349a69c3395f1a57c642dc62Ying WangDictionaryValue* FindDictionaryWithValue(const ListValue* list,
5305436638acc7c010349a69c3395f1a57c642dc62Ying Wang                                         std::string key,
5405436638acc7c010349a69c3395f1a57c642dc62Ying Wang                                         std::string value) {
5505436638acc7c010349a69c3395f1a57c642dc62Ying Wang  for (ListValue::const_iterator i = list->begin(); i != list->end(); ++i) {
5605436638acc7c010349a69c3395f1a57c642dc62Ying Wang    if (!(*i)->IsType(Value::TYPE_DICTIONARY))
5705436638acc7c010349a69c3395f1a57c642dc62Ying Wang      continue;
5805436638acc7c010349a69c3395f1a57c642dc62Ying Wang    DictionaryValue* dictionary = static_cast<DictionaryValue*>(*i);
5905436638acc7c010349a69c3395f1a57c642dc62Ying Wang    std::string found_value;
6005436638acc7c010349a69c3395f1a57c642dc62Ying Wang    if (dictionary->GetString(key, &found_value) && found_value == value)
6105436638acc7c010349a69c3395f1a57c642dc62Ying Wang      return dictionary;
6205436638acc7c010349a69c3395f1a57c642dc62Ying Wang  }
6305436638acc7c010349a69c3395f1a57c642dc62Ying Wang  return NULL;
6405436638acc7c010349a69c3395f1a57c642dc62Ying Wang}
65
66}  // namespace
67
68namespace extensions {
69
70// static
71bool 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 = '/';
77  }
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;
89  }
90  return true;
91}
92
93VerifiedContents::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) {
98}
99
100VerifiedContents::~VerifiedContents() {
101}
102
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// }
122bool 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;
127
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());
132
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;
138
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;
145
146  ListValue* hashes_list = NULL;
147  if (!dictionary->GetList(kContentHashesKey, &hashes_list))
148    return false;
149
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;
157
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;
164
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;
169
170    ListValue* files = NULL;
171    if (!hashes->GetList(kFilesKey, &files))
172      return false;
173
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);
192    }
193
194    break;
195  }
196  return true;
197}
198
199bool 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();
204}
205
206bool 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;
215  }
216  return false;
217}
218
219// We're loosely following the "JSON Web Signature" draft spec for signing
220// a JSON payload:
221//
222//   http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26
223//
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).
260bool 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());
270
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;
288  }
289
290  ListValue* signatures = NULL;
291  if (!signed_content->GetList(kSignaturesKey, &signatures))
292    return false;
293
294  DictionaryValue* signature_dict =
295      FindDictionaryWithValue(signatures, kHeaderKidKey, kWebstoreKId);
296  if (!signature_dict)
297    return false;
298
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;
307
308  std::string encoded_payload;
309  if (!signed_content->GetString(kPayloadKey, &encoded_payload))
310    return false;
311
312  valid_signature_ =
313      VerifySignature(protected_value, encoded_payload, decoded_signature);
314  if (!valid_signature_ && !ignore_invalid_signature)
315    return false;
316
317  if (!FixupBase64Encoding(&encoded_payload) ||
318      !base::Base64Decode(encoded_payload, payload))
319    return false;
320
321  return true;
322}
323
324bool 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;
337  }
338
339  signature_verifier.VerifyUpdate(
340      reinterpret_cast<const uint8*>(protected_value.data()),
341      protected_value.size());
342
343  std::string dot(".");
344  signature_verifier.VerifyUpdate(reinterpret_cast<const uint8*>(dot.data()),
345                                  dot.size());
346
347  signature_verifier.VerifyUpdate(
348      reinterpret_cast<const uint8*>(payload.data()), payload.size());
349
350  if (!signature_verifier.VerifyFinal()) {
351    VLOG(1) << "Could not verify signature - VerifyFinal failure";
352    return false;
353  }
354  return true;
355}
356
357}  // namespace extensions
358