Tighten up supported PSS combinations in X.509.

Matching Chromium, Go, and TLS 1.3, only allow SHA-256, SHA-384, and
SHA-512 RSA-PSS signatures, where MGF-1 and message hash match and salt
length is hash length. Sadly, we are stuck tolerating an explicit
trailerField for now. See the certificates in cl/362617931.

This also fixes an overflow bug in handling the salt length. On
platforms with 64-bit long and 32-bit int, we would misinterpret, e.g,
2^62 + 32 as 32. Also clean up the error-handling of maskHash. It was
previously handled in a very confusing way; syntax errors in maskHash
would succeed and only be noticed later, in rsa_mgf1_decode.

I haven't done it in this change, but as a followup, we can, like
Chromium, reduce X.509 signature algorithms down to a single enum.

Update-Note: Unusual RSA-PSS combinations in X.509 are no longer
accepted. This same change (actually a slightly stricter version) has
already landed in Chrome.

Bug: 489
Change-Id: I85ca3a4e14f76358cac13e66163887f6dade1ace
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/53865
Auto-Submit: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/crypto/x509/rsa_pss.c b/crypto/x509/rsa_pss.c
index f5716a6..42b4f21 100644
--- a/crypto/x509/rsa_pss.c
+++ b/crypto/x509/rsa_pss.c
@@ -56,6 +56,7 @@
 #include <openssl/x509.h>
 
 #include <assert.h>
+#include <limits.h>
 
 #include <openssl/asn1.h>
 #include <openssl/asn1t.h>
@@ -87,9 +88,9 @@
 
 
 // Given an MGF1 Algorithm ID decode to an Algorithm Identifier
-static X509_ALGOR *rsa_mgf1_decode(X509_ALGOR *alg) {
-  if (alg == NULL || alg->parameter == NULL ||
-      OBJ_obj2nid(alg->algorithm) != NID_mgf1 ||
+static X509_ALGOR *rsa_mgf1_decode(const X509_ALGOR *alg) {
+  if (OBJ_obj2nid(alg->algorithm) != NID_mgf1 ||
+      alg->parameter == NULL ||
       alg->parameter->type != V_ASN1_SEQUENCE) {
     return NULL;
   }
@@ -99,30 +100,27 @@
   return d2i_X509_ALGOR(NULL, &p, plen);
 }
 
-static RSA_PSS_PARAMS *rsa_pss_decode(const X509_ALGOR *alg,
-                                      X509_ALGOR **pmaskHash) {
-  *pmaskHash = NULL;
-
+static RSA_PSS_PARAMS *rsa_pss_decode(const X509_ALGOR *alg) {
   if (alg->parameter == NULL || alg->parameter->type != V_ASN1_SEQUENCE) {
     return NULL;
   }
 
   const uint8_t *p = alg->parameter->value.sequence->data;
   int plen = alg->parameter->value.sequence->length;
-  RSA_PSS_PARAMS *pss = d2i_RSA_PSS_PARAMS(NULL, &p, plen);
-  if (pss == NULL) {
-    return NULL;
-  }
-
-  *pmaskHash = rsa_mgf1_decode(pss->maskGenAlgorithm);
-  return pss;
+  return d2i_RSA_PSS_PARAMS(NULL, &p, plen);
 }
 
-// allocate and set algorithm ID from EVP_MD, default SHA1
+static int is_allowed_pss_md(const EVP_MD *md) {
+  int md_type = EVP_MD_type(md);
+  return md_type == NID_sha256 || md_type == NID_sha384 ||
+         md_type == NID_sha512;
+}
+
+// rsa_md_to_algor sets |*palg| to an |X509_ALGOR| describing the digest |md|,
+// which must be an allowed PSS digest.
 static int rsa_md_to_algor(X509_ALGOR **palg, const EVP_MD *md) {
-  if (EVP_MD_type(md) == NID_sha1) {
-    return 1;
-  }
+  // SHA-1 should be omitted (DEFAULT), but we do not allow SHA-1.
+  assert(is_allowed_pss_md(md));
   *palg = X509_ALGOR_new();
   if (*palg == NULL) {
     return 0;
@@ -131,15 +129,13 @@
   return 1;
 }
 
-// Allocate and set MGF1 algorithm ID from EVP_MD
+// rsa_md_to_mgf1 sets |*palg| to an |X509_ALGOR| describing MGF-1 with the
+// digest |mgf1md|, which must be an allowed PSS digest.
 static int rsa_md_to_mgf1(X509_ALGOR **palg, const EVP_MD *mgf1md) {
+  // SHA-1 should be omitted (DEFAULT), but we do not allow SHA-1.
+  assert(is_allowed_pss_md(mgf1md));
   X509_ALGOR *algtmp = NULL;
   ASN1_STRING *stmp = NULL;
-  *palg = NULL;
-
-  if (EVP_MD_type(mgf1md) == NID_sha1) {
-    return 1;
-  }
   // need to embed algorithm ID inside another
   if (!rsa_md_to_algor(&algtmp, mgf1md) ||
       !ASN1_item_pack(algtmp, ASN1_ITEM_rptr(X509_ALGOR), &stmp)) {
@@ -162,37 +158,35 @@
   return 0;
 }
 
-// convert algorithm ID to EVP_MD, default SHA1
-static const EVP_MD *rsa_algor_to_md(X509_ALGOR *alg) {
-  const EVP_MD *md;
+static const EVP_MD *rsa_algor_to_md(const X509_ALGOR *alg) {
   if (!alg) {
-    return EVP_sha1();
-  }
-  md = EVP_get_digestbyobj(alg->algorithm);
-  if (md == NULL) {
+    // If omitted, PSS defaults to SHA-1, which we do not allow.
     OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PSS_PARAMETERS);
+    return NULL;
+  }
+  const EVP_MD *md = EVP_get_digestbyobj(alg->algorithm);
+  if (md == NULL || !is_allowed_pss_md(md)) {
+    OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PSS_PARAMETERS);
+    return NULL;
   }
   return md;
 }
 
-// convert MGF1 algorithm ID to EVP_MD, default SHA1
-static const EVP_MD *rsa_mgf1_to_md(const X509_ALGOR *alg,
-                                    X509_ALGOR *maskHash) {
-  const EVP_MD *md;
+static const EVP_MD *rsa_mgf1_to_md(const X509_ALGOR *alg) {
   if (!alg) {
-    return EVP_sha1();
-  }
-  // Check mask and lookup mask hash algorithm
-  if (OBJ_obj2nid(alg->algorithm) != NID_mgf1 || maskHash == NULL) {
+    // If omitted, PSS defaults to MGF-1 with SHA-1, which we do not allow.
     OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PSS_PARAMETERS);
     return NULL;
   }
-  md = EVP_get_digestbyobj(maskHash->algorithm);
-  if (md == NULL) {
+  // Check mask and lookup mask hash algorithm.
+  X509_ALGOR *maskHash = rsa_mgf1_decode(alg);
+  if (maskHash == NULL) {
     OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PSS_PARAMETERS);
     return NULL;
   }
-  return md;
+  const EVP_MD *ret = rsa_algor_to_md(maskHash);
+  X509_ALGOR_free(maskHash);
+  return ret;
 }
 
 int x509_rsa_ctx_to_pss(EVP_MD_CTX *ctx, X509_ALGOR *algor) {
@@ -204,18 +198,14 @@
     return 0;
   }
 
-  EVP_PKEY *pk = EVP_PKEY_CTX_get0_pkey(ctx->pctx);
+  if (sigmd != mgf1md || !is_allowed_pss_md(sigmd)) {
+    OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PSS_PARAMETERS);
+    return 0;
+  }
+  int md_len = EVP_MD_size(sigmd);
   if (saltlen == -1) {
-    saltlen = EVP_MD_size(sigmd);
-  } else if (saltlen == -2) {
-    // TODO(davidben): Forbid this mode. The world has largely standardized on
-    // salt length matching hash length.
-    saltlen = EVP_PKEY_size(pk) - EVP_MD_size(sigmd) - 2;
-    if (((EVP_PKEY_bits(pk) - 1) & 0x7) == 0) {
-      saltlen--;
-    }
-  } else if (saltlen != (int)EVP_MD_size(sigmd)) {
-    // We only allow salt length matching hash length and, for now, the -2 case.
+    saltlen = md_len;
+  } else if (saltlen != md_len) {
     OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PSS_PARAMETERS);
     return 0;
   }
@@ -227,11 +217,12 @@
     goto err;
   }
 
-  if (saltlen != 20) {
-    pss->saltLength = ASN1_INTEGER_new();
-    if (!pss->saltLength || !ASN1_INTEGER_set(pss->saltLength, saltlen)) {
-      goto err;
-    }
+  // The DEFAULT value is 20, but this does not match any supported digest.
+  assert(saltlen != 20);
+  pss->saltLength = ASN1_INTEGER_new();
+  if (!pss->saltLength ||  //
+      !ASN1_INTEGER_set(pss->saltLength, saltlen)) {
+    goto err;
   }
 
   if (!rsa_md_to_algor(&pss->hashAlgorithm, sigmd) ||
@@ -260,33 +251,38 @@
 
   // Decode PSS parameters
   int ret = 0;
-  X509_ALGOR *maskHash;
-  RSA_PSS_PARAMS *pss = rsa_pss_decode(sigalg, &maskHash);
+  RSA_PSS_PARAMS *pss = rsa_pss_decode(sigalg);
   if (pss == NULL) {
     OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PSS_PARAMETERS);
     goto err;
   }
 
-  const EVP_MD *mgf1md = rsa_mgf1_to_md(pss->maskGenAlgorithm, maskHash);
+  const EVP_MD *mgf1md = rsa_mgf1_to_md(pss->maskGenAlgorithm);
   const EVP_MD *md = rsa_algor_to_md(pss->hashAlgorithm);
   if (mgf1md == NULL || md == NULL) {
     goto err;
   }
 
-  int saltlen = 20;
-  if (pss->saltLength != NULL) {
-    saltlen = ASN1_INTEGER_get(pss->saltLength);
-
-    // Could perform more salt length sanity checks but the main
-    // RSA routines will trap other invalid values anyway.
-    if (saltlen < 0) {
-      OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PSS_PARAMETERS);
-      goto err;
-    }
+  // We require the MGF-1 and signing hashes to match.
+  if (mgf1md != md) {
+    OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PSS_PARAMETERS);
+    goto err;
   }
 
-  // low-level routines support only trailer field 0xbc (value 1)
-  // and PKCS#1 says we should reject any other value anyway.
+  // We require the salt length be the hash length. The DEFAULT value is 20, but
+  // this does not match any supported salt length.
+  uint64_t salt_len = 0;
+  if (pss->saltLength == NULL ||
+      !ASN1_INTEGER_get_uint64(&salt_len, pss->saltLength) ||
+      salt_len != EVP_MD_size(md)) {
+    OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PSS_PARAMETERS);
+    goto err;
+  }
+  assert(salt_len <= INT_MAX);
+
+  // The trailer field must be 1 (0xbc). This value is DEFAULT, so the structure
+  // is required to omit it in DER. Although a syntax error, we also tolerate an
+  // explicitly-encoded value. See the certificates in cl/362617931.
   if (pss->trailerField != NULL && ASN1_INTEGER_get(pss->trailerField) != 1) {
     OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PSS_PARAMETERS);
     goto err;
@@ -295,7 +291,7 @@
   EVP_PKEY_CTX *pctx;
   if (!EVP_DigestVerifyInit(ctx, &pctx, md, NULL, pkey) ||
       !EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) ||
-      !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, saltlen) ||
+      !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, (int)salt_len) ||
       !EVP_PKEY_CTX_set_rsa_mgf1_md(pctx, mgf1md)) {
     goto err;
   }
@@ -304,7 +300,6 @@
 
 err:
   RSA_PSS_PARAMS_free(pss);
-  X509_ALGOR_free(maskHash);
   return ret;
 }
 
@@ -313,8 +308,8 @@
   assert(OBJ_obj2nid(sigalg->algorithm) == NID_rsassaPss);
 
   int rv = 0;
-  X509_ALGOR *maskHash;
-  RSA_PSS_PARAMS *pss = rsa_pss_decode(sigalg, &maskHash);
+  X509_ALGOR *maskHash = NULL;
+  RSA_PSS_PARAMS *pss = rsa_pss_decode(sigalg);
   if (!pss) {
     if (BIO_puts(bp, " (INVALID PSS PARAMETERS)\n") <= 0) {
       goto err;
@@ -344,17 +339,17 @@
   }
 
   if (pss->maskGenAlgorithm) {
-    if (i2a_ASN1_OBJECT(bp, pss->maskGenAlgorithm->algorithm) <= 0 ||
-        BIO_puts(bp, " with ") <= 0) {
-      goto err;
-    }
-
-    if (maskHash) {
-      if (i2a_ASN1_OBJECT(bp, maskHash->algorithm) <= 0) {
+    maskHash = rsa_mgf1_decode(pss->maskGenAlgorithm);
+    if (maskHash == NULL) {
+      if (BIO_puts(bp, "INVALID") <= 0) {
         goto err;
       }
-    } else if (BIO_puts(bp, "INVALID") <= 0) {
-      goto err;
+    } else {
+      if (i2a_ASN1_OBJECT(bp, pss->maskGenAlgorithm->algorithm) <= 0 ||
+          BIO_puts(bp, " with ") <= 0 ||
+          i2a_ASN1_OBJECT(bp, maskHash->algorithm) <= 0) {
+        goto err;
+      }
     }
   } else if (BIO_puts(bp, "mgf1 with sha1 (default)") <= 0) {
     goto err;
diff --git a/crypto/x509/test/pss_sha1.pem b/crypto/x509/test/pss_sha1.pem
new file mode 100644
index 0000000..1ab90d4
--- /dev/null
+++ b/crypto/x509/test/pss_sha1.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICpTCCAY2gAwIBAgIBADANBgkqhkiG9w0BAQowADAUMRIwEAYDVQQDDAlCb3Jp
+bmdTU0wwIhgPMDAwMDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowFDESMBAG
+A1UEAwwJQm9yaW5nU1NMMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+ugvahBkSAUF1fC49vb1bvlPrcl80kop1iLpiuYoz4Qptwy57+EWssZBcHprZ5BkW
+f6PeGZ7F5AX1PyJbGHZLqvMCvViP6pd4MFox/igESISEHEixoiXCzepBrhtp5UQS
+jHD4D4hKtgdMgVxX+LRtwgW3mnu/vBu7rzpr/DS8io99p3lqZ1Aky+aNlcMj6MYy
+8U+YFEevb/V0lRY9oqwmW7BHnXikm/vi6sjIS350U8zb/mRzYeIs2R65LUduTL50
++UMgat9ocewI2dv8aO9Dph+8NdGtg8LFYyTTHcUxJoMr1PTOgnmET19WJH4PrFwk
+7ZE1QJQQ1L4iKmPeQistuQIDAQABMA0GCSqGSIb3DQEBCjAAA4IBAQAQvYDcDDNx
+QPctGNiZTH9N2I2wdVXHmsybRW7tXWVYm+yE8IzfVUUBkCL5WvbLxlujMAbQpHp8
+EnKECVsNAklHAAQ6KFDTngyDAjdyGiNKKMm37UW/I7BkdFZE+jBYKoVU5xeLSPm1
+jNKQWqjGnaZ+wV7Fl8Zy+QOr7Z35zrDNbCF/EkzoE6+i/bbqXIgu5x14rj9c4JAs
+aKPpTtpDI1zt9BfGMPsBxsxeckqnG8OlNc6YI8svAK849naTAPx93jDWmDBYqfsb
+MeZOo9+AfUP7pjoDZHsQCmPdmlDgAtMvi8K1oFbw4BBTu+CaCzhNS5xEbITef09i
+tjiySH0Q5r01
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha1_explicit.pem b/crypto/x509/test/pss_sha1_explicit.pem
new file mode 100644
index 0000000..7847916
--- /dev/null
+++ b/crypto/x509/test/pss_sha1_explicit.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/TCCAbmgAwIBAgIBADA5BgkqhkiG9w0BAQowLKALMAkGBSsOAwIaBQChGDAW
+BgkqhkiG9w0BAQgwCQYFKw4DAhoFAKIDAgEUMBQxEjAQBgNVBAMMCUJvcmluZ1NT
+TDAiGA8wMDAwMDEwMTAwMDAwMFoYDzk5OTkxMjMxMjM1OTU5WjAUMRIwEAYDVQQD
+DAlCb3JpbmdTU0wwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6C9qE
+GRIBQXV8Lj29vVu+U+tyXzSSinWIumK5ijPhCm3DLnv4RayxkFwemtnkGRZ/o94Z
+nsXkBfU/IlsYdkuq8wK9WI/ql3gwWjH+KARIhIQcSLGiJcLN6kGuG2nlRBKMcPgP
+iEq2B0yBXFf4tG3CBbeae7+8G7uvOmv8NLyKj32neWpnUCTL5o2VwyPoxjLxT5gU
+R69v9XSVFj2irCZbsEedeKSb++LqyMhLfnRTzNv+ZHNh4izZHrktR25MvnT5QyBq
+32hx7AjZ2/xo70OmH7w10a2DwsVjJNMdxTEmgyvU9M6CeYRPX1Ykfg+sXCTtkTVA
+lBDUviIqY95CKy25AgMBAAEwOQYJKoZIhvcNAQEKMCygCzAJBgUrDgMCGgUAoRgw
+FgYJKoZIhvcNAQEIMAkGBSsOAwIaBQCiAwIBFAOCAQEATo0Z3YqPt4fzBXz22vyH
+7Ckr1cicKTeE3lV8LYHII4easVkueN7HrfrpTPu04kn4Y8pjprh0gRj9vcf6i6Sj
+khPnfmXTTbeFxHs763BQVAOoutgteyUhBZ5UjqaXnnF7PYhyG/0ykxWryvius+dz
+ujhW9T0aPo95GWITtj1NHzGmCjQYqUSrfkJynC8c/juTo3MLWrMnirDsAYizTg4W
+CWBfeMKRfAH6aOybSBNZh7/KU+ZiFPKJi+NKPPaZNZa0l1JZ46LL1NWVq6bybZH8
+ncNZpooQKTfCaK221pbqxx4YIJT0NoICU8291LSNLz8/5uBkjUo744cF4tuNFZ4k
+sg==
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha1_mgf1_syntax_error.pem b/crypto/x509/test/pss_sha1_mgf1_syntax_error.pem
new file mode 100644
index 0000000..3ba32f1
--- /dev/null
+++ b/crypto/x509/test/pss_sha1_mgf1_syntax_error.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICxzCCAZ6gAwIBAgIBADAeBgkqhkiG9w0BAQowEaEPMA0GCSqGSIb3DQEBCDAA
+MBQxEjAQBgNVBAMMCUJvcmluZ1NTTDAiGA8wMDAwMDEwMTAwMDAwMFoYDzk5OTkx
+MjMxMjM1OTU5WjAUMRIwEAYDVQQDDAlCb3JpbmdTU0wwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQC6C9qEGRIBQXV8Lj29vVu+U+tyXzSSinWIumK5ijPh
+Cm3DLnv4RayxkFwemtnkGRZ/o94ZnsXkBfU/IlsYdkuq8wK9WI/ql3gwWjH+KARI
+hIQcSLGiJcLN6kGuG2nlRBKMcPgPiEq2B0yBXFf4tG3CBbeae7+8G7uvOmv8NLyK
+j32neWpnUCTL5o2VwyPoxjLxT5gUR69v9XSVFj2irCZbsEedeKSb++LqyMhLfnRT
+zNv+ZHNh4izZHrktR25MvnT5QyBq32hx7AjZ2/xo70OmH7w10a2DwsVjJNMdxTEm
+gyvU9M6CeYRPX1Ykfg+sXCTtkTVAlBDUviIqY95CKy25AgMBAAEwHgYJKoZIhvcN
+AQEKMBGhDzANBgkqhkiG9w0BAQgwAAOCAQEANdpvRLqZLsYfruBHXjviZaKoHeoQ
+1ixqeSLzcP0KzWRT3H3tX46KuYABaMurK0yPMDfW6oLCfJa3fUFt0FYJYnf/w7mp
+MsmOf+7aaY8oYqI6wRwtAB0JQcC2tKsio+UEiI6hZq2ghhGa5c+YLXhN4Dt+/cK9
+UkisKL4O61jKulfaErOsUSaYTo9/PJpPcUhE/zVtsfAGJH0ojSCrSpEYv4TNO9Qm
+WOJ4hMreEOLVxw4xC65wRmOWl4JpGxle1mNzjsL4kOcDwsnepEOcpAqJronQ+HnI
+1RCR04oEnOOWYAtFxuWzTds3BjszGPRSu3srGZpaI1j/kB+a3g/7hXufOA==
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha224.pem b/crypto/x509/test/pss_sha224.pem
new file mode 100644
index 0000000..422615d
--- /dev/null
+++ b/crypto/x509/test/pss_sha224.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAcGgAwIBAgIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCBAUA
+oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCBAUAogMCARwwFDESMBAGA1UEAwwJ
+Qm9yaW5nU1NMMCIYDzAwMDAwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMBQx
+EjAQBgNVBAMMCUJvcmluZ1NTTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBALoL2oQZEgFBdXwuPb29W75T63JfNJKKdYi6YrmKM+EKbcMue/hFrLGQXB6a
+2eQZFn+j3hmexeQF9T8iWxh2S6rzAr1Yj+qXeDBaMf4oBEiEhBxIsaIlws3qQa4b
+aeVEEoxw+A+ISrYHTIFcV/i0bcIFt5p7v7wbu686a/w0vIqPfad5amdQJMvmjZXD
+I+jGMvFPmBRHr2/1dJUWPaKsJluwR514pJv74urIyEt+dFPM2/5kc2HiLNkeuS1H
+bky+dPlDIGrfaHHsCNnb/GjvQ6YfvDXRrYPCxWMk0x3FMSaDK9T0zoJ5hE9fViR+
+D6xcJO2RNUCUENS+Iipj3kIrLbkCAwEAATBBBgkqhkiG9w0BAQowNKAPMA0GCWCG
+SAFlAwQCBAUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCBAUAogMCARwDggEB
+AHttVo+XfLdlRXHc59yGAr7wQgqIbWOD6QSo4Tb4XbAh4nc9MQhccfT4YOMyHa9I
+i1VyE9e2dna8gt9VfIyCeTnNS1RYbKagaUzo2dt/G0lQjfXRVkB1yobJEaAzZ8kg
+cbknjlrlMEtHW+ET2vTHKvOjHjHXy3GYt8ynSldnotikqVobW5Kd/nYR7s9SR1Yz
+VhweDRcWeb3IYSAx953t1voky/7pTltcyb5FfJLIPmj2AsoFSRnXrj1Sx0K+S68m
+E0TdKwOTr2QN9nmHrIbCIeiAVeBlIOY6jH8TjobwFV2Y3RlqC+8S+Vgje/Hg/jrV
+Q9fS9+RIVSOFZiOgW/1hjAA=
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha256.pem b/crypto/x509/test/pss_sha256.pem
new file mode 100644
index 0000000..fc8ce18
--- /dev/null
+++ b/crypto/x509/test/pss_sha256.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAcGgAwIBAgIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUA
+oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwFDESMBAGA1UEAwwJ
+Qm9yaW5nU1NMMCIYDzAwMDAwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMBQx
+EjAQBgNVBAMMCUJvcmluZ1NTTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBALoL2oQZEgFBdXwuPb29W75T63JfNJKKdYi6YrmKM+EKbcMue/hFrLGQXB6a
+2eQZFn+j3hmexeQF9T8iWxh2S6rzAr1Yj+qXeDBaMf4oBEiEhBxIsaIlws3qQa4b
+aeVEEoxw+A+ISrYHTIFcV/i0bcIFt5p7v7wbu686a/w0vIqPfad5amdQJMvmjZXD
+I+jGMvFPmBRHr2/1dJUWPaKsJluwR514pJv74urIyEt+dFPM2/5kc2HiLNkeuS1H
+bky+dPlDIGrfaHHsCNnb/GjvQ6YfvDXRrYPCxWMk0x3FMSaDK9T0zoJ5hE9fViR+
+D6xcJO2RNUCUENS+Iipj3kIrLbkCAwEAATBBBgkqhkiG9w0BAQowNKAPMA0GCWCG
+SAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASADggEB
+ALVhRTv78XF2RjMusWAuTknDWkju6uvtq+iTpwCWaJO2QA2L3spEiUw52PsW9gQW
+AIOttLlgQFPD3dt3OL0FBK5y79Rh/h/mrOpwbVoMOHSsgikVZhbQ0D30Y8LQYMTD
+cxDYgPbnI4Q1VatdcCR8aavSDfV4JGPpJPkz8QX6HaFAoCUAz5UhiiS3MT8IzucO
+nNOV7AH9yfWDfvCWDGyuIYphjFZ761VjZFFIGJuXZ9uDXDDjNxlLwO7sci/pwO89
+OiRM40RxkS9vl8MjIsFSMGXOR+mf+FNtQ2vF1ZqCVxPWFuHHwmXycqrLuY3fOboF
+tF5Q3O1V7sh5Bs47h29KbQU=
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha256_explicit_trailer.pem b/crypto/x509/test/pss_sha256_explicit_trailer.pem
new file mode 100644
index 0000000..52820b7
--- /dev/null
+++ b/crypto/x509/test/pss_sha256_explicit_trailer.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDFzCCAcagAwIBAgIBADBGBgkqhkiG9w0BAQowOaAPMA0GCWCGSAFlAwQCAQUA
+oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASCjAwIBATAUMRIwEAYD
+VQQDDAlCb3JpbmdTU0wwIhgPMDAwMDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1
+OVowFDESMBAGA1UEAwwJQm9yaW5nU1NMMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAugvahBkSAUF1fC49vb1bvlPrcl80kop1iLpiuYoz4Qptwy57+EWs
+sZBcHprZ5BkWf6PeGZ7F5AX1PyJbGHZLqvMCvViP6pd4MFox/igESISEHEixoiXC
+zepBrhtp5UQSjHD4D4hKtgdMgVxX+LRtwgW3mnu/vBu7rzpr/DS8io99p3lqZ1Ak
+y+aNlcMj6MYy8U+YFEevb/V0lRY9oqwmW7BHnXikm/vi6sjIS350U8zb/mRzYeIs
+2R65LUduTL50+UMgat9ocewI2dv8aO9Dph+8NdGtg8LFYyTTHcUxJoMr1PTOgnmE
+T19WJH4PrFwk7ZE1QJQQ1L4iKmPeQistuQIDAQABMEYGCSqGSIb3DQEBCjA5oA8w
+DQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAIBBQCiAwIB
+IKMDAgEBA4IBAQA6p/W2ZA06aYzRXL1v/VnU11udk5+UIbGAuhSVv9a+zJ4/79UX
+Xk4/otg74fq/Ayy3hPm9lcOTGbXYHSgY4eFR8J1VS/P2ZPRCyypXwLSYg+Yt5Fdc
+SaA2JVF2jrgMbIAzBsyz4CCOzajcF/he8+NmH7pDZhLGv4pIWaFqAPrGntIpPwVW
+Qib62z9qzeexXIm+1Jp3nh21sXLbWdBM5tt5NNqST+cgzzrRnPtwQTbdcKk9i4Jh
+3/BairkWclVHxibtNPoLvhU91tJw61rES29x3vG//7WwEoT2aLCkp9/ZHrM5ukpe
+RxWXU+JZXiO59WWDCdK0YAuoteozepn4Cwei
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha256_mgf1_sha384.pem b/crypto/x509/test/pss_sha256_mgf1_sha384.pem
new file mode 100644
index 0000000..dd3b4e9
--- /dev/null
+++ b/crypto/x509/test/pss_sha256_mgf1_sha384.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAcGgAwIBAgIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUA
+oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCASAwFDESMBAGA1UEAwwJ
+Qm9yaW5nU1NMMCIYDzAwMDAwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMBQx
+EjAQBgNVBAMMCUJvcmluZ1NTTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBALoL2oQZEgFBdXwuPb29W75T63JfNJKKdYi6YrmKM+EKbcMue/hFrLGQXB6a
+2eQZFn+j3hmexeQF9T8iWxh2S6rzAr1Yj+qXeDBaMf4oBEiEhBxIsaIlws3qQa4b
+aeVEEoxw+A+ISrYHTIFcV/i0bcIFt5p7v7wbu686a/w0vIqPfad5amdQJMvmjZXD
+I+jGMvFPmBRHr2/1dJUWPaKsJluwR514pJv74urIyEt+dFPM2/5kc2HiLNkeuS1H
+bky+dPlDIGrfaHHsCNnb/GjvQ6YfvDXRrYPCxWMk0x3FMSaDK9T0zoJ5hE9fViR+
+D6xcJO2RNUCUENS+Iipj3kIrLbkCAwEAATBBBgkqhkiG9w0BAQowNKAPMA0GCWCG
+SAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCASADggEB
+AFMuNrMzLKfCvjDw35e5aoOPsTHKmkreUl4yHjUxX3i0heSkvy3FFcXhGjOscySF
+hBoAZU1DJaIHGzq2/k9Z3pk+NFhLg6tlHvLgcySHl4kR4sqTceJeOgy4RHJU04Gv
+wFAfRXx8QTJr1d00EPBoSnj7afDtvcRkzDSsgQ+YiQ9zjvt+uzuhZ25CUw8KY+Xy
+CZqQYE5yIMMRoKZExPcXuWTD8Ho5pVxjeLv2+nEO73NaAP0FwisCuY98ng0ffTgG
+5biORaDLzoTQv0QXtHS5TRUd6ycQ7nW28M4U1ZP9s9gj1zl+emvmi1UjNs3bcRW3
+Jk0lRwKo8awDUhfWJ/YPIns=
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha256_mgf1_syntax_error.pem b/crypto/x509/test/pss_sha256_mgf1_syntax_error.pem
new file mode 100644
index 0000000..5d77bf2
--- /dev/null
+++ b/crypto/x509/test/pss_sha256_mgf1_syntax_error.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8zCCAbSgAwIBAgIBADA0BgkqhkiG9w0BAQowJ6APMA0GCWCGSAFlAwQCAQUA
+oQ8wDQYJKoZIhvcNAQEIMACiAwIBIDAUMRIwEAYDVQQDDAlCb3JpbmdTU0wwIhgP
+MDAwMDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowFDESMBAGA1UEAwwJQm9y
+aW5nU1NMMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAugvahBkSAUF1
+fC49vb1bvlPrcl80kop1iLpiuYoz4Qptwy57+EWssZBcHprZ5BkWf6PeGZ7F5AX1
+PyJbGHZLqvMCvViP6pd4MFox/igESISEHEixoiXCzepBrhtp5UQSjHD4D4hKtgdM
+gVxX+LRtwgW3mnu/vBu7rzpr/DS8io99p3lqZ1Aky+aNlcMj6MYy8U+YFEevb/V0
+lRY9oqwmW7BHnXikm/vi6sjIS350U8zb/mRzYeIs2R65LUduTL50+UMgat9ocewI
+2dv8aO9Dph+8NdGtg8LFYyTTHcUxJoMr1PTOgnmET19WJH4PrFwk7ZE1QJQQ1L4i
+KmPeQistuQIDAQABMDQGCSqGSIb3DQEBCjAnoA8wDQYJYIZIAWUDBAIBBQChDzAN
+BgkqhkiG9w0BAQgwAKIDAgEgA4IBAQAJ0RuKq+OgFlcm2EMk+VXH8hSo87N3wcyK
+9SzLwONh2uVYR3W1ig+/EwqK0M9w5UwvSVNdFa3m2qVXApprUm7eCJ2c7rWiBkQy
+sVCHwWVCseItZ8ipJHHz0uC5k3EFBSVbsbRdTJ8FPbRDBXXn2iSz6OzUJVZ5PfV1
+XRC81Aoi8DbhFwNga7/mJ80Ru4UGEePT6SsyUU5sgZ0w37r+5VPQgL4oOMO2NKz+
+O060CbcDcOXrCm1x0BXIOc7gZEao5mriJTFQTq4PsuMnJ9a7aU+PYPfTxptr/VxB
+yJJSi0sjTpqmWKStBjeZlEYq57nWZLmueA9xf8T1Ah6WRDRFo8m1
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha256_omit_nulls.pem b/crypto/x509/test/pss_sha256_omit_nulls.pem
new file mode 100644
index 0000000..c5b025b
--- /dev/null
+++ b/crypto/x509/test/pss_sha256_omit_nulls.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBTCCAb2gAwIBAgIBADA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEa
+MBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgGiAwIBIDAUMRIwEAYDVQQDDAlCb3Jp
+bmdTU0wwIhgPMDAwMDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowFDESMBAG
+A1UEAwwJQm9yaW5nU1NMMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+ugvahBkSAUF1fC49vb1bvlPrcl80kop1iLpiuYoz4Qptwy57+EWssZBcHprZ5BkW
+f6PeGZ7F5AX1PyJbGHZLqvMCvViP6pd4MFox/igESISEHEixoiXCzepBrhtp5UQS
+jHD4D4hKtgdMgVxX+LRtwgW3mnu/vBu7rzpr/DS8io99p3lqZ1Aky+aNlcMj6MYy
+8U+YFEevb/V0lRY9oqwmW7BHnXikm/vi6sjIS350U8zb/mRzYeIs2R65LUduTL50
++UMgat9ocewI2dv8aO9Dph+8NdGtg8LFYyTTHcUxJoMr1PTOgnmET19WJH4PrFwk
+7ZE1QJQQ1L4iKmPeQistuQIDAQABMD0GCSqGSIb3DQEBCjAwoA0wCwYJYIZIAWUD
+BAIBoRowGAYJKoZIhvcNAQEIMAsGCWCGSAFlAwQCAaIDAgEgA4IBAQAss/sOR0J5
+rAmctg1qnUYeUKr3RN2MTAb58ZsbE9Gvjr4lgdRo4ADIwqfKYcEe3Xms0WO8gAle
+efbzrcqM1wZ6wjdcZEI9xz2L5moX0lD40Jd18OFe4wrmt7eMCaz+gTdHx+5uQy53
+4H+Vw6IUeO9m1K1wqDkwsiPWv22FqKghD07wKiNR7bkPnQDERRRN6UliCFfMEXOx
+h9IbYJQUIVvBFqkI9C/lKbrmxdS7fv3wFnu61knkh7JNIXXWNdYHHRDqNDZOhakn
+0wf3qY359CofKk+9kg/9cj1lP8HwgATxL69pBmSvM7O6ybDRhp4+/oySQCbcOIfs
+IOo8AoyPo4u2
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha256_salt31.pem b/crypto/x509/test/pss_sha256_salt31.pem
new file mode 100644
index 0000000..964df20
--- /dev/null
+++ b/crypto/x509/test/pss_sha256_salt31.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAcGgAwIBAgIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUA
+oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCAR8wFDESMBAGA1UEAwwJ
+Qm9yaW5nU1NMMCIYDzAwMDAwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMBQx
+EjAQBgNVBAMMCUJvcmluZ1NTTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBALoL2oQZEgFBdXwuPb29W75T63JfNJKKdYi6YrmKM+EKbcMue/hFrLGQXB6a
+2eQZFn+j3hmexeQF9T8iWxh2S6rzAr1Yj+qXeDBaMf4oBEiEhBxIsaIlws3qQa4b
+aeVEEoxw+A+ISrYHTIFcV/i0bcIFt5p7v7wbu686a/w0vIqPfad5amdQJMvmjZXD
+I+jGMvFPmBRHr2/1dJUWPaKsJluwR514pJv74urIyEt+dFPM2/5kc2HiLNkeuS1H
+bky+dPlDIGrfaHHsCNnb/GjvQ6YfvDXRrYPCxWMk0x3FMSaDK9T0zoJ5hE9fViR+
+D6xcJO2RNUCUENS+Iipj3kIrLbkCAwEAATBBBgkqhkiG9w0BAQowNKAPMA0GCWCG
+SAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCAR8DggEB
+ACRCt8NtcKkDg86ub1PiWSws4b/5v9ujPbatTrocCOXnob3Z4dnHKpjeUC0et/ex
+s4hlluZ9WHb+WgR5LP7I1eIE5C1RIL5aVLguSBi8/qQICNVgeMvZSgv/mDJ0eiv2
+xztcYlDwANPIh2RDpVyD6qUphvH8W6vrd6mo3aYgegigaDr/8d01MZh5s4120iWn
++8XKXNep8YqLhYemn3WeXtvK4vEEdFln6WeRRlGKevX9LqOegshs8HgKjjYRH++I
+c5HtwRZFkkMnXOV0XyG5zPrsx0qDcJCGHrC20bM+ZZz60QOtb1BSMIS5KjMy5OPQ
+bueM0YwX72tPKox+3lJgXAk=
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha256_salt_overflow.pem b/crypto/x509/test/pss_sha256_salt_overflow.pem
new file mode 100644
index 0000000..85f5f3a
--- /dev/null
+++ b/crypto/x509/test/pss_sha256_salt_overflow.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAcigAwIBAgIBADBIBgkqhkiG9w0BAQowO6APMA0GCWCGSAFlAwQCAQUA
+oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogoCCEAAAAAAAAAgMBQxEjAQ
+BgNVBAMMCUJvcmluZ1NTTDAiGA8wMDAwMDEwMTAwMDAwMFoYDzk5OTkxMjMxMjM1
+OTU5WjAUMRIwEAYDVQQDDAlCb3JpbmdTU0wwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQC6C9qEGRIBQXV8Lj29vVu+U+tyXzSSinWIumK5ijPhCm3DLnv4
+RayxkFwemtnkGRZ/o94ZnsXkBfU/IlsYdkuq8wK9WI/ql3gwWjH+KARIhIQcSLGi
+JcLN6kGuG2nlRBKMcPgPiEq2B0yBXFf4tG3CBbeae7+8G7uvOmv8NLyKj32neWpn
+UCTL5o2VwyPoxjLxT5gUR69v9XSVFj2irCZbsEedeKSb++LqyMhLfnRTzNv+ZHNh
+4izZHrktR25MvnT5QyBq32hx7AjZ2/xo70OmH7w10a2DwsVjJNMdxTEmgyvU9M6C
+eYRPX1Ykfg+sXCTtkTVAlBDUviIqY95CKy25AgMBAAEwSAYJKoZIhvcNAQEKMDug
+DzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIK
+AghAAAAAAAAAIAOCAQEAn7ufGUhwZUfP13AEXFZGo7tpWYKbiDpVnHTtj/21/SWd
+TXP1LJWIJuo8pGs/ZsXI+XoXJMk01G1wVPPUny/D7T9WXLW191UFzIGO0bJZIfv2
+M9YbRkTsCiAYUuyFUlwwSLMqMXrZjRXlgulv3DjWrDHrAqfst847Ety24P1uYG7C
+m4JV8Sa0SIKRntd00YYmk6oUZNgEzUps7moLsOEox3U2s6wTipl/++9H5CI5mQTS
+fdGMRzEsuJRfXkgMccEfDw2wvNfmNzILGDsvxjCilkEisuMPxlRptSk5agYFujAW
+D3QjJGCgGlSXhN3JuH9S2/0N5gRQpN/98beTTXpxvg==
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha256_unknown_mgf.pem b/crypto/x509/test/pss_sha256_unknown_mgf.pem
new file mode 100644
index 0000000..e296652
--- /dev/null
+++ b/crypto/x509/test/pss_sha256_unknown_mgf.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAcSgAwIBAgIBADBEBgkqhkiG9w0BAQowN6APMA0GCWCGSAFlAwQCAQUA
+oR8wHQYMKoZIhvcSBAGEtwkAMA0GCWCGSAFlAwQCAQUAogMCASAwFDESMBAGA1UE
+AwwJQm9yaW5nU1NMMCIYDzAwMDAwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTla
+MBQxEjAQBgNVBAMMCUJvcmluZ1NTTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALoL2oQZEgFBdXwuPb29W75T63JfNJKKdYi6YrmKM+EKbcMue/hFrLGQ
+XB6a2eQZFn+j3hmexeQF9T8iWxh2S6rzAr1Yj+qXeDBaMf4oBEiEhBxIsaIlws3q
+Qa4baeVEEoxw+A+ISrYHTIFcV/i0bcIFt5p7v7wbu686a/w0vIqPfad5amdQJMvm
+jZXDI+jGMvFPmBRHr2/1dJUWPaKsJluwR514pJv74urIyEt+dFPM2/5kc2HiLNke
+uS1Hbky+dPlDIGrfaHHsCNnb/GjvQ6YfvDXRrYPCxWMk0x3FMSaDK9T0zoJ5hE9f
+ViR+D6xcJO2RNUCUENS+Iipj3kIrLbkCAwEAATBEBgkqhkiG9w0BAQowN6APMA0G
+CWCGSAFlAwQCAQUAoR8wHQYMKoZIhvcSBAGEtwkAMA0GCWCGSAFlAwQCAQUAogMC
+ASADggEBACdP7ToiiM+6TqeAbKRqsPuFX9Q0qJtjX8mGVyzOEy402hEhglMeL9il
+FFFKCgo/wux4VIbw7BVLFDfYPcj8E7snj+J0mITIFt5oCwXG+MrJUnfklBEEyelF
+kVyr1ZVIwsk/lWSaOcQocbx8/szRRhwwVTT7BySnMV4e/y7Z9beye6tl7lg4373K
+E4akVubalnkoTHP5fZ6f0AfOXJEnLw2Mn0zjwrZmasMyXxLH8ApUVruPBQHY7IQB
+sMAURzkRtcCyzhQniJFcu7oMmkj+v8FVG02792DOWP+Y/yC0ccmHcvDbdzJW5SDm
+rBvNkmnPt6sYs7/RLRaVky7cRMAQrFs=
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha256_wrong_trailer.pem b/crypto/x509/test/pss_sha256_wrong_trailer.pem
new file mode 100644
index 0000000..dda8501
--- /dev/null
+++ b/crypto/x509/test/pss_sha256_wrong_trailer.pem
@@ -0,0 +1,22 @@
+This certificate has a trailerField of 2, but the signature was still
+generated with the standard 0xbc suffix.
+
+-----BEGIN CERTIFICATE-----
+MIIDFzCCAcagAwIBAgIBADBGBgkqhkiG9w0BAQowOaAPMA0GCWCGSAFlAwQCAQUA
+oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASCjAwIBAjAUMRIwEAYD
+VQQDDAlCb3JpbmdTU0wwIhgPMDAwMDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1
+OVowFDESMBAGA1UEAwwJQm9yaW5nU1NMMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAugvahBkSAUF1fC49vb1bvlPrcl80kop1iLpiuYoz4Qptwy57+EWs
+sZBcHprZ5BkWf6PeGZ7F5AX1PyJbGHZLqvMCvViP6pd4MFox/igESISEHEixoiXC
+zepBrhtp5UQSjHD4D4hKtgdMgVxX+LRtwgW3mnu/vBu7rzpr/DS8io99p3lqZ1Ak
+y+aNlcMj6MYy8U+YFEevb/V0lRY9oqwmW7BHnXikm/vi6sjIS350U8zb/mRzYeIs
+2R65LUduTL50+UMgat9ocewI2dv8aO9Dph+8NdGtg8LFYyTTHcUxJoMr1PTOgnmE
+T19WJH4PrFwk7ZE1QJQQ1L4iKmPeQistuQIDAQABMEYGCSqGSIb3DQEBCjA5oA8w
+DQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAIBBQCiAwIB
+IKMDAgECA4IBAQCVtDpbDRnbn7UQd8sPq+XSHk2nwvlhwSKZ0XZ0vwBGkNuhO/SY
+BhVUez+uGxXnJE+MkVAAh3NOdEQV6apEX5tymCyB0vqwLQB86gANOt82kF/tOW4M
+Sv5f6L8GJbYtpTd/cyAMEs7U/X9O5W1G9sLINWeukUYHPVKjj/tE3NfLCE8SWlw4
+SCnbuhs5WXnEgUP/9JgL8xyI6bxn9E2OUvqD+U24k0PbtAdk09697gUYUDQlmxi6
+MRoQYKTezNJt4DXRhqUlokWiF5D42MaMz5WXtLQaBfbqQB6n1Ln+hTaGwWP6xNSm
+Mip0TYOwAQdcTvr8UoQUJR/90SX+S4m6cu4d
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha384.pem b/crypto/x509/test/pss_sha384.pem
new file mode 100644
index 0000000..faf59b8
--- /dev/null
+++ b/crypto/x509/test/pss_sha384.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAcGgAwIBAgIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAgUA
+oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATAwFDESMBAGA1UEAwwJ
+Qm9yaW5nU1NMMCIYDzAwMDAwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMBQx
+EjAQBgNVBAMMCUJvcmluZ1NTTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBALoL2oQZEgFBdXwuPb29W75T63JfNJKKdYi6YrmKM+EKbcMue/hFrLGQXB6a
+2eQZFn+j3hmexeQF9T8iWxh2S6rzAr1Yj+qXeDBaMf4oBEiEhBxIsaIlws3qQa4b
+aeVEEoxw+A+ISrYHTIFcV/i0bcIFt5p7v7wbu686a/w0vIqPfad5amdQJMvmjZXD
+I+jGMvFPmBRHr2/1dJUWPaKsJluwR514pJv74urIyEt+dFPM2/5kc2HiLNkeuS1H
+bky+dPlDIGrfaHHsCNnb/GjvQ6YfvDXRrYPCxWMk0x3FMSaDK9T0zoJ5hE9fViR+
+D6xcJO2RNUCUENS+Iipj3kIrLbkCAwEAATBBBgkqhkiG9w0BAQowNKAPMA0GCWCG
+SAFlAwQCAgUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATADggEB
+AASUWBIEjb0TkXJKhj3RNRRe0D+KWFpyAn/kdBgDce/LEQVywj8IeS+s9z9TcGEK
+iKr8tPIsNUM1agj3gd1zWuM5rbUABQyGzeWcfjmhmhK5mnCSOu/OD+DJjWqyHpQq
+/Qf2djrpXJXKVNoSBzci4KUpFIfKMT4KjnUKY9L8lxfl9zaPeIaFeXgtyhHYnpjX
+vyomoLaL3cXeeKIffgPa9s9QZGx2fKOnFmcS3eKL9pIcj4Z6K8+Nchg6Z2qYKWtM
+hH1ZFlNDC3VOPgNkHoBrU6gE5fQv6lO64egL9pM0bpaOi7drhHKjkw2URi4C9KGK
+P9GfRmqV9Y9UlJsLSGIghxs=
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/pss_sha512.pem b/crypto/x509/test/pss_sha512.pem
new file mode 100644
index 0000000..1beb991
--- /dev/null
+++ b/crypto/x509/test/pss_sha512.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDDTCCAcGgAwIBAgIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAwUA
+oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAwUAogMCAUAwFDESMBAGA1UEAwwJ
+Qm9yaW5nU1NMMCIYDzAwMDAwMTAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMBQx
+EjAQBgNVBAMMCUJvcmluZ1NTTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBALoL2oQZEgFBdXwuPb29W75T63JfNJKKdYi6YrmKM+EKbcMue/hFrLGQXB6a
+2eQZFn+j3hmexeQF9T8iWxh2S6rzAr1Yj+qXeDBaMf4oBEiEhBxIsaIlws3qQa4b
+aeVEEoxw+A+ISrYHTIFcV/i0bcIFt5p7v7wbu686a/w0vIqPfad5amdQJMvmjZXD
+I+jGMvFPmBRHr2/1dJUWPaKsJluwR514pJv74urIyEt+dFPM2/5kc2HiLNkeuS1H
+bky+dPlDIGrfaHHsCNnb/GjvQ6YfvDXRrYPCxWMk0x3FMSaDK9T0zoJ5hE9fViR+
+D6xcJO2RNUCUENS+Iipj3kIrLbkCAwEAATBBBgkqhkiG9w0BAQowNKAPMA0GCWCG
+SAFlAwQCAwUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAwUAogMCAUADggEB
+ADwQHPC6MMvgBBfgYHRvcdQqfzPb3I9HJa+Y01BAIAeICadmyB389cjhv9X1mFi9
+xJOhsuek71D4YrOghExMbGXIAlalAq/rQHTzXV6cqiSrbUmmvsLmlilOeODIjuUC
+z3Ldc5LvwU8nima46jP/ryMYaIjCpbNsH/MzAbYO/CNm8osjeJoKhGyozkj9tr2T
+JaSFe5icta2WHfjfLP7wSkIf3NdfXNkBIBKMdHCuiEgeUeSColpgAFfngFKIJ3EG
+EbgptjPoePJ0T6VOdwzSX+QhGLabaOiX0ptrhAwuCNHVAKuf5wCWKvl6mYgBaDfB
+YFmL46BAX2ghB+WM/FSizPs=
+-----END CERTIFICATE-----
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 1bb6ff2..1bcc569 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -184,26 +184,6 @@
 -----END CERTIFICATE-----
 )";
 
-// kExamplePSSCert is an example RSA-PSS self-signed certificate, signed with
-// the default hash functions.
-static const char kExamplePSSCert[] = R"(
------BEGIN CERTIFICATE-----
-MIICYjCCAcagAwIBAgIJAI3qUyT6SIfzMBIGCSqGSIb3DQEBCjAFogMCAWowRTEL
-MAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVy
-bmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xNDEwMDkxOTA5NTVaFw0xNTEwMDkxOTA5
-NTVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
-DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwgZ8wDQYJKoZIhvcNAQEBBQADgY0A
-MIGJAoGBAPi4bIO0vNmoV8CltFl2jFQdeesiUgR+0zfrQf2D+fCmhRU0dXFahKg8
-0u9aTtPel4rd/7vPCqqGkr64UOTNb4AzMHYTj8p73OxaymPHAyXvqIqDWHYg+hZ3
-13mSYwFIGth7Z/FSVUlO1m5KXNd6NzYM3t2PROjCpywrta9kS2EHAgMBAAGjUDBO
-MB0GA1UdDgQWBBTQQfuJQR6nrVrsNF1JEflVgXgfEzAfBgNVHSMEGDAWgBTQQfuJ
-QR6nrVrsNF1JEflVgXgfEzAMBgNVHRMEBTADAQH/MBIGCSqGSIb3DQEBCjAFogMC
-AWoDgYEASUy2RZcgNbNQZA0/7F+V1YTLEXwD16bm+iSVnzGwtexmQVEYIZG74K/w
-xbdZQdTbpNJkp1QPjPfh0zsatw6dmt5QoZ8K8No0DjR9dgf+Wvv5WJvJUIQBoAVN
-Z0IL+OQFz6+LcTHxD27JJCebrATXZA0wThGTQDm7crL+a+SujBY=
------END CERTIFICATE-----
-)";
-
 // kBadPSSCertPEM is a self-signed RSA-PSS certificate with bad parameters.
 static const char kBadPSSCertPEM[] = R"(
 -----BEGIN CERTIFICATE-----
@@ -1750,13 +1730,46 @@
 }
 
 TEST(X509Test, TestPSS) {
-  bssl::UniquePtr<X509> cert(CertFromPEM(kExamplePSSCert));
-  ASSERT_TRUE(cert);
+  static const char *kGoodCerts[] = {
+      "crypto/x509/test/pss_sha256.pem",
+      "crypto/x509/test/pss_sha384.pem",
+      "crypto/x509/test/pss_sha512.pem",
+      // We accept inputs with and without explicit NULLs. See RFC 4055,
+      // section 2.1.
+      "crypto/x509/test/pss_sha256_omit_nulls.pem",
+      // Although invalid, we tolerate an explicit trailerField value. See the
+      // certificates in cl/362617931.
+      "crypto/x509/test/pss_sha256_explicit_trailer.pem",
+  };
+  for (const char *path : kGoodCerts) {
+    SCOPED_TRACE(path);
+    bssl::UniquePtr<X509> cert = CertFromPEM(GetTestData(path).c_str());
+    ASSERT_TRUE(cert);
+    bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get()));
+    ASSERT_TRUE(pkey);
+    EXPECT_TRUE(X509_verify(cert.get(), pkey.get()));
+  }
 
