// 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_signature_verifier.h"

#include "base/containers/span.h"
#include "base/format_macros.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/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/cbor/cbor_writer.h"
#include "content/browser/web_package/signed_exchange_consts.h"
#include "content/browser/web_package/signed_exchange_envelope.h"
#include "content/browser/web_package/signed_exchange_signature_header_field.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "crypto/signature_verifier.h"
#include "net/cert/asn1_util.h"
#include "net/cert/x509_util.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/evp.h"

namespace content {

namespace {

// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#signature-validity
// Step 7. "Let message be the concatenation of the following byte strings."
constexpr uint8_t kMessageHeader[] =
    // 7.1. "A string that consists of octet 32 (0x20) repeated 64 times."
    // [spec text]
    "\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20"
    "\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20"
    "\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20"
    "\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20"
    // 7.2. "A context string: the ASCII encoding of "HTTP Exchange 1"." ...
    // "but implementations of drafts MUST NOT use it and MUST use another
    // draft-specific string beginning with "HTTP Exchange 1 " instead."
    // [spec text]
    // 7.3. "A single 0 byte which serves as a separator." [spec text]
    "HTTP Exchange 1 b1";

base::Optional<cbor::CBORValue> GenerateCanonicalRequestCBOR(
    const SignedExchangeEnvelope& envelope) {
  cbor::CBORValue::MapValue map;
  map.insert_or_assign(
      cbor::CBORValue(kMethodKey, cbor::CBORValue::Type::BYTE_STRING),
      cbor::CBORValue(envelope.request_method(),
                      cbor::CBORValue::Type::BYTE_STRING));
  map.insert_or_assign(
      cbor::CBORValue(kUrlKey, cbor::CBORValue::Type::BYTE_STRING),
      cbor::CBORValue(envelope.request_url().spec(),
                      cbor::CBORValue::Type::BYTE_STRING));

  return cbor::CBORValue(map);
}

base::Optional<cbor::CBORValue> GenerateCanonicalResponseCBOR(
    const SignedExchangeEnvelope& envelope) {
  const auto& headers = envelope.response_headers();
  cbor::CBORValue::MapValue map;
  std::string response_code_str =
      base::NumberToString(envelope.response_code());
  map.insert_or_assign(
      cbor::CBORValue(kStatusKey, cbor::CBORValue::Type::BYTE_STRING),
      cbor::CBORValue(response_code_str, cbor::CBORValue::Type::BYTE_STRING));
  for (const auto& pair : headers) {
    map.insert_or_assign(
        cbor::CBORValue(pair.first, cbor::CBORValue::Type::BYTE_STRING),
        cbor::CBORValue(pair.second, cbor::CBORValue::Type::BYTE_STRING));
  }
  return cbor::CBORValue(map);
}

// Generate CBORValue from |envelope| as specified in:
// https://wicg.github.io/webpackage/draft-yasskin-httpbis-origin-signed-exchanges-impl.html#cbor-representation
base::Optional<cbor::CBORValue> GenerateCanonicalExchangeHeadersCBOR(
    const SignedExchangeEnvelope& envelope) {
  auto req_val = GenerateCanonicalRequestCBOR(envelope);
  if (!req_val)
    return base::nullopt;
  auto res_val = GenerateCanonicalResponseCBOR(envelope);
  if (!res_val)
    return base::nullopt;

  cbor::CBORValue::ArrayValue array;
  array.push_back(std::move(*req_val));
  array.push_back(std::move(*res_val));
  return cbor::CBORValue(array);
}

// Generate a CBOR map value as specified in
// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#signature-validity
// Step 7.4.
base::Optional<cbor::CBORValue> GenerateSignedMessageCBOR(
    const SignedExchangeEnvelope& envelope) {
  auto headers_val = GenerateCanonicalExchangeHeadersCBOR(envelope);
  if (!headers_val)
    return base::nullopt;

  // 7.4. "The bytes of the canonical CBOR serialization (Section 3.4) of
  // a CBOR map mapping:" [spec text]
  cbor::CBORValue::MapValue map;
  // 7.4.1. "If cert-sha256 is set: The text string "cert-sha256" to the byte
  // string value of cert-sha256." [spec text]
  if (envelope.signature().cert_sha256.has_value()) {
    map.insert_or_assign(
        cbor::CBORValue(kCertSha256Key),
        cbor::CBORValue(
            base::StringPiece(reinterpret_cast<const char*>(
                                  envelope.signature().cert_sha256->data),
                              sizeof(envelope.signature().cert_sha256->data)),
            cbor::CBORValue::Type::BYTE_STRING));
  }
  // 7.4.2. "The text string "validity-url" to the byte string value of
  // validity-url." [spec text]
  map.insert_or_assign(cbor::CBORValue(kValidityUrlKey),
                       cbor::CBORValue(envelope.signature().validity_url.spec(),
                                       cbor::CBORValue::Type::BYTE_STRING));
  // 7.4.3. "The text string "date" to the integer value of date." [spec text]
  if (!base::IsValueInRangeForNumericType<int64_t>(envelope.signature().date))
    return base::nullopt;

  map.insert_or_assign(
      cbor::CBORValue(kDateKey),
      cbor::CBORValue(base::checked_cast<int64_t>(envelope.signature().date)));
  // 7.4.4. "The text string "expires" to the integer value of expires."
  // [spec text]
  if (!base::IsValueInRangeForNumericType<int64_t>(
          envelope.signature().expires))
    return base::nullopt;

  map.insert_or_assign(cbor::CBORValue(kExpiresKey),
                       cbor::CBORValue(base::checked_cast<int64_t>(
                           envelope.signature().expires)));
  // 7.4.5. "The text string "headers" to the CBOR representation
  // (Section 3.2) of exchange's headers." [spec text]
  map.insert_or_assign(cbor::CBORValue(kHeadersKey), std::move(*headers_val));
  return cbor::CBORValue(map);
}

base::Optional<crypto::SignatureVerifier::SignatureAlgorithm>
GetSignatureAlgorithm(scoped_refptr<net::X509Certificate> cert,
                      SignedExchangeDevToolsProxy* devtools_proxy) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"), "VerifySignature");
  base::StringPiece spki;
  if (!net::asn1::ExtractSPKIFromDERCert(
          net::x509_util::CryptoBufferAsStringPiece(cert->cert_buffer()),
          &spki)) {
    signed_exchange_utils::ReportErrorAndTraceEvent(devtools_proxy,
                                                    "Failed to extract SPKI.");
    return base::nullopt;
  }

