Send a consistent alert when the peer sends a bad signature algorithm

I noticed that runner tests had a very weird test expectation on the
alerts sent around sigalg failures. I think this was an (unimportant)
bug on our end.

If the peer picks a sigalg that we didn't advertise, we send
illegal_parameter. However, it if picks an advertised sigalg that is
invalid in context (protocol version, public key), we end up catching it
very late in ssl_public_key_verify (by way of setup_ctx) and sending
decrypt_error.

Instead, have tls12_check_peer_sigalg check this so we consistently send
illegal_parameter. (Probably this should all fold into
ssl_public_key_verify with an alert out_param, but so it goes.)

Change-Id: I09fb84e9c1ee39b2683fa0b67dd6135d31f51c97
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/69367
Commit-Queue: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/ssl/extensions.cc b/ssl/extensions.cc
index 8b2de59..586edbd 100644
--- a/ssl/extensions.cc
+++ b/ssl/extensions.cc
@@ -441,16 +441,18 @@
 }
 
 bool tls12_check_peer_sigalg(const SSL_HANDSHAKE *hs, uint8_t *out_alert,
-                             uint16_t sigalg) {
-  for (uint16_t verify_sigalg : tls12_get_verify_sigalgs(hs)) {
-    if (verify_sigalg == sigalg) {
-      return true;
-    }
+                             uint16_t sigalg, EVP_PKEY *pkey) {
+  // The peer must have selected an algorithm that is consistent with its public
+  // key, the TLS version, and what we advertised.
+  Span<const uint16_t> sigalgs = tls12_get_verify_sigalgs(hs);
+  if (std::find(sigalgs.begin(), sigalgs.end(), sigalg) == sigalgs.end() ||
+      !ssl_pkey_supports_algorithm(hs->ssl, pkey, sigalg)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SIGNATURE_TYPE);
+    *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+    return false;
   }
 
-  OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SIGNATURE_TYPE);
-  *out_alert = SSL_AD_ILLEGAL_PARAMETER;
-  return false;
+  return true;
 }
 
 // tls_extension represents a TLS extension that is handled internally.
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index b958dce..f674515 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -1169,7 +1169,8 @@
         return ssl_hs_error;
       }
       uint8_t alert = SSL_AD_DECODE_ERROR;
-      if (!tls12_check_peer_sigalg(hs, &alert, signature_algorithm)) {
+      if (!tls12_check_peer_sigalg(hs, &alert, signature_algorithm,
+                                   hs->peer_pubkey.get())) {
         ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
         return ssl_hs_error;
       }
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc
index 1a25ea7..afa0927 100644
--- a/ssl/handshake_server.cc
+++ b/ssl/handshake_server.cc
@@ -1650,7 +1650,8 @@
       return ssl_hs_error;
     }
     uint8_t alert = SSL_AD_DECODE_ERROR;
-    if (!tls12_check_peer_sigalg(hs, &alert, signature_algorithm)) {
+    if (!tls12_check_peer_sigalg(hs, &alert, signature_algorithm,
+                                 hs->peer_pubkey.get())) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
       return ssl_hs_error;
     }
diff --git a/ssl/internal.h b/ssl/internal.h
index 5744dfe..7145d13 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -2454,10 +2454,10 @@
 bool tls12_add_verify_sigalgs(const SSL_HANDSHAKE *hs, CBB *out);
 
 // tls12_check_peer_sigalg checks if |sigalg| is acceptable for the peer
-// signature. It returns true on success and false on error, setting
+// signature from |pkey|. It returns true on success and false on error, setting
 // |*out_alert| to an alert to send.
 bool tls12_check_peer_sigalg(const SSL_HANDSHAKE *hs, uint8_t *out_alert,
-                             uint16_t sigalg);
+                             uint16_t sigalg, EVP_PKEY *pkey);
 
 
 // Underdocumented functions.
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 39b7611..2e1b407 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -10105,12 +10105,12 @@
 				signError = ":NO_COMMON_SIGNATURE_ALGORITHMS:"
 				signLocalError = "remote error: handshake failure"
 				verifyError = ":WRONG_SIGNATURE_TYPE:"
-				verifyLocalError = "remote error"
+				verifyLocalError = "remote error: illegal parameter"
 				rejectByDefault = true
 			}
 			if rejectByDefault {
 				defaultError = ":WRONG_SIGNATURE_TYPE:"
-				defaultLocalError = "remote error"
+				defaultLocalError = "remote error: illegal parameter"
 			}
 
 			suffix := "-" + alg.name + "-" + ver.name
diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc
index 4a9b78e..f6d9547 100644
--- a/ssl/tls13_both.cc
+++ b/ssl/tls13_both.cc
@@ -335,7 +335,8 @@
   }
 
   uint8_t alert = SSL_AD_DECODE_ERROR;
-  if (!tls12_check_peer_sigalg(hs, &alert, signature_algorithm)) {
+  if (!tls12_check_peer_sigalg(hs, &alert, signature_algorithm,
+                               hs->peer_pubkey.get())) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return false;
   }