Bug 1588941 - Send empty client cert msg when signature scheme selection fails. r=mt
authorKevin Jacobs <kjacobs@mozilla.com>
Fri, 07 Aug 2020 16:36:31 +0000
changeset 15730 41ecb7fe55461da66328758a08776f2291ea4d0b
parent 15729 330bdab498a31ddd26d14585e3188aa9d4529d1b
child 15731 c06f22733446c6fb55362b9707fa714c15caf04e
push id3820
push userkjacobs@mozilla.com
push dateFri, 07 Aug 2020 18:11:37 +0000
reviewersmt
bugs1588941
Bug 1588941 - Send empty client cert msg when signature scheme selection fails. r=mt `ssl3_CompleteHandleCertificateRequest` does essentially two things: 1) Calls the `getClientAuthData` hook for certificate selection, and 2) calls `ssl_PickClientSignatureScheme` to select an appropriate signature scheme when a cert is selected. If the first function returns SECFailure, we default to sending an empty certificate message. If the latter fails, however, this bubbles up as a [[ https://searchfox.org/mozilla-central/rev/56bb74ea8e04bdac57c33cbe9b54d889b9262ade/security/nss/lib/ssl/tls13con.c#2670 | fatal error ]] (and an assertion failure) on the connection. Importantly, the signature scheme selection can fail for reasons that should not be considered fatal - notably when an RSA-PSS cert is selected, but the token on which the key resides does not actually support PSS. This patch treats the failure to find a usable signature scheme as a "no certificate" response, rather than killing the connection entirely. Differential Revision: https://phabricator.services.mozilla.com/D85451
gtests/ssl_gtest/ssl_auth_unittest.cc
lib/ssl/ssl3con.c
--- a/gtests/ssl_gtest/ssl_auth_unittest.cc
+++ b/gtests/ssl_gtest/ssl_auth_unittest.cc
@@ -910,16 +910,114 @@ TEST_P(TlsConnectTls12, ClientAuthNoSigA
   server_->RequestClientAuth(true);
 
   ConnectExpectAlert(client_, kTlsAlertHandshakeFailure);
 
   server_->CheckErrorCode(SSL_ERROR_HANDSHAKE_FAILURE_ALERT);
   client_->CheckErrorCode(SSL_ERROR_UNSUPPORTED_SIGNATURE_ALGORITHM);
 }
 
+static SECStatus GetEcClientAuthDataHook(void* self, PRFileDesc* fd,
+                                         CERTDistNames* caNames,
+                                         CERTCertificate** clientCert,
+                                         SECKEYPrivateKey** clientKey) {
+  ScopedCERTCertificate cert;
+  ScopedSECKEYPrivateKey priv;
+  // use a different certificate than TlsAgent::kClient
+  if (!TlsAgent::LoadCertificate(TlsAgent::kServerEcdsa256, &cert, &priv)) {
+    return SECFailure;
+  }
+
+  *clientCert = cert.release();
+  *clientKey = priv.release();
+  return SECSuccess;
+}
+
+TEST_P(TlsConnectTls12Plus, ClientAuthDisjointSchemes) {
+  EnsureTlsSetup();
+  client_->SetupClientAuth();
+  server_->RequestClientAuth(true);
+
+  SSLSignatureScheme server_scheme = ssl_sig_rsa_pss_rsae_sha256;
+  std::vector<SSLSignatureScheme> client_schemes{
+      ssl_sig_rsa_pss_rsae_sha256, ssl_sig_ecdsa_secp256r1_sha256};
+  SECStatus rv =
+      SSL_SignatureSchemePrefSet(server_->ssl_fd(), &server_scheme, 1);
+  EXPECT_EQ(SECSuccess, rv);
+  rv = SSL_SignatureSchemePrefSet(
+      client_->ssl_fd(), client_schemes.data(),
+      static_cast<unsigned int>(client_schemes.size()));
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Select an EC cert that's incompatible with server schemes.
+  EXPECT_EQ(SECSuccess,
+            SSL_GetClientAuthDataHook(client_->ssl_fd(),
+                                      GetEcClientAuthDataHook, nullptr));
+
+  StartConnect();
+  client_->Handshake();  // CH
+  server_->Handshake();  // SH
+  client_->Handshake();
+  if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
+    ASSERT_EQ(TlsAgent::STATE_CONNECTED, client_->state());
+    ExpectAlert(server_, kTlsAlertCertificateRequired);
+    server_->Handshake();  // Alert
+    server_->CheckErrorCode(SSL_ERROR_NO_CERTIFICATE);
+    client_->Handshake();  // Receive Alert
+    client_->CheckErrorCode(SSL_ERROR_RX_CERTIFICATE_REQUIRED_ALERT);
+  } else {
+    ASSERT_EQ(TlsAgent::STATE_CONNECTING, client_->state());
+    ExpectAlert(server_, kTlsAlertBadCertificate);
+    server_->Handshake();  // Alert
+    server_->CheckErrorCode(SSL_ERROR_NO_CERTIFICATE);
+    client_->Handshake();  // Receive Alert
+    client_->CheckErrorCode(SSL_ERROR_BAD_CERT_ALERT);
+  }
+}
+
+TEST_F(TlsConnectStreamTls13, PostHandshakeAuthDisjointSchemes) {
+  EnsureTlsSetup();
+  SSLSignatureScheme server_scheme = ssl_sig_rsa_pss_rsae_sha256;
+  std::vector<SSLSignatureScheme> client_schemes{
+      ssl_sig_rsa_pss_rsae_sha256, ssl_sig_ecdsa_secp256r1_sha256};
+  SECStatus rv =
+      SSL_SignatureSchemePrefSet(server_->ssl_fd(), &server_scheme, 1);
+  EXPECT_EQ(SECSuccess, rv);
+  rv = SSL_SignatureSchemePrefSet(
+      client_->ssl_fd(), client_schemes.data(),
+      static_cast<unsigned int>(client_schemes.size()));
+  EXPECT_EQ(SECSuccess, rv);
+
+  client_->SetupClientAuth();
+  client_->SetOption(SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE);
+
+  // Select an EC cert that's incompatible with server schemes.
+  EXPECT_EQ(SECSuccess,
+            SSL_GetClientAuthDataHook(client_->ssl_fd(),
+                                      GetEcClientAuthDataHook, nullptr));
+
+  Connect();
+
+  // Send CertificateRequest.
+  EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd()))
+      << "Unexpected error: " << PORT_ErrorToName(PORT_GetError());
+
+  // Need to do a round-trip so that the post-handshake message is
+  // handled on both client and server.
+  server_->SendData(50);
+  client_->ReadBytes(50);
+  client_->SendData(50);
+  server_->ReadBytes(50);
+
+  ScopedCERTCertificate cert1(SSL_PeerCertificate(server_->ssl_fd()));
+  ASSERT_EQ(nullptr, cert1.get());
+  ScopedCERTCertificate cert2(SSL_LocalCertificate(client_->ssl_fd()));
+  ASSERT_EQ(nullptr, cert2.get());
+}
+
 static const SSLSignatureScheme kSignatureSchemeEcdsaSha384[] = {
     ssl_sig_ecdsa_secp384r1_sha384};
 static const SSLSignatureScheme kSignatureSchemeEcdsaSha256[] = {
     ssl_sig_ecdsa_secp256r1_sha256};
 static const SSLSignatureScheme kSignatureSchemeRsaSha384[] = {
     ssl_sig_rsa_pkcs1_sha384};
 static const SSLSignatureScheme kSignatureSchemeRsaSha256[] = {
     ssl_sig_rsa_pkcs1_sha256};
