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