Support OpenSSL APIs SSL[_CTX]_set1_sigalgs[_list].

These functions can be used to configure the signature algorithms. One
of them is a string mini-languaging parsing function, which we generally
dislike because it defeats static analysis. However, some dependent
projects (in this case TensorFlow) need it and we also dislike making
people patch.

Change-Id: I13f990c896a7f7332d78b1c351357d418ade8d11
Reviewed-on: https://boringssl-review.googlesource.com/30304
Reviewed-by: Steven Valdez <svaldez@google.com>
diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata
index e788f0b..46ff44f 100644
--- a/crypto/err/ssl.errordata
+++ b/crypto/err/ssl.errordata
@@ -54,6 +54,7 @@
 SSL,143,DTLS_MESSAGE_TOO_BIG
 SSL,257,DUPLICATE_EXTENSION
 SSL,264,DUPLICATE_KEY_SHARE
+SSL,296,DUPLICATE_SIGNATURE_ALGORITHM
 SSL,283,EARLY_DATA_NOT_IN_USE
 SSL,144,ECC_CERT_NOT_FOR_SIGNING
 SSL,282,EMPTY_HELLO_RETRY_REQUEST
@@ -77,6 +78,7 @@
 SSL,159,INVALID_MESSAGE
 SSL,251,INVALID_OUTER_RECORD_TYPE
 SSL,269,INVALID_SCT_LIST
+SSL,295,INVALID_SIGNATURE_ALGORITHM
 SSL,160,INVALID_SSL_SESSION
 SSL,161,INVALID_TICKET_KEYS_LENGTH
 SSL,162,LENGTH_MISMATCH
diff --git a/fuzz/ssl_ctx_api.cc b/fuzz/ssl_ctx_api.cc
index ac6202d7..c050770 100644
--- a/fuzz/ssl_ctx_api.cc
+++ b/fuzz/ssl_ctx_api.cc
@@ -475,6 +475,12 @@
         }
         SSL_CTX_set_grease_enabled(ctx, b);
       },
+      [](SSL_CTX *ctx, CBS *cbs) {
+        SSL_CTX_set1_sigalgs(ctx, (const int *)CBS_data(cbs), CBS_len(cbs) / 2);
+      },
+      [](SSL_CTX *ctx, CBS *cbs) {
+        SSL_CTX_set1_sigalgs_list(ctx, (const char *) CBS_data(cbs));
+      },
   };
 
   bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
diff --git a/include/openssl/evp.h b/include/openssl/evp.h
index c187ac1..7994b84 100644
--- a/include/openssl/evp.h
+++ b/include/openssl/evp.h
@@ -169,6 +169,7 @@
 
 #define EVP_PKEY_NONE NID_undef
 #define EVP_PKEY_RSA NID_rsaEncryption
+#define EVP_PKEY_RSA_PSS NID_rsassaPss
 #define EVP_PKEY_DSA NID_dsa
 #define EVP_PKEY_EC NID_X9_62_id_ecPublicKey
 #define EVP_PKEY_ED25519 NID_ED25519
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index cf424a7..a24af46 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3920,6 +3920,54 @@
                                             DH *(*cb)(SSL *ssl, int is_export,
                                                       int keylength));
 