  CBS cbs;
  CBS_init(&cbs, reinterpret_cast<const uint8_t*>(spki.data()), spki.size());
  bssl::UniquePtr<EVP_PKEY> pkey(EVP_parse_public_key(&cbs));
  if (!pkey || CBS_len(&cbs) != 0) {
    signed_exchange_utils::ReportErrorAndTraceEvent(
        devtools_proxy, "Failed to parse public key.");
    return base::nullopt;
  }

  int pkey_id = EVP_PKEY_id(pkey.get());
  if (pkey_id != EVP_PKEY_EC) {
    signed_exchange_utils::ReportErrorAndTraceEvent(
        devtools_proxy,
        base::StringPrintf("Unsupported public key type: %d. Only ECDSA keys "
                           "on the secp256r1 curve are supported.",
                           pkey_id));
    return base::nullopt;
  }

  const EC_GROUP* group = EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey.get()));
  int curve_name = EC_GROUP_get_curve_name(group);
  if (curve_name == NID_X9_62_prime256v1)
    return crypto::SignatureVerifier::ECDSA_SHA256;
  signed_exchange_utils::ReportErrorAndTraceEvent(
      devtools_proxy,
      base::StringPrintf("Unsupported EC group: %d. Only ECDSA keys on the "
                         "secp256r1 curve are supported.",
                         curve_name));
  return base::nullopt;
}

bool VerifySignature(base::span<const uint8_t> sig,
                     base::span<const uint8_t> msg,
                     scoped_refptr<net::X509Certificate> cert,
                     crypto::SignatureVerifier::SignatureAlgorithm algorithm,
                     SignedExchangeDevToolsProxy* devtools_proxy) {
  crypto::SignatureVerifier verifier;
  if (!net::x509_util::SignatureVerifierInitWithCertificate(
          &verifier, algorithm, sig, cert->cert_buffer())) {
    signed_exchange_utils::ReportErrorAndTraceEvent(
        devtools_proxy, "SignatureVerifierInitWithCertificate failed.");
    return false;
  }
  verifier.VerifyUpdate(msg);
  if (!verifier.VerifyFinal()) {
    signed_exchange_utils::ReportErrorAndTraceEvent(devtools_proxy,
                                                    "VerifyFinal failed.");
    return false;
  }
  return true;
}

std::string HexDump(const std::vector<uint8_t>& msg) {
  std::string output;
  for (const auto& byte : msg) {
    base::StringAppendF(&output, "%02x", byte);
  }
  return output;
}

base::Optional<std::vector<uint8_t>> GenerateSignedMessage(
    const SignedExchangeEnvelope& envelope) {
  TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
                     "GenerateSignedMessage");

  // GenerateSignedMessageCBOR corresponds to Step 7.4.
  base::Optional<cbor::CBORValue> cbor_val =
      GenerateSignedMessageCBOR(envelope);
  if (!cbor_val) {
    TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("loading"),
                     "GenerateSignedMessage", "error",
                     "GenerateSignedMessageCBOR failed.");
    return base::nullopt;
  }

  base::Optional<std::vector<uint8_t>> cbor_message =
      cbor::CBORWriter::Write(*cbor_val);
  if (!cbor_message) {
    TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("loading"),
                     "GenerateSignedMessage", "error",
                     "CBORWriter::Write failed.");
    return base::nullopt;
  }

  // https://wicg.github.io/webpackage/draft-yasskin-httpbis-origin-signed-exchanges-impl.html#signature-validity
  // Step 7. "Let message be the concatenation of the following byte strings."
  std::vector<uint8_t> message;
  // see kMessageHeader for Steps 7.1 to 7.3.
  message.reserve(arraysize(kMessageHeader) + cbor_message->size());
  message.insert(message.end(), std::begin(kMessageHeader),
                 std::end(kMessageHeader));
  // 7.4. "The bytes of the canonical CBOR serialization (Section 3.4) of
  // a CBOR map mapping:" [spec text]
  message.insert(message.end(), cbor_message->begin(), cbor_message->end());
  TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("loading"),
                   "GenerateSignedMessage", "dump", HexDump(message));
  return message;
}

