Add support for signing to the CertificateView API.

The signing here is needed to provide a reasonable ProofSource implementation without depending on Chromium or google3, that can be used both in unit tests and by public API users.

The verify here is required to support QUIC crypto in unit tests.

gfe-relnote: n/a (code not used in production)
PiperOrigin-RevId: 307097439
Change-Id: I2d544c5e6cd4b60ed0965dfb93b9476d75524394
diff --git a/quic/core/crypto/certificate_view.cc b/quic/core/crypto/certificate_view.cc
index 6400528..5176987 100644
--- a/quic/core/crypto/certificate_view.cc
+++ b/quic/core/crypto/certificate_view.cc
@@ -8,12 +8,16 @@
 #include <memory>
 
 #include "third_party/boringssl/src/include/openssl/bytestring.h"
+#include "third_party/boringssl/src/include/openssl/digest.h"
 #include "third_party/boringssl/src/include/openssl/ec.h"
 #include "third_party/boringssl/src/include/openssl/evp.h"
 #include "third_party/boringssl/src/include/openssl/nid.h"
+#include "third_party/boringssl/src/include/openssl/ssl.h"
 #include "net/third_party/quiche/src/quic/core/crypto/boring_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_optional.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
 
 // The literals below were encoded using `ascii2der | xxd -i`.  The comments
@@ -27,6 +31,60 @@
 // 2.5.29.17
 constexpr uint8_t kSubjectAltNameOid[] = {0x55, 0x1d, 0x11};
 