+// SSL_CTX_set1_sigalgs takes |num_values| ints and interprets them as pairs
+// where the first is the nid of a hash function and the second is an
+// |EVP_PKEY_*| value. It configures the signature algorithm preferences for
+// |ctx| based on them and returns one on success or zero on error.
+//
+// This API is compatible with OpenSSL. However, BoringSSL-specific code should
+// prefer |SSL_CTX_set_signing_algorithm_prefs| because it's clearer and it's
+// more convenient to codesearch for specific algorithm values.
+OPENSSL_EXPORT int SSL_CTX_set1_sigalgs(SSL_CTX *ctx, const int *values,
+                                        size_t num_values);
+
+// SSL_set1_sigalgs takes |num_values| ints and interprets them as pairs where
+// the first is the nid of a hash function and the second is an |EVP_PKEY_*|
+// value. It configures the signature algorithm preferences for |ssl| based on
+// them and returns one on success or zero on error.
+//
+// This API is compatible with OpenSSL. However, BoringSSL-specific code should
+// prefer |SSL_CTX_set_signing_algorithm_prefs| because it's clearer and it's
+// more convenient to codesearch for specific algorithm values.
+OPENSSL_EXPORT int SSL_set1_sigalgs(SSL *ssl, const int *values,
+                                    size_t num_values);
+
+// SSL_CTX_set1_sigalgs_list takes a textual specification of a set of signature
+// algorithms and configures them on |ctx|. It returns one on success and zero
+// on error. See
+// https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_set1_sigalgs_list.html for
+// a description of the text format. Also note that TLS 1.3 names (e.g.
+// "rsa_pkcs1_md5_sha1") can also be used (as in OpenSSL, although OpenSSL
+// doesn't document that).
+//
+// This API is compatible with OpenSSL. However, BoringSSL-specific code should
+// prefer |SSL_CTX_set_signing_algorithm_prefs| because it's clearer and it's
+// more convenient to codesearch for specific algorithm values.
+OPENSSL_EXPORT int SSL_CTX_set1_sigalgs_list(SSL_CTX *ctx, const char *str);
+
+// SSL_set1_sigalgs_list takes a textual specification of a set of signature
+// algorithms and configures them on |ssl|. It returns one on success and zero
+// on error. See
+// https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_set1_sigalgs_list.html for
+// a description of the text format. Also note that TLS 1.3 names (e.g.
+// "rsa_pkcs1_md5_sha1") can also be used (as in OpenSSL, although OpenSSL
+// doesn't document that).
+//
+// This API is compatible with OpenSSL. However, BoringSSL-specific code should
+// prefer |SSL_CTX_set_signing_algorithm_prefs| because it's clearer and it's
+// more convenient to codesearch for specific algorithm values.
+OPENSSL_EXPORT int SSL_set1_sigalgs_list(SSL *ssl, const char *str);
+
 
 #define SSL_set_app_data(s, arg) (SSL_set_ex_data(s, 0, (char *)(arg)))
 #define SSL_get_app_data(s) (SSL_get_ex_data(s, 0))
@@ -4730,6 +4778,8 @@
 #define SSL_R_CERT_DECOMPRESSION_FAILED 292
 #define SSL_R_UNCOMPRESSED_CERT_TOO_LARGE 293
 #define SSL_R_UNKNOWN_CERT_COMPRESSION_ALG 294
+#define SSL_R_INVALID_SIGNATURE_ALGORITHM 295
+#define SSL_R_DUPLICATE_SIGNATURE_ALGORITHM 296
 #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000
 #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010
 #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020
diff --git a/ssl/internal.h b/ssl/internal.h
index 56cbd3d..cc3e075 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -2416,6 +2416,10 @@
   // Contains the QUIC transport params that this endpoint will send.
   Array<uint8_t> quic_transport_params;
 
+  // verify_sigalgs, if not empty, is the set of signature algorithms
+  // accepted from the peer in decreasing order of preference.
+  Array<uint16_t> verify_sigalgs;
+
   // srtp_profiles is the list of configured SRTP protection profiles for
   // DTLS-SRTP.
   UniquePtr<STACK_OF(SRTP_PROTECTION_PROFILE)> srtp_profiles;
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 9084610..36c26cd 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -671,7 +671,8 @@
 
   if (!ssl->config->supported_group_list.CopyFrom(ctx->supported_group_list) ||
       !ssl->config->alpn_client_proto_list.CopyFrom(
-          ctx->alpn_client_proto_list)) {
+          ctx->alpn_client_proto_list) ||
+      !ssl->config->verify_sigalgs.CopyFrom(ctx->verify_sigalgs)) {
     return nullptr;
   }
 
diff --git a/ssl/ssl_privkey.cc b/ssl/ssl_privkey.cc
index f5c387b..fecac39 100644
--- a/ssl/ssl_privkey.cc
+++ b/ssl/ssl_privkey.cc
@@ -409,38 +409,49 @@
   ctx->cert->key_method = key_method;
 }
 
