Index: components/gcm_driver/crypto/encryption_header_parsers.cc |
diff --git a/components/gcm_driver/crypto/encryption_header_parsers.cc b/components/gcm_driver/crypto/encryption_header_parsers.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7ab7646dcc31c527a53010792c67dbe9fed22801 |
--- /dev/null |
+++ b/components/gcm_driver/crypto/encryption_header_parsers.cc |
@@ -0,0 +1,193 @@ |
+// Copyright 2015 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 "components/gcm_driver/crypto/encryption_header_parsers.h" |
+ |
+#include <map> |
+#include <string> |
+#include <vector> |
+ |
+#include "base/base64.h" |
+#include "base/logging.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "base/strings/string_tokenizer.h" |
+#include "base/strings/string_util.h" |
+#include "net/http/http_util.h" |
+ |
+namespace gcm { |
+ |
+namespace { |
+ |
+const char kKeyIdName[] = "keyid"; |
+const char kSaltName[] = "salt"; |
+const char kRecordSizeName[] = "rs"; |
+const char kKeyName[] = "key"; |
+const char kDiffieHellmanName[] = "dh"; |
+ |
+using NameValueMap = std::map<std::string, std::string>; |
+using NameValueMapVector = std::vector<NameValueMap>; |
+ |
+// Parses |input| as a header value containing multiple name-value-lists. |
+bool ParseMultipleNameValueListsHeader(const std::string& input, |
+ NameValueMapVector* output) { |
+ base::StringTokenizer tokenizer(input, ","); |
+ tokenizer.set_quote_chars("'\""); |
+ |
+ while (tokenizer.GetNext()) { |
+ NameValueMap name_value_map; |
+ |
+ net::HttpUtil::NameValuePairsIterator parser(tokenizer.token_begin(), |
johnme
2015/07/21 14:34:43
I notice that NameValuePairsIterator allows arbitr
Peter Beverloo
2015/07/21 17:51:30
I'd like to defer to Ryan for final judgement here
|
+ tokenizer.token_end(), ';'); |
+ if (!parser.valid()) |
johnme
2015/07/21 14:34:43
It's not actually possible for valid to be false b
Peter Beverloo
2015/07/21 17:51:30
Done.
|
+ return false; |
johnme
2015/07/21 14:34:43
It might also make sense to call output->clear() b
Peter Beverloo
2015/07/21 17:51:30
Done.
|
+ |
+ while (parser.GetNext()) |
+ name_value_map[parser.name()] = parser.value(); |
+ |
+ output->push_back(name_value_map); |
+ } |
+ |
+ return true; |
+} |
+ |
+// TODO(peter): Generalize a URL-safe base64 implementation. |
johnme
2015/07/21 14:34:43
Please link to https://tools.ietf.org/html/rfc4648
Peter Beverloo
2015/07/21 17:51:30
Done.
|
+bool Base64DecodeUrlSafe(const std::string& input, std::string* output) { |
+ if (input.find_first_of("+/=") != std::string::npos) |
johnme
2015/07/21 14:34:43
Why do you disallow '='? Whilst padding is technic
Peter Beverloo
2015/07/21 17:51:30
Done.
|
+ return false; |
+ |
+ // Add padding. |
+ size_t padded_size = (input.size() + 3) - (input.size() + 3) % 4; |
+ std::string padded_input(input); |
+ padded_input.resize(padded_size, '='); |
+ |
+ // Convert to standard base64 alphabet. |
+ base::ReplaceChars(padded_input, "-", "+", &padded_input); |
+ base::ReplaceChars(padded_input, "_", "/", &padded_input); |
+ |
+ return base::Base64Decode(padded_input, output); |
+} |
+ |
+// Parses the "salt" field of the Encryption header. Must be a base64 URL |
johnme
2015/07/21 14:34:43
Nit: "base64url" for consistency with the naming i
Peter Beverloo
2015/07/21 17:51:30
Done.
|
+// encoded string that decodes to a string exactly 16 bytes in length. |
+bool ParseSalt(const std::string& value, std::string* output) { |
+ std::string decoded_value; |
+ if (!Base64DecodeUrlSafe(value, &decoded_value)) |
+ return false; |
+ |
+ if (decoded_value.size() != 16) |
+ return false; |
+ |
+ output->swap(decoded_value); |
+ return true; |
+} |
+ |
+// Parses the "rs" field of the Encryption header. When present, the value must |
+// be a positive decimal integer greater than 1. |
+bool ParseRecordSize(const std::string& value, int64_t* output) { |
+ int64_t decimal_value = 0; |
+ if (!base::StringToInt64(value, &decimal_value)) |
+ return false; |
+ |
+ if (decimal_value <= 1) |
+ return false; |
+ |
+ *output = decimal_value; |
+ return true; |
+} |
+ |
+// Parses the "key" field of the Encryption-Key header. When present, the value |
+// must be a base64 URL encoded string that decodes to a string at least 16 |
+// bytes in length. |
+bool ParseKey(const std::string& value, std::string* output) { |
+ std::string decoded_value; |
+ if (!Base64DecodeUrlSafe(value, &decoded_value)) |
+ return false; |
+ |
+ if (decoded_value.size() < 16) |
+ return false; |
+ |
+ output->swap(decoded_value); |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+bool ParseEncryptionHeader(const std::string& input, |
+ std::vector<EncryptionHeaderValue>* result) { |
+ DCHECK(result); |
+ |
+ NameValueMapVector parsed_input; |
+ if (!ParseMultipleNameValueListsHeader(input, &parsed_input)) |
+ return false; |
+ |
+ for (const auto& value_map : parsed_input) { |
+ EncryptionHeaderValue value; |
+ |
+ // Optional field: "keyid". May contain any string. |
+ const auto& keyid_iter = value_map.find(kKeyIdName); |
+ if (keyid_iter != value_map.end()) |
+ value.keyid = keyid_iter->second; |
+ |
+ // Required field: "salt". Must contain a base64 URL-encoded string that |
+ // decodes to a string that is exactly 16-bytes in length. |
+ const auto& salt_iter = value_map.find(kSaltName); |
+ if (salt_iter == value_map.end()) |
+ return false; |
johnme
2015/07/21 14:34:43
If value_map is completely empty, it should be ok
Peter Beverloo
2015/07/21 17:51:30
Done.
|
+ |
+ if (!ParseSalt(salt_iter->second, &value.salt)) |
+ return false; |
+ |
+ // Optional field: "rs". Must contain a positive decimal integer greater |
+ // than one when supplied. |
+ const auto& record_size_iter = value_map.find(kRecordSizeName); |
+ if (record_size_iter != value_map.end()) { |
+ if (!ParseRecordSize(record_size_iter->second, &value.rs)) |
+ return false; |
+ } |
+ |
+ result->push_back(value); |
+ } |
+ |
+ return true; |
+} |
+ |
+bool ParseEncryptionKeyHeader(const std::string& input, |
+ std::vector<EncryptionKeyHeaderValue>* result) { |
+ DCHECK(result); |
+ |
+ NameValueMapVector parsed_input; |
+ if (!ParseMultipleNameValueListsHeader(input, &parsed_input)) |
+ return false; |
+ |
+ for (const auto& value_map : parsed_input) { |
+ EncryptionKeyHeaderValue value; |
+ |
+ // Optional field: "keyid". May contain any string. |
+ const auto& keyid_iter = value_map.find(kKeyIdName); |
+ if (keyid_iter != value_map.end()) |
+ value.keyid = keyid_iter->second; |
+ |
+ // Optional field: "key". Must contain a base64 URL-encoded string with the |
+ // explicit encryption key. |
+ const auto& key_iter = value_map.find(kKeyName); |
+ if (key_iter != value_map.end()) { |
+ if (!ParseKey(key_iter->second, &value.key)) |
+ return false; |
+ } |
+ |
+ // Optional field: "dh". Must contain a base64 URL-encoded string with the |
+ // Diffie-Hellman share (either modp or elliptic curve). |
+ const auto& dh_iter = value_map.find(kDiffieHellmanName); |
+ if (dh_iter != value_map.end()) { |
+ if (!Base64DecodeUrlSafe(dh_iter->second, &value.dh)) |
+ return false; |
+ } |
+ |
+ result->push_back(value); |
+ } |
+ |
+ return true; |
+} |
+ |
+} // namespace gcm |