+enum class PublicKeyType {
+  kRsa,
+  kP256,
+  kP384,
+  kEd25519,
+  kUnknown,
+};
+
+PublicKeyType PublicKeyTypeFromKey(EVP_PKEY* public_key) {
+  switch (EVP_PKEY_id(public_key)) {
+    case EVP_PKEY_RSA:
+      return PublicKeyType::kRsa;
+    case EVP_PKEY_EC: {
+      const EC_KEY* key = EVP_PKEY_get0_EC_KEY(public_key);
+      if (key == nullptr) {
+        return PublicKeyType::kUnknown;
+      }
+      const EC_GROUP* group = EC_KEY_get0_group(key);
+      if (group == nullptr) {
+        return PublicKeyType::kUnknown;
+      }
+      const int curve_nid = EC_GROUP_get_curve_name(group);
+      switch (curve_nid) {
+        case NID_X9_62_prime256v1:
+          return PublicKeyType::kP256;
+        case NID_secp384r1:
+          return PublicKeyType::kP384;
+        default:
+          return PublicKeyType::kUnknown;
+      }
+    }
+    case EVP_PKEY_ED25519:
+      return PublicKeyType::kEd25519;
+    default:
+      return PublicKeyType::kUnknown;
+  }
+}
+
+PublicKeyType PublicKeyTypeFromSignatureAlgorithm(
+    uint16_t signature_algorithm) {
+  switch (signature_algorithm) {
+    case SSL_SIGN_RSA_PSS_RSAE_SHA256:
+      return PublicKeyType::kRsa;
+    case SSL_SIGN_ECDSA_SECP256R1_SHA256:
+      return PublicKeyType::kP384;
+    case SSL_SIGN_ECDSA_SECP384R1_SHA384:
+      return PublicKeyType::kP384;
+    case SSL_SIGN_ED25519:
+      return PublicKeyType::kEd25519;
+    default:
+      return PublicKeyType::kUnknown;
+  }
+}
+
 }  // namespace
 
 namespace quic {
@@ -215,32 +273,103 @@
   // The goal is to allow at minimum any certificate that would be allowed on a
   // regular Web session over TLS 1.3 while ensuring we do not expose any
   // algorithms we don't want to support long-term.
-  switch (EVP_PKEY_id(public_key_.get())) {
-    case EVP_PKEY_RSA:
+  PublicKeyType key_type = PublicKeyTypeFromKey(public_key_.get());
+  switch (key_type) {
+    case PublicKeyType::kRsa:
       return EVP_PKEY_bits(public_key_.get()) >= 2048;
-    case EVP_PKEY_EC: {
-      const EC_KEY* key = EVP_PKEY_get0_EC_KEY(public_key_.get());
-      if (key == nullptr) {
-        return false;
-      }
-      const EC_GROUP* group = EC_KEY_get0_group(key);
-      if (group == nullptr) {
-        return false;
-      }
-      const int curve_nid = EC_GROUP_get_curve_name(group);
-      switch (curve_nid) {
-        case NID_X9_62_prime256v1:
-        case NID_secp384r1:
-          return true;
-        default:
-          return false;
-      }
-    }
-    case EVP_PKEY_ED25519:
+    case PublicKeyType::kP256:
+    case PublicKeyType::kP384:
+    case PublicKeyType::kEd25519:
       return true;
     default:
       return false;
   }
 }
 
+bool CertificateView::VerifySignature(quiche::QuicheStringPiece data,
+                                      quiche::QuicheStringPiece signature,
+                                      uint16_t signature_algorithm) const {
+  if (PublicKeyTypeFromSignatureAlgorithm(signature_algorithm) !=
+      PublicKeyTypeFromKey(public_key_.get())) {
+    QUIC_BUG << "Mismatch between the requested signature algorithm and the "
+                "type of the public key.";
+    return false;
+  }
+
+  bssl::ScopedEVP_MD_CTX md_ctx;
+  EVP_PKEY_CTX* pctx;
+  if (!EVP_DigestVerifyInit(
+          md_ctx.get(), &pctx,
+          SSL_get_signature_algorithm_digest(signature_algorithm), nullptr,
+          public_key_.get())) {
+    return false;
+  }
+  if (SSL_is_signature_algorithm_rsa_pss(signature_algorithm)) {
+    if (!EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) ||
+        !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1)) {
+      return false;
+    }
+  }
+  return EVP_DigestVerify(
+      md_ctx.get(), reinterpret_cast<const uint8_t*>(signature.data()),
+      signature.size(), reinterpret_cast<const uint8_t*>(data.data()),
+      data.size());
+}
+
+std::unique_ptr<CertificatePrivateKey> CertificatePrivateKey::LoadFromDer(
+    quiche::QuicheStringPiece private_key) {
+  std::unique_ptr<CertificatePrivateKey> result(new CertificatePrivateKey());
+  CBS private_key_cbs = StringPieceToCbs(private_key);
+  result->private_key_.reset(EVP_parse_private_key(&private_key_cbs));
+  if (result->private_key_ == nullptr || CBS_len(&private_key_cbs) != 0) {
+    return nullptr;
+  }
+  return result;
+}
+
+std::string CertificatePrivateKey::Sign(quiche::QuicheStringPiece input,
+                                        uint16_t signature_algorithm) {
+  if (PublicKeyTypeFromSignatureAlgorithm(signature_algorithm) !=
+      PublicKeyTypeFromKey(private_key_.get())) {
+    QUIC_BUG << "Mismatch between the requested signature algorithm and the "
+                "type of the private key.";
+    return "";
+  }
+
+  bssl::ScopedEVP_MD_CTX md_ctx;
+  EVP_PKEY_CTX* pctx;
+  if (!EVP_DigestSignInit(
+          md_ctx.get(), &pctx,
+          SSL_get_signature_algorithm_digest(signature_algorithm),
+          /*e=*/nullptr, private_key_.get())) {
+    return "";
+  }
+  if (SSL_is_signature_algorithm_rsa_pss(signature_algorithm)) {
+    if (!EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) ||
+        !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1)) {
+      return "";
+    }
+  }
+
+  std::string output;
+  size_t output_size;
+  if (!EVP_DigestSign(md_ctx.get(), /*out_sig=*/nullptr, &output_size,
+                      reinterpret_cast<const uint8_t*>(input.data()),
+                      input.size())) {
+    return "";
+  }
+  output.resize(output_size);
+  if (!EVP_DigestSign(
+          md_ctx.get(), reinterpret_cast<uint8_t*>(&output[0]), &output_size,
+          reinterpret_cast<const uint8_t*>(input.data()), input.size())) {
+    return "";
+  }
+  output.resize(output_size);
+  return output;
+}
+
+bool CertificatePrivateKey::MatchesPublicKey(const CertificateView& view) {
+  return EVP_PKEY_cmp(view.public_key(), private_key_.get()) == 1;
+}
+
 }  // namespace quic
