910e62b5创建于 1月15日历史提交
// 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