Commit 1fafce25 authored by David Van Cleve's avatar David Van Cleve Committed by Commit Bot

Add a Trust Tokens request signing helper.

This CL implements the request signing operation of the Trust Tokens
protocol by adding a signing helper to //services/network. Request
signing involves the following steps:
1. Retrieve a Signed Redemption Record (SRR) from storage and attach
it as a request header.
2. Optionally, add a timestamp header.
3. Construct a canonical representation of the request, including a
collection of the request headers specified by the caller, and compute a
signature over this canonical representation using a stored public key
associated with the SRR.
4. Attach this signature---but _not_ the request's canonical
representation, which server-side consumers will be able to
reconstruct---as a request header.

The Trust Tokens design doc [*] contains the normative description
of how to construct this canonical signing data.

[*]: https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#heading=h.6a92f2gfl9le

Bug: 1042962
Change-Id: I0acf0a7ba29d193013411db911f612d694667a17
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2050756
Commit-Queue: David Van Cleve <davidvc@chromium.org>
Reviewed-by: default avatarChris Palmer <palmer@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Reviewed-by: default avatarMatt Menke <mmenke@chromium.org>
Reviewed-by: default avatarCharlie Harrison <csharrison@chromium.org>
Cr-Commit-Position: refs/heads/master@{#748051}
parent d2c5a96b
...@@ -26,7 +26,11 @@ source_set("trust_tokens") { ...@@ -26,7 +26,11 @@ source_set("trust_tokens") {
"trust_token_operation_status.h", "trust_token_operation_status.h",
"trust_token_parameterization.h", "trust_token_parameterization.h",
"trust_token_persister.h", "trust_token_persister.h",
"trust_token_request_canonicalizer.cc",
"trust_token_request_canonicalizer.h",
"trust_token_request_helper.h", "trust_token_request_helper.h",
"trust_token_request_signing_helper.cc",
"trust_token_request_signing_helper.h",
"trust_token_store.cc", "trust_token_store.cc",
"trust_token_store.h", "trust_token_store.h",
"types.cc", "types.cc",
...@@ -36,6 +40,7 @@ source_set("trust_tokens") { ...@@ -36,6 +40,7 @@ source_set("trust_tokens") {
deps = [ deps = [
":storage_proto", ":storage_proto",
"//base", "//base",
"//components/cbor",
"//components/sqlite_proto", "//components/sqlite_proto",
"//services/network/public/cpp", "//services/network/public/cpp",
"//services/network/public/mojom", "//services/network/public/mojom",
...@@ -74,6 +79,8 @@ source_set("tests") { ...@@ -74,6 +79,8 @@ source_set("tests") {
"trust_token_database_owner_unittest.cc", "trust_token_database_owner_unittest.cc",
"trust_token_key_commitment_controller_unittest.cc", "trust_token_key_commitment_controller_unittest.cc",
"trust_token_persister_unittest.cc", "trust_token_persister_unittest.cc",
"trust_token_request_canonicalizer_unittest.cc",
"trust_token_request_signing_helper_unittest.cc",
"trust_token_store_unittest.cc", "trust_token_store_unittest.cc",
"types_unittest.cc", "types_unittest.cc",
] ]
...@@ -84,6 +91,7 @@ source_set("tests") { ...@@ -84,6 +91,7 @@ source_set("tests") {
":trust_tokens", ":trust_tokens",
"//base", "//base",
"//base/test:test_support", "//base/test:test_support",
"//components/cbor",
"//components/sqlite_proto", "//components/sqlite_proto",
"//net", "//net",
"//net:test_support", "//net:test_support",
......
include_rules = [ include_rules = [
"+components/cbor",
"+components/sqlite_proto", "+components/sqlite_proto",
"+third_party/protobuf/src/google/protobuf", "+third_party/protobuf/src/google/protobuf",
"+sql", "+sql",
......
...@@ -32,6 +32,11 @@ constexpr char kTrustTokensRequestHeaderSecSignature[] = "Sec-Signature"; ...@@ -32,6 +32,11 @@ constexpr char kTrustTokensRequestHeaderSecSignature[] = "Sec-Signature";
constexpr char kTrustTokensRequestHeaderSecSignedRedemptionRecord[] = constexpr char kTrustTokensRequestHeaderSecSignedRedemptionRecord[] =
"Sec-Signed-Redemption-Record"; "Sec-Signed-Redemption-Record";
// As a request header during the request signing operation, provides the list
// of headers included in the signing data's canonical request data. An absent
// header denotes an empty list.
constexpr char kTrustTokensRequestHeaderSignedHeaders[] = "Signed-Headers";
} // namespace network } // namespace network
#endif // SERVICES_NETWORK_TRUST_TOKENS_TRUST_TOKEN_HTTP_HEADERS_H_ #endif // SERVICES_NETWORK_TRUST_TOKENS_TRUST_TOKEN_HTTP_HEADERS_H_
// Copyright 2020 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 "services/network/trust_tokens/trust_token_request_canonicalizer.h"
#include <string>
#include "base/strings/string_piece.h"
#include "components/cbor/values.h"
#include "components/cbor/writer.h"
#include "services/network/public/mojom/trust_tokens.mojom-shared.h"
#include "services/network/trust_tokens/trust_token_http_headers.h"
#include "services/network/trust_tokens/trust_token_request_signing_helper.h"
namespace network {
base::Optional<std::vector<uint8_t>>
TrustTokenRequestCanonicalizer::Canonicalize(
net::URLRequest* request,
base::StringPiece public_key,
mojom::TrustTokenSignRequestData sign_request_data) const {
DCHECK(sign_request_data == mojom::TrustTokenSignRequestData::kInclude ||
sign_request_data == mojom::TrustTokenSignRequestData::kHeadersOnly);
// It seems like there's no conceivable way in which keys could be empty
// during normal use, so reject in this case as a common-sense safety measure.
if (public_key.empty())
return base::nullopt;
cbor::Value::MapValue canonicalized_request;
// Here and below, the lines beginning with numbers are a reproduction of the
// normative pseudocode form the design doc.
// 1. If sign-request-data is 'include', add 'url': <request_url> to the
// structure.
// 1a. The key and value are both of CBOR type “text string”.
if (sign_request_data == mojom::TrustTokenSignRequestData::kInclude) {
canonicalized_request.emplace(
TrustTokenRequestSigningHelper::kCanonicalizedRequestDataUrlKey,
request->url().spec());
}
// 2. If sign-request-data is 'include' or 'headers-only', for each value
// header_name in the Signed-Headers request header, if the request has a
// header with a name that is a case-insensitive match of header_name, add
// <lowercased(header_name)>: <header value> to the map.
// - Each key and value are of CBOR type “text string”.
std::vector<std::string> headers_to_add;
std::string signed_headers_header;
if (request->extra_request_headers().GetHeader(
kTrustTokensRequestHeaderSignedHeaders, &signed_headers_header)) {
base::Optional<std::vector<std::string>> maybe_headers_to_add =
internal::ParseTrustTokenSignedHeadersHeader(signed_headers_header);
if (!maybe_headers_to_add)
return base::nullopt;
headers_to_add.swap(*maybe_headers_to_add);
}
for (const std::string& header_name : headers_to_add) {
std::string header_value;
// GetHeader matches case-insensitive names.
if (request->extra_request_headers().GetHeader(header_name,
&header_value)) {
canonicalized_request.emplace(base::ToLowerASCII(header_name),
header_value);
}
}
// 3. Add 'public-key': <pk> to the map
// - The key is of CBOR type “text string”; the value is of CBOR type “byte
// string”.
canonicalized_request.emplace(
std::piecewise_construct,
std::forward_as_tuple(TrustTokenRequestSigningHelper::
kCanonicalizedRequestDataPublicKeyKey),
std::forward_as_tuple(public_key, cbor::Value::Type::BYTE_STRING));
return cbor::Writer::Write(cbor::Value(std::move(canonicalized_request)));
}
} // namespace network
// Copyright 2020 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.
#ifndef SERVICES_NETWORK_TRUST_TOKENS_TRUST_TOKEN_REQUEST_CANONICALIZER_H_
#define SERVICES_NETWORK_TRUST_TOKENS_TRUST_TOKEN_REQUEST_CANONICALIZER_H_
#include <vector>
#include "base/optional.h"
#include "base/strings/string_piece_forward.h"
#include "net/url_request/url_request.h"
#include "services/network/public/mojom/trust_tokens.mojom-shared.h"
namespace network {
// A TrustTokenRequestCanonicalizer turns a (URLRequest, public key) pair into
// the corresponding "canonical request data," which is a serialized CBOR
// structure comprising the public key and a collection of request data.
//
// Constructing this is a step in the Trust Tokens protocol's request signing
// operation. Exactly what request data is included alongside the public key
// depends on the parameterization of the operation, but it will always include
// a (potentially empty) caller-specified collection of request headers chosen
// from the TrustTokenRequestSigningHelper::kSignableRequestHeaders allowlist.
//
// The normative pseudocode for this operation currently lives in the Trust
// Tokens design doc's "Signature generation" section.
class TrustTokenRequestCanonicalizer {
public:
TrustTokenRequestCanonicalizer() = default;
virtual ~TrustTokenRequestCanonicalizer() = default;
TrustTokenRequestCanonicalizer(const TrustTokenRequestCanonicalizer&) =
delete;
TrustTokenRequestCanonicalizer& operator=(
const TrustTokenRequestCanonicalizer&) = delete;
// Attempts to canonicalize |request| according to the pseudocode in the
// design doc's "Signature generation" section, obtaining the headers to sign
// by inspecting |request|'s Signed-Headers header. |sign_request_data|'s
// value denotes whether the signing data should be more (kInclude) or less
// (kHeadersOnly) descriptive; refer to the normative pseudocode for details.
//
// |request| is passed as a mutable argument because, in the future, some
// forms of canonicalization may involve temporarily mutating |request|, in
// particular by reading its upload data.
//
// Returns nullopt if |request|'s Signed-Headers header is malformed (i.e.,
// not a valid Structured Headers list of atoms); if |public_key| is empty; or
// if there is an internal error during serialization.
//
// REQUIRES: |sign_request_data| is kInclude or kHeadersOnly.
virtual base::Optional<std::vector<uint8_t>> Canonicalize(
net::URLRequest* request,
base::StringPiece public_key,
mojom::TrustTokenSignRequestData sign_request_data) const;
};
} // namespace network
#endif // SERVICES_NETWORK_TRUST_TOKENS_TRUST_TOKEN_REQUEST_CANONICALIZER_H_
// Copyright 2020 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 "services/network/trust_tokens/trust_token_request_canonicalizer.h"
#include <memory>
#include "components/cbor/values.h"
#include "components/cbor/writer.h"
#include "net/url_request/url_request.h"
#include "services/network/public/mojom/trust_tokens.mojom-shared.h"
#include "services/network/trust_tokens/trust_token_http_headers.h"
#include "services/network/trust_tokens/trust_token_request_canonicalizer.h"
#include "services/network/trust_tokens/trust_token_request_signing_helper.h"
#include "services/network/trust_tokens/trust_token_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
// Adopt the Trust Tokens fixture to create URLRequests without boilerplate
using TrustTokenRequestCanonicalizerTest = TrustTokenRequestHelperTest;
// Check that an empty request with an empty public key (and no headers to sign)
// serializes correctly. Expected CBOR maps:
//
// SignRequestData::kHeadersOnly:
// { "public_key": b"key" }
//
// SignRequestData::kInclude:
// { "url": "", "public_key": b"key" }
TEST_F(TrustTokenRequestCanonicalizerTest, Empty) {
TrustTokenRequestCanonicalizer canonicalizer;
cbor::Value::MapValue expected_cbor;
expected_cbor[cbor::Value(
TrustTokenRequestSigningHelper::kCanonicalizedRequestDataPublicKeyKey)] =
cbor::Value("key", cbor::Value::Type::BYTE_STRING);
std::unique_ptr<net::URLRequest> request = MakeURLRequest("");
EXPECT_EQ(canonicalizer.Canonicalize(
request.get(), /*public_key=*/"key",
mojom::TrustTokenSignRequestData::kHeadersOnly),
cbor::Writer::Write(cbor::Value(expected_cbor)));
expected_cbor[cbor::Value(
TrustTokenRequestSigningHelper::kCanonicalizedRequestDataUrlKey)] =
cbor::Value("");
EXPECT_EQ(
canonicalizer.Canonicalize(request.get(), /*public_key=*/"key",
mojom::TrustTokenSignRequestData::kInclude),
cbor::Writer::Write(cbor::Value(expected_cbor)));
}
// Canonicalize a request with a nonempty public key and a nonempty URL.
//
// SignRequestData::kHeadersOnly:
// { "public_key": b"key" }
//
// SignRequestData::kInclude:
// { "url": "https://issuer.com/", "public_key": b"key" }
TEST_F(TrustTokenRequestCanonicalizerTest, Simple) {
TrustTokenRequestCanonicalizer canonicalizer;
cbor::Value::MapValue expected_cbor;
expected_cbor[cbor::Value(
TrustTokenRequestSigningHelper::kCanonicalizedRequestDataPublicKeyKey)] =
cbor::Value("key", cbor::Value::Type::BYTE_STRING);
std::unique_ptr<net::URLRequest> request =
MakeURLRequest("https://issuer.com/");
EXPECT_EQ(canonicalizer.Canonicalize(
request.get(), /*public_key=*/"key",
mojom::TrustTokenSignRequestData::kHeadersOnly),
cbor::Writer::Write(cbor::Value(expected_cbor)));
expected_cbor[cbor::Value(
TrustTokenRequestSigningHelper::kCanonicalizedRequestDataUrlKey)] =
cbor::Value("https://issuer.com/");
EXPECT_EQ(
canonicalizer.Canonicalize(request.get(), /*public_key=*/"key",
mojom::TrustTokenSignRequestData::kInclude),
cbor::Writer::Write(cbor::Value(expected_cbor)));
}
// Canonicalize a request with a nonempty public key, some signed headers, and a
// nonempty URL.
//
// Expected CBOR maps:
//
// SignRequestData::kHeadersOnly:
// { "public_key": b"key", "first_header": "first_header_value",
// "second_header": "second_header_value" }
//
// SignRequestData::kInclude:
// { "url": "https://issuer.com/", "public_key": b"key",
// "first_header": "first_header_value", "second_header":
// "second_header_value" }
TEST_F(TrustTokenRequestCanonicalizerTest, WithSignedHeaders) {
TrustTokenRequestCanonicalizer canonicalizer;
cbor::Value::MapValue expected_cbor;
expected_cbor[cbor::Value(
TrustTokenRequestSigningHelper::kCanonicalizedRequestDataPublicKeyKey)] =
cbor::Value("key", cbor::Value::Type::BYTE_STRING);
std::unique_ptr<net::URLRequest> request =
MakeURLRequest("https://issuer.com/");
// Capitalization should be normalized.
request->SetExtraRequestHeaderByName("First_HeadER", "first_header_value",
/*overwrite=*/true);
request->SetExtraRequestHeaderByName("second_header", "second_header_value",
/*overwrite=*/true);
request->SetExtraRequestHeaderByName(kTrustTokensRequestHeaderSignedHeaders,
" first_header , second_header ",
/*overwrite=*/true);
expected_cbor[cbor::Value("first_header")] =
cbor::Value("first_header_value");
expected_cbor[cbor::Value("second_header")] =
cbor::Value("second_header_value");
EXPECT_EQ(canonicalizer.Canonicalize(
request.get(), /*public_key=*/"key",
mojom::TrustTokenSignRequestData::kHeadersOnly),
cbor::Writer::Write(cbor::Value(expected_cbor)));
expected_cbor[cbor::Value(
TrustTokenRequestSigningHelper::kCanonicalizedRequestDataUrlKey)] =
cbor::Value("https://issuer.com/");
EXPECT_EQ(
canonicalizer.Canonicalize(request.get(), /*public_key=*/"key",
mojom::TrustTokenSignRequestData::kInclude),
cbor::Writer::Write(cbor::Value(expected_cbor)));
}
// Canonicalizing a request with a malformed Signed-Headers header should fail.
TEST_F(TrustTokenRequestCanonicalizerTest, RejectsMalformedSignedHeaders) {
TrustTokenRequestCanonicalizer canonicalizer;
std::unique_ptr<net::URLRequest> request =
MakeURLRequest("https://issuer.com/");
// Set the Signed-Headers header to something that is *not* the serialization
// of a Structured Headers token. (Tokens can't start with quotes.)
request->SetExtraRequestHeaderByName(kTrustTokensRequestHeaderSignedHeaders,
"\"", /*overwrite=*/true);
EXPECT_FALSE(canonicalizer.Canonicalize(
request.get(), /*public_key=*/"key",
mojom::TrustTokenSignRequestData::kHeadersOnly));
}
// Canonicalizing a request with an empty key should fail.
TEST_F(TrustTokenRequestCanonicalizerTest, RejectsEmptyKey) {
TrustTokenRequestCanonicalizer canonicalizer;
std::unique_ptr<net::URLRequest> request =
MakeURLRequest("https://issuer.com/");
EXPECT_FALSE(canonicalizer.Canonicalize(
request.get(), /*public_key=*/"",
mojom::TrustTokenSignRequestData::kHeadersOnly));
}
} // namespace network
...@@ -21,6 +21,7 @@ namespace network { ...@@ -21,6 +21,7 @@ namespace network {
// attaching cached redemption records). // attaching cached redemption records).
class TrustTokenRequestHelper { class TrustTokenRequestHelper {
public: public:
TrustTokenRequestHelper() = default;
virtual ~TrustTokenRequestHelper() = default; virtual ~TrustTokenRequestHelper() = default;
TrustTokenRequestHelper(const TrustTokenRequestHelper&) = delete; TrustTokenRequestHelper(const TrustTokenRequestHelper&) = delete;
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
namespace network { namespace network {
TrustTokenRequestHelperTest::TrustTokenRequestHelperTest() = default; TrustTokenRequestHelperTest::TrustTokenRequestHelperTest(
base::test::TaskEnvironment::TimeSource time_source)
: env_(time_source) {}
TrustTokenRequestHelperTest::~TrustTokenRequestHelperTest() = default; TrustTokenRequestHelperTest::~TrustTokenRequestHelperTest() = default;
std::unique_ptr<net::URLRequest> TrustTokenRequestHelperTest::MakeURLRequest( std::unique_ptr<net::URLRequest> TrustTokenRequestHelperTest::MakeURLRequest(
......
...@@ -27,7 +27,9 @@ namespace network { ...@@ -27,7 +27,9 @@ namespace network {
// constructing net::URLRequests. // constructing net::URLRequests.
class TrustTokenRequestHelperTest : public ::testing::Test { class TrustTokenRequestHelperTest : public ::testing::Test {
public: public:
TrustTokenRequestHelperTest(); explicit TrustTokenRequestHelperTest(
base::test::TaskEnvironment::TimeSource time_source =
base::test::TaskEnvironment::TimeSource::DEFAULT);
~TrustTokenRequestHelperTest() override; ~TrustTokenRequestHelperTest() override;
TrustTokenRequestHelperTest(const TrustTokenRequestHelperTest&) = delete; TrustTokenRequestHelperTest(const TrustTokenRequestHelperTest&) = delete;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment