Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(420)

Unified Diff: extensions/browser/verified_contents.cc

Issue 278593005: Extension content verification: new class for parsing data from store (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix windows compile in unit tests file Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « extensions/browser/verified_contents.h ('k') | extensions/browser/verified_contents_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: extensions/browser/verified_contents.cc
diff --git a/extensions/browser/verified_contents.cc b/extensions/browser/verified_contents.cc
new file mode 100644
index 0000000000000000000000000000000000000000..964291bc38d56d451ded245e0cd9c1a88628495b
--- /dev/null
+++ b/extensions/browser/verified_contents.cc
@@ -0,0 +1,312 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/verified_contents.h"
+
+#include "base/base64.h"
+#include "base/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "crypto/signature_verifier.h"
+#include "extensions/common/extension.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::Value;
+
+namespace {
+
+// Note: this structure is an ASN.1 which encodes the algorithm used with its
+// parameters. The signature algorithm is "RSA256" aka "RSASSA-PKCS-v1_5 using
+// SHA-256 hash algorithm". This is defined in PKCS #1 (RFC 3447).
+// It is encoding: { OID sha256WithRSAEncryption PARAMETERS NULL }
+const uint8 kSignatureAlgorithm[15] = {0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x0b, 0x05, 0x00};
+
+const char kBlockSizeKey[] = "block_size";
+const char kContentHashesKey[] = "content_hashes";
+const char kFilesKey[] = "files";
+const char kFormatKey[] = "format";
+const char kHashBlockSizeKey[] = "hash_block_size";
+const char kHeaderKidKey[] = "header.kid";
+const char kItemIdKey[] = "item_id";
+const char kItemVersionKey[] = "item_version";
+const char kPathKey[] = "path";
+const char kPayloadKey[] = "payload";
+const char kProtectedKey[] = "protected";
+const char kRootHashKey[] = "root_hash";
+const char kSignatureKey[] = "signature";
+const char kSignaturesKey[] = "signatures";
+const char kTreeHash[] = "treehash";
+const char kWebstoreKId[] = "webstore";
+
+// This function fixes up a string in base64url encoding to be in standard
+// base64.
+//
+// The JSON signing spec we're following uses "base64url" encoding (RFC 4648
+// section 5 without padding). The slight differences from regular base64
+// encoding are:
+// 1. uses '_' instead of '/'
+// 2. uses '-' instead of '+'
+// 3. omits trailing '=' padding
+bool FixupBase64Encoding(std::string* input) {
+ for (std::string::iterator i = input->begin(); i != input->end(); ++i) {
+ if (*i == '-')
+ *i = '+';
+ else if (*i == '_')
+ *i = '/';
+ }
+ switch (input->size() % 4) {
+ case 0:
+ break;
+ case 2:
+ input->append("==");
+ break;
+ case 3:
+ input->append("=");
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace extensions {
+
+VerifiedContents::VerifiedContents(const uint8* public_key, int public_key_size)
+ : public_key_(public_key),
+ public_key_size_(public_key_size),
+ valid_signature_(false), // Guilty until proven innocent.
+ block_size_(0) {
+}
+
+VerifiedContents::~VerifiedContents() {
+}
+
+// The format of the payload json is:
+// {
+// "content_hashes": [
+// {
+// "block_size": 4096,
+// "hash_block_size": 4096,
+// "format": "treehash",
+// "files": [
+// {
+// "path": "foo/bar",
+// "root_hash": "<hex encoded bytes>"
+// },
+// ...
+// ]
+// }
+// ]
+// }
+bool VerifiedContents::InitFrom(const base::FilePath& path,
+ bool ignore_invalid_signature) {
+ std::string payload;
+ if (!GetPayload(path, &payload, ignore_invalid_signature))
+ return false;
+
+ scoped_ptr<base::Value> value(base::JSONReader::Read(payload));
+ if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY))
+ return false;
+ DictionaryValue* dictionary = static_cast<DictionaryValue*>(value.get());
+
+ std::string item_id;
+ if (!dictionary->GetString(kItemIdKey, &item_id) ||
+ !Extension::IdIsValid(item_id))
+ return false;
+ extension_id_ = item_id;
+
+ std::string version_string;
+ if (!dictionary->GetString(kItemVersionKey, &version_string))
+ return false;
+ version_ = base::Version(version_string);
+ if (!version_.IsValid())
+ return false;
+
+ ListValue* hashes_list = NULL;
+ if (!dictionary->GetList(kContentHashesKey, &hashes_list))
+ return false;
+
+ for (size_t i = 0; i < hashes_list->GetSize(); i++) {
+ DictionaryValue* hashes = NULL;
+ if (!hashes_list->GetDictionary(i, &hashes))
+ return false;
+ std::string format;
+ if (!hashes->GetString(kFormatKey, &format) || format != kTreeHash)
+ continue;
+
+ int block_size = 0;
+ int hash_block_size = 0;
+ if (!hashes->GetInteger(kBlockSizeKey, &block_size) ||
+ !hashes->GetInteger(kHashBlockSizeKey, &hash_block_size))
+ return false;
+ block_size_ = block_size;
+
+ // We don't support using a different block_size and hash_block_size at
+ // the moment.
+ if (block_size_ != hash_block_size)
+ return false;
+
+ ListValue* files = NULL;
+ if (!hashes->GetList(kFilesKey, &files))
+ return false;
+
+ for (size_t j = 0; j < files->GetSize(); j++) {
+ DictionaryValue* data = NULL;
+ if (!files->GetDictionary(j, &data))
+ return false;
+ std::string file_path_string;
+ std::string encoded_root_hash;
+ std::vector<uint8> root_hash;
+ if (!data->GetString(kPathKey, &file_path_string) ||
+ !base::IsStringUTF8(file_path_string) ||
+ !data->GetString(kRootHashKey, &encoded_root_hash) ||
+ !base::HexStringToBytes(encoded_root_hash, &root_hash))
+ return false;
+ base::FilePath file_path =
+ base::FilePath::FromUTF8Unsafe(file_path_string);
+ root_hashes_[file_path] = std::string();
+ root_hashes_[file_path].append(root_hash.begin(), root_hash.end());
+ }
+
+ break;
+ }
+ return true;
+}
+
+const std::string* VerifiedContents::GetTreeHashRoot(
+ const base::FilePath& relative_path) {
+ std::map<base::FilePath, std::string>::const_iterator i =
+ root_hashes_.find(relative_path);
+ if (i == root_hashes_.end())
+ return NULL;
+ return &i->second;
+}
+
+// We're loosely following the "JSON Web Signature" draft spec for signing
+// a JSON payload:
+//
+// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26
+//
+// The idea is that you have some JSON that you want to sign, so you
+// base64-encode that and put it as the "payload" field in a containing
+// dictionary. There might be signatures of it done with multiple
+// alrogirhtms/parameters, so the payload is followed by a list of one or more
+// signature sections. Each signature section specifies the
+// algorithm/parameters in a JSON object which is base64url encoded into one
+// string and put into a "protected" field in the signature. Then the encoded
+// "payload" and "protected" strings are concatenated with a "." in between
+// them and those bytes are signed and the resulting signature is base64url
+// encoded and placed in the "signature" field. E.g.
+// {
+// "payload": "<base64url encoded JSON to sign>",
+// "signatures": [
+// {
+// "protected": "<base64url encoded JSON with algorithm/parameters>",
+// "header": {
+// <object with metadata about this signature, eg a key identifier>
+// }
+// "signature":
+// "<base64url encoded signature done over payload || . || protected>"
+// },
+// ... <zero or more additional signatures> ...
+// ]
+// }
+//
+// There might be both a signature generated with a webstore private key and a
+// signature generated with the extension's private key - for now we only
+// verify the webstore one (since the id is in the payload, so we can trust
+// that it is for a given extension), but in the future we may validate using
+// the extension's key too (eg for non-webstore hosted extensions such as
+// enterprise installs).
+bool VerifiedContents::GetPayload(const base::FilePath& path,
+ std::string* payload,
+ bool ignore_invalid_signature) {
+ std::string contents;
+ if (!base::ReadFileToString(path, &contents))
+ return false;
+ scoped_ptr<base::Value> value(base::JSONReader::Read(contents));
+ if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY))
+ return false;
+ DictionaryValue* dictionary = static_cast<DictionaryValue*>(value.get());
+
+ ListValue* signatures = NULL;
+ if (!dictionary->GetList(kSignaturesKey, &signatures))
+ return false;
+
+ std::string protected_value;
+ std::string decoded_signature;
+ for (size_t i = 0; i < signatures->GetSize(); i++) {
+ DictionaryValue* signature_dict = NULL;
+ if (!signatures->GetDictionary(i, &signature_dict))
+ return false;
+ std::string kid;
+ if (!signature_dict->GetString(kHeaderKidKey, &kid))
+ continue;
+ if (kid == std::string(kWebstoreKId)) {
+ std::string encoded_signature;
+ if (!signature_dict->GetString(kProtectedKey, &protected_value) ||
+ !signature_dict->GetString(kSignatureKey, &encoded_signature) ||
+ !FixupBase64Encoding(&encoded_signature) ||
+ !base::Base64Decode(encoded_signature, &decoded_signature))
+ return false;
+ break;
+ }
+ }
+ std::string encoded_payload;
+ if (!dictionary->GetString(kPayloadKey, &encoded_payload))
+ return false;
+
+ valid_signature_ =
+ VerifySignature(protected_value, encoded_payload, decoded_signature);
+ if (!valid_signature_ && !ignore_invalid_signature)
+ return false;
+
+ if (!FixupBase64Encoding(&encoded_payload) ||
+ !base::Base64Decode(encoded_payload, payload))
+ return false;
+
+ return true;
+}
+
+bool VerifiedContents::VerifySignature(const std::string& protected_value,
+ const std::string& payload,
+ const std::string& signature_bytes) {
+ crypto::SignatureVerifier signature_verifier;
+ if (!signature_verifier.VerifyInit(
+ kSignatureAlgorithm,
+ sizeof(kSignatureAlgorithm),
+ reinterpret_cast<const uint8*>(signature_bytes.data()),
+ signature_bytes.size(),
+ public_key_,
+ public_key_size_)) {
+ VLOG(1) << "Could not verify signature - VerifyInit failure";
+ return false;
+ }
+
+ signature_verifier.VerifyUpdate(
+ reinterpret_cast<const uint8*>(protected_value.data()),
+ protected_value.size());
+
+ std::string dot(".");
+ signature_verifier.VerifyUpdate(reinterpret_cast<const uint8*>(dot.data()),
+ dot.size());
+
+ signature_verifier.VerifyUpdate(
+ reinterpret_cast<const uint8*>(payload.data()), payload.size());
+
+ if (!signature_verifier.VerifyFinal()) {
+ VLOG(1) << "Could not verify signature - VerifyFinal failure";
+ return false;
+ }
+ return true;
+}
+
+} // namespace extensions
« no previous file with comments | « extensions/browser/verified_contents.h ('k') | extensions/browser/verified_contents_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698