diff --git a/quic/core/crypto/certificate_view.h b/quic/core/crypto/certificate_view.h
index 466bb9a..1707440 100644
--- a/quic/core/crypto/certificate_view.h
+++ b/quic/core/crypto/certificate_view.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <vector>
 
+#include "third_party/boringssl/src/include/openssl/base.h"
 #include "third_party/boringssl/src/include/openssl/bytestring.h"
 #include "third_party/boringssl/src/include/openssl/evp.h"
 #include "net/third_party/quiche/src/quic/core/crypto/boring_utils.h"
@@ -28,7 +29,7 @@
   static std::unique_ptr<CertificateView> ParseSingleCertificate(
       quiche::QuicheStringPiece certificate);
 
-  EVP_PKEY* public_key() { return public_key_.get(); }
+  const EVP_PKEY* public_key() const { return public_key_.get(); }
 
   const std::vector<quiche::QuicheStringPiece>& subject_alt_name_domains()
       const {
@@ -38,6 +39,11 @@
     return subject_alt_name_ips_;
   }
 
+  // |signature_algorithm| is a TLS signature algorithm ID.
+  bool VerifySignature(quiche::QuicheStringPiece data,
+                       quiche::QuicheStringPiece signature,
+                       uint16_t signature_algorithm) const;
+
  private:
   CertificateView() = default;
 
@@ -53,6 +59,28 @@
   bool ValidatePublicKeyParameters();
 };
 
+// CertificatePrivateKey represents a private key that can be used with an X.509
+// certificate.
+class QUIC_EXPORT_PRIVATE CertificatePrivateKey {
+ public:
+  // Loads a DER-encoded PrivateKeyInfo structure (RFC 5958) as a private key.
+  static std::unique_ptr<CertificatePrivateKey> LoadFromDer(
+      quiche::QuicheStringPiece private_key);
+
+  // |signature_algorithm| is a TLS signature algorithm ID.
+  std::string Sign(quiche::QuicheStringPiece input,
+                   uint16_t signature_algorithm);
+
+  // Verifies that the private key in question matches the public key of the
+  // certificate |view|.
+  bool MatchesPublicKey(const CertificateView& view);
+
+ private:
+  CertificatePrivateKey() = default;
+
+  bssl::UniquePtr<EVP_PKEY> private_key_;
+};
+
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_VIEW_H_
diff --git a/quic/core/crypto/certificate_view_test.cc b/quic/core/crypto/certificate_view_test.cc
index 6be0e4a..899ae13 100644
--- a/quic/core/crypto/certificate_view_test.cc
+++ b/quic/core/crypto/certificate_view_test.cc
@@ -4,8 +4,11 @@
 
 #include "net/third_party/quiche/src/quic/core/crypto/certificate_view.h"
 
+#include <memory>
+
 #include "third_party/boringssl/src/include/openssl/base.h"
 #include "third_party/boringssl/src/include/openssl/evp.h"
+#include "third_party/boringssl/src/include/openssl/ssl.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
@@ -16,7 +19,7 @@
 using testing::ElementsAre;
 
 // A test certificate generated by //net/tools/quic/certs/generate-certs.sh.