+static constexpr size_t kMaxSignatureAlgorithmNameLen = 23;
+
+// This was "constexpr" rather than "const", but that triggered a bug in MSVC
+// where it didn't pad the strings to the correct length.
+static const struct {
+  uint16_t signature_algorithm;
+  const char name[kMaxSignatureAlgorithmNameLen];
+} kSignatureAlgorithmNames[] = {
+    {SSL_SIGN_RSA_PKCS1_MD5_SHA1, "rsa_pkcs1_md5_sha1"},
+    {SSL_SIGN_RSA_PKCS1_SHA1, "rsa_pkcs1_sha1"},
+    {SSL_SIGN_RSA_PKCS1_SHA256, "rsa_pkcs1_sha256"},
+    {SSL_SIGN_RSA_PKCS1_SHA384, "rsa_pkcs1_sha384"},
+    {SSL_SIGN_RSA_PKCS1_SHA512, "rsa_pkcs1_sha512"},
+    {SSL_SIGN_ECDSA_SHA1, "ecdsa_sha1"},
+    {SSL_SIGN_ECDSA_SECP256R1_SHA256, "ecdsa_secp256r1_sha256"},
+    {SSL_SIGN_ECDSA_SECP384R1_SHA384, "ecdsa_secp384r1_sha384"},
+    {SSL_SIGN_ECDSA_SECP521R1_SHA512, "ecdsa_secp521r1_sha512"},
+    {SSL_SIGN_RSA_PSS_RSAE_SHA256, "rsa_pss_rsae_sha256"},
+    {SSL_SIGN_RSA_PSS_RSAE_SHA384, "rsa_pss_rsae_sha384"},
+    {SSL_SIGN_RSA_PSS_RSAE_SHA512, "rsa_pss_rsae_sha512"},
+    {SSL_SIGN_ED25519, "ed25519"},
+};
+
 const char *SSL_get_signature_algorithm_name(uint16_t sigalg,
                                              int include_curve) {
-  switch (sigalg) {
-    case SSL_SIGN_RSA_PKCS1_MD5_SHA1:
-      return "rsa_pkcs1_md5_sha1";
-    case SSL_SIGN_RSA_PKCS1_SHA1:
-      return "rsa_pkcs1_sha1";
-    case SSL_SIGN_RSA_PKCS1_SHA256:
-      return "rsa_pkcs1_sha256";
-    case SSL_SIGN_RSA_PKCS1_SHA384:
-      return "rsa_pkcs1_sha384";
-    case SSL_SIGN_RSA_PKCS1_SHA512:
-      return "rsa_pkcs1_sha512";
-    case SSL_SIGN_ECDSA_SHA1:
-      return "ecdsa_sha1";
-    case SSL_SIGN_ECDSA_SECP256R1_SHA256:
-      return include_curve ? "ecdsa_secp256r1_sha256" : "ecdsa_sha256";
-    case SSL_SIGN_ECDSA_SECP384R1_SHA384:
-      return include_curve ? "ecdsa_secp384r1_sha384" : "ecdsa_sha384";
-    case SSL_SIGN_ECDSA_SECP521R1_SHA512:
-      return include_curve ? "ecdsa_secp521r1_sha512" : "ecdsa_sha512";
-    case SSL_SIGN_RSA_PSS_RSAE_SHA256:
-      return "rsa_pss_rsae_sha256";
-    case SSL_SIGN_RSA_PSS_RSAE_SHA384:
-      return "rsa_pss_rsae_sha384";
-    case SSL_SIGN_RSA_PSS_RSAE_SHA512:
-      return "rsa_pss_rsae_sha512";
-    case SSL_SIGN_ED25519:
-      return "ed25519";
-    default:
-      return NULL;
+  if (!include_curve) {
+    switch (sigalg) {
+      case SSL_SIGN_ECDSA_SECP256R1_SHA256:
+        return "ecdsa_sha256";
+      case SSL_SIGN_ECDSA_SECP384R1_SHA384:
+        return "ecdsa_sha384";
+      case SSL_SIGN_ECDSA_SECP521R1_SHA512:
+        return "ecdsa_sha512";
+    }
   }
+
+  for (const auto &candidate : kSignatureAlgorithmNames) {
+    if (candidate.signature_algorithm == sigalg) {
+      return candidate.name;
+    }
+  }
+
+  return NULL;
 }
 
 int SSL_get_signature_algorithm_key_type(uint16_t sigalg) {
@@ -474,6 +485,315 @@
   return ssl->config->cert->sigalgs.CopyFrom(MakeConstSpan(prefs, num_prefs));
 }
 
+static constexpr struct {
+  int pkey_type;
+  int hash_nid;
+  uint16_t signature_algorithm;
+} kSignatureAlgorithmsMapping[] = {
+    {EVP_PKEY_RSA, NID_sha1, SSL_SIGN_RSA_PKCS1_SHA1},
+    {EVP_PKEY_RSA, NID_sha256, SSL_SIGN_RSA_PKCS1_SHA256},
+    {EVP_PKEY_RSA, NID_sha384, SSL_SIGN_RSA_PKCS1_SHA384},
+    {EVP_PKEY_RSA, NID_sha512, SSL_SIGN_RSA_PKCS1_SHA512},
+    {EVP_PKEY_RSA_PSS, NID_sha256, SSL_SIGN_RSA_PSS_RSAE_SHA256},
+    {EVP_PKEY_RSA_PSS, NID_sha384, SSL_SIGN_RSA_PSS_RSAE_SHA384},
+    {EVP_PKEY_RSA_PSS, NID_sha512, SSL_SIGN_RSA_PSS_RSAE_SHA512},
+    {EVP_PKEY_EC, NID_sha1, SSL_SIGN_ECDSA_SHA1},
+    {EVP_PKEY_EC, NID_sha256, SSL_SIGN_ECDSA_SECP256R1_SHA256},
+    {EVP_PKEY_EC, NID_sha384, SSL_SIGN_ECDSA_SECP384R1_SHA384},
+    {EVP_PKEY_EC, NID_sha512, SSL_SIGN_ECDSA_SECP521R1_SHA512},
+    {EVP_PKEY_ED25519, NID_undef, SSL_SIGN_ED25519},
+};
+
+static bool parse_sigalg_pairs(Array<uint16_t> *out, const int *values,
+                               size_t num_values) {
+  if ((num_values & 1) == 1) {
+    return false;
+  }
+
+  const size_t num_pairs = num_values / 2;
+  if (!out->Init(num_pairs)) {
+    return false;
+  }
+
+  for (size_t i = 0; i < num_values; i += 2) {
+    const int hash_nid = values[i];
+    const int pkey_type = values[i+1];
+
+    bool found = false;
+    for (const auto &candidate : kSignatureAlgorithmsMapping) {
+      if (candidate.pkey_type == pkey_type && candidate.hash_nid == hash_nid) {
+        (*out)[i / 2] = candidate.signature_algorithm;
+        found = true;
+        break;
+      }
+    }
+
+    if (!found) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+      ERR_add_error_dataf("unknown hash:%d pkey:%d", hash_nid, pkey_type);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static int compare_uint16_t(const void *p1, const void *p2) {
+  uint16_t u1 = *((const uint16_t *)p1);
+  uint16_t u2 = *((const uint16_t *)p2);
+  if (u1 < u2) {
+    return -1;
+  } else if (u1 > u2) {
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+static bool sigalgs_unique(Span<const uint16_t> in_sigalgs) {
+  Array<uint16_t> sigalgs;
+  if (!sigalgs.CopyFrom(in_sigalgs)) {
+    return false;
+  }
+
+  qsort(sigalgs.data(), sigalgs.size(), sizeof(uint16_t), compare_uint16_t);
+
+  for (size_t i = 1; i < sigalgs.size(); i++) {
+    if (sigalgs[i - 1] == sigalgs[i]) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DUPLICATE_SIGNATURE_ALGORITHM);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+int SSL_CTX_set1_sigalgs(SSL_CTX *ctx, const int *values, size_t num_values) {
+  Array<uint16_t> sigalgs;
+  if (!parse_sigalg_pairs(&sigalgs, values, num_values) ||
+      !sigalgs_unique(sigalgs)) {
+    return 0;
+  }
+
+  if (!SSL_CTX_set_signing_algorithm_prefs(ctx, sigalgs.data(),
+                                           sigalgs.size()) ||
+      !ctx->verify_sigalgs.CopyFrom(sigalgs)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+int SSL_set1_sigalgs(SSL *ssl, const int *values, size_t num_values) {
+  if (!ssl->config) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  Array<uint16_t> sigalgs;
+  if (!parse_sigalg_pairs(&sigalgs, values, num_values) ||
+      !sigalgs_unique(sigalgs)) {
+    return 0;
+  }
+
+  if (!SSL_set_signing_algorithm_prefs(ssl, sigalgs.data(), sigalgs.size()) ||
+      !ssl->config->verify_sigalgs.CopyFrom(sigalgs)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+static bool parse_sigalgs_list(Array<uint16_t> *out, const char *str) {
+  // str looks like "RSA+SHA1:ECDSA+SHA256:ecdsa_secp256r1_sha256".
+
+  // Count colons to give the number of output elements from any successful
+  // parse.
+  size_t num_elements = 1;
+  size_t len = 0;
+  for (const char *p = str; *p; p++) {
+    len++;
+    if (*p == ':') {
+      num_elements++;
+    }
+  }
+
+  if (!out->Init(num_elements)) {
+    return false;
+  }
+  size_t out_i = 0;
+
+  enum {
+    pkey_or_name,
+    hash_name,
+  } state = pkey_or_name;
+
+  char buf[kMaxSignatureAlgorithmNameLen];
+  // buf_used is always < sizeof(buf). I.e. it's always safe to write
+  // buf[buf_used] = 0.
+  size_t buf_used = 0;
+
+  int pkey_type = 0, hash_nid = 0;
+
+  // Note that the loop runs to len+1, i.e. it'll process the terminating NUL.
+  for (size_t offset = 0; offset < len+1; offset++) {
+    const char c = str[offset];
+
+    switch (c) {
+      case '+':
+        if (state == hash_name) {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("+ found in hash name at offset %zu", offset);
+          return false;
+        }
+        if (buf_used == 0) {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("empty public key type at offset %zu", offset);
+          return false;
+        }
+        buf[buf_used] = 0;
+
+        if (strcmp(buf, "RSA") == 0) {
+          pkey_type = EVP_PKEY_RSA;
+        } else if (strcmp(buf, "RSA-PSS") == 0 ||
+                   strcmp(buf, "PSS") == 0) {
+          pkey_type = EVP_PKEY_RSA_PSS;
+        } else if (strcmp(buf, "ECDSA") == 0) {
+          pkey_type = EVP_PKEY_EC;
+        } else {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("unknown public key type '%s'", buf);
+          return false;
+        }
+
+        state = hash_name;
+        buf_used = 0;
+        break;
+
+      case ':':
+        OPENSSL_FALLTHROUGH;
+      case 0:
+        if (buf_used == 0) {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("empty element at offset %zu", offset);
+          return false;
+        }
+
+        buf[buf_used] = 0;
+
+        if (state == pkey_or_name) {
+          // No '+' was seen thus this is a TLS 1.3-style name.
+          bool found = false;
+          for (const auto &candidate : kSignatureAlgorithmNames) {
+            if (strcmp(candidate.name, buf) == 0) {
+              assert(out_i < num_elements);
+              (*out)[out_i++] = candidate.signature_algorithm;
+              found = true;
+              break;
+            }
+          }
+
+          if (!found) {
+            OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+            ERR_add_error_dataf("unknown signature algorithm '%s'", buf);
+            return false;
+          }
+        } else {
+          if (strcmp(buf, "SHA1") == 0) {
+            hash_nid = NID_sha1;
+          } else if (strcmp(buf, "SHA256") == 0) {
+            hash_nid = NID_sha256;
+          } else if (strcmp(buf, "SHA384") == 0) {
+            hash_nid = NID_sha384;
+          } else if (strcmp(buf, "SHA512") == 0) {
+            hash_nid = NID_sha512;
+          } else {
+            OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+            ERR_add_error_dataf("unknown hash function '%s'", buf);
+            return false;
+          }
+
+          bool found = false;
+          for (const auto &candidate : kSignatureAlgorithmsMapping) {
+            if (candidate.pkey_type == pkey_type &&
+                candidate.hash_nid == hash_nid) {
+              assert(out_i < num_elements);
+              (*out)[out_i++] = candidate.signature_algorithm;
+              found = true;
+              break;
+            }
+          }
+
+          if (!found) {
+            OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+            ERR_add_error_dataf("unknown pkey:%d hash:%s", pkey_type, buf);
+            return false;
+          }
+        }
+
+        state = pkey_or_name;
+        buf_used = 0;
+        break;
+
+      default:
+        if (buf_used == sizeof(buf) - 1) {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("substring too long at offset %zu", offset);
+          return false;
+        }
+
+        if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
+            (c >= 'A' && c <= 'Z') || c == '-' || c == '_') {
+          buf[buf_used++] = c;
+        } else {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("invalid character 0x%02x at offest %zu", c,
+                              offset);
+          return false;
+        }
+    }
+  }
+
+  assert(out_i == out->size());
+  return true;
+}
+
+int SSL_CTX_set1_sigalgs_list(SSL_CTX *ctx, const char *str) {
+  Array<uint16_t> sigalgs;
+  if (!parse_sigalgs_list(&sigalgs, str) ||
+      !sigalgs_unique(sigalgs)) {
+    return 0;
+  }
+
+  if (!SSL_CTX_set_signing_algorithm_prefs(ctx, sigalgs.data(),
+                                           sigalgs.size()) ||
+      !ctx->verify_sigalgs.CopyFrom(sigalgs)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+int SSL_set1_sigalgs_list(SSL *ssl, const char *str) {
+  if (!ssl->config) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  Array<uint16_t> sigalgs;
+  if (!parse_sigalgs_list(&sigalgs, str) ||
+      !sigalgs_unique(sigalgs)) {
+    return 0;
+  }
+
+  if (!SSL_set_signing_algorithm_prefs(ssl, sigalgs.data(), sigalgs.size()) ||
+      !ssl->config->verify_sigalgs.CopyFrom(sigalgs)) {
+    return 0;
+  }
+
+  return 1;
+}
+
 int SSL_CTX_set_verify_algorithm_prefs(SSL_CTX *ctx, const uint16_t *prefs,
                                        size_t num_prefs) {
   return ctx->verify_sigalgs.CopyFrom(MakeConstSpan(prefs, num_prefs));
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index 695982b..835a998 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -4118,6 +4118,144 @@
   EXPECT_EQ(43, byte);
 }
 
+static std::string SigAlgsToString(Span<const uint16_t> sigalgs) {
+  std::string ret = "{";
+
+  for (uint16_t v : sigalgs) {
+    if (ret.size() > 1) {
+      ret += ", ";
+    }
+
+    char buf[8];
+    snprintf(buf, sizeof(buf) - 1, "0x%02x", v);
+    buf[sizeof(buf)-1] = 0;
+    ret += std::string(buf);
+  }
+
+  ret += "}";
+  return ret;
+}
+
+void ExpectSigAlgsEqual(Span<const uint16_t> expected,
+                        Span<const uint16_t> actual) {
+  bool matches = false;
+  if (expected.size() == actual.size()) {
+    matches = true;
+
+    for (size_t i = 0; i < expected.size(); i++) {
+      if (expected[i] != actual[i]) {
+        matches = false;
+        break;
+      }
+    }
+  }
+
+  if (!matches) {
+    ADD_FAILURE() << "expected: " << SigAlgsToString(expected)
+                  << " got: " << SigAlgsToString(actual);
+  }
+}
+
+TEST(SSLTest, SigAlgs) {
+  static const struct {
+    std::vector<int> input;
+    bool ok;
+    std::vector<uint16_t> expected;
+  } kTests[] = {
+      {{}, true, {}},
+      {{1}, false, {}},
+      {{1, 2, 3}, false, {}},
+      {{NID_sha256, EVP_PKEY_ED25519}, false, {}},
+      {{NID_sha256, EVP_PKEY_RSA, NID_sha256, EVP_PKEY_RSA}, false, {}},
+
+      {{NID_sha256, EVP_PKEY_RSA}, true, {SSL_SIGN_RSA_PKCS1_SHA256}},
+      {{NID_sha512, EVP_PKEY_RSA}, true, {SSL_SIGN_RSA_PKCS1_SHA512}},
+      {{NID_sha256, EVP_PKEY_RSA_PSS}, true, {SSL_SIGN_RSA_PSS_RSAE_SHA256}},
+      {{NID_undef, EVP_PKEY_ED25519}, true, {SSL_SIGN_ED25519}},
+      {{NID_undef, EVP_PKEY_ED25519, NID_sha384, EVP_PKEY_EC},
+       true,
+       {SSL_SIGN_ED25519, SSL_SIGN_ECDSA_SECP384R1_SHA384}},
+  };
+
+  UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
+
+  unsigned n = 1;
+  for (const auto &test : kTests) {
+    SCOPED_TRACE(n++);
+
+    const bool ok =
+        SSL_CTX_set1_sigalgs(ctx.get(), test.input.data(), test.input.size());
+    EXPECT_EQ(ok, test.ok);
+
+    if (!ok) {
+      ERR_clear_error();
+    }
+
+    if (!test.ok) {
+      continue;
+    }
+
+    ExpectSigAlgsEqual(test.expected, ctx->cert->sigalgs);
+  }
+}
+
+TEST(SSLTest, SigAlgsList) {
+  static const struct {
+    const char *input;
+    bool ok;
+    std::vector<uint16_t> expected;
+  } kTests[] = {
+      {"", false, {}},
+      {":", false, {}},
+      {"+", false, {}},
+      {"RSA", false, {}},
+      {"RSA+", false, {}},
+      {"RSA+SHA256:", false, {}},
+      {":RSA+SHA256:", false, {}},
+      {":RSA+SHA256+:", false, {}},
+      {"!", false, {}},
+      {"\x01", false, {}},
+      {"RSA+SHA256:RSA+SHA384:RSA+SHA256", false, {}},
+      {"RSA-PSS+SHA256:rsa_pss_rsae_sha256", false, {}},
+
+      {"RSA+SHA256", true, {SSL_SIGN_RSA_PKCS1_SHA256}},
+      {"RSA+SHA256:ed25519",
+       true,
+       {SSL_SIGN_RSA_PKCS1_SHA256, SSL_SIGN_ED25519}},
+      {"ECDSA+SHA256:RSA+SHA512",
+       true,
+       {SSL_SIGN_ECDSA_SECP256R1_SHA256, SSL_SIGN_RSA_PKCS1_SHA512}},
+      {"ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256",
+       true,
+       {SSL_SIGN_ECDSA_SECP256R1_SHA256, SSL_SIGN_RSA_PSS_RSAE_SHA256}},
+      {"RSA-PSS+SHA256", true, {SSL_SIGN_RSA_PSS_RSAE_SHA256}},
+      {"PSS+SHA256", true, {SSL_SIGN_RSA_PSS_RSAE_SHA256}},
+  };
+
+  UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
+
+  unsigned n = 1;
+  for (const auto &test : kTests) {
+    SCOPED_TRACE(n++);
+
+    const bool ok = SSL_CTX_set1_sigalgs_list(ctx.get(), test.input);
+    EXPECT_EQ(ok, test.ok);
+
+    if (!ok) {
+      if (test.ok) {
+        ERR_print_errors_fp(stderr);
+      }
+      ERR_clear_error();
+    }
+
+    if (!test.ok) {
+      continue;
+    }
+
+    ExpectSigAlgsEqual(test.expected, ctx->cert->sigalgs);
+  }
+}
+
 TEST_P(SSLVersionTest, VerifyBeforeCertRequest) {
   // Configure the server to request client certificates.
   SSL_CTX_set_custom_verify(
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc
index 430b13d..dde767e 100644
--- a/ssl/t1_lib.cc
+++ b/ssl/t1_lib.cc
@@ -492,8 +492,8 @@
 static SSLSignatureAlgorithmList tls12_get_verify_sigalgs(const SSL *ssl,
                                                           bool for_certs) {
   SSLSignatureAlgorithmList ret;
-  if (!ssl->ctx->verify_sigalgs.empty()) {
-    ret.list = ssl->ctx->verify_sigalgs;
+  if (!ssl->config->verify_sigalgs.empty()) {
+    ret.list = ssl->config->verify_sigalgs;
   } else {
     ret.list = kVerifySignatureAlgorithms;
     ret.skip_ed25519 = !ssl->ctx->ed25519_enabled;