Add an API to disable RSA-PSS for certificates.

Chrome uses the platform certificate verifier and thus cannot reliably
expect PSS signatures to work in all configurations. Add an API for the
consumer to inform BoringSSL of this ability. We will then adjust our
advertisements accordingly.

Note that, because TLS 1.2 does not have the signature_algorithms_cert
extension, turning off TLS 1.3 and using this API will stop advertising
RSA-PSS. I believe this is the correct behavior given the semantics of
that code point.

The tests check the various combinations here, as well as checking that
the peer never sends signature_algorithms_cert identical to
signature_algorithms.

Bug: 229
Change-Id: I8c33a93efdc9252097e3899425b49548fc42a93a
Reviewed-on: https://boringssl-review.googlesource.com/27488
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: Steven Valdez <svaldez@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 1ad8fbf..51162ea 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2467,10 +2467,19 @@
 OPENSSL_EXPORT int SSL_set1_verify_cert_store(SSL *ssl, X509_STORE *store);
 
 // SSL_CTX_set_ed25519_enabled configures whether |ctx| advertises support for
-// the Ed25519 signature algorithm when using the default preference list.
+// the Ed25519 signature algorithm when using the default preference list. It is
+// disabled by default and may be enabled if the certificate verifier supports
+// Ed25519.
 OPENSSL_EXPORT void SSL_CTX_set_ed25519_enabled(SSL_CTX *ctx, int enabled);
 
-// SSL_CTX_set_verify_algorithm_prefs confingures |ctx| to use |prefs| as the
+// SSL_CTX_set_rsa_pss_rsae_certs_enabled configures whether |ctx| advertises
+// support for rsa_pss_rsae_* signatures within the certificate chain. It is
+// enabled by default but should be disabled if using a custom certificate
+// verifier which does not support RSA-PSS signatures.
+OPENSSL_EXPORT void SSL_CTX_set_rsa_pss_rsae_certs_enabled(SSL_CTX *ctx,
+                                                           int enabled);
+
+// SSL_CTX_set_verify_algorithm_prefs configures |ctx| to use |prefs| as the
 // preference list when verifying signature's from the peer's long-term key. It
 // returns one on zero on error. |prefs| should not include the internal-only
 // value |SSL_SIGN_RSA_PKCS1_MD5_SHA1|.
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
index 3424f3d..2aa018b 100644
--- a/include/openssl/tls1.h
+++ b/include/openssl/tls1.h
@@ -219,6 +219,7 @@
 #define TLSEXT_TYPE_cookie 44
 #define TLSEXT_TYPE_psk_key_exchange_modes 45
 #define TLSEXT_TYPE_certificate_authorities 47
+#define TLSEXT_TYPE_signature_algorithms_cert 50
 #define TLSEXT_TYPE_key_share 51
 
 // ExtensionType value from RFC5746
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc
index 7ade8fc..a121c81 100644
--- a/ssl/handshake_server.cc
+++ b/ssl/handshake_server.cc
@@ -902,9 +902,12 @@
         !CBB_add_u8(&cert_types, SSL3_CT_RSA_SIGN) ||
         (ssl_protocol_version(ssl) >= TLS1_VERSION &&
          !CBB_add_u8(&cert_types, TLS_CT_ECDSA_SIGN)) ||
+        // TLS 1.2 has no way to specify different signature algorithms for
+        // certificates and the online signature, so emit the more restrictive
+        // certificate list.
         (ssl_protocol_version(ssl) >= TLS1_2_VERSION &&
          (!CBB_add_u16_length_prefixed(&body, &sigalgs_cbb) ||
-          !tls12_add_verify_sigalgs(ssl, &sigalgs_cbb))) ||
+          !tls12_add_verify_sigalgs(ssl, &sigalgs_cbb, true /* certs */))) ||
         !ssl_add_client_CA_list(ssl, &body) ||
         !ssl_add_message_cbb(ssl, cbb.get())) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
diff --git a/ssl/internal.h b/ssl/internal.h
index 07d099d..8e90fd2 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1739,8 +1739,10 @@
 bool tls1_choose_signature_algorithm(SSL_HANDSHAKE *hs, uint16_t *out);
 
 // tls12_add_verify_sigalgs adds the signature algorithms acceptable for the
