verified_contents.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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. #include "extensions/browser/verified_contents.h"
  5. #include <stddef.h>
  6. #include <algorithm>
  7. #include "base/base64url.h"
  8. #include "base/containers/contains.h"
  9. #include "base/files/file_util.h"
  10. #include "base/json/json_reader.h"
  11. #include "base/logging.h"
  12. #include "base/memory/ptr_util.h"
  13. #include "base/strings/string_util.h"
  14. #include "base/timer/elapsed_timer.h"
  15. #include "base/values.h"
  16. #include "build/build_config.h"
  17. #include "components/crx_file/id_util.h"
  18. #include "crypto/signature_verifier.h"
  19. #include "extensions/browser/content_verifier/content_verifier_utils.h"
  20. #include "extensions/browser/content_verifier/scoped_uma_recorder.h"
  21. #include "extensions/common/extension.h"
  22. namespace {
  23. const char kBlockSizeKey[] = "block_size";
  24. const char kContentHashesKey[] = "content_hashes";
  25. const char kDescriptionKey[] = "description";
  26. const char kFilesKey[] = "files";
  27. const char kFormatKey[] = "format";
  28. const char kHashBlockSizeKey[] = "hash_block_size";
  29. const char kHeaderKidKey[] = "header.kid";
  30. const char kItemIdKey[] = "item_id";
  31. const char kItemVersionKey[] = "item_version";
  32. const char kPathKey[] = "path";
  33. const char kPayloadKey[] = "payload";
  34. const char kProtectedKey[] = "protected";
  35. const char kRootHashKey[] = "root_hash";
  36. const char kSignatureKey[] = "signature";
  37. const char kSignaturesKey[] = "signatures";
  38. const char kSignedContentKey[] = "signed_content";
  39. const char kTreeHashPerFile[] = "treehash per file";
  40. const char kTreeHash[] = "treehash";
  41. const char kWebstoreKId[] = "webstore";
  42. // Helper function to iterate over a list of dictionaries, returning the
  43. // dictionary that has |key| -> |value| in it, if any, or null.
  44. const base::Value* FindDictionaryWithValue(const base::Value& list,
  45. const std::string& key,
  46. const std::string& value) {
  47. DCHECK(list.is_list());
  48. for (const base::Value& item : list.GetListDeprecated()) {
  49. if (!item.is_dict())
  50. continue;
  51. // Finds a path because the |key| may include '.'.
  52. const std::string* found_value = item.FindStringPath(key);
  53. if (found_value && *found_value == value)
  54. return &item;
  55. }
  56. return nullptr;
  57. }
  58. const char kUMAVerifiedContentsInitResult[] =
  59. "Extensions.ContentVerification.VerifiedContentsInitResult";
  60. const char kUMAVerifiedContentsInitTime[] =
  61. "Extensions.ContentVerification.VerifiedContentsInitTime";
  62. } // namespace
  63. namespace extensions {
  64. VerifiedContents::VerifiedContents(base::span<const uint8_t> public_key)
  65. : public_key_(public_key),
  66. valid_signature_(false), // Guilty until proven innocent.
  67. block_size_(0) {}
  68. VerifiedContents::~VerifiedContents() {
  69. }
  70. // The format of the payload json is:
  71. // {
  72. // "item_id": "<extension id>",
  73. // "item_version": "<extension version>",
  74. // "content_hashes": [
  75. // {
  76. // "block_size": 4096,
  77. // "hash_block_size": 4096,
  78. // "format": "treehash",
  79. // "files": [
  80. // {
  81. // "path": "foo/bar",
  82. // "root_hash": "<base64url encoded bytes>"
  83. // },
  84. // ...
  85. // ]
  86. // }
  87. // ]
  88. // }
  89. // static.
  90. std::unique_ptr<VerifiedContents> VerifiedContents::CreateFromFile(
  91. base::span<const uint8_t> public_key,
  92. const base::FilePath& path) {
  93. std::string contents;
  94. if (!base::ReadFileToString(path, &contents))
  95. return nullptr;
  96. return Create(public_key, contents);
  97. }
  98. std::unique_ptr<VerifiedContents> VerifiedContents::Create(
  99. base::span<const uint8_t> public_key,
  100. base::StringPiece contents) {
  101. ScopedUMARecorder<kUMAVerifiedContentsInitTime,
  102. kUMAVerifiedContentsInitResult>
  103. uma_recorder;
  104. // Note: VerifiedContents constructor is private.
  105. auto verified_contents = base::WrapUnique(new VerifiedContents(public_key));
  106. std::string payload;
  107. if (!verified_contents->GetPayload(contents, &payload))
  108. return nullptr;
  109. absl::optional<base::Value> dictionary = base::JSONReader::Read(payload);
  110. if (!dictionary || !dictionary->is_dict())
  111. return nullptr;
  112. const std::string* item_id = dictionary->FindStringKey(kItemIdKey);
  113. if (!item_id || !crx_file::id_util::IdIsValid(*item_id))
  114. return nullptr;
  115. verified_contents->extension_id_ = *item_id;
  116. const std::string* version_string =
  117. dictionary->FindStringKey(kItemVersionKey);
  118. if (!version_string)
  119. return nullptr;
  120. verified_contents->version_ = base::Version(*version_string);
  121. if (!verified_contents->version_.IsValid())
  122. return nullptr;
  123. const base::Value* hashes_list = dictionary->FindListKey(kContentHashesKey);
  124. if (!hashes_list)
  125. return nullptr;
  126. for (const base::Value& hashes : hashes_list->GetListDeprecated()) {
  127. if (!hashes.is_dict())
  128. return nullptr;
  129. const std::string* format = hashes.FindStringKey(kFormatKey);
  130. if (!format || *format != kTreeHash)
  131. continue;
  132. absl::optional<int> block_size = hashes.FindIntKey(kBlockSizeKey);
  133. absl::optional<int> hash_block_size = hashes.FindIntKey(kHashBlockSizeKey);
  134. if (!block_size || !hash_block_size)
  135. return nullptr;
  136. verified_contents->block_size_ = *block_size;
  137. // We don't support using a different block_size and hash_block_size at
  138. // the moment.
  139. if (verified_contents->block_size_ != *hash_block_size)
  140. return nullptr;
  141. const base::Value* files = hashes.FindListKey(kFilesKey);
  142. if (!files)
  143. return nullptr;
  144. for (const base::Value& data : files->GetListDeprecated()) {
  145. if (!data.is_dict())
  146. return nullptr;
  147. const std::string* file_path_string = data.FindStringKey(kPathKey);
  148. const std::string* encoded_root_hash = data.FindStringKey(kRootHashKey);
  149. std::string root_hash;
  150. if (!file_path_string || !encoded_root_hash ||
  151. !base::IsStringUTF8(*file_path_string) ||
  152. !base::Base64UrlDecode(*encoded_root_hash,
  153. base::Base64UrlDecodePolicy::IGNORE_PADDING,
  154. &root_hash)) {
  155. return nullptr;
  156. }
  157. content_verifier_utils::CanonicalRelativePath canonical_path =
  158. content_verifier_utils::CanonicalizeRelativePath(
  159. base::FilePath::FromUTF8Unsafe(*file_path_string));
  160. auto i = verified_contents->root_hashes_.insert(
  161. std::make_pair(canonical_path, std::string()));
  162. i->second.swap(root_hash);
  163. }
  164. break;
  165. }
  166. uma_recorder.RecordSuccess();
  167. return verified_contents;
  168. }
  169. bool VerifiedContents::HasTreeHashRoot(
  170. const base::FilePath& relative_path) const {
  171. return base::Contains(
  172. root_hashes_,
  173. content_verifier_utils::CanonicalizeRelativePath(relative_path));
  174. }
  175. bool VerifiedContents::TreeHashRootEquals(const base::FilePath& relative_path,
  176. const std::string& expected) const {
  177. return TreeHashRootEqualsForCanonicalPath(
  178. content_verifier_utils::CanonicalizeRelativePath(relative_path),
  179. expected);
  180. }
  181. // We're loosely following the "JSON Web Signature" draft spec for signing
  182. // a JSON payload:
  183. //
  184. // http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26
  185. //
  186. // The idea is that you have some JSON that you want to sign, so you
  187. // base64-encode that and put it as the "payload" field in a containing
  188. // dictionary. There might be signatures of it done with multiple
  189. // algorithms/parameters, so the payload is followed by a list of one or more
  190. // signature sections. Each signature section specifies the
  191. // algorithm/parameters in a JSON object which is base64url encoded into one
  192. // string and put into a "protected" field in the signature. Then the encoded
  193. // "payload" and "protected" strings are concatenated with a "." in between
  194. // them and those bytes are signed and the resulting signature is base64url
  195. // encoded and placed in the "signature" field. To allow for extensibility, we
  196. // wrap this, so we can include additional kinds of payloads in the future. E.g.
  197. // [
  198. // {
  199. // "description": "treehash per file",
  200. // "signed_content": {
  201. // "payload": "<base64url encoded JSON to sign>",
  202. // "signatures": [
  203. // {
  204. // "protected": "<base64url encoded JSON with algorithm/parameters>",
  205. // "header": {
  206. // <object with metadata about this signature, eg a key identifier>
  207. // }
  208. // "signature":
  209. // "<base64url encoded signature over payload || . || protected>"
  210. // },
  211. // ... <zero or more additional signatures> ...
  212. // ]
  213. // }
  214. // }
  215. // ]
  216. // There might be both a signature generated with a webstore private key and a
  217. // signature generated with the extension's private key - for now we only
  218. // verify the webstore one (since the id is in the payload, so we can trust
  219. // that it is for a given extension), but in the future we may validate using
  220. // the extension's key too (eg for non-webstore hosted extensions such as
  221. // enterprise installs).
  222. bool VerifiedContents::GetPayload(base::StringPiece contents,
  223. std::string* payload) {
  224. absl::optional<base::Value> top_list = base::JSONReader::Read(contents);
  225. if (!top_list || !top_list->is_list())
  226. return false;
  227. // Find the "treehash per file" signed content, e.g.
  228. // [
  229. // {
  230. // "description": "treehash per file",
  231. // "signed_content": {
  232. // "signatures": [ ... ],
  233. // "payload": "..."
  234. // }
  235. // }
  236. // ]
  237. const base::Value* dictionary =
  238. FindDictionaryWithValue(*top_list, kDescriptionKey, kTreeHashPerFile);
  239. if (!dictionary)
  240. return false;
  241. const base::Value* signed_content =
  242. dictionary->FindDictKey(kSignedContentKey);
  243. if (!signed_content)
  244. return false;
  245. const base::Value* signatures = signed_content->FindListKey(kSignaturesKey);
  246. if (!signatures)
  247. return false;
  248. const base::Value* signature_dict =
  249. FindDictionaryWithValue(*signatures, kHeaderKidKey, kWebstoreKId);
  250. if (!signature_dict)
  251. return false;
  252. const std::string* protected_value =
  253. signature_dict->FindStringKey(kProtectedKey);
  254. const std::string* encoded_signature =
  255. signature_dict->FindStringKey(kSignatureKey);
  256. std::string decoded_signature;
  257. if (!protected_value || !encoded_signature ||
  258. !base::Base64UrlDecode(*encoded_signature,
  259. base::Base64UrlDecodePolicy::IGNORE_PADDING,
  260. &decoded_signature))
  261. return false;
  262. const std::string* encoded_payload =
  263. signed_content->FindStringKey(kPayloadKey);
  264. if (!encoded_payload)
  265. return false;
  266. valid_signature_ =
  267. VerifySignature(*protected_value, *encoded_payload, decoded_signature);
  268. if (!valid_signature_)
  269. return false;
  270. if (!base::Base64UrlDecode(*encoded_payload,
  271. base::Base64UrlDecodePolicy::IGNORE_PADDING,
  272. payload))
  273. return false;
  274. return true;
  275. }
  276. bool VerifiedContents::VerifySignature(const std::string& protected_value,
  277. const std::string& payload,
  278. const std::string& signature_bytes) {
  279. crypto::SignatureVerifier signature_verifier;
  280. if (!signature_verifier.VerifyInit(
  281. crypto::SignatureVerifier::RSA_PKCS1_SHA256,
  282. base::as_bytes(base::make_span(signature_bytes)), public_key_)) {
  283. VLOG(1) << "Could not verify signature - VerifyInit failure";
  284. return false;
  285. }
  286. signature_verifier.VerifyUpdate(
  287. base::as_bytes(base::make_span(protected_value)));
  288. std::string dot(".");
  289. signature_verifier.VerifyUpdate(base::as_bytes(base::make_span(dot)));
  290. signature_verifier.VerifyUpdate(base::as_bytes(base::make_span(payload)));
  291. if (!signature_verifier.VerifyFinal()) {
  292. VLOG(1) << "Could not verify signature - VerifyFinal failure";
  293. return false;
  294. }
  295. return true;
  296. }
  297. bool VerifiedContents::TreeHashRootEqualsForCanonicalPath(
  298. const content_verifier_utils::CanonicalRelativePath&
  299. canonical_relative_path,
  300. const std::string& expected) const {
  301. std::pair<RootHashes::const_iterator, RootHashes::const_iterator> hashes =
  302. root_hashes_.equal_range(canonical_relative_path);
  303. for (auto iter = hashes.first; iter != hashes.second; ++iter) {
  304. if (expected == iter->second)
  305. return true;
  306. }
  307. return false;
  308. }
  309. } // namespace extensions