-  bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get()));
-  ASSERT_TRUE(pkey);
-
-  ASSERT_TRUE(X509_verify(cert.get(), pkey.get()));
+  static const char *kBadCerts[] = {
+      "crypto/x509/test/pss_sha1_explicit.pem",
+      "crypto/x509/test/pss_sha1_mgf1_syntax_error.pem",
+      "crypto/x509/test/pss_sha1.pem",
+      "crypto/x509/test/pss_sha224.pem",
+      "crypto/x509/test/pss_sha256_mgf1_sha384.pem",
+      "crypto/x509/test/pss_sha256_mgf1_syntax_error.pem",
+      "crypto/x509/test/pss_sha256_salt_overflow.pem",
+      "crypto/x509/test/pss_sha256_salt31.pem",
+      "crypto/x509/test/pss_sha256_unknown_mgf.pem",
+      "crypto/x509/test/pss_sha256_wrong_trailer.pem",
+  };
+  for (const char *path : kBadCerts) {
+    SCOPED_TRACE(path);
+    bssl::UniquePtr<X509> cert = CertFromPEM(GetTestData(path).c_str());
+    ASSERT_TRUE(cert);
+    bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get()));
+    ASSERT_TRUE(pkey);
+    EXPECT_FALSE(X509_verify(cert.get(), pkey.get()));
+  }
 }
 
 TEST(X509Test, TestPSSBadParameters) {
@@ -1857,18 +1870,10 @@
       EVP_DigestSignInit(md_ctx.get(), NULL, EVP_sha256(), NULL, pkey.get()));
   ASSERT_TRUE(SignatureRoundTrips(md_ctx.get(), pkey.get()));
 
-  // Test RSA-PSS with custom parameters.
-  md_ctx.Reset();
-  EVP_PKEY_CTX *pkey_ctx;
-  ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, EVP_sha256(), NULL,
-                                 pkey.get()));
-  ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING));
-  ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, EVP_sha512()));
-  ASSERT_TRUE(SignatureRoundTrips(md_ctx.get(), pkey.get()));
-
   // RSA-PSS with salt length matching hash length should work when passing in
   // -1 or the value explicitly.
   md_ctx.Reset();
+  EVP_PKEY_CTX *pkey_ctx;
   ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, EVP_sha256(), NULL,
                                  pkey.get()));
   ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING));
@@ -1881,6 +1886,37 @@
   ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING));
   ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, 32));
   ASSERT_TRUE(SignatureRoundTrips(md_ctx.get(), pkey.get()));
+
+  // RSA-PSS with SHA-1 is not supported.
+  md_ctx.Reset();
+  ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, EVP_sha1(), NULL,
+                                 pkey.get()));
+  ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING));
+  ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1));
+  bssl::UniquePtr<X509> cert = CertFromPEM(kLeafPEM);
+  ASSERT_TRUE(cert);
+  EXPECT_FALSE(X509_sign_ctx(cert.get(), md_ctx.get()));
+
+  // RSA-PSS with mismatched hashes is not supported.
+  md_ctx.Reset();
+  ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, EVP_sha256(), NULL,
+                                 pkey.get()));
+  ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING));
+  ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1));
+  ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, EVP_sha512()));
+  cert = CertFromPEM(kLeafPEM);
+  ASSERT_TRUE(cert);
+  EXPECT_FALSE(X509_sign_ctx(cert.get(), md_ctx.get()));
+
+  // RSA-PSS with the wrong salt length is not supported.
+  md_ctx.Reset();
+  ASSERT_TRUE(EVP_DigestSignInit(md_ctx.get(), &pkey_ctx, EVP_sha256(), NULL,
+                                 pkey.get()));
+  ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING));
+  ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, 33));
+  cert = CertFromPEM(kLeafPEM);
+  ASSERT_TRUE(cert);
+  EXPECT_FALSE(X509_sign_ctx(cert.get(), md_ctx.get()));
 }
 
 // Test the APIs for manually signing a certificate.