-constexpr char kTestCertificate[] = {
+constexpr char kTestCertificateRaw[] = {
     '\x30', '\x82', '\x03', '\xb4', '\x30', '\x82', '\x02', '\x9c', '\xa0',
     '\x03', '\x02', '\x01', '\x02', '\x02', '\x01', '\x01', '\x30', '\x0d',
     '\x06', '\x09', '\x2a', '\x86', '\x48', '\x86', '\xf7', '\x0d', '\x01',
@@ -124,11 +127,155 @@
     '\xd3', '\xfb', '\xba', '\xaf', '\xd9', '\x61', '\x14', '\x3c', '\xe0',
     '\xa1', '\xa9', '\x51', '\x51', '\x0f', '\xad', '\x60'};
 
+constexpr quiche::QuicheStringPiece kTestCertificate(
+    kTestCertificateRaw,
+    sizeof(kTestCertificateRaw));
+
+constexpr char kTestCertificatePrivateKeyRaw[] = {
+    '\x30', '\x82', '\x04', '\xbc', '\x02', '\x01', '\x00', '\x30', '\x0d',
+    '\x06', '\x09', '\x2a', '\x86', '\x48', '\x86', '\xf7', '\x0d', '\x01',
+    '\x01', '\x01', '\x05', '\x00', '\x04', '\x82', '\x04', '\xa6', '\x30',
+    '\x82', '\x04', '\xa2', '\x02', '\x01', '\x00', '\x02', '\x82', '\x01',
+    '\x01', '\x00', '\xc5', '\xe2', '\x51', '\x6d', '\x3f', '\xd6', '\x28',
+    '\xf2', '\xad', '\x34', '\x73', '\x87', '\x64', '\xca', '\x33', '\x19',
+    '\x33', '\xb7', '\x75', '\x91', '\xab', '\x31', '\x19', '\x2b', '\xe3',
+    '\xa4', '\x26', '\x09', '\x29', '\x8b', '\x2d', '\xf7', '\x52', '\x75',
+    '\xa7', '\x55', '\x15', '\xf0', '\x11', '\xc7', '\xc2', '\xc4', '\xed',
+    '\x18', '\x1b', '\x33', '\x0b', '\x71', '\x32', '\xe6', '\x35', '\x89',
+    '\xcd', '\x2d', '\x5a', '\x05', '\x57', '\x4e', '\xc2', '\x78', '\x75',
+    '\x65', '\x72', '\x2d', '\x8a', '\x17', '\x83', '\xd6', '\x32', '\x90',
+    '\x85', '\xf8', '\x22', '\xe2', '\x65', '\xa9', '\xe0', '\xa0', '\xfe',
+    '\x19', '\xb2', '\x39', '\x2d', '\x14', '\x03', '\x10', '\x2f', '\xcc',
+    '\x8b', '\x5e', '\xaa', '\x25', '\x27', '\x0d', '\xa3', '\x37', '\x10',
+    '\x0c', '\x17', '\xec', '\xf0', '\x8b', '\xc5', '\x6b', '\xed', '\x6b',
+    '\x5e', '\xb2', '\xe2', '\x35', '\x3e', '\x46', '\x3b', '\xf7', '\xf6',
+    '\x59', '\xb1', '\xe0', '\x16', '\xa6', '\xfb', '\x03', '\xbf', '\x84',
+    '\x4f', '\xce', '\x64', '\x15', '\x0d', '\x59', '\x99', '\xa6', '\xf0',
+    '\x7f', '\x8a', '\x33', '\x4b', '\xbb', '\x0b', '\xb8', '\xf2', '\xd1',
+    '\x27', '\x90', '\x8f', '\x38', '\xf8', '\x5a', '\x41', '\x82', '\x07',
+    '\x9b', '\x0d', '\xd9', '\x52', '\xe0', '\x70', '\xff', '\xde', '\xda',
+    '\xd8', '\x25', '\x4e', '\x2f', '\x2d', '\x9f', '\xaf', '\x92', '\x63',
+    '\xc7', '\x42', '\xb4', '\xdc', '\x16', '\x95', '\x23', '\x05', '\x02',
+    '\x6b', '\xb0', '\xe8', '\xc5', '\xfe', '\x15', '\x9a', '\xe8', '\x7d',
+    '\x2f', '\xdc', '\x43', '\xf4', '\x70', '\x91', '\x1a', '\x93', '\xbe',
+    '\x71', '\xaf', '\x85', '\x84', '\xdb', '\xcf', '\x6b', '\x5c', '\x80',
+    '\xb2', '\xd3', '\xf3', '\x42', '\x6e', '\x24', '\xec', '\x2a', '\x62',
+    '\x99', '\xc6', '\x3c', '\xe5', '\x32', '\xe5', '\x72', '\x37', '\x30',
+    '\x9b', '\x0b', '\xe4', '\x06', '\xb4', '\x64', '\x26', '\x95', '\x59',
+    '\xba', '\xf1', '\x53', '\x83', '\x3d', '\x99', '\x6d', '\xf0', '\x80',
+    '\xe2', '\xdb', '\x6b', '\x34', '\x52', '\x06', '\x77', '\x3c', '\x73',
+    '\xbe', '\xc6', '\xe3', '\xce', '\xb2', '\x11', '\x02', '\x03', '\x01',
+    '\x00', '\x01', '\x02', '\x82', '\x01', '\x00', '\x39', '\x75', '\xac',
+    '\x1b', '\x43', '\x0c', '\x16', '\xbb', '\xd0', '\xdb', '\x88', '\x28',
+    '\x6a', '\x75', '\xe4', '\x3c', '\x8f', '\x2d', '\xd8', '\x6f', '\xc1',
+    '\xfb', '\xf1', '\xc9', '\x32', '\xc2', '\xb9', '\x60', '\xb3', '\xb5',
+    '\x7c', '\x55', '\x72', '\x96', '\x43', '\x4e', '\x8b', '\x9e', '\x38',
+    '\x2b', '\x7f', '\x3c', '\xdb', '\x73', '\xc2', '\x82', '\x21', '\xf2',
+    '\x6e', '\xcb', '\x36', '\x04', '\x9b', '\x95', '\x6d', '\xac', '\x5b',
+    '\x5b', '\xbd', '\x50', '\x69', '\x16', '\x59', '\xff', '\x2b', '\x38',
+    '\x04', '\xca', '\x2f', '\xc8', '\x93', '\x7e', '\x27', '\xf3', '\x01',
+    '\x7e', '\x40', '\x81', '\xbf', '\x07', '\x0b', '\x1f', '\x5b', '\x1d',
+    '\x92', '\x7e', '\x22', '\xc3', '\x0c', '\x3d', '\x22', '\xbe', '\xc3',
+    '\x06', '\x4c', '\xbc', '\x72', '\x66', '\x70', '\x94', '\x16', '\x8d',
+    '\x1f', '\x78', '\x65', '\x6a', '\x66', '\x07', '\x1f', '\x74', '\x42',
+    '\x6e', '\xf6', '\x7e', '\xdc', '\x03', '\xd3', '\x88', '\xb4', '\x4b',
+    '\x2c', '\x5c', '\x3c', '\x42', '\x59', '\x42', '\x1f', '\x01', '\x13',
+    '\x31', '\xc5', '\x22', '\xe7', '\x6a', '\x96', '\xf2', '\xfb', '\x66',
+    '\xfe', '\xc8', '\xa1', '\x7e', '\x24', '\x96', '\x5f', '\x02', '\xee',
+    '\x38', '\x21', '\xa5', '\x14', '\xd2', '\xa6', '\x35', '\x70', '\x6c',
+    '\x8d', '\xa6', '\xd8', '\x2a', '\xd2', '\x45', '\x31', '\x5f', '\x67',
+    '\x9e', '\x35', '\x57', '\x6a', '\xc4', '\x15', '\xe7', '\xba', '\x60',
+    '\x2f', '\x8e', '\x52', '\x4e', '\xfc', '\x6f', '\xa0', '\x08', '\x91',
+    '\x31', '\x71', '\x06', '\x68', '\x19', '\x48', '\xc7', '\x81', '\x0d',
+    '\x5e', '\x52', '\x93', '\x57', '\xcc', '\xfe', '\x46', '\xac', '\xa9',
+    '\x4f', '\xe2', '\x96', '\x4f', '\xaf', '\x12', '\xfb', '\xc2', '\x4b',
+    '\xc4', '\x8d', '\x3b', '\xb0', '\x38', '\xe4', '\xbb', '\x8d', '\x19',
+    '\x81', '\xe4', '\x74', '\x63', '\x9c', '\x8d', '\xaa', '\x84', '\x82',
+    '\x91', '\xdf', '\xdc', '\x45', '\xf0', '\x39', '\xb2', '\xb4', '\xac',
+    '\x45', '\xda', '\x3f', '\x30', '\x4d', '\x46', '\xb1', '\xe1', '\xb2',
+    '\x9d', '\xdf', '\xd8', '\xc4', '\xa2', '\xef', '\xe9', '\x1a', '\x97',
+    '\x79', '\x02', '\x81', '\x81', '\x00', '\xe5', '\x23', '\xb8', '\xd7',
+    '\x09', '\x54', '\x54', '\x3b', '\xb6', '\x78', '\x78', '\x67', '\x57',
+    '\x65', '\xc5', '\xd4', '\x74', '\xaf', '\x05', '\x4f', '\xb5', '\xc8',
+    '\x8c', '\x1b', '\xd1', '\x9a', '\x2c', '\xd6', '\xe4', '\x68', '\xd1',
+    '\xaf', '\x3d', '\x72', '\x42', '\x50', '\xc8', '\xdd', '\xb1', '\xee',
+    '\x77', '\x52', '\xb8', '\xb1', '\x31', '\xbe', '\xf0', '\x74', '\x78',
+    '\x42', '\x59', '\xea', '\x13', '\x8b', '\x82', '\x00', '\x54', '\x22',
+    '\xd2', '\x0a', '\x24', '\xb0', '\x1f', '\x1e', '\x76', '\x27', '\xae',
+    '\x63', '\xc6', '\x6b', '\x59', '\x28', '\x1d', '\xa0', '\x9f', '\x42',
+    '\x30', '\xf1', '\xe3', '\x59', '\x1c', '\x4f', '\x31', '\x49', '\xff',
+    '\x45', '\x7e', '\x6b', '\xef', '\xe9', '\x6f', '\xde', '\xaf', '\x1e',
+    '\x04', '\x96', '\x61', '\x4e', '\x9f', '\x58', '\xf5', '\x0d', '\x64',
+    '\x08', '\x48', '\x0a', '\xae', '\xac', '\xe4', '\x76', '\x91', '\xdd',
+    '\x6e', '\x33', '\x97', '\xc5', '\x96', '\xda', '\xff', '\xbc', '\x42',
+    '\x5b', '\x71', '\xb5', '\x76', '\xae', '\x01', '\xb3', '\x02', '\x81',
+    '\x81', '\x00', '\xdd', '\x14', '\xa5', '\x6c', '\x89', '\x2b', '\x80',
+    '\x78', '\xf6', '\xc3', '\x80', '\x4d', '\x53', '\x54', '\xb3', '\x2b',
+    '\x40', '\xce', '\x98', '\x16', '\xa0', '\xbf', '\x72', '\xf1', '\xe3',
+    '\xdc', '\xe9', '\x0b', '\x45', '\x23', '\x86', '\x38', '\x4c', '\x29',
+    '\xf1', '\xa0', '\xe0', '\x2c', '\xfa', '\x86', '\x3f', '\x01', '\x90',
+    '\xc5', '\x1b', '\x96', '\x10', '\x44', '\x84', '\xfb', '\xec', '\x3c',
+    '\x74', '\x6c', '\x0d', '\xcc', '\xc3', '\xcd', '\x1b', '\x28', '\x12',
+    '\xaa', '\xb4', '\x67', '\x80', '\xc8', '\xd9', '\x1b', '\x7d', '\xe7',
+    '\x54', '\x39', '\x03', '\x6d', '\xba', '\xaa', '\x6f', '\xf7', '\x93',
+    '\x1f', '\x94', '\x76', '\xd6', '\xab', '\x9b', '\xda', '\x3d', '\x89',
+    '\x37', '\x83', '\xfe', '\x72', '\x2a', '\xbb', '\x6f', '\x36', '\xc5',
+    '\xe0', '\xae', '\x65', '\xf9', '\xbb', '\xc6', '\xe2', '\x98', '\x0f',
+    '\xbd', '\xf6', '\x22', '\xf8', '\x35', '\x5b', '\x99', '\xe6', '\xff',
+    '\x6d', '\x6e', '\xb2', '\x92', '\x93', '\x64', '\x25', '\xc1', '\xe8',
+    '\x9c', '\x6b', '\x73', '\x2b', '\x02', '\x81', '\x80', '\x13', '\x30',
+    '\x1a', '\x9a', '\x67', '\x3d', '\x98', '\x90', '\x27', '\x87', '\x8f',
+    '\x0d', '\x98', '\x53', '\xfd', '\x6c', '\xfd', '\x18', '\x6a', '\xe9',
+    '\x71', '\xdf', '\x89', '\x5c', '\x0b', '\x01', '\x4e', '\x1f', '\xf0',
+    '\xa0', '\x96', '\x6e', '\x86', '\x46', '\xbb', '\x26', '\xe8', '\xab',
+    '\x27', '\xeb', '\x40', '\x32', '\xbd', '\x24', '\x99', '\x75', '\xd3',
+    '\xcc', '\xed', '\x05', '\x21', '\x62', '\x68', '\xa0', '\x96', '\x12',
+    '\x50', '\xf9', '\x59', '\x7d', '\x5f', '\xf5', '\x1f', '\xa5', '\xfd',
+    '\x5e', '\xf5', '\x4b', '\x85', '\xa2', '\x17', '\xa5', '\x34', '\x55',
+    '\xef', '\x00', '\x2b', '\xf9', '\x15', '\x80', '\xb0', '\xce', '\x30',
+    '\xe2', '\x71', '\x6d', '\xf0', '\x58', '\x39', '\x8e', '\xe2', '\xbf',
+    '\x53', '\x0a', '\xc0', '\x77', '\x97', '\x4e', '\x6e', '\x29', '\x94',
+    '\xdb', '\xba', '\x34', '\xb7', '\x53', '\xad', '\xac', '\xec', '\xb4',
+    '\xc1', '\x22', '\x39', '\xc8', '\x38', '\x3d', '\x63', '\x94', '\x93',
+    '\x35', '\xc0', '\x98', '\xc7', '\xbc', '\xda', '\x63', '\x57', '\xe1',
+    '\x02', '\x81', '\x80', '\x51', '\x71', '\x7c', '\xab', '\x6a', '\x30',
+    '\xe3', '\x68', '\x2c', '\x87', '\xc2', '\xe9', '\x39', '\x8c', '\x97',
+    '\x60', '\x94', '\xc4', '\x46', '\xd4', '\xf7', '\x2c', '\xf0', '\x1c',
+    '\x5a', '\x34', '\x14', '\x89', '\xf9', '\x53', '\x67', '\xeb', '\xaf',
+    '\x6b', '\x38', '\x3f', '\x6a', '\xb6', '\x47', '\x28', '\x53', '\x67',
+    '\xb1', '\x3c', '\x5b', '\xb8', '\x41', '\x8f', '\xec', '\x69', '\x9e',
+    '\x12', '\x7b', '\x55', '\x1f', '\x14', '\x53', '\x01', '\x69', '\x42',
+    '\xae', '\xf5', '\xc1', '\xf5', '\xeb', '\x44', '\x92', '\x6e', '\x85',
+    '\x48', '\x46', '\x07', '\xa6', '\xd2', '\xb2', '\x94', '\x7d', '\x20',
+    '\xf8', '\x4b', '\x06', '\xf7', '\x6c', '\x87', '\xd5', '\xa7', '\x65',
+    '\x49', '\xfa', '\x70', '\x9e', '\xb8', '\xd2', '\x33', '\x30', '\x7a',
+    '\x3e', '\x15', '\x52', '\x49', '\xf0', '\xe1', '\x13', '\x18', '\x80',
+    '\xaa', '\x33', '\xf1', '\xcb', '\xda', '\x22', '\x55', '\xf7', '\x71',
+    '\x58', '\xa1', '\xa8', '\xc9', '\x12', '\x24', '\x48', '\x1d', '\x7c',
+    '\xbc', '\xc3', '\x7a', '\xf5', '\xf7', '\x02', '\x81', '\x80', '\x41',
+    '\x7c', '\xae', '\x6e', '\x48', '\x3f', '\xb5', '\x0b', '\x99', '\xaa',
+    '\xc5', '\xea', '\x81', '\xad', '\x84', '\x6b', '\x29', '\x78', '\x4b',
+    '\x18', '\xdb', '\x0e', '\xd3', '\x3e', '\x60', '\x8b', '\xef', '\x65',
+    '\x4d', '\x58', '\x25', '\x3a', '\x08', '\xb5', '\x21', '\xb6', '\x61',
+    '\x0c', '\xfa', '\xf0', '\x69', '\x78', '\x4e', '\x68', '\x36', '\xdb',
+    '\x41', '\x4b', '\x50', '\xd8', '\xd3', '\x8e', '\x3d', '\x74', '\x80',
+    '\x8e', '\xa0', '\xe6', '\xda', '\xec', '\x70', '\x89', '\x77', '\xb2',
+    '\x9d', '\xd6', '\x6e', '\x0a', '\xc4', '\xbd', '\xf6', '\x9a', '\x07',
+    '\x15', '\xba', '\x55', '\x9f', '\xd4', '\x4d', '\x3a', '\x0f', '\x51',
+    '\x12', '\xa4', '\xd9', '\xc2', '\x98', '\x76', '\xc5', '\xb7', '\x29',
+    '\x40', '\xca', '\xf4', '\xbb', '\x74', '\x2d', '\x71', '\x03', '\x4d',
+    '\xe7', '\x05', '\x75', '\xc0', '\x8d', '\x96', '\x7e', '\x59', '\xa1',
+    '\x8b', '\x3b', '\xa3', '\x2b', '\xa5', '\xa3', '\xc8', '\xf7', '\xd3',
+    '\x3e', '\x6b', '\x2e', '\xfa', '\x4f', '\x4d', '\xe6', '\xbe', '\xd3',
+    '\x59'};
+
+constexpr quiche::QuicheStringPiece kTestCertificatePrivateKey(
+    kTestCertificatePrivateKeyRaw,
+    sizeof(kTestCertificatePrivateKeyRaw));
+
 TEST(CertificateViewTest, Parse) {
-  quiche::QuicheStringPiece certificate(kTestCertificate,
-                                        sizeof(kTestCertificate));
   std::unique_ptr<CertificateView> view =
-      CertificateView::ParseSingleCertificate(certificate);
+      CertificateView::ParseSingleCertificate(kTestCertificate);
   ASSERT_TRUE(view != nullptr);
 
   EXPECT_THAT(view->subject_alt_name_domains(),
@@ -140,5 +287,27 @@
   EXPECT_EQ(EVP_PKEY_id(view->public_key()), EVP_PKEY_RSA);
 }
 
+TEST(CertificateViewTest, SignAndVerify) {
+  std::unique_ptr<CertificatePrivateKey> key =
+      CertificatePrivateKey::LoadFromDer(kTestCertificatePrivateKey);
+  ASSERT_TRUE(key != nullptr);
+
+  std::string data = "A really important message";
+  std::string signature = key->Sign(data, SSL_SIGN_RSA_PSS_RSAE_SHA256);
+  ASSERT_FALSE(signature.empty());
+
+  std::unique_ptr<CertificateView> view =
+      CertificateView::ParseSingleCertificate(kTestCertificate);
+  ASSERT_TRUE(view != nullptr);
+  EXPECT_TRUE(key->MatchesPublicKey(*view));
+
+  EXPECT_TRUE(
+      view->VerifySignature(data, signature, SSL_SIGN_RSA_PSS_RSAE_SHA256));
+  EXPECT_FALSE(view->VerifySignature("An unimportant message", signature,
+                                     SSL_SIGN_RSA_PSS_RSAE_SHA256));
+  EXPECT_FALSE(view->VerifySignature(data, "Not a signature",
+                                     SSL_SIGN_RSA_PSS_RSAE_SHA256));
+}
+
 }  // namespace
 }  // namespace quic