blob: 5a9c66a4b3c3bd64de274f87946d74e883dad424 [file] [log] [blame]
// Copyright 2018 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 "content/browser/web_package/signed_exchange_header_parser.h"
#include <map>
#include "base/base64.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/web_package/signed_exchange_consts.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "crypto/sha2.h"
namespace content {
namespace {
// This covers the characters allowed in Numbers, Labels, and Binary Content.
constexpr char kTokenChars[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_+-*/";
struct ParameterisedLabel {
std::string label;
std::map<std::string, std::string> params;
};
// Parser for (a subset of) Structured Headers defined in [SH].
// [SH] https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-02
class StructuredHeaderParser {
public:
explicit StructuredHeaderParser(const base::StringPiece& str)
: input_(str), failed_(false) {}
bool ParsedSuccessfully() const { return !failed_ && input_.empty(); }
// Parses a List ([SH] 4.8) of Strings.
void ParseStringList(std::vector<std::string>* values) {
values->push_back(ReadString());
while (!failed_) {
SkipWhitespaces();
if (!ConsumeChar(','))
break;
SkipWhitespaces();
values->push_back(ReadString());
}
}
// Parses a List ([SH] 4.8) of Parameterised Labels.
void ParseParameterisedLabelList(std::vector<ParameterisedLabel>* values) {
values->push_back(ParameterisedLabel());
ParseParameterisedLabel(&values->back());
while (!failed_) {
SkipWhitespaces();
if (!ConsumeChar(','))
break;
SkipWhitespaces();
values->push_back(ParameterisedLabel());
ParseParameterisedLabel(&values->back());
}
}
// Parses a Parameterised Label ([SH] 4.4).
void ParseParameterisedLabel(ParameterisedLabel* out) {
std::string label = ReadToken();
if (label.empty()) {
DVLOG(1) << "ParseParameterisedLabel: Label expected, got '"
<< input_.front() << "'";
failed_ = true;
return;
}
out->label = label;
while (!failed_) {
SkipWhitespaces();
if (!ConsumeChar(';'))
break;
SkipWhitespaces();
std::string name = ReadToken();
if (name.empty()) {
DVLOG(1) << "ParseParameterisedLabel: Label expected, got '"
<< input_.front() << "'";
failed_ = true;
return;
}
std::string value;
if (ConsumeChar('='))
value = ReadItem();
if (!out->params.insert(std::make_pair(name, value)).second) {
DVLOG(1) << "ParseParameterisedLabel: duplicated parameter: " << name;
failed_ = true;
return;
}
}
}
private:
void SkipWhitespaces() {
input_ = base::TrimWhitespaceASCII(input_, base::TRIM_LEADING);
}
std::string ReadToken() {
size_t len = input_.find_first_not_of(kTokenChars);
if (len == base::StringPiece::npos)
len = input_.size();
std::string token(input_.substr(0, len));
input_.remove_prefix(len);
return token;
}
bool ConsumeChar(char expected) {
if (!input_.empty() && input_.front() == expected) {
input_.remove_prefix(1);
return true;
}
return false;
}
// [SH] 4.2. Strings
std::string ReadString() {
std::string s;
if (!ConsumeChar('"')) {
DVLOG(1) << "ReadString: '\"' expected, got '" << input_.front() << "'";
failed_ = true;
return s;
}
while (!ConsumeChar('"')) {
size_t len = input_.find_first_of("\"\\");
if (len == base::StringPiece::npos) {
DVLOG(1) << "ReadString: missing closing '\"'";
failed_ = true;
return s;
}
s.append(std::string(input_.substr(0, len)));
input_.remove_prefix(len);
if (ConsumeChar('\\')) {
if (input_.empty()) {
DVLOG(1) << "ReadString: backslash at string end";
failed_ = true;
return s;
}
s.push_back(input_.front());
input_.remove_prefix(1);
}
}
return s;
}
// [SH] 4.5. Binary Content
std::string ReadBinary() {
if (!ConsumeChar('*')) {
DVLOG(1) << "ReadBinary: '*' expected, got '" << input_.front() << "'";
failed_ = true;
return std::string();
}
std::string base64 = ReadToken();
// Binary Content does not have padding, so we have to add it.
base64.resize((base64.size() + 3) / 4 * 4, '=');
std::string binary;
if (!base::Base64Decode(base64, &binary)) {
DVLOG(1) << "ReadBinary: failed to decode base64: " << base64;
failed_ = true;
}
return binary;
}
// [SH] 4.6. Items
std::string ReadItem() {
if (input_.empty()) {
DVLOG(1) << "ReadItem: unexpected EOF";
failed_ = true;
return std::string();
}
switch (input_.front()) {
case '"':
return ReadString();
case '*':
return ReadBinary();
default: // label or number
return ReadToken();
}
}
base::StringPiece input_;
bool failed_;
};
} // namespace
// static
base::Optional<std::vector<SignedExchangeHeaderParser::Signature>>
SignedExchangeHeaderParser::ParseSignature(
base::StringPiece signature_str,
SignedExchangeDevToolsProxy* devtools_proxy) {
TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHeaderParser::ParseSignature");
StructuredHeaderParser parser(signature_str);
std::vector<ParameterisedLabel> values;
parser.ParseParameterisedLabelList(&values);
if (!parser.ParsedSuccessfully()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeHeaderParser::ParseSignature",
"Failed to parse signature header.");
return base::nullopt;
}
std::vector<Signature> signatures;
signatures.reserve(values.size());
for (auto& value : values) {
signatures.push_back(Signature());
Signature& sig = signatures.back();
sig.label = value.label;
sig.sig = value.params["sig"];
if (sig.sig.empty()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeHeaderParser::ParseSignature",
"'sig' parameter is not set,");
return base::nullopt;
}
sig.integrity = value.params["integrity"];
if (sig.integrity.empty()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeHeaderParser::ParseSignature",
"'integrity' parameter is not set.");
return base::nullopt;
}
sig.cert_url = GURL(value.params["certUrl"]);
if (!sig.cert_url.is_valid() || sig.cert_url.has_ref()) {
// TODO(https://crbug.com/819467) : When we will support "ed25519Key", the
// params may not have "certUrl".
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeHeaderParser::ParseSignature",
"'certUrl' parameter is not a valid URL.");
return base::nullopt;
}
const std::string cert_sha256_string = value.params[kCertSha256Key];
if (cert_sha256_string.size() != crypto::kSHA256Length) {
// TODO(https://crbug.com/819467) : When we will support "ed25519Key", the
// params may not have "certSha256".
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeHeaderParser::ParseSignature",
"'certSha256' parameter is not a SHA-256 digest.");
return base::nullopt;
}
net::SHA256HashValue cert_sha256;
memcpy(&cert_sha256.data, cert_sha256_string.data(), crypto::kSHA256Length);
sig.cert_sha256 = std::move(cert_sha256);
// TODO(https://crbug.com/819467): Support ed25519key.
// sig.ed25519_key = value.params["ed25519Key"];
sig.validity_url = GURL(value.params[kValidityUrlKey]);
if (!sig.validity_url.is_valid()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeHeaderParser::ParseSignature",
"'validityUrl' parameter is not a valid URL.");
return base::nullopt;
}
if (sig.validity_url.has_ref()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeHeaderParser::ParseSignature",
"'validityUrl' parameter can't have a fragment.");
return base::nullopt;
}
if (!base::StringToUint64(value.params[kDateKey], &sig.date)) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeHeaderParser::ParseSignature",
"'date' parameter is not a number.");
return base::nullopt;
}
if (!base::StringToUint64(value.params[kExpiresKey], &sig.expires)) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeHeaderParser::ParseSignature",
"'expires' parameter is not a number.");
return base::nullopt;
}
}
TRACE_EVENT_END0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHeaderParser::ParseSignature");
return signatures;
}
// static
bool SignedExchangeHeaderParser::GetVersionParamFromContentType(
base::StringPiece content_type,
base::Optional<std::string>* version_param) {
DCHECK(version_param);
StructuredHeaderParser parser(content_type);
ParameterisedLabel parameterised_label;
parser.ParseParameterisedLabel(&parameterised_label);
if (!parser.ParsedSuccessfully())
return false;
const auto it = parameterised_label.params.find("v");
if (it == parameterised_label.params.end()) {
*version_param = base::nullopt;
} else {
*version_param = it->second;
}
return true;
}
SignedExchangeHeaderParser::Signature::Signature() = default;
SignedExchangeHeaderParser::Signature::Signature(const Signature& other) =
default;
SignedExchangeHeaderParser::Signature::~Signature() = default;
} // namespace content