Home | History | Annotate | Download | only in browser
      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/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"
     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 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";
     49 
     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,
     53                                          std::string key,
     54                                          std::string value) {
     55   for (ListValue::const_iterator i = list->begin(); i != list->end(); ++i) {
     56     if (!(*i)->IsType(Value::TYPE_DICTIONARY))
     57       continue;
     58     DictionaryValue* dictionary = static_cast<DictionaryValue*>(*i);
     59     std::string found_value;
     60     if (dictionary->GetString(key, &found_value) && found_value == value)
     61       return dictionary;
     62   }
     63   return NULL;
     64 }
     65 
     66 }  // namespace
     67 
     68 namespace extensions {
     69 
     70 // static
     71 bool 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 
     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.
     97       block_size_(0) {
     98 }
     99 
    100 VerifiedContents::~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 // }
    122 bool 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 
    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();
    204 }
    205 
    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();
    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).
    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))
    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 
    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(
    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