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 15using base::DictionaryValue; 16using base::ListValue; 17using base::Value; 18 19namespace { 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 } 25const uint8 kSignatureAlgorithm[15] = {0x30, 0x0d, 0x06, 0x09, 0x2a, 26 0x86, 0x48, 0x86, 0xf7, 0x0d, 27 0x01, 0x01, 0x0b, 0x05, 0x00}; 28 29const char kBlockSizeKey[] = "block_size"; 30const char kContentHashesKey[] = "content_hashes"; 31const char kDescriptionKey[] = "description"; 32const char kFilesKey[] = "files"; 33const char kFormatKey[] = "format"; 34const char kHashBlockSizeKey[] = "hash_block_size"; 35const char kHeaderKidKey[] = "header.kid"; 36const char kItemIdKey[] = "item_id"; 37const char kItemVersionKey[] = "item_version"; 38const char kPathKey[] = "path"; 39const char kPayloadKey[] = "payload"; 40const char kProtectedKey[] = "protected"; 41const char kRootHashKey[] = "root_hash"; 42const char kSignatureKey[] = "signature"; 43const char kSignaturesKey[] = "signatures"; 44const char kSignedContentKey[] = "signed_content"; 45const char kTreeHashPerFile[] = "treehash per file"; 46const char kTreeHash[] = "treehash"; 47const 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. 51DictionaryValue* 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 67namespace extensions { 68 69// static 70bool 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 92VerifiedContents::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 99VerifiedContents::~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// } 121bool 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#if defined(FILE_PATH_USES_WIN_SEPARATORS) 189 file_path = file_path.NormalizePathSeparators(); 190#endif // defined(FILE_PATH_USES_WIN_SEPARATORS) 191 root_hashes_[file_path] = std::string(); 192 root_hashes_[file_path].swap(root_hash); 193 } 194 195 break; 196 } 197 return true; 198} 199 200const std::string* VerifiedContents::GetTreeHashRoot( 201 const base::FilePath& relative_path) { 202 std::map<base::FilePath, std::string>::const_iterator i = 203 root_hashes_.find(relative_path); 204 if (i == root_hashes_.end()) 205 return NULL; 206 return &i->second; 207} 208 209// We're loosely following the "JSON Web Signature" draft spec for signing 210// a JSON payload: 211// 212// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26 213// 214// The idea is that you have some JSON that you want to sign, so you 215// base64-encode that and put it as the "payload" field in a containing 216// dictionary. There might be signatures of it done with multiple 217// algorithms/parameters, so the payload is followed by a list of one or more 218// signature sections. Each signature section specifies the 219// algorithm/parameters in a JSON object which is base64url encoded into one 220// string and put into a "protected" field in the signature. Then the encoded 221// "payload" and "protected" strings are concatenated with a "." in between 222// them and those bytes are signed and the resulting signature is base64url 223// encoded and placed in the "signature" field. To allow for extensibility, we 224// wrap this, so we can include additional kinds of payloads in the future. E.g. 225// [ 226// { 227// "description": "treehash per file", 228// "signed_content": { 229// "payload": "<base64url encoded JSON to sign>", 230// "signatures": [ 231// { 232// "protected": "<base64url encoded JSON with algorithm/parameters>", 233// "header": { 234// <object with metadata about this signature, eg a key identifier> 235// } 236// "signature": 237// "<base64url encoded signature over payload || . || protected>" 238// }, 239// ... <zero or more additional signatures> ... 240// ] 241// } 242// } 243// ] 244// There might be both a signature generated with a webstore private key and a 245// signature generated with the extension's private key - for now we only 246// verify the webstore one (since the id is in the payload, so we can trust 247// that it is for a given extension), but in the future we may validate using 248// the extension's key too (eg for non-webstore hosted extensions such as 249// enterprise installs). 250bool VerifiedContents::GetPayload(const base::FilePath& path, 251 std::string* payload, 252 bool ignore_invalid_signature) { 253 std::string contents; 254 if (!base::ReadFileToString(path, &contents)) 255 return false; 256 scoped_ptr<base::Value> value(base::JSONReader::Read(contents)); 257 if (!value.get() || !value->IsType(Value::TYPE_LIST)) 258 return false; 259 ListValue* top_list = static_cast<ListValue*>(value.get()); 260 261 // Find the "treehash per file" signed content, e.g. 262 // [ 263 // { 264 // "description": "treehash per file", 265 // "signed_content": { 266 // "signatures": [ ... ], 267 // "payload": "..." 268 // } 269 // } 270 // ] 271 DictionaryValue* dictionary = 272 FindDictionaryWithValue(top_list, kDescriptionKey, kTreeHashPerFile); 273 DictionaryValue* signed_content = NULL; 274 if (!dictionary || 275 !dictionary->GetDictionaryWithoutPathExpansion(kSignedContentKey, 276 &signed_content)) { 277 return false; 278 } 279 280 ListValue* signatures = NULL; 281 if (!signed_content->GetList(kSignaturesKey, &signatures)) 282 return false; 283 284 DictionaryValue* signature_dict = 285 FindDictionaryWithValue(signatures, kHeaderKidKey, kWebstoreKId); 286 if (!signature_dict) 287 return false; 288 289 std::string protected_value; 290 std::string encoded_signature; 291 std::string decoded_signature; 292 if (!signature_dict->GetString(kProtectedKey, &protected_value) || 293 !signature_dict->GetString(kSignatureKey, &encoded_signature) || 294 !FixupBase64Encoding(&encoded_signature) || 295 !base::Base64Decode(encoded_signature, &decoded_signature)) 296 return false; 297 298 std::string encoded_payload; 299 if (!signed_content->GetString(kPayloadKey, &encoded_payload)) 300 return false; 301 302 valid_signature_ = 303 VerifySignature(protected_value, encoded_payload, decoded_signature); 304 if (!valid_signature_ && !ignore_invalid_signature) 305 return false; 306 307 if (!FixupBase64Encoding(&encoded_payload) || 308 !base::Base64Decode(encoded_payload, payload)) 309 return false; 310 311 return true; 312} 313 314bool VerifiedContents::VerifySignature(const std::string& protected_value, 315 const std::string& payload, 316 const std::string& signature_bytes) { 317 crypto::SignatureVerifier signature_verifier; 318 if (!signature_verifier.VerifyInit( 319 kSignatureAlgorithm, 320 sizeof(kSignatureAlgorithm), 321 reinterpret_cast<const uint8*>(signature_bytes.data()), 322 signature_bytes.size(), 323 public_key_, 324 public_key_size_)) { 325 VLOG(1) << "Could not verify signature - VerifyInit failure"; 326 return false; 327 } 328 329 signature_verifier.VerifyUpdate( 330 reinterpret_cast<const uint8*>(protected_value.data()), 331 protected_value.size()); 332 333 std::string dot("."); 334 signature_verifier.VerifyUpdate(reinterpret_cast<const uint8*>(dot.data()), 335 dot.size()); 336 337 signature_verifier.VerifyUpdate( 338 reinterpret_cast<const uint8*>(payload.data()), payload.size()); 339 340 if (!signature_verifier.VerifyFinal()) { 341 VLOG(1) << "Could not verify signature - VerifyFinal failure"; 342 return false; 343 } 344 return true; 345} 346 347} // namespace extensions 348