OLD | NEW |
(Empty) | |
| 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_number_conversions.h" |
| 11 #include "base/strings/string_util.h" |
| 12 #include "base/values.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 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 kTreeHash[] = "treehash"; |
| 45 const char kWebstoreKId[] = "webstore"; |
| 46 |
| 47 // This function fixes up a string in base64url encoding to be in standard |
| 48 // base64. |
| 49 // |
| 50 // The JSON signing spec we're following uses "base64url" encoding (RFC 4648 |
| 51 // section 5 without padding). The slight differences from regular base64 |
| 52 // encoding are: |
| 53 // 1. uses '_' instead of '/' |
| 54 // 2. uses '-' instead of '+' |
| 55 // 3. omits trailing '=' padding |
| 56 bool FixupBase64Encoding(std::string* input) { |
| 57 for (std::string::iterator i = input->begin(); i != input->end(); ++i) { |
| 58 if (*i == '-') |
| 59 *i = '+'; |
| 60 else if (*i == '_') |
| 61 *i = '/'; |
| 62 } |
| 63 switch (input->size() % 4) { |
| 64 case 0: |
| 65 break; |
| 66 case 2: |
| 67 input->append("=="); |
| 68 break; |
| 69 case 3: |
| 70 input->append("="); |
| 71 break; |
| 72 default: |
| 73 return false; |
| 74 } |
| 75 return true; |
| 76 } |
| 77 |
| 78 } // namespace |
| 79 |
| 80 namespace extensions { |
| 81 |
| 82 VerifiedContents::VerifiedContents(const uint8* public_key, int public_key_size) |
| 83 : public_key_(public_key), |
| 84 public_key_size_(public_key_size), |
| 85 valid_signature_(false), // Guilty until proven innocent. |
| 86 block_size_(0) { |
| 87 } |
| 88 |
| 89 VerifiedContents::~VerifiedContents() { |
| 90 } |
| 91 |
| 92 // The format of the payload json is: |
| 93 // { |
| 94 // "content_hashes": [ |
| 95 // { |
| 96 // "block_size": 4096, |
| 97 // "hash_block_size": 4096, |
| 98 // "format": "treehash", |
| 99 // "files": [ |
| 100 // { |
| 101 // "path": "foo/bar", |
| 102 // "root_hash": "<hex encoded bytes>" |
| 103 // }, |
| 104 // ... |
| 105 // ] |
| 106 // } |
| 107 // ] |
| 108 // } |
| 109 bool VerifiedContents::InitFrom(const base::FilePath& path, |
| 110 bool ignore_invalid_signature) { |
| 111 std::string payload; |
| 112 if (!GetPayload(path, &payload, ignore_invalid_signature)) |
| 113 return false; |
| 114 |
| 115 scoped_ptr<base::Value> value(base::JSONReader::Read(payload)); |
| 116 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) |
| 117 return false; |
| 118 DictionaryValue* dictionary = static_cast<DictionaryValue*>(value.get()); |
| 119 |
| 120 std::string item_id; |
| 121 if (!dictionary->GetString(kItemIdKey, &item_id) || |
| 122 !Extension::IdIsValid(item_id)) |
| 123 return false; |
| 124 extension_id_ = item_id; |
| 125 |
| 126 std::string version_string; |
| 127 if (!dictionary->GetString(kItemVersionKey, &version_string)) |
| 128 return false; |
| 129 version_ = base::Version(version_string); |
| 130 if (!version_.IsValid()) |
| 131 return false; |
| 132 |
| 133 ListValue* hashes_list = NULL; |
| 134 if (!dictionary->GetList(kContentHashesKey, &hashes_list)) |
| 135 return false; |
| 136 |
| 137 for (size_t i = 0; i < hashes_list->GetSize(); i++) { |
| 138 DictionaryValue* hashes = NULL; |
| 139 if (!hashes_list->GetDictionary(i, &hashes)) |
| 140 return false; |
| 141 std::string format; |
| 142 if (!hashes->GetString(kFormatKey, &format) || format != kTreeHash) |
| 143 continue; |
| 144 |
| 145 int block_size = 0; |
| 146 int hash_block_size = 0; |
| 147 if (!hashes->GetInteger(kBlockSizeKey, &block_size) || |
| 148 !hashes->GetInteger(kHashBlockSizeKey, &hash_block_size)) |
| 149 return false; |
| 150 block_size_ = block_size; |
| 151 |
| 152 // We don't support using a different block_size and hash_block_size at |
| 153 // the moment. |
| 154 if (block_size_ != hash_block_size) |
| 155 return false; |
| 156 |
| 157 ListValue* files = NULL; |
| 158 if (!hashes->GetList(kFilesKey, &files)) |
| 159 return false; |
| 160 |
| 161 for (size_t j = 0; j < files->GetSize(); j++) { |
| 162 DictionaryValue* data = NULL; |
| 163 if (!files->GetDictionary(j, &data)) |
| 164 return false; |
| 165 std::string file_path_string; |
| 166 std::string encoded_root_hash; |
| 167 std::vector<uint8> root_hash; |
| 168 if (!data->GetString(kPathKey, &file_path_string) || |
| 169 !base::IsStringUTF8(file_path_string) || |
| 170 !data->GetString(kRootHashKey, &encoded_root_hash) || |
| 171 !base::HexStringToBytes(encoded_root_hash, &root_hash)) |
| 172 return false; |
| 173 base::FilePath file_path = |
| 174 base::FilePath::FromUTF8Unsafe(file_path_string); |
| 175 root_hashes_[file_path] = std::string(); |
| 176 root_hashes_[file_path].append(root_hash.begin(), root_hash.end()); |
| 177 } |
| 178 |
| 179 break; |
| 180 } |
| 181 return true; |
| 182 } |
| 183 |
| 184 const std::string* VerifiedContents::GetTreeHashRoot( |
| 185 const base::FilePath& relative_path) { |
| 186 std::map<base::FilePath, std::string>::const_iterator i = |
| 187 root_hashes_.find(relative_path); |
| 188 if (i == root_hashes_.end()) |
| 189 return NULL; |
| 190 return &i->second; |
| 191 } |
| 192 |
| 193 // We're loosely following the "JSON Web Signature" draft spec for signing |
| 194 // a JSON payload: |
| 195 // |
| 196 // http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26 |
| 197 // |
| 198 // The idea is that you have some JSON that you want to sign, so you |
| 199 // base64-encode that and put it as the "payload" field in a containing |
| 200 // dictionary. There might be signatures of it done with multiple |
| 201 // alrogirhtms/parameters, so the payload is followed by a list of one or more |
| 202 // signature sections. Each signature section specifies the |
| 203 // algorithm/parameters in a JSON object which is base64url encoded into one |
| 204 // string and put into a "protected" field in the signature. Then the encoded |
| 205 // "payload" and "protected" strings are concatenated with a "." in between |
| 206 // them and those bytes are signed and the resulting signature is base64url |
| 207 // encoded and placed in the "signature" field. E.g. |
| 208 // { |
| 209 // "payload": "<base64url encoded JSON to sign>", |
| 210 // "signatures": [ |
| 211 // { |
| 212 // "protected": "<base64url encoded JSON with algorithm/parameters>", |
| 213 // "header": { |
| 214 // <object with metadata about this signature, eg a key identifier> |
| 215 // } |
| 216 // "signature": |
| 217 // "<base64url encoded signature done over payload || . || protected>" |
| 218 // }, |
| 219 // ... <zero or more additional signatures> ... |
| 220 // ] |
| 221 // } |
| 222 // |
| 223 // There might be both a signature generated with a webstore private key and a |
| 224 // signature generated with the extension's private key - for now we only |
| 225 // verify the webstore one (since the id is in the payload, so we can trust |
| 226 // that it is for a given extension), but in the future we may validate using |
| 227 // the extension's key too (eg for non-webstore hosted extensions such as |
| 228 // enterprise installs). |
| 229 bool VerifiedContents::GetPayload(const base::FilePath& path, |
| 230 std::string* payload, |
| 231 bool ignore_invalid_signature) { |
| 232 std::string contents; |
| 233 if (!base::ReadFileToString(path, &contents)) |
| 234 return false; |
| 235 scoped_ptr<base::Value> value(base::JSONReader::Read(contents)); |
| 236 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) |
| 237 return false; |
| 238 DictionaryValue* dictionary = static_cast<DictionaryValue*>(value.get()); |
| 239 |
| 240 ListValue* signatures = NULL; |
| 241 if (!dictionary->GetList(kSignaturesKey, &signatures)) |
| 242 return false; |
| 243 |
| 244 std::string protected_value; |
| 245 std::string decoded_signature; |
| 246 for (size_t i = 0; i < signatures->GetSize(); i++) { |
| 247 DictionaryValue* signature_dict = NULL; |
| 248 if (!signatures->GetDictionary(i, &signature_dict)) |
| 249 return false; |
| 250 std::string kid; |
| 251 if (!signature_dict->GetString(kHeaderKidKey, &kid)) |
| 252 continue; |
| 253 if (kid == std::string(kWebstoreKId)) { |
| 254 std::string encoded_signature; |
| 255 if (!signature_dict->GetString(kProtectedKey, &protected_value) || |
| 256 !signature_dict->GetString(kSignatureKey, &encoded_signature) || |
| 257 !FixupBase64Encoding(&encoded_signature) || |
| 258 !base::Base64Decode(encoded_signature, &decoded_signature)) |
| 259 return false; |
| 260 break; |
| 261 } |
| 262 } |
| 263 std::string encoded_payload; |
| 264 if (!dictionary->GetString(kPayloadKey, &encoded_payload)) |
| 265 return false; |
| 266 |
| 267 valid_signature_ = |
| 268 VerifySignature(protected_value, encoded_payload, decoded_signature); |
| 269 if (!valid_signature_ && !ignore_invalid_signature) |
| 270 return false; |
| 271 |
| 272 if (!FixupBase64Encoding(&encoded_payload) || |
| 273 !base::Base64Decode(encoded_payload, payload)) |
| 274 return false; |
| 275 |
| 276 return true; |
| 277 } |
| 278 |
| 279 bool VerifiedContents::VerifySignature(const std::string& protected_value, |
| 280 const std::string& payload, |
| 281 const std::string& signature_bytes) { |
| 282 crypto::SignatureVerifier signature_verifier; |
| 283 if (!signature_verifier.VerifyInit( |
| 284 kSignatureAlgorithm, |
| 285 sizeof(kSignatureAlgorithm), |
| 286 reinterpret_cast<const uint8*>(signature_bytes.data()), |
| 287 signature_bytes.size(), |
| 288 public_key_, |
| 289 public_key_size_)) { |
| 290 VLOG(1) << "Could not verify signature - VerifyInit failure"; |
| 291 return false; |
| 292 } |
| 293 |
| 294 signature_verifier.VerifyUpdate( |
| 295 reinterpret_cast<const uint8*>(protected_value.data()), |
| 296 protected_value.size()); |
| 297 |
| 298 std::string dot("."); |
| 299 signature_verifier.VerifyUpdate(reinterpret_cast<const uint8*>(dot.data()), |
| 300 dot.size()); |
| 301 |
| 302 signature_verifier.VerifyUpdate( |
| 303 reinterpret_cast<const uint8*>(payload.data()), payload.size()); |
| 304 |
| 305 if (!signature_verifier.VerifyFinal()) { |
| 306 VLOG(1) << "Could not verify signature - VerifyFinal failure"; |
| 307 return false; |
| 308 } |
| 309 return true; |
| 310 } |
| 311 |
| 312 } // namespace extensions |
OLD | NEW |