blob: 67b1f36a64e44eb87123f88b4200a4f5148e54c7 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/test/trust_token_request_handler.h"
#include <optional>
#include <string>
#include "base/base64.h"
#include "base/check.h"
#include "base/containers/span.h"
#include "base/functional/callback.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "components/cbor/reader.h"
#include "components/cbor/values.h"
#include "crypto/sha2.h"
#include "net/http/http_request_headers.h"
#include "net/http/structured_headers.h"
#include "services/network/public/cpp/trust_token_http_headers.h"
#include "services/network/trust_tokens/scoped_boringssl_bytes.h"
#include "services/network/trust_tokens/types.h"
#include "third_party/boringssl/src/include/openssl/curve25519.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/trust_token.h"
namespace network::test {
namespace {
struct IssuanceKeyPair {
// Token signing and verification keys:
std::vector<uint8_t> signing;
std::vector<uint8_t> verification;
// Default to a very long expiry time, but allow this to be overridden when
// specific tests want to do so.
base::Time expiry = base::Time::Max();
};
IssuanceKeyPair GenerateIssuanceKeyPair(int id) {
IssuanceKeyPair keys;
keys.signing.resize(TRUST_TOKEN_MAX_PRIVATE_KEY_SIZE);
keys.verification.resize(TRUST_TOKEN_MAX_PUBLIC_KEY_SIZE);
size_t signing_key_len, verification_key_len;
CHECK(TRUST_TOKEN_generate_key(
TRUST_TOKEN_experiment_v2_pmb(), keys.signing.data(), &signing_key_len,
keys.signing.size(), keys.verification.data(), &verification_key_len,
keys.verification.size(), id));
keys.signing.resize(signing_key_len);
keys.verification.resize(verification_key_len);
return keys;
}
// This convenience helper prevents forgetting whether the inequality is weak or
// strict.
bool HasKeyPairExpired(const IssuanceKeyPair& p) {
return p.expiry <= base::Time::Now();
}
} // namespace
TrustTokenRequestHandler::Options::Options() = default;
TrustTokenRequestHandler::Options::~Options() = default;
TrustTokenRequestHandler::Options::Options(const Options&) = default;
TrustTokenRequestHandler::Options& TrustTokenRequestHandler::Options::operator=(
const Options&) = default;
struct TrustTokenRequestHandler::Rep {
// The protocol version to use.
std::string protocol_version;
// The commitment ID to use.
int id;
// Issue at most this many tokens per issuance.
int batch_size;
std::vector<IssuanceKeyPair> issuance_keys;
// Whether to peremptorily reject issuance and redemption or whether to
// actually process the provided input.
ServerOperationOutcome issuance_outcome;
ServerOperationOutcome redemption_outcome;
// Creates a BoringSSL token issuer context suitable for issuance or
// redemption, using only the unexpired key pairs from |issuance_keys|.
bssl::UniquePtr<TRUST_TOKEN_ISSUER> CreateIssuerContextFromUnexpiredKeys()
const;
// This is a structured representation of the most recent input to
// RecordSignedRequest.
std::optional<TrustTokenSignedRequest> last_incoming_signed_request;
};
bssl::UniquePtr<TRUST_TOKEN_ISSUER>
TrustTokenRequestHandler::Rep::CreateIssuerContextFromUnexpiredKeys() const {
bssl::UniquePtr<TRUST_TOKEN_ISSUER> ret(
TRUST_TOKEN_ISSUER_new(TRUST_TOKEN_experiment_v2_pmb(), batch_size));
if (!ret) {
return nullptr;
}
for (const IssuanceKeyPair& key_pair : issuance_keys) {
if (HasKeyPairExpired(key_pair)) {
continue;
}
if (!TRUST_TOKEN_ISSUER_add_key(ret.get(), key_pair.signing.data(),
key_pair.signing.size())) {
return nullptr;
}
}
// Copying the comment from evp.h:
// The [Ed25519] RFC 8032 private key format is the 32-byte prefix of
// |ED25519_sign|'s 64-byte private key.
uint8_t public_key[32], private_key[64];
ED25519_keypair(public_key, private_key);
bssl::UniquePtr<EVP_PKEY> issuer_rr_key(EVP_PKEY_new_raw_private_key(
EVP_PKEY_ED25519, /*unused=*/nullptr, private_key,
/*len=*/32));
if (!issuer_rr_key) {
return nullptr;
}
if (!TRUST_TOKEN_ISSUER_set_srr_key(ret.get(), issuer_rr_key.get())) {
return nullptr;
}
return ret;
}
TrustTokenRequestHandler::TrustTokenRequestHandler(Options options) {
UpdateOptions(std::move(options));
}
TrustTokenRequestHandler::TrustTokenRequestHandler()
: TrustTokenRequestHandler(Options()) {}
TrustTokenRequestHandler::~TrustTokenRequestHandler() = default;
std::string TrustTokenRequestHandler::GetKeyCommitmentRecord() const {
base::AutoLock lock(mutex_);
base::Value::Dict dict;
const std::string protocol_string = internal::ProtocolVersionToString(
mojom::TrustTokenProtocolVersion::kTrustTokenV3Pmb);
dict.SetByDottedPath(protocol_string + ".protocol_version",
rep_->protocol_version);
dict.SetByDottedPath(protocol_string + ".id", rep_->id);
dict.SetByDottedPath(protocol_string + ".batchsize", rep_->batch_size);
for (size_t i = 0; i < rep_->issuance_keys.size(); ++i) {
dict.SetByDottedPath(
protocol_string + ".keys." + base::NumberToString(i) + ".Y",
base::Base64Encode(base::span(rep_->issuance_keys[i].verification)));
dict.SetByDottedPath(
protocol_string + ".keys." + base::NumberToString(i) + ".expiry",
base::NumberToString(
(rep_->issuance_keys[i].expiry - base::Time::UnixEpoch())
.InMicroseconds()));
}
// It's OK to be a bit crashy in exceptional failure cases because it
// indicates a serious coding error in this test-only code; we'd like to find
// this out sooner rather than later.
std::optional<std::string> ret = base::WriteJson(dict);
CHECK(ret);
return *ret;
}
std::optional<std::string> TrustTokenRequestHandler::Issue(
std::string_view issuance_request) {
base::AutoLock lock(mutex_);
if (rep_->issuance_outcome == ServerOperationOutcome::kUnconditionalFailure) {
return std::nullopt;
}
bssl::UniquePtr<TRUST_TOKEN_ISSUER> issuer_ctx =
rep_->CreateIssuerContextFromUnexpiredKeys();
std::string decoded_issuance_request;
if (!base::Base64Decode(issuance_request, &decoded_issuance_request)) {
return std::nullopt;
}
// TODO(davidvc): Perhaps make this configurable? Not a high priority, though.
constexpr uint8_t kPrivateMetadata = 0;
ScopedBoringsslBytes decoded_issuance_response;
size_t num_tokens_issued = 0;
bool ok = false;
for (size_t i = 0; i < rep_->issuance_keys.size(); ++i) {
if (HasKeyPairExpired(rep_->issuance_keys[i])) {
continue;
}
if (TRUST_TOKEN_ISSUER_issue(
issuer_ctx.get(),
&decoded_issuance_response.mutable_ptr()->AsEphemeralRawAddr(),
decoded_issuance_response.mutable_len(), &num_tokens_issued,
base::as_byte_span(decoded_issuance_request).data(),
decoded_issuance_request.size(),
/*public_metadata=*/static_cast<uint32_t>(i), kPrivateMetadata,
rep_->batch_size)) {
ok = true;
break;
}
}
if (!ok) {
return std::nullopt;
}
return base::Base64Encode(decoded_issuance_response.as_span());
}
std::optional<std::string> TrustTokenRequestHandler::Redeem(
std::string_view redemption_request) {
base::AutoLock lock(mutex_);
if (rep_->redemption_outcome ==
ServerOperationOutcome::kUnconditionalFailure) {
return std::nullopt;
}
bssl::UniquePtr<TRUST_TOKEN_ISSUER> issuer_ctx =
rep_->CreateIssuerContextFromUnexpiredKeys();
std::string decoded_redemption_request;
if (!base::Base64Decode(redemption_request, &decoded_redemption_request)) {
return std::nullopt;
}
TRUST_TOKEN* redeemed_token;
ScopedBoringsslBytes redeemed_client_data;
uint32_t received_public_metadata;
uint8_t received_private_metadata;
if (!TRUST_TOKEN_ISSUER_redeem(
issuer_ctx.get(), &received_public_metadata,
&received_private_metadata, &redeemed_token,
&redeemed_client_data.mutable_ptr()->AsEphemeralRawAddr(),
redeemed_client_data.mutable_len(),
base::as_byte_span(decoded_redemption_request).data(),
decoded_redemption_request.size())) {
return std::nullopt;
}
// Put the issuer-receied token in a smart pointer so it will get deleted on
// leaving scope.
bssl::UniquePtr<TRUST_TOKEN> redeemed_token_scoper(redeemed_token);
return base::Base64Encode(base::as_byte_span(base::StringPrintf(
"%d:%d", received_public_metadata, received_private_metadata)));
}
void TrustTokenRequestHandler::RecordSignedRequest(
const GURL& destination,
const net::HttpRequestHeaders& headers) {
base::AutoLock lock(mutex_);
rep_->last_incoming_signed_request =
TrustTokenSignedRequest{destination, headers};
}
std::optional<TrustTokenSignedRequest>
TrustTokenRequestHandler::last_incoming_signed_request() const {
base::AutoLock lock(mutex_);
return rep_->last_incoming_signed_request;
}
void TrustTokenRequestHandler::UpdateOptions(Options options) {
base::AutoLock lock(mutex_);
rep_ = std::make_unique<Rep>();
rep_->protocol_version = options.protocol_version;
rep_->id = options.id;
rep_->batch_size = options.batch_size;
rep_->issuance_outcome = options.issuance_outcome;
rep_->redemption_outcome = options.redemption_outcome;
for (int i = 0; i < options.num_keys; ++i) {
rep_->issuance_keys.push_back(GenerateIssuanceKeyPair(i));
}
}
} // namespace network::test