-// peer signature to |out|. It returns true on success and false on error.
-bool tls12_add_verify_sigalgs(const SSL *ssl, CBB *out);
+// peer signature to |out|. It returns true on success and false on error. If
+// |for_certs| is true, the potentially more restrictive list of algorithms for
+// certificates is used. Otherwise, the online signature one is used.
+bool tls12_add_verify_sigalgs(const SSL *ssl, CBB *out, bool for_certs);
 
 // tls12_check_peer_sigalg checks if |sigalg| is acceptable for the peer
 // signature. It returns true on success and false on error, setting
@@ -1748,6 +1750,11 @@
 bool tls12_check_peer_sigalg(const SSL *ssl, uint8_t *out_alert,
                              uint16_t sigalg);
 
+// tls12_has_different_verify_sigalgs_for_certs returns whether |ssl| has a
+// different, more restrictive, list of signature algorithms acceptable for the
+// certificate than the online signature.
+bool tls12_has_different_verify_sigalgs_for_certs(const SSL *ssl);
+
 
 // Underdocumented functions.
 //
@@ -2227,6 +2234,10 @@
   // ed25519_enabled is whether Ed25519 is advertised in the handshake.
   bool ed25519_enabled:1;
 
+  // rsa_pss_rsae_certs_enabled is whether rsa_pss_rsae_* are supported by the
+  // certificate verifier.
+  bool rsa_pss_rsae_certs_enabled:1;
+
   // false_start_allowed_without_alpn is whether False Start (if
   // |SSL_MODE_ENABLE_FALSE_START| is enabled) is allowed without ALPN.
   bool false_start_allowed_without_alpn:1;
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 14fb0ff..6d6a41b 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -580,6 +580,8 @@
   // problems, the feature will be removed entirely.
   ret->mode = SSL_MODE_NO_AUTO_CHAIN;
 
+  ret->rsa_pss_rsae_certs_enabled = true;
+
   // Lock the SSL_CTX to the specified version, for compatibility with legacy
   // uses of SSL_METHOD.
   if (!SSL_CTX_set_max_proto_version(ret, method->version) ||
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc
index f862ab4..53e9e11 100644
--- a/ssl/t1_lib.cc
+++ b/ssl/t1_lib.cc
@@ -464,44 +464,78 @@
     SSL_SIGN_RSA_PKCS1_SHA1,
 };
 
