blob: 1932527b9fefac8c389f27f7aedd87e2d8de97e6 [file] [log] [blame]
// 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 "base/base64url.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "net/http/http_util.h"
namespace gcm {
namespace {
// The default record size in bytes, as defined in section two of
// https://tools.ietf.org/html/draft-thomson-http-encryption-02.
const uint64_t kDefaultRecordSizeBytes = 4096;
// Decodes the string between |begin| and |end| using base64url, and writes the
// decoded value to |*salt|. Returns whether the string could be decoded.
bool ValueToDecodedString(const std::string::const_iterator& begin,
const std::string::const_iterator& end,
std::string* salt) {
const base::StringPiece value(begin, end);
if (value.empty())
return false;
return base::Base64UrlDecode(
value, base::Base64UrlDecodePolicy::IGNORE_PADDING, salt);
}
// Parses the record size between |begin| and |end|, and writes the value to
// |*rs|. The value must be an unsigned, 64-bit integer greater than zero that
// does not start with a plus. Returns whether the record size was valid.
bool RecordSizeToInt(const std::string::const_iterator& begin,
const std::string::const_iterator& end,
uint64_t* rs) {
const base::StringPiece value(begin, end);
if (value.empty())
return false;
// Parsing the "rs" parameter uses stricter semantics than parsing rules for
// normal integers, in that we want to reject values such as "+5" for
// compatibility with UAs that use other number parsing mechanisms.
if (value[0] == '+')
return false;
if (!base::StringToUint64(value, rs))
return false;
// The record size MUST be greater than 1.
return *rs > 1;
}
// Parses the string between |input_begin| and |input_end| according to the
// extended ABNF syntax for the Encryption HTTP header, per the "parameter"
// rule from RFC 7231 (https://tools.ietf.org/html/rfc7231).
//
// encryption_params = [ parameter *( ";" parameter ) ]
//
// This implementation applies the parameters defined in section 3.1 of the
// HTTP encryption encoding document:
//
// https://tools.ietf.org/html/draft-thomson-http-encryption-02#section-3.1
//
// This means that the three supported parameters are:
//
// [ "keyid" "=" string ]
// [ ";" "salt" "=" base64url ]
// [ ";" "rs" "=" octet-count ]
bool ParseEncryptionHeaderValuesImpl(std::string::const_iterator input_begin,
std::string::const_iterator input_end,
EncryptionHeaderValues* values) {
net::HttpUtil::NameValuePairsIterator name_value_pairs(
input_begin, input_end, ';',
net::HttpUtil::NameValuePairsIterator::Values::REQUIRED,
net::HttpUtil::NameValuePairsIterator::Quotes::NOT_STRICT);
while (name_value_pairs.GetNext()) {
const base::StringPiece name(name_value_pairs.name_begin(),
name_value_pairs.name_end());
if (base::LowerCaseEqualsASCII(name, "keyid")) {
values->keyid.assign(name_value_pairs.value_begin(),
name_value_pairs.value_end());
} else if (base::LowerCaseEqualsASCII(name, "salt")) {
if (!ValueToDecodedString(name_value_pairs.value_begin(),
name_value_pairs.value_end(), &values->salt)) {
return false;
}
} else if (base::LowerCaseEqualsASCII(name, "rs")) {
if (!RecordSizeToInt(name_value_pairs.value_begin(),
name_value_pairs.value_end(), &values->rs)) {
return false;
}
} else {
// Silently ignore unknown directives for forward compatibility.
}
}
return name_value_pairs.valid();
}
// Parses the string between |input_begin| and |input_end| according to the
// extended ABNF syntax for the Crypto-Key HTTP header, per the "parameter" rule
// from RFC 7231 (https://tools.ietf.org/html/rfc7231).
//
// encryption_params = [ parameter *( ";" parameter ) ]
//
// This implementation applies the parameters defined in section 4 of the
// HTTP encryption encoding document:
//
//https://tools.ietf.org/html/draft-thomson-http-encryption-02#section-4
//
// This means that the three supported parameters are:
//
// [ "keyid" "=" string ]
// [ ";" "aesgcm128" "=" base64url ]
// [ ";" "dh" "=" base64url ]
bool ParseCryptoKeyHeaderValuesImpl(std::string::const_iterator input_begin,
std::string::const_iterator input_end,
CryptoKeyHeaderValues* values) {
net::HttpUtil::NameValuePairsIterator name_value_pairs(
input_begin, input_end, ';',
net::HttpUtil::NameValuePairsIterator::Values::REQUIRED,
net::HttpUtil::NameValuePairsIterator::Quotes::NOT_STRICT);
while (name_value_pairs.GetNext()) {
const base::StringPiece name(name_value_pairs.name_begin(),
name_value_pairs.name_end());
if (base::LowerCaseEqualsASCII(name, "keyid")) {
values->keyid.assign(name_value_pairs.value_begin(),
name_value_pairs.value_end());
} else if (base::LowerCaseEqualsASCII(name, "aesgcm128")) {
if (!ValueToDecodedString(name_value_pairs.value_begin(),
name_value_pairs.value_end(),
&values->aesgcm128)) {
return false;
}
} else if (base::LowerCaseEqualsASCII(name, "dh")) {
if (!ValueToDecodedString(name_value_pairs.value_begin(),
name_value_pairs.value_end(), &values->dh)) {
return false;
}
} else {
// Silently ignore unknown directives for forward compatibility.
}
}
return name_value_pairs.valid();
}
} // namespace
bool ParseEncryptionHeader(const std::string& input,
std::vector<EncryptionHeaderValues>* values) {
DCHECK(values);
std::vector<EncryptionHeaderValues> candidate_values;
net::HttpUtil::ValuesIterator value_iterator(input.begin(), input.end(), ',');
while (value_iterator.GetNext()) {
EncryptionHeaderValues candidate_value;
candidate_value.rs = kDefaultRecordSizeBytes;
if (!ParseEncryptionHeaderValuesImpl(value_iterator.value_begin(),
value_iterator.value_end(),
&candidate_value)) {
return false;
}
candidate_values.push_back(candidate_value);
}
values->swap(candidate_values);
return true;
}
bool ParseCryptoKeyHeader(const std::string& input,
std::vector<CryptoKeyHeaderValues>* values) {
DCHECK(values);
std::vector<CryptoKeyHeaderValues> candidate_values;
net::HttpUtil::ValuesIterator value_iterator(input.begin(), input.end(), ',');
while (value_iterator.GetNext()) {
CryptoKeyHeaderValues candidate_value;
if (!ParseCryptoKeyHeaderValuesImpl(value_iterator.value_begin(),
value_iterator.value_end(),
&candidate_value)) {
return false;
}
candidate_values.push_back(candidate_value);
}
values->swap(candidate_values);
return true;
}
} // namespace gcm