base::Time TimeFromSignedExchangeUnixTime(uint64_t t) {
  return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(t);
}

// Implements steps 5-6 of
// https://wicg.github.io/webpackage/draft-yasskin-httpbis-origin-signed-exchanges-impl.html#signature-validity
bool VerifyTimestamps(const SignedExchangeEnvelope& envelope,
                      const base::Time& verification_time) {
  base::Time expires_time =
      TimeFromSignedExchangeUnixTime(envelope.signature().expires);
  base::Time creation_time =
      TimeFromSignedExchangeUnixTime(envelope.signature().date);

  // 5. "If expires is more than 7 days (604800 seconds) after date, return
  // "invalid"." [spec text]
  if ((expires_time - creation_time).InSeconds() > 604800)
    return false;

  // 6. "If the current time is before date or after expires, return
  // "invalid"."
  if (verification_time < creation_time || expires_time < verification_time)
    return false;

  return true;
}

}  // namespace

SignedExchangeSignatureVerifier::Result SignedExchangeSignatureVerifier::Verify(
    const SignedExchangeEnvelope& envelope,
    scoped_refptr<net::X509Certificate> certificate,
    const base::Time& verification_time,
    SignedExchangeDevToolsProxy* devtools_proxy) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
               "SignedExchangeSignatureVerifier::Verify");

  if (!VerifyTimestamps(envelope, verification_time)) {
    signed_exchange_utils::ReportErrorAndTraceEvent(
        devtools_proxy,
        base::StringPrintf(
            "Invalid timestamp. creation_time: %" PRIu64
            ", expires_time: %" PRIu64 ", verification_time: %" PRIu64,
            envelope.signature().date, envelope.signature().expires,
            (verification_time - base::Time::UnixEpoch()).InSeconds()));
    return Result::kErrInvalidTimestamp;
  }

  if (!certificate) {
    signed_exchange_utils::ReportErrorAndTraceEvent(devtools_proxy,
                                                    "No certificate set.");
    return Result::kErrNoCertificate;
  }

  if (!envelope.signature().cert_sha256.has_value()) {
    signed_exchange_utils::ReportErrorAndTraceEvent(devtools_proxy,
                                                    "No cert-sha256 set.");
    return Result::kErrNoCertificateSHA256;
  }

  // The main-certificate is the first certificate in certificate-chain.
  if (*envelope.signature().cert_sha256 !=
      net::X509Certificate::CalculateFingerprint256(
          certificate->cert_buffer())) {
    signed_exchange_utils::ReportErrorAndTraceEvent(devtools_proxy,
                                                    "cert-sha256 mismatch.");
    return Result::kErrCertificateSHA256Mismatch;
  }

  auto message = GenerateSignedMessage(envelope);
  if (!message) {
    signed_exchange_utils::ReportErrorAndTraceEvent(
        devtools_proxy, "Failed to reconstruct signed message.");
    return Result::kErrInvalidSignatureFormat;
  }

  base::Optional<crypto::SignatureVerifier::SignatureAlgorithm> algorithm =
      GetSignatureAlgorithm(certificate, devtools_proxy);
  if (!algorithm)
    return Result::kErrUnsupportedCertType;

  const std::string& sig = envelope.signature().sig;
  if (!VerifySignature(
          base::make_span(reinterpret_cast<const uint8_t*>(sig.data()),
                          sig.size()),
          *message, certificate, *algorithm, devtools_proxy)) {
    signed_exchange_utils::ReportErrorAndTraceEvent(
        devtools_proxy, "Failed to verify signature \"sig\".");
    return Result::kErrSignatureVerificationFailed;
  }

  if (!base::EqualsCaseInsensitiveASCII(envelope.signature().integrity,
                                        "mi-draft2")) {
    signed_exchange_utils::ReportErrorAndTraceEvent(
        devtools_proxy,
        "The current implemention only supports \"mi\" integrity scheme.");
    return Result::kErrInvalidSignatureIntegrity;
  }
  return Result::kSuccess;
}

base::Optional<std::vector<uint8_t>>
SignedExchangeSignatureVerifier::EncodeCanonicalExchangeHeaders(
    const SignedExchangeEnvelope& envelope) {
  base::Optional<cbor::CBORValue> cbor_val =
      GenerateCanonicalExchangeHeadersCBOR(envelope);
  if (!cbor_val)
    return base::nullopt;

  return cbor::CBORWriter::Write(*cbor_val);
}

}  // namespace content