-bool tls12_add_verify_sigalgs(const SSL *ssl, CBB *out) {
-  bool use_default = ssl->ctx->num_verify_sigalgs == 0;
-  Span<const uint16_t> sigalgs = kVerifySignatureAlgorithms;
-  if (!use_default) {
-    sigalgs = MakeConstSpan(ssl->ctx->verify_sigalgs,
-                            ssl->ctx->num_verify_sigalgs);
+struct SSLSignatureAlgorithmList {
+  bool Next(uint16_t *out) {
+    while (!list.empty()) {
+      uint16_t sigalg = list[0];
+      list = list.subspan(1);
+      if (skip_ed25519 && sigalg == SSL_SIGN_ED25519) {
+        continue;
+      }
+      if (skip_rsa_pss_rsae && SSL_is_signature_algorithm_rsa_pss(sigalg)) {
+        continue;
+      }
+      *out = sigalg;
+      return true;
+    }
+    return false;
   }
 
-  for (uint16_t sigalg : sigalgs) {
-    if (use_default &&
-        sigalg == SSL_SIGN_ED25519 &&
-        !ssl->ctx->ed25519_enabled) {
-      continue;
+  bool operator==(const SSLSignatureAlgorithmList &other) const {
+    SSLSignatureAlgorithmList a = *this;
+    SSLSignatureAlgorithmList b = other;
+    uint16_t a_val, b_val;
+    while (a.Next(&a_val)) {
+      if (!b.Next(&b_val) ||
+          a_val != b_val) {
+        return false;
+      }
     }
+    return !b.Next(&b_val);
+  }
+
+  bool operator!=(const SSLSignatureAlgorithmList &other) const {
+    return !(*this == other);
+  }
+
+  Span<const uint16_t> list;
+  bool skip_ed25519 = false;
+  bool skip_rsa_pss_rsae = false;
+};
+
+static SSLSignatureAlgorithmList tls12_get_verify_sigalgs(const SSL *ssl,
+                                                          bool for_certs) {
+  SSLSignatureAlgorithmList ret;
+  if (ssl->ctx->num_verify_sigalgs != 0) {
+    ret.list =
+        MakeConstSpan(ssl->ctx->verify_sigalgs, ssl->ctx->num_verify_sigalgs);
+  } else {
+    ret.list = kVerifySignatureAlgorithms;
+    ret.skip_ed25519 = !ssl->ctx->ed25519_enabled;
+  }
+  if (for_certs) {
+    ret.skip_rsa_pss_rsae = !ssl->ctx->rsa_pss_rsae_certs_enabled;
+  }
+  return ret;
+}
+
+bool tls12_add_verify_sigalgs(const SSL *ssl, CBB *out, bool for_certs) {
+  SSLSignatureAlgorithmList list = tls12_get_verify_sigalgs(ssl, for_certs);
+  uint16_t sigalg;
+  while (list.Next(&sigalg)) {
     if (!CBB_add_u16(out, sigalg)) {
       return false;
     }
   }
-
   return true;
 }
 
 bool tls12_check_peer_sigalg(const SSL *ssl, uint8_t *out_alert,
                              uint16_t sigalg) {
-  const uint16_t *sigalgs = kVerifySignatureAlgorithms;
-  size_t num_sigalgs = OPENSSL_ARRAY_SIZE(kVerifySignatureAlgorithms);
-  if (ssl->ctx->num_verify_sigalgs != 0) {
-    sigalgs = ssl->ctx->verify_sigalgs;
-    num_sigalgs = ssl->ctx->num_verify_sigalgs;
-  }
-
-  for (size_t i = 0; i < num_sigalgs; i++) {
-    if (sigalgs == kVerifySignatureAlgorithms &&
-        sigalgs[i] == SSL_SIGN_ED25519 &&
-        !ssl->ctx->ed25519_enabled) {
-      continue;
-    }
-    if (sigalg == sigalgs[i]) {
+  SSLSignatureAlgorithmList list = tls12_get_verify_sigalgs(ssl, false);
+  uint16_t verify_sigalg;
+  while (list.Next(&verify_sigalg)) {
+    if (verify_sigalg == sigalg) {
       return true;
     }
   }
@@ -511,6 +545,11 @@
   return false;
 }
 
+bool tls12_has_different_verify_sigalgs_for_certs(const SSL *ssl) {
+  return tls12_get_verify_sigalgs(ssl, true) !=
+         tls12_get_verify_sigalgs(ssl, false);
+}
+
 // tls_extension represents a TLS extension that is handled internally. The
 // |init| function is called for each handshake, before any other functions of
 // the extension. Then the add and parse callbacks are called as needed.
@@ -985,11 +1024,23 @@
     return true;
   }
 
+  // Prior to TLS 1.3, there was no way to signal different signature algorithm
+  // preferences between the online signature and certificates. If we do not
+  // send the signature_algorithms_cert extension, use the potentially more
+  // restrictive certificate list.
+  //
+  // TODO(davidben): When TLS 1.3 is finalized, we can likely remove the TLS 1.3
+  // check both here and in signature_algorithms_cert. |hs->max_version| is not
+  // the negotiated version. Rather the expectation is that any server consuming
+  // signature algorithms added in TLS 1.3 will also know to look at
+  // signature_algorithms_cert. For now, TLS 1.3 is not quite yet final and it
+  // seems prudent to condition this new extension on it.
+  bool for_certs = hs->max_version < TLS1_3_VERSION;
   CBB contents, sigalgs_cbb;
   if (!CBB_add_u16(out, TLSEXT_TYPE_signature_algorithms) ||
       !CBB_add_u16_length_prefixed(out, &contents) ||
       !CBB_add_u16_length_prefixed(&contents, &sigalgs_cbb) ||
-      !tls12_add_verify_sigalgs(ssl, &sigalgs_cbb) ||
+      !tls12_add_verify_sigalgs(ssl, &sigalgs_cbb, for_certs) ||
       !CBB_flush(out)) {
     return false;
   }
@@ -1016,6 +1067,35 @@
 }
 
 
+// Signature Algorithms for Certificates.
+//
+// https://tools.ietf.org/html/draft-ietf-tls-tls13-23#section-4.2.3
+
+static bool ext_sigalgs_cert_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
+  SSL *const ssl = hs->ssl;
+  // If this extension is omitted, it defaults to the signature_algorithms
+  // extension, so only emit it if the list is different.
+  //
+  // This extension is also new in TLS 1.3, so omit it if TLS 1.3 is disabled.
+  // There is a corresponding version check in |ext_sigalgs_add_clienthello|.
+  if (hs->max_version < TLS1_3_VERSION ||
+      !tls12_has_different_verify_sigalgs_for_certs(ssl)) {
+    return true;
+  }
+
+  CBB contents, sigalgs_cbb;
+  if (!CBB_add_u16(out, TLSEXT_TYPE_signature_algorithms_cert) ||
+      !CBB_add_u16_length_prefixed(out, &contents) ||
+      !CBB_add_u16_length_prefixed(&contents, &sigalgs_cbb) ||
+      !tls12_add_verify_sigalgs(ssl, &sigalgs_cbb, true /* certs */) ||
+      !CBB_flush(out)) {
+    return false;
+  }
+
+  return true;
+}
+
+
 // OCSP Stapling.
 //
 // https://tools.ietf.org/html/rfc6066#section-8
@@ -2726,6 +2806,14 @@
     dont_add_serverhello,
   },
   {
+    TLSEXT_TYPE_signature_algorithms_cert,
+    NULL,
+    ext_sigalgs_cert_add_clienthello,
+    forbid_parse_serverhello,
+    ignore_parse_clienthello,
+    dont_add_serverhello,
+  },
+  {
     TLSEXT_TYPE_status_request,
     NULL,
     ext_ocsp_add_clienthello,
@@ -3776,6 +3864,10 @@
   ctx->ed25519_enabled = !!enabled;
 }
 
+void SSL_CTX_set_rsa_pss_rsae_certs_enabled(SSL_CTX *ctx, int enabled) {
+  ctx->rsa_pss_rsae_certs_enabled = !!enabled;
+}
+
 int SSL_extension_supported(unsigned extension_value) {
   uint32_t index;
   return extension_value == TLSEXT_TYPE_padding ||
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index ccd221f..824dc94 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1288,6 +1288,9 @@
   if (config->enable_ed25519) {
     SSL_CTX_set_ed25519_enabled(ssl_ctx.get(), 1);
   }
+  if (config->no_rsa_pss_rsae_certs) {
+    SSL_CTX_set_rsa_pss_rsae_certs_enabled(ssl_ctx.get(), 0);
+  }
 
   if (!config->verify_prefs.empty()) {
     std::vector<uint16_t> u16s(config->verify_prefs.begin(),
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 8f354e9..e330fa5 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -10,6 +10,7 @@
 	"crypto/ecdsa"
 	"crypto/rand"
 	"crypto/x509"
+	"errors"
 	"fmt"
 	"io"
 	"math/big"
@@ -122,13 +123,13 @@
 	extensionTokenBinding               uint16 = 24
 	extensionQUICTransportParams        uint16 = 26
 	extensionSessionTicket              uint16 = 35
-	extensionPreSharedKey               uint16 = 41    // draft-ietf-tls-tls13-16
-	extensionEarlyData                  uint16 = 42    // draft-ietf-tls-tls13-16
-	extensionSupportedVersions          uint16 = 43    // draft-ietf-tls-tls13-16
-	extensionCookie                     uint16 = 44    // draft-ietf-tls-tls13-16
-	extensionPSKKeyExchangeModes        uint16 = 45    // draft-ietf-tls-tls13-18
-	extensionTicketEarlyDataInfo        uint16 = 46    // draft-ietf-tls-tls13-18
-	extensionCertificateAuthorities     uint16 = 47    // draft-ietf-tls-tls13-21
+	extensionPreSharedKey               uint16 = 41    // draft-ietf-tls-tls13-23
+	extensionEarlyData                  uint16 = 42    // draft-ietf-tls-tls13-23
+	extensionSupportedVersions          uint16 = 43    // draft-ietf-tls-tls13-23
+	extensionCookie                     uint16 = 44    // draft-ietf-tls-tls13-23
+	extensionPSKKeyExchangeModes        uint16 = 45    // draft-ietf-tls-tls13-23
+	extensionCertificateAuthorities     uint16 = 47    // draft-ietf-tls-tls13-23
+	extensionSignatureAlgorithmsCert    uint16 = 50    // draft-ietf-tls-tls13-23
 	extensionKeyShare                   uint16 = 51    // draft-ietf-tls-tls13-23
 	extensionCustom                     uint16 = 1234  // not IANA assigned
 	extensionNextProtoNeg               uint16 = 13172 // not IANA assigned
@@ -531,6 +532,15 @@
 	NumRSABadValues
 )
 
+type RSAPSSSupport int
+
+const (
+	RSAPSSSupportAny RSAPSSSupport = iota
+	RSAPSSSupportNone
+	RSAPSSSupportOnlineSignatureOnly
+	RSAPSSSupportBoth
+)
+
 type ProtocolBugs struct {
 	// InvalidSignature specifies that the signature in a ServerKeyExchange
 	// or CertificateVerify message should be invalid.
@@ -1562,6 +1572,10 @@
 	// SendCompressedCoordinates, if true, causes ECDH key shares over NIST
 	// curves to use compressed coordinates.
 	SendCompressedCoordinates bool
+
+	// ExpectRSAPSSSupport specifies the level of RSA-PSS support expected
+	// from the peer.
+	ExpectRSAPSSSupport RSAPSSSupport
 }
 
 func (c *Config) serverInit() {
@@ -2000,3 +2014,50 @@
 	}
 	return false
 }
+
+func checkRSAPSSSupport(support RSAPSSSupport, sigAlgs, sigAlgsCert []signatureAlgorithm) error {
+	if sigAlgsCert == nil {
+		sigAlgsCert = sigAlgs
+	} else if eqSignatureAlgorithms(sigAlgs, sigAlgsCert) {
+		// The peer should have only sent the list once.
+		return errors.New("tls: signature_algorithms and signature_algorithms_cert extensions were identical")
+	}
+
+	if support == RSAPSSSupportAny {
+		return nil
+	}
+
+	var foundPSS, foundPSSCert bool
+	for _, sigAlg := range sigAlgs {
+		if sigAlg == signatureRSAPSSWithSHA256 || sigAlg == signatureRSAPSSWithSHA384 || sigAlg == signatureRSAPSSWithSHA512 {
+			foundPSS = true
+			break
+		}
+	}
+	for _, sigAlg := range sigAlgsCert {
+		if sigAlg == signatureRSAPSSWithSHA256 || sigAlg == signatureRSAPSSWithSHA384 || sigAlg == signatureRSAPSSWithSHA512 {
+			foundPSSCert = true
+			break
+		}
+	}
+
+	expectPSS := support != RSAPSSSupportNone
+	if foundPSS != expectPSS {
+		if expectPSS {
+			return errors.New("tls: peer did not support PSS")
+		} else {
+			return errors.New("tls: peer unexpectedly supported PSS")
+		}
+	}
+
+	expectPSSCert := support == RSAPSSSupportBoth
+	if foundPSSCert != expectPSSCert {
+		if expectPSSCert {
+			return errors.New("tls: peer did not support PSS in certificates")
+		} else {
+			return errors.New("tls: peer unexpectedly supported PSS in certificates")
+		}
+	}
+
+	return nil
+}
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index d74c953..4178dc1 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -843,6 +843,10 @@
 				return errors.New("tls: expected no certificate_authorities extension")
 			}
 
+			if err := checkRSAPSSSupport(c.config.Bugs.ExpectRSAPSSSupport, certReq.signatureAlgorithms, certReq.signatureAlgorithmsCert); err != nil {
+				return err
+			}
+
 			if c.config.Bugs.IgnorePeerSignatureAlgorithmPreferences {
 				certReq.signatureAlgorithms = c.config.signSignatureAlgorithms()
 			}
@@ -1163,6 +1167,9 @@
 	certReq, ok := msg.(*certificateRequestMsg)
 	if ok {
 		certRequested = true
+		if err := checkRSAPSSSupport(c.config.Bugs.ExpectRSAPSSSupport, certReq.signatureAlgorithms, certReq.signatureAlgorithmsCert); err != nil {
+			return err
+		}
 		if c.config.Bugs.IgnorePeerSignatureAlgorithmPreferences {
 			certReq.signatureAlgorithms = c.config.signSignatureAlgorithms()
 		}
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index b9fb89d..64936b4 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -275,6 +275,7 @@
 	ticketSupported         bool
 	sessionTicket           []uint8
 	signatureAlgorithms     []signatureAlgorithm
+	signatureAlgorithmsCert []signatureAlgorithm
 	supportedVersions       []uint16
 	secureRenegotiation     []byte
 	alpnProtocols           []string
@@ -327,6 +328,7 @@
 		m.ticketSupported == m1.ticketSupported &&
 		bytes.Equal(m.sessionTicket, m1.sessionTicket) &&
 		eqSignatureAlgorithms(m.signatureAlgorithms, m1.signatureAlgorithms) &&
+		eqSignatureAlgorithms(m.signatureAlgorithmsCert, m1.signatureAlgorithmsCert) &&
 		eqUint16s(m.supportedVersions, m1.supportedVersions) &&
 		bytes.Equal(m.secureRenegotiation, m1.secureRenegotiation) &&
 		(m.secureRenegotiation == nil) == (m1.secureRenegotiation == nil) &&
@@ -495,6 +497,14 @@
 			signatureAlgorithms.addU16(uint16(sigAlg))
 		}
 	}
+	if len(m.signatureAlgorithmsCert) > 0 {
+		extensions.addU16(extensionSignatureAlgorithmsCert)
+		signatureAlgorithmsCertExtension := extensions.addU16LengthPrefixed()
+		signatureAlgorithmsCert := signatureAlgorithmsCertExtension.addU16LengthPrefixed()
+		for _, sigAlg := range m.signatureAlgorithmsCert {
+			signatureAlgorithmsCert.addU16(uint16(sigAlg))
+		}
+	}
 	if len(m.supportedVersions) > 0 {
 		extensions.addU16(extensionSupportedVersions)
 		supportedVersionsExtension := extensions.addU16LengthPrefixed()
@@ -618,11 +628,14 @@
 	return m.raw
 }
 
-func parseSignatureAlgorithms(reader *byteReader, out *[]signatureAlgorithm) bool {
+func parseSignatureAlgorithms(reader *byteReader, out *[]signatureAlgorithm, allowEmpty bool) bool {
 	var sigAlgs byteReader
 	if !reader.readU16LengthPrefixed(&sigAlgs) {
 		return false
 	}
+	if !allowEmpty && len(sigAlgs) == 0 {
+		return false
+	}
 	*out = make([]signatureAlgorithm, 0, len(sigAlgs)/2)
 	for len(sigAlgs) > 0 {
 		var v uint16
@@ -676,6 +689,7 @@
 	m.ticketSupported = false
 	m.sessionTicket = nil
 	m.signatureAlgorithms = nil
+	m.signatureAlgorithmsCert = nil
 	m.supportedVersions = nil
 	m.alpnProtocols = nil
 	m.extendedMasterSecret = false
@@ -806,7 +820,11 @@
 			}
 		case extensionSignatureAlgorithms:
 			// https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
-			if !parseSignatureAlgorithms(&body, &m.signatureAlgorithms) || len(body) != 0 {
+			if !parseSignatureAlgorithms(&body, &m.signatureAlgorithms, false) || len(body) != 0 {
+				return false
+			}
+		case extensionSignatureAlgorithmsCert:
+			if !parseSignatureAlgorithms(&body, &m.signatureAlgorithmsCert, false) || len(body) != 0 {
 				return false
 			}
 		case extensionSupportedVersions:
@@ -1839,12 +1857,13 @@
 	// TLS 1.3.
 	hasRequestContext bool
 
-	certificateTypes       []byte
-	requestContext         []byte
-	signatureAlgorithms    []signatureAlgorithm
-	certificateAuthorities [][]byte
-	hasCAExtension         bool
-	customExtension        uint16
+	certificateTypes        []byte
+	requestContext          []byte
+	signatureAlgorithms     []signatureAlgorithm
+	signatureAlgorithmsCert []signatureAlgorithm
+	certificateAuthorities  [][]byte
+	hasCAExtension          bool
+	customExtension         uint16
 }
 
 func (m *certificateRequestMsg) marshal() []byte {
@@ -1869,6 +1888,13 @@
 				signatureAlgorithms.addU16(uint16(sigAlg))
 			}
 		}
+		if len(m.signatureAlgorithmsCert) > 0 {
+			extensions.addU16(extensionSignatureAlgorithmsCert)
+			signatureAlgorithmsCert := extensions.addU16LengthPrefixed().addU16LengthPrefixed()
+			for _, sigAlg := range m.signatureAlgorithmsCert {
+				signatureAlgorithmsCert.addU16(uint16(sigAlg))
+			}
+		}
 		if len(m.certificateAuthorities) > 0 {
 			extensions.addU16(extensionCertificateAuthorities)
 			certificateAuthorities := extensions.addU16LengthPrefixed().addU16LengthPrefixed()
@@ -1939,7 +1965,11 @@
 			}
 			switch extension {
 			case extensionSignatureAlgorithms:
-				if !parseSignatureAlgorithms(&body, &m.signatureAlgorithms) || len(body) != 0 {
+				if !parseSignatureAlgorithms(&body, &m.signatureAlgorithms, false) || len(body) != 0 {
+					return false
+				}
+			case extensionSignatureAlgorithmsCert:
+				if !parseSignatureAlgorithms(&body, &m.signatureAlgorithmsCert, false) || len(body) != 0 {
 					return false
 				}
 			case extensionCertificateAuthorities:
@@ -1953,7 +1983,9 @@
 		if !reader.readU8LengthPrefixedBytes(&m.certificateTypes) {
 			return false
 		}
-		if m.hasSignatureAlgorithm && !parseSignatureAlgorithms(&reader, &m.signatureAlgorithms) {
+		// In TLS 1.2, the supported_signature_algorithms field in
+		// CertificateRequest may be empty.
+		if m.hasSignatureAlgorithm && !parseSignatureAlgorithms(&reader, &m.signatureAlgorithms, true) {
 			return false
 		}
 		if !parseCAs(&reader, &m.certificateAuthorities) ||
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 0c24592..e5f2944 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -341,6 +341,10 @@
 		return fmt.Errorf("tls: expected dummy PQ padding extension of length %d, but got one of length %d", expected, config.Bugs.ExpectDummyPQPaddingLength)
 	}
 
+	if err := checkRSAPSSSupport(config.Bugs.ExpectRSAPSSSupport, hs.clientHello.signatureAlgorithms, hs.clientHello.signatureAlgorithmsCert); err != nil {
+		return err
+	}
+
 	applyBugsToClientHello(hs.clientHello, config)
 
 	return nil
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 683fd3a..cf29956 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -9456,6 +9456,119 @@
 			"-verify-prefs", strconv.Itoa(int(signatureEd25519)),
 		},
 	})
+
+	rsaPSSSupportTests := []struct {
+		name                string
+		expectRSAPSSSupport RSAPSSSupport
+		verifyPrefs         []signatureAlgorithm
+		noCerts             bool
+	}{
+		// By default, RSA-PSS is fully advertised.
+		{
+			name:                "Default",
+			expectRSAPSSSupport: RSAPSSSupportBoth,
+		},
+		// Disabling RSA-PSS certificates makes it online-signature-only.
+		{
+			name:                "Default-NoCerts",
+			expectRSAPSSSupport: RSAPSSSupportOnlineSignatureOnly,
+			noCerts:             true,
+		},
+		// The above also apply if verify preferences were explicitly configured.
+		{
+			name:                "ConfigPSS",
+			expectRSAPSSSupport: RSAPSSSupportBoth,
+			verifyPrefs:         []signatureAlgorithm{signatureRSAPSSWithSHA256, signatureRSAPKCS1WithSHA256, signatureECDSAWithP256AndSHA256},
+		},
+		{
+			name:                "ConfigPSS-NoCerts",
+			expectRSAPSSSupport: RSAPSSSupportOnlineSignatureOnly,
+			verifyPrefs:         []signatureAlgorithm{signatureRSAPSSWithSHA256, signatureRSAPKCS1WithSHA256, signatureECDSAWithP256AndSHA256},
+			noCerts:             true,
+		},
+		// If verify preferences were explicitly configured without RSA-PSS support,
+		// NoCerts is a no-op and the shim correctly only sends one extension.
+		// (This is checked internally in the runner.)
+		{
+			name:                "ConfigNoPSS",
+			expectRSAPSSSupport: RSAPSSSupportNone,
+			verifyPrefs:         []signatureAlgorithm{signatureRSAPKCS1WithSHA256, signatureECDSAWithP256AndSHA256},
+		},
+		{
+			name:                "ConfigNoPSS-NoCerts",
+			expectRSAPSSSupport: RSAPSSSupportNone,
+			verifyPrefs:         []signatureAlgorithm{signatureRSAPKCS1WithSHA256, signatureECDSAWithP256AndSHA256},
+			noCerts:             true,
+		},
+	}
+
+	for _, test := range rsaPSSSupportTests {
+		var pssFlags []string
+		for _, pref := range test.verifyPrefs {
+			pssFlags = append(pssFlags, "-verify-prefs", strconv.Itoa(int(pref)))
+		}
+		if test.noCerts {
+			pssFlags = append(pssFlags, "-no-rsa-pss-rsae-certs")
+		}
+		for _, ver := range tlsVersions {
+			if ver.version < VersionTLS12 {
+				continue
+			}
+
+			// TLS 1.2 cannot express different RSAPSSSupportOnlineSignatureOnly,
+			// so it decays to RSAPSSSupportNone.
+			expect := test.expectRSAPSSSupport
+			if ver.version < VersionTLS13 && expect == RSAPSSSupportOnlineSignatureOnly {
+				expect = RSAPSSSupportNone
+			}
+
+			// If the configuration results in no RSA-PSS support, the handshake won't complete.
+			// (The test, however, still covers the RSA-PSS assertion.)
+			var localError string
+			var shouldFail bool
+			if ver.version >= VersionTLS13 && expect == RSAPSSSupportNone {
+				shouldFail = true
+				localError = "tls: no common signature algorithms"
+			}
+
+			flags := []string{"-max-version", ver.shimFlag(tls)}
+			flags = append(flags, pssFlags...)
+			testCases = append(testCases, testCase{
+				name: fmt.Sprintf("RSAPSSSupport-%s-%s-Client", test.name, ver.name),
+				config: Config{
+					MinVersion:   ver.version,
+					MaxVersion:   ver.version,
+					Certificates: []Certificate{rsaCertificate},
+					Bugs: ProtocolBugs{
+						ExpectRSAPSSSupport: expect,
+					},
+				},
+				tls13Variant:       ver.tls13Variant,
+				flags:              flags,
+				shouldFail:         shouldFail,
+				expectedLocalError: localError,
+			})
+
+			serverFlags := []string{"-require-any-client-certificate"}
+			serverFlags = append(flags, serverFlags...)
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     fmt.Sprintf("RSAPSSSupport-%s-%s-Server", test.name, ver.name),
+				config: Config{
+					MinVersion:   ver.version,
+					MaxVersion:   ver.version,
+					Certificates: []Certificate{rsaCertificate},
+					Bugs: ProtocolBugs{
+						ExpectRSAPSSSupport: expect,
+					},
+				},
+				tls13Variant:       ver.tls13Variant,
+				flags:              serverFlags,
+				shouldFail:         shouldFail,
+				expectedLocalError: localError,
+			})
+		}
+	}
 }
 
 // timeouts is the retransmit schedule for BoringSSL. It doubles and
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index f50251d..f74267d 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -133,6 +133,7 @@
   { "-expect-draft-downgrade", &TestConfig::expect_draft_downgrade },
   { "-handoff", &TestConfig::handoff },
   { "-expect-dummy-pq-padding", &TestConfig::expect_dummy_pq_padding },
