Chromium Code Reviews| 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 |