diff --git a/sources.cmake b/sources.cmake
index b3355a7..aaca5a4 100644
--- a/sources.cmake
+++ b/sources.cmake
@@ -102,6 +102,21 @@
   crypto/x509/test/many_names1.pem
   crypto/x509/test/many_names2.pem
   crypto/x509/test/many_names3.pem
+  crypto/x509/test/pss_sha1_explicit.pem
+  crypto/x509/test/pss_sha1_mgf1_syntax_error.pem
+  crypto/x509/test/pss_sha1.pem
+  crypto/x509/test/pss_sha224.pem
+  crypto/x509/test/pss_sha256_explicit_trailer.pem
+  crypto/x509/test/pss_sha256_mgf1_sha384.pem
+  crypto/x509/test/pss_sha256_mgf1_syntax_error.pem
+  crypto/x509/test/pss_sha256_omit_nulls.pem
+  crypto/x509/test/pss_sha256_salt_overflow.pem
+  crypto/x509/test/pss_sha256_salt31.pem
+  crypto/x509/test/pss_sha256_unknown_mgf.pem
+  crypto/x509/test/pss_sha256_wrong_trailer.pem
+  crypto/x509/test/pss_sha256.pem
+  crypto/x509/test/pss_sha384.pem
+  crypto/x509/test/pss_sha512.pem
   crypto/x509/test/some_names1.pem
   crypto/x509/test/some_names2.pem
   crypto/x509/test/some_names3.pem