+  { "-no-rsa-pss-rsae-certs", &TestConfig::no_rsa_pss_rsae_certs },
 };
 
 const Flag<std::string> kStringFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index fb479d1..95f38e0 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -154,6 +154,7 @@
   int dummy_pq_padding_len = 0;
   bool handoff = false;
   bool expect_dummy_pq_padding = false;
+  bool no_rsa_pss_rsae_certs = false;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_initial,
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index 9d7f5e0..fe0449d 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -619,10 +619,22 @@
         !CBB_add_u16_length_prefixed(&cert_request_extensions,
                                      &sigalg_contents) ||
         !CBB_add_u16_length_prefixed(&sigalg_contents, &sigalgs_cbb) ||
-        !tls12_add_verify_sigalgs(ssl, &sigalgs_cbb)) {
+        !tls12_add_verify_sigalgs(ssl, &sigalgs_cbb,
+                                  false /* online signature */)) {
       return ssl_hs_error;
     }
 
+    if (tls12_has_different_verify_sigalgs_for_certs(ssl)) {
+      if (!CBB_add_u16(&cert_request_extensions,
+                       TLSEXT_TYPE_signature_algorithms_cert) ||
+          !CBB_add_u16_length_prefixed(&cert_request_extensions,
+                                       &sigalg_contents) ||
+          !CBB_add_u16_length_prefixed(&sigalg_contents, &sigalgs_cbb) ||
+          !tls12_add_verify_sigalgs(ssl, &sigalgs_cbb, true /* certs */)) {
+        return ssl_hs_error;
+      }
+    }
+
     if (ssl_has_client_CAs(ssl)) {
       CBB ca_contents;
       if (!CBB_add_u16(&cert_request_extensions,