--- a/lib/ssl/ssl3con.c
+++ b/lib/ssl/ssl3con.c
@@ -7648,51 +7648,52 @@ ssl3_CompleteHandleCertificateRequest(ss
         case SECWouldBlock: /* getClientAuthData has put up a dialog box. */
             ssl3_SetAlwaysBlock(ss);
             break; /* not an error */
 
         case SECSuccess:
             /* check what the callback function returned */
             if ((!ss->ssl3.clientCertificate) || (!ss->ssl3.clientPrivateKey)) {
                 /* we are missing either the key or cert */
-                if (ss->ssl3.clientCertificate) {
-                    /* got a cert, but no key - free it */
-                    CERT_DestroyCertificate(ss->ssl3.clientCertificate);
-                    ss->ssl3.clientCertificate = NULL;
-                }
-                if (ss->ssl3.clientPrivateKey) {
-                    /* got a key, but no cert - free it */
-                    SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
-                    ss->ssl3.clientPrivateKey = NULL;
-                }
                 goto send_no_certificate;
             }
             /* Setting ssl3.clientCertChain non-NULL will cause
              * ssl3_HandleServerHelloDone to call SendCertificate.
              */
             ss->ssl3.clientCertChain = CERT_CertChainFromCert(
                 ss->ssl3.clientCertificate,
                 certUsageSSLClient, PR_FALSE);
             if (ss->ssl3.clientCertChain == NULL) {
-                CERT_DestroyCertificate(ss->ssl3.clientCertificate);
-                ss->ssl3.clientCertificate = NULL;
-                SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
-                ss->ssl3.clientPrivateKey = NULL;
                 goto send_no_certificate;
             }
             if (ss->ssl3.hs.hashType == handshake_hash_record ||
                 ss->ssl3.hs.hashType == handshake_hash_single) {
                 rv = ssl_PickClientSignatureScheme(ss, signatureSchemes,
                                                    signatureSchemeCount);
+                if (rv != SECSuccess) {
+                    /* This should only happen if our schemes changed or
+                     * if an RSA-PSS cert was selected, but the token
+                     * does not support PSS schemes. */
+                    goto send_no_certificate;
+                }
             }
             break; /* not an error */
 
         case SECFailure:
         default:
         send_no_certificate:
+            CERT_DestroyCertificate(ss->ssl3.clientCertificate);
+            SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
+            ss->ssl3.clientCertificate = NULL;
+            ss->ssl3.clientPrivateKey = NULL;
+            if (ss->ssl3.clientCertChain) {
+                CERT_DestroyCertificateList(ss->ssl3.clientCertChain);
+                ss->ssl3.clientCertChain = NULL;
+            }
+
             if (ss->version > SSL_LIBRARY_VERSION_3_0) {
                 ss->ssl3.sendEmptyCert = PR_TRUE;
             } else {
                 (void)SSL3_SendAlert(ss, alert_warning, no_certificate);
             }
             rv = SECSuccess;
             break;
     }