Add SHA-256-only support for EVP_PKEY_RSA_PSS

While, in principle, PSS is better than PKCS#1 v1.5, and
algorithm-specific keys are better than mixing them up, RSASSA-PSS was
so badly mis-standardized in RFC 3447 and RFC 4055 that this is not
worth it. Any marginal benefits one might get from PSS is completely
overshadowed by the mountain of unforced errors those two RFCs made.
Applications are better off just using ECDSA.

Nonetheless, it is a thing we are now supporting. Add off-by-default
support for EVP_PKEY_RSA_PSS, only using the SHA-256 parameter set. In
OpenSSL's implementation, the underlying RSA object stores an
RSA_PSS_PARAMS, though the RSA-level APIs don't enforce the parameters,
only the EVP-level APIs do. For now, since the SHA-256 parameters are
the only ones we support, I have not bothered adding extra state to the
RSA object. If we need to add more parameters, we can store the
rsa_pss_params_t enum on the RSA object. (Preferably after we've split
the BCM and non-BCM halves of the RSA object.)

This support is off by default and must remain so. We have a bit of a
mess API-wise: OpenSSL made EVP_PKEY_get0_RSA work with
EVP_PKEY_RSA_PSS. This is plausible in that applications may want to
inspect RSA components and that is, for now, the API to do so. However,
existing callers generally assume a non-NULL EVP_PKEY_get0_RSA return
implies EVP_PKEY_RSA. Changing this will break those callers.

Thus the opt-in not only limits a badly-designed key type, but also
prevents existing callers from being exposed to this unexpected state.

These keys are not wired up to libssl and we have no plans to do so.

Bug: 384818542
Change-Id: I4d99be86ce1d891a2e50335ef097913707ede55a
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/81656
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/evp/evp_ctx.cc b/crypto/evp/evp_ctx.cc
index 31d0ca9..53c8b8d 100644
--- a/crypto/evp/evp_ctx.cc
+++ b/crypto/evp/evp_ctx.cc
@@ -25,6 +25,9 @@
 #include "internal.h"
 
 
+// |EVP_PKEY_RSA_PSS| is intentionally omitted from this list. These are types
+// that can be created without an |EVP_PKEY|, and we do not support
+// |EVP_PKEY_RSA_PSS| keygen.
 static const EVP_PKEY_CTX_METHOD *const evp_methods[] = {
     &rsa_pkey_meth,    &ec_pkey_meth,   &ed25519_pkey_meth,
     &x25519_pkey_meth, &hkdf_pkey_meth,
diff --git a/crypto/evp/evp_test.cc b/crypto/evp/evp_test.cc
index 25af7ed..25beb0c 100644
--- a/crypto/evp/evp_test.cc
+++ b/crypto/evp/evp_test.cc
@@ -93,6 +93,7 @@
 
 static const std::map<std::string, AlgorithmInfo> kAllAlgorithms = {
     {"RSA", {EVP_pkey_rsa(), EVP_PKEY_RSA, true}},
+    {"RSA-PSS-SHA-256", {EVP_pkey_rsa_pss_sha256(), EVP_PKEY_RSA_PSS, false}},
     {"EC-P-224", {EVP_pkey_ec_p224(), EVP_PKEY_EC, true}},
     {"EC-P-256", {EVP_pkey_ec_p256(), EVP_PKEY_EC, true}},
     {"EC-P-384", {EVP_pkey_ec_p384(), EVP_PKEY_EC, true}},
diff --git a/crypto/evp/internal.h b/crypto/evp/internal.h
index 9ceb6f7..847d083 100644
--- a/crypto/evp/internal.h
+++ b/crypto/evp/internal.h
@@ -275,11 +275,13 @@
 extern const EVP_PKEY_ASN1_METHOD dsa_asn1_meth;
 extern const EVP_PKEY_ASN1_METHOD ec_asn1_meth;
 extern const EVP_PKEY_ASN1_METHOD rsa_asn1_meth;
+extern const EVP_PKEY_ASN1_METHOD rsa_pss_sha256_asn1_meth;
 extern const EVP_PKEY_ASN1_METHOD ed25519_asn1_meth;
 extern const EVP_PKEY_ASN1_METHOD x25519_asn1_meth;
 extern const EVP_PKEY_ASN1_METHOD dh_asn1_meth;
 
 extern const EVP_PKEY_CTX_METHOD rsa_pkey_meth;
+extern const EVP_PKEY_CTX_METHOD rsa_pss_sha256_pkey_meth;
 extern const EVP_PKEY_CTX_METHOD ec_pkey_meth;
 extern const EVP_PKEY_CTX_METHOD ed25519_pkey_meth;
 extern const EVP_PKEY_CTX_METHOD x25519_pkey_meth;
diff --git a/crypto/evp/p_rsa.cc b/crypto/evp/p_rsa.cc
index 36e057f..9692307 100644
--- a/crypto/evp/p_rsa.cc
+++ b/crypto/evp/p_rsa.cc
@@ -46,15 +46,36 @@
   const EVP_MD *mgf1md = nullptr;
   // PSS salt length
   int saltlen = RSA_PSS_SALTLEN_DIGEST;
+  // restrict_pss_params, if true, indicates that the PSS signing/verifying
+  // parameters are restricted by the key's parameters. |md| and |mgf1md| may
+  // not change, and |saltlen| must be at least |md|'s hash length.
+  bool restrict_pss_params = false;
   bssl::Array<uint8_t> oaep_label;
 };
 
+static bool is_pss_only(const EVP_PKEY_CTX *ctx) {
+  return ctx->pmeth->pkey_id == EVP_PKEY_RSA_PSS;
+}
+
 static int pkey_rsa_init(EVP_PKEY_CTX *ctx) {
   RSA_PKEY_CTX *rctx = bssl::New<RSA_PKEY_CTX>();
   if (!rctx) {
     return 0;
   }
 
+  if (is_pss_only(ctx)) {
+    rctx->pad_mode = RSA_PKCS1_PSS_PADDING;
+    // Pick up PSS parameters from the key. For now, we only support the SHA-256
+    // parameter set, so every key is necessarily SHA-256. If we ever support
+    // other parameters, we will need more state in |EVP_PKEY| and to translate
+    // that state into defaults here.
+    if (ctx->pkey != nullptr) {
+      rctx->md = rctx->mgf1md = EVP_sha256();
+      rctx->saltlen = EVP_MD_size(rctx->md);
+      rctx->restrict_pss_params = true;
+    }
+  }
+
   ctx->data = rctx;
   return 1;
 }
@@ -78,6 +99,7 @@
   dctx->md = sctx->md;
   dctx->mgf1md = sctx->mgf1md;
   dctx->saltlen = sctx->saltlen;
+  dctx->restrict_pss_params = sctx->restrict_pss_params;
   if (!dctx->oaep_label.CopyFrom(sctx->oaep_label)) {
     return 0;
   }
@@ -317,6 +339,11 @@
   RSA_PKEY_CTX *rctx = reinterpret_cast<RSA_PKEY_CTX *>(ctx->data);
   switch (type) {
     case EVP_PKEY_CTRL_RSA_PADDING:
+      // PSS keys cannot be switched to other padding types.
+      if (is_pss_only(ctx) && p1 != RSA_PKCS1_PSS_PADDING) {
+        OPENSSL_PUT_ERROR(EVP, EVP_R_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE);
+        return 0;
+      }
       if (!is_known_padding(p1) || !check_padding_md(rctx->md, p1) ||
           (p1 == RSA_PKCS1_PSS_PADDING &&
            0 == (ctx->operation & (EVP_PKEY_OP_SIGN | EVP_PKEY_OP_VERIFY))) ||
@@ -345,8 +372,20 @@
         *(int *)p2 = rctx->saltlen;
       } else {
         // Negative salt lengths are special values.
-        if (p1 < 0 &&
-            (p1 != RSA_PSS_SALTLEN_DIGEST && p1 != RSA_PSS_SALTLEN_AUTO)) {
+        if (p1 < 0) {
+          if (p1 != RSA_PSS_SALTLEN_DIGEST && p1 != RSA_PSS_SALTLEN_AUTO) {
+            return 0;
+          }
+          // All our PSS restrictions accept saltlen == hashlen, so allow
+          // |RSA_PSS_SALTLEN_DIGEST|. Reject |RSA_PSS_SALTLEN_AUTO| for
+          // simplicity.
+          if (rctx->restrict_pss_params && p1 != RSA_PSS_SALTLEN_DIGEST) {
+            OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_PSS_SALTLEN);
+            return 0;
+          }
+        } else if (rctx->restrict_pss_params &&
+                   static_cast<size_t>(p1) < EVP_MD_size(rctx->md)) {
+          OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_PSS_SALTLEN);
           return 0;
         }
         rctx->saltlen = p1;
@@ -381,12 +420,19 @@
       }
       return 1;
 
-    case EVP_PKEY_CTRL_MD:
-      if (!check_padding_md(reinterpret_cast<EVP_MD *>(p2), rctx->pad_mode)) {
+    case EVP_PKEY_CTRL_MD: {
+      const EVP_MD *md = reinterpret_cast<EVP_MD *>(p2);
+      if (!check_padding_md(md, rctx->pad_mode)) {
         return 0;
       }
-      rctx->md = reinterpret_cast<EVP_MD *>(p2);
+      if (rctx->restrict_pss_params &&
+          EVP_MD_type(rctx->md) != EVP_MD_type(md)) {
+        OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_DIGEST_TYPE);
+        return 0;
+      }
+      rctx->md = md;
       return 1;
+    }
 
     case EVP_PKEY_CTRL_GET_MD:
       *(const EVP_MD **)p2 = rctx->md;
@@ -406,7 +452,13 @@
           *(const EVP_MD **)p2 = rctx->md;
         }
       } else {
-        rctx->mgf1md = reinterpret_cast<EVP_MD *>(p2);
+        const EVP_MD *md = reinterpret_cast<EVP_MD *>(p2);
+        if (rctx->restrict_pss_params &&
+            EVP_MD_type(rctx->mgf1md) != EVP_MD_type(md)) {
+          OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_MGF1_MD);
+          return 0;
+        }
+        rctx->mgf1md = md;
       }
       return 1;
 
@@ -467,60 +519,100 @@
     pkey_rsa_cleanup,
     pkey_rsa_keygen,
     pkey_rsa_sign,
-    NULL /* sign_message */,
+    /*sign_message=*/nullptr,
     pkey_rsa_verify,
-    NULL /* verify_message */,
+    /*verify_message=*/nullptr,
     pkey_rsa_verify_recover,
     pkey_rsa_encrypt,
     pkey_rsa_decrypt,
-    NULL /* derive */,
-    NULL /* paramgen */,
+    /*derive=*/nullptr,
+    /*paramgen=*/nullptr,
     pkey_rsa_ctrl,
 };
 
+const EVP_PKEY_CTX_METHOD rsa_pss_sha256_pkey_meth = {
+    EVP_PKEY_RSA_PSS,
+    pkey_rsa_init,
+    pkey_rsa_copy,
+    pkey_rsa_cleanup,
+    // In OpenSSL, |EVP_PKEY_RSA_PSS| supports key generation and fills in PSS
+    // parameters based on a separate set of keygen-targetted setters:
+    // |EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen|,
+    // |EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md|, and
+    // |EVP_PKEY_CTX_rsa_pss_key_digest|. We do not currently implement this
+    // because we only support one parameter set.
+    /*keygen=*/nullptr,
+    pkey_rsa_sign,
+    /*sign_message=*/nullptr,
+    pkey_rsa_verify,
+    /*verify_message=*/nullptr,
+    /*verify_recover=*/nullptr,
+    /*encrypt=*/nullptr,
+    /*decrypt=*/nullptr,
+    /*derive=*/nullptr,
+    /*paramgen=*/nullptr,
+    pkey_rsa_ctrl,
+};
+
+static int rsa_or_rsa_pss_ctrl(EVP_PKEY_CTX *ctx, int optype, int cmd, int p1,
+                               void *p2) {
+  if (!ctx || !ctx->pmeth || !ctx->pmeth->ctrl) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_COMMAND_NOT_SUPPORTED);
+    return 0;
+  }
+  if (ctx->pmeth->pkey_id != EVP_PKEY_RSA &&
+      ctx->pmeth->pkey_id != EVP_PKEY_RSA_PSS) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
+    return 0;
+  }
+  return EVP_PKEY_CTX_ctrl(ctx, /*keytype=*/-1, optype, cmd, p1, p2);
+}
+
 int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *ctx, int padding) {
-  return EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, -1, EVP_PKEY_CTRL_RSA_PADDING,
-                           padding, NULL);
+  return rsa_or_rsa_pss_ctrl(ctx, -1, EVP_PKEY_CTRL_RSA_PADDING, padding,
+                             nullptr);
 }
 
 int EVP_PKEY_CTX_get_rsa_padding(EVP_PKEY_CTX *ctx, int *out_padding) {
-  return EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, -1, EVP_PKEY_CTRL_GET_RSA_PADDING,
-                           0, out_padding);
+  return rsa_or_rsa_pss_ctrl(ctx, -1, EVP_PKEY_CTRL_GET_RSA_PADDING, 0,
+                             out_padding);
 }
 
 int EVP_PKEY_CTX_set_rsa_pss_keygen_md(EVP_PKEY_CTX *ctx, const EVP_MD *md) {
+  // We currently do not support keygen with |EVP_PKEY_RSA_PSS|.
   return 0;
 }
 
 int EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(EVP_PKEY_CTX *ctx, int salt_len) {
+  // We currently do not support keygen with |EVP_PKEY_RSA_PSS|.
   return 0;
 }
 
 int EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(EVP_PKEY_CTX *ctx,
                                             const EVP_MD *md) {
+  // We currently do not support keygen with |EVP_PKEY_RSA_PSS|.
   return 0;
 }
 
 int EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_PKEY_CTX *ctx, int salt_len) {
-  return EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA,
-                           (EVP_PKEY_OP_SIGN | EVP_PKEY_OP_VERIFY),
-                           EVP_PKEY_CTRL_RSA_PSS_SALTLEN, salt_len, NULL);
+  return rsa_or_rsa_pss_ctrl(ctx, (EVP_PKEY_OP_SIGN | EVP_PKEY_OP_VERIFY),
+                             EVP_PKEY_CTRL_RSA_PSS_SALTLEN, salt_len, nullptr);
 }
 
 int EVP_PKEY_CTX_get_rsa_pss_saltlen(EVP_PKEY_CTX *ctx, int *out_salt_len) {
-  return EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA,
-                           (EVP_PKEY_OP_SIGN | EVP_PKEY_OP_VERIFY),
-                           EVP_PKEY_CTRL_GET_RSA_PSS_SALTLEN, 0, out_salt_len);
+  return rsa_or_rsa_pss_ctrl(ctx, (EVP_PKEY_OP_SIGN | EVP_PKEY_OP_VERIFY),
+                             EVP_PKEY_CTRL_GET_RSA_PSS_SALTLEN, 0,
+                             out_salt_len);
 }
 
 int EVP_PKEY_CTX_set_rsa_keygen_bits(EVP_PKEY_CTX *ctx, int bits) {
-  return EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, EVP_PKEY_OP_KEYGEN,
-                           EVP_PKEY_CTRL_RSA_KEYGEN_BITS, bits, NULL);
+  return rsa_or_rsa_pss_ctrl(ctx, EVP_PKEY_OP_KEYGEN,
+                             EVP_PKEY_CTRL_RSA_KEYGEN_BITS, bits, nullptr);
 }
 
 int EVP_PKEY_CTX_set_rsa_keygen_pubexp(EVP_PKEY_CTX *ctx, BIGNUM *e) {
-  return EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, EVP_PKEY_OP_KEYGEN,
-                           EVP_PKEY_CTRL_RSA_KEYGEN_PUBEXP, 0, e);
+  return rsa_or_rsa_pss_ctrl(ctx, EVP_PKEY_OP_KEYGEN,
+                             EVP_PKEY_CTRL_RSA_KEYGEN_PUBEXP, 0, e);
 }
 
 int EVP_PKEY_CTX_set_rsa_oaep_md(EVP_PKEY_CTX *ctx, const EVP_MD *md) {
@@ -534,15 +626,13 @@
 }
 
 int EVP_PKEY_CTX_set_rsa_mgf1_md(EVP_PKEY_CTX *ctx, const EVP_MD *md) {
-  return EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA,
-                           EVP_PKEY_OP_TYPE_SIG | EVP_PKEY_OP_TYPE_CRYPT,
-                           EVP_PKEY_CTRL_RSA_MGF1_MD, 0, (void *)md);
+  return rsa_or_rsa_pss_ctrl(ctx, EVP_PKEY_OP_TYPE_SIG | EVP_PKEY_OP_TYPE_CRYPT,
+                             EVP_PKEY_CTRL_RSA_MGF1_MD, 0, (void *)md);
 }
 
 int EVP_PKEY_CTX_get_rsa_mgf1_md(EVP_PKEY_CTX *ctx, const EVP_MD **out_md) {
-  return EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA,
-                           EVP_PKEY_OP_TYPE_SIG | EVP_PKEY_OP_TYPE_CRYPT,
-                           EVP_PKEY_CTRL_GET_RSA_MGF1_MD, 0, (void *)out_md);
+  return rsa_or_rsa_pss_ctrl(ctx, EVP_PKEY_OP_TYPE_SIG | EVP_PKEY_OP_TYPE_CRYPT,
+                             EVP_PKEY_CTRL_GET_RSA_MGF1_MD, 0, (void *)out_md);
 }
 
 int EVP_PKEY_CTX_set0_rsa_oaep_label(EVP_PKEY_CTX *ctx, uint8_t *label,
diff --git a/crypto/evp/p_rsa_asn1.cc b/crypto/evp/p_rsa_asn1.cc
index 6a64d39..531ea87 100644
--- a/crypto/evp/p_rsa_asn1.cc
+++ b/crypto/evp/p_rsa_asn1.cc
@@ -20,8 +20,10 @@
 #include <openssl/err.h>
 #include <openssl/mem.h>
 #include <openssl/rsa.h>
+#include <openssl/span.h>
 
 #include "../fipsmodule/rsa/internal.h"
+#include "../rsa/internal.h"
 #include "internal.h"
 
 
@@ -58,18 +60,22 @@
     return evp_decode_error;
   }
 
-  RSA *rsa = RSA_parse_public_key(key);
-  if (rsa == nullptr || CBS_len(key) != 0) {
+  bssl::UniquePtr<RSA> rsa(
+      RSA_public_key_from_bytes(CBS_data(key), CBS_len(key)));
+  if (rsa == nullptr) {
     OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
-    RSA_free(rsa);
     return evp_decode_error;
   }
 
-  EVP_PKEY_assign_RSA(out, rsa);
+  EVP_PKEY_assign_RSA(out, rsa.release());
   return evp_decode_ok;
 }
 
 static int rsa_pub_cmp(const EVP_PKEY *a, const EVP_PKEY *b) {
+  // We currently assume that all |EVP_PKEY_RSA_PSS| keys have the same
+  // parameters, so this vacuously compares parameters. If we ever support
+  // multiple PSS parameter sets, we probably should compare them too. Note,
+  // however, that OpenSSL does not compare parameters here.
   const RSA *a_rsa = reinterpret_cast<const RSA *>(a->pkey);
   const RSA *b_rsa = reinterpret_cast<const RSA *>(b->pkey);
   return BN_cmp(RSA_get0_n(b_rsa), RSA_get0_n(a_rsa)) == 0 &&
@@ -106,14 +112,108 @@
     return evp_decode_error;
   }
 
-  RSA *rsa = RSA_parse_private_key(key);
-  if (rsa == nullptr || CBS_len(key) != 0) {
+  bssl::UniquePtr<RSA> rsa(
+      RSA_private_key_from_bytes(CBS_data(key), CBS_len(key)));
+  if (rsa == nullptr) {
     OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
-    RSA_free(rsa);
     return evp_decode_error;
   }
 
-  EVP_PKEY_assign_RSA(out, rsa);
+  EVP_PKEY_assign_RSA(out, rsa.release());
+  return evp_decode_ok;
+}
+
+static evp_decode_result_t rsa_decode_pss_params_sha256(CBS *params) {
+  // For now, we only support the SHA-256 parameter set. If we want to support
+  // more, we'll need to record a little more state in the |EVP_PKEY|.
+  if (CBS_len(params) == 0) {
+    return evp_decode_unsupported;
+  }
+  rsa_pss_params_t pss_params;
+  if (!rsa_parse_pss_params(params, &pss_params,
+                            /*allow_explicit_trailer=*/false) ||
+      CBS_len(params) != 0) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
+    return evp_decode_error;
+  }
+  return pss_params == rsa_pss_sha256 ? evp_decode_ok : evp_decode_unsupported;
+}
+
+static int rsa_pub_encode_pss_sha256(CBB *out, const EVP_PKEY *key) {
+  const RSA *rsa = reinterpret_cast<const RSA *>(key->pkey);
+  CBB spki, algorithm, key_bitstring;
+  if (!CBB_add_asn1(out, &spki, CBS_ASN1_SEQUENCE) ||
+      !CBB_add_asn1(&spki, &algorithm, CBS_ASN1_SEQUENCE) ||
+      !CBB_add_asn1_element(&algorithm, CBS_ASN1_OBJECT,
+                            rsa_pss_sha256_asn1_meth.oid,
+                            rsa_pss_sha256_asn1_meth.oid_len) ||
+      !rsa_marshal_pss_params(&algorithm, rsa_pss_sha256) ||
+      !CBB_add_asn1(&spki, &key_bitstring, CBS_ASN1_BITSTRING) ||
+      !CBB_add_u8(&key_bitstring, 0 /* padding */) ||
+      !RSA_marshal_public_key(&key_bitstring, rsa) ||  //
+      !CBB_flush(out)) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR);
+    return 0;
+  }
+
+  return 1;
+}
+
+static evp_decode_result_t rsa_pub_decode_pss_sha256(const EVP_PKEY_ALG *alg,
+                                                     EVP_PKEY *out, CBS *params,
+                                                     CBS *key) {
+  evp_decode_result_t ret = rsa_decode_pss_params_sha256(params);
+  if (ret != evp_decode_ok) {
+    return ret;
+  }
+
+  bssl::UniquePtr<RSA> rsa(
+      RSA_public_key_from_bytes(CBS_data(key), CBS_len(key)));
+  if (rsa == nullptr) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
+    return evp_decode_error;
+  }
+
+  evp_pkey_set0(out, &rsa_pss_sha256_asn1_meth, rsa.release());
+  return evp_decode_ok;
+}
+
+static int rsa_priv_encode_pss_sha256(CBB *out, const EVP_PKEY *key) {
+  const RSA *rsa = reinterpret_cast<const RSA *>(key->pkey);
+  CBB pkcs8, algorithm, private_key;
+  if (!CBB_add_asn1(out, &pkcs8, CBS_ASN1_SEQUENCE) ||
+      !CBB_add_asn1_uint64(&pkcs8, 0 /* version */) ||
+      !CBB_add_asn1(&pkcs8, &algorithm, CBS_ASN1_SEQUENCE) ||
+      !CBB_add_asn1_element(&algorithm, CBS_ASN1_OBJECT,
+                            rsa_pss_sha256_asn1_meth.oid,
+                            rsa_pss_sha256_asn1_meth.oid_len) ||
+      !rsa_marshal_pss_params(&algorithm, rsa_pss_sha256) ||
+      !CBB_add_asn1(&pkcs8, &private_key, CBS_ASN1_OCTETSTRING) ||
+      !RSA_marshal_private_key(&private_key, rsa) ||  //
+      !CBB_flush(out)) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR);
+    return 0;
+  }
+
+  return 1;
+}
+
+static evp_decode_result_t rsa_priv_decode_pss_sha256(const EVP_PKEY_ALG *alg,
+                                                      EVP_PKEY *out,
+                                                      CBS *params, CBS *key) {
+  evp_decode_result_t ret = rsa_decode_pss_params_sha256(params);
+  if (ret != evp_decode_ok) {
+    return ret;
+  }
+
+  bssl::UniquePtr<RSA> rsa(
+      RSA_private_key_from_bytes(CBS_data(key), CBS_len(key)));
+  if (rsa == nullptr) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
+    return evp_decode_error;
+  }
+
+  evp_pkey_set0(out, &rsa_pss_sha256_asn1_meth, rsa.release());
   return evp_decode_ok;
 }
 
@@ -152,25 +252,60 @@
     rsa_priv_decode,
     rsa_priv_encode,
 
-    /*set_priv_raw=*/NULL,
-    /*set_pub_raw=*/NULL,
-    /*get_priv_raw=*/NULL,
-    /*get_pub_raw=*/NULL,
-    /*set1_tls_encodedpoint=*/NULL,
-    /*get1_tls_encodedpoint=*/NULL,
+    /*set_priv_raw=*/nullptr,
+    /*set_pub_raw=*/nullptr,
+    /*get_priv_raw=*/nullptr,
+    /*get_pub_raw=*/nullptr,
+    /*set1_tls_encodedpoint=*/nullptr,
+    /*get1_tls_encodedpoint=*/nullptr,
 
     rsa_opaque,
 
     int_rsa_size,
     rsa_bits,
 
-    0,
-    0,
-    0,
+    /*param_missing=*/nullptr,
+    /*param_copy=*/nullptr,
+    /*param_cmp=*/nullptr,
 
     int_rsa_free,
 };
 
+const EVP_PKEY_ASN1_METHOD rsa_pss_sha256_asn1_meth = {
+    EVP_PKEY_RSA_PSS,
+    // 1.2.840.113549.1.1.10
+    {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a},
+    9,
+
+    &rsa_pss_sha256_pkey_meth,
+
+    rsa_pub_decode_pss_sha256,
+    rsa_pub_encode_pss_sha256,
+    rsa_pub_cmp,
+
+    rsa_priv_decode_pss_sha256,
+    rsa_priv_encode_pss_sha256,
+
+    /*set_priv_raw=*/nullptr,
+    /*set_pub_raw=*/nullptr,
+    /*get_priv_raw=*/nullptr,
+    /*get_pub_raw=*/nullptr,
+    /*set1_tls_encodedpoint=*/nullptr,
+    /*get1_tls_encodedpoint=*/nullptr,
+
+    rsa_opaque,
+
+    int_rsa_size,
+    rsa_bits,
+
+    /*param_missing=*/nullptr,
+    /*param_copy=*/nullptr,
+    /*param_cmp=*/nullptr,
+
+    int_rsa_free,
+};
+
+
 const EVP_PKEY_ALG *EVP_pkey_rsa(void) {
   static const EVP_PKEY_ALG kAlg = {
       /*method=*/&rsa_asn1_meth,
@@ -179,6 +314,14 @@
   return &kAlg;
 }
 
+const EVP_PKEY_ALG *EVP_pkey_rsa_pss_sha256(void) {
+  static const EVP_PKEY_ALG kAlg = {
+      /*method=*/&rsa_pss_sha256_asn1_meth,
+      /*ec_group=*/nullptr,
+  };
+  return &kAlg;
+}
+
 int EVP_PKEY_set1_RSA(EVP_PKEY *pkey, RSA *key) {
   if (EVP_PKEY_assign_RSA(pkey, key)) {
     RSA_up_ref(key);
@@ -196,7 +339,8 @@
 }
 
 RSA *EVP_PKEY_get0_RSA(const EVP_PKEY *pkey) {
-  if (EVP_PKEY_id(pkey) != EVP_PKEY_RSA) {
+  int pkey_id = EVP_PKEY_id(pkey);
+  if (pkey_id != EVP_PKEY_RSA && pkey_id != EVP_PKEY_RSA_PSS) {
     OPENSSL_PUT_ERROR(EVP, EVP_R_EXPECTING_AN_RSA_KEY);
     return NULL;
   }
diff --git a/crypto/evp/test/rsa_tests.txt b/crypto/evp/test/rsa_tests.txt
index 902db0f..451a69d 100644
--- a/crypto/evp/test/rsa_tests.txt
+++ b/crypto/evp/test/rsa_tests.txt
@@ -42,6 +42,79 @@
 Input = 3083000122300d06092a864886f70d01010105000382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
 Error = DECODE_ERROR
 
+# The same key but constrained to PSS, SHA-256, and salt length >= 32
+PrivateKey = RSA-2048-PSS-SHA256
+Algorithm = RSA-PSS-SHA-256
+Input = 308204f0020100304106092a864886f70d01010a3034a00f300d06096086480165030402010500a11c301a06092a864886f70d010108300d06096086480165030402010500a203020120048204a6308204a20201000282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f02030100010282010060297ac7991b167a06d6b24758b8cbe208beb9b2d9ec9738bd80f90a2e35005dd7ce292d9e29ba885bd316fef1f20913bc0ac90d6b0808b2414d82104441d8624a33ce0233c8f780a48b375aff02d76712228a702484db3f9ebecccfbbee1709dba182800d949e9e4216e0bff3558388f8bd90da373a1d82743ec3fbdd1427fd16825a657a316912e8695365117ca2f845c909405fcac55f895fc15d20386c26ee78c9e99075029a178a6c1e4cf0c200e8a9cfb27e9d156f86e6c2adc22b1a84a1cd5ca5b2790875d79407c84b352395cb81cc3fed5bb043b69ede0c07204550025cee8c5f440170b6120bb48e0f747bcd8f522110850df043c428dfd187053102818100f6f961b47cbc035d3aedebc7de850a956b65ecdb9cf60764063f15aa48553c58d972fe6675056e35ddfdc37bf3b9f2f622ee271337256849c9bef2176fe8f7c3f8bb91ba374dd53baf3dec814d2bdec10c1fdc88cdd16876f26b1edfa3f094197edf4d42ff1fb2971103b898ca859c427287086a842ab410bb69cf2d35af6be302818100d47e724a7ff41048b270c2524a4101878b73159bb73d3dbc187b220e635b3534f96e243a184d93f860b6bfbb6b71c1ed9a1e1f458583023c301e96a692c1a08b53d0ec9ca910100d80451e3b7dc6a01bac4aecef8df798846bc235a08cbba2cf4c06804cc11219e95608c714e3f1430d491fadbba32a5751a04f97745834c9a502818021f2452bb9b95dfd028c914bf799f1ca77e89a95d50d3c16d384f8455f8bd7af9eb3dfa3d591d9842def235f7630a8e48c088ff6642e101794535a933e1e976fa8509fc728b2da0c4a1a08d7fcf37abaae1ff3001aca1dc1bbb05d9dffbaa1a09f7fb1eef38237d9ebccc722b9338436dde7119112798c26809c1a8dec4320610281801f7510aa62c2d8de4a3c53282781f41e02d0e8b402ae78432e449c48110161a11403f02d01880a8dcc938152d79721a4711a607ac4471ebf964810f95be47a45e60499e29f4c9773c83773404f606637728c2d0351bb03c326c8bb73a721e7fa5440ea2172bba1465fcc30dcb0d9f89930e815aa1f7f9729a857e00e0338dd590281804d1f0d756fe77e01099a652f50a88b7b685dc5bf00981d5d2376fd0c6fe29cd5b638734479305a73ad3c1599d39eae3bae035fbd6fed07c28de705933879a06e48e6a603686ed8e2560a5f6af1f2c24faf4aa960e382186f15eedce9a2491ae730680dd4cf778b70faa86826ab3223477cc91377b19a6d5a2eaea219760beed5
+RSAParamN = cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f
+RSAParamE = 010001
+RSAParamD = 60297ac7991b167a06d6b24758b8cbe208beb9b2d9ec9738bd80f90a2e35005dd7ce292d9e29ba885bd316fef1f20913bc0ac90d6b0808b2414d82104441d8624a33ce0233c8f780a48b375aff02d76712228a702484db3f9ebecccfbbee1709dba182800d949e9e4216e0bff3558388f8bd90da373a1d82743ec3fbdd1427fd16825a657a316912e8695365117ca2f845c909405fcac55f895fc15d20386c26ee78c9e99075029a178a6c1e4cf0c200e8a9cfb27e9d156f86e6c2adc22b1a84a1cd5ca5b2790875d79407c84b352395cb81cc3fed5bb043b69ede0c07204550025cee8c5f440170b6120bb48e0f747bcd8f522110850df043c428dfd1870531
+RSAParamP = f6f961b47cbc035d3aedebc7de850a956b65ecdb9cf60764063f15aa48553c58d972fe6675056e35ddfdc37bf3b9f2f622ee271337256849c9bef2176fe8f7c3f8bb91ba374dd53baf3dec814d2bdec10c1fdc88cdd16876f26b1edfa3f094197edf4d42ff1fb2971103b898ca859c427287086a842ab410bb69cf2d35af6be3
+RSAParamQ = d47e724a7ff41048b270c2524a4101878b73159bb73d3dbc187b220e635b3534f96e243a184d93f860b6bfbb6b71c1ed9a1e1f458583023c301e96a692c1a08b53d0ec9ca910100d80451e3b7dc6a01bac4aecef8df798846bc235a08cbba2cf4c06804cc11219e95608c714e3f1430d491fadbba32a5751a04f97745834c9a5
+RSAParamDMP1 = 21f2452bb9b95dfd028c914bf799f1ca77e89a95d50d3c16d384f8455f8bd7af9eb3dfa3d591d9842def235f7630a8e48c088ff6642e101794535a933e1e976fa8509fc728b2da0c4a1a08d7fcf37abaae1ff3001aca1dc1bbb05d9dffbaa1a09f7fb1eef38237d9ebccc722b9338436dde7119112798c26809c1a8dec432061
+RSAParamDMQ1 = 1f7510aa62c2d8de4a3c53282781f41e02d0e8b402ae78432e449c48110161a11403f02d01880a8dcc938152d79721a4711a607ac4471ebf964810f95be47a45e60499e29f4c9773c83773404f606637728c2d0351bb03c326c8bb73a721e7fa5440ea2172bba1465fcc30dcb0d9f89930e815aa1f7f9729a857e00e0338dd59
+RSAParamIQMP = 4d1f0d756fe77e01099a652f50a88b7b685dc5bf00981d5d2376fd0c6fe29cd5b638734479305a73ad3c1599d39eae3bae035fbd6fed07c28de705933879a06e48e6a603686ed8e2560a5f6af1f2c24faf4aa960e382186f15eedce9a2491ae730680dd4cf778b70faa86826ab3223477cc91377b19a6d5a2eaea219760beed5
+
+PublicKey = RSA-2048-SPKI-PSS-SHA256
+Algorithm = RSA-PSS-SHA-256
+Input = 30820156304106092a864886f70d01010a3034a00f300d06096086480165030402010500a11c301a06092a864886f70d010108300d06096086480165030402010500a2030201200382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
+RSAParamN = cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f
+RSAParamE = 010001
+
+# The same key but constrained to PSS with any parameters (not supported)
+PrivateKey = RSA-2048-PSS
+Input = 308204ba020100300b06092a864886f70d01010a048204a6308204a20201000282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f02030100010282010060297ac7991b167a06d6b24758b8cbe208beb9b2d9ec9738bd80f90a2e35005dd7ce292d9e29ba885bd316fef1f20913bc0ac90d6b0808b2414d82104441d8624a33ce0233c8f780a48b375aff02d76712228a702484db3f9ebecccfbbee1709dba182800d949e9e4216e0bff3558388f8bd90da373a1d82743ec3fbdd1427fd16825a657a316912e8695365117ca2f845c909405fcac55f895fc15d20386c26ee78c9e99075029a178a6c1e4cf0c200e8a9cfb27e9d156f86e6c2adc22b1a84a1cd5ca5b2790875d79407c84b352395cb81cc3fed5bb043b69ede0c07204550025cee8c5f440170b6120bb48e0f747bcd8f522110850df043c428dfd187053102818100f6f961b47cbc035d3aedebc7de850a956b65ecdb9cf60764063f15aa48553c58d972fe6675056e35ddfdc37bf3b9f2f622ee271337256849c9bef2176fe8f7c3f8bb91ba374dd53baf3dec814d2bdec10c1fdc88cdd16876f26b1edfa3f094197edf4d42ff1fb2971103b898ca859c427287086a842ab410bb69cf2d35af6be302818100d47e724a7ff41048b270c2524a4101878b73159bb73d3dbc187b220e635b3534f96e243a184d93f860b6bfbb6b71c1ed9a1e1f458583023c301e96a692c1a08b53d0ec9ca910100d80451e3b7dc6a01bac4aecef8df798846bc235a08cbba2cf4c06804cc11219e95608c714e3f1430d491fadbba32a5751a04f97745834c9a502818021f2452bb9b95dfd028c914bf799f1ca77e89a95d50d3c16d384f8455f8bd7af9eb3dfa3d591d9842def235f7630a8e48c088ff6642e101794535a933e1e976fa8509fc728b2da0c4a1a08d7fcf37abaae1ff3001aca1dc1bbb05d9dffbaa1a09f7fb1eef38237d9ebccc722b9338436dde7119112798c26809c1a8dec4320610281801f7510aa62c2d8de4a3c53282781f41e02d0e8b402ae78432e449c48110161a11403f02d01880a8dcc938152d79721a4711a607ac4471ebf964810f95be47a45e60499e29f4c9773c83773404f606637728c2d0351bb03c326c8bb73a721e7fa5440ea2172bba1465fcc30dcb0d9f89930e815aa1f7f9729a857e00e0338dd590281804d1f0d756fe77e01099a652f50a88b7b685dc5bf00981d5d2376fd0c6fe29cd5b638734479305a73ad3c1599d39eae3bae035fbd6fed07c28de705933879a06e48e6a603686ed8e2560a5f6af1f2c24faf4aa960e382186f15eedce9a2491ae730680dd4cf778b70faa86826ab3223477cc91377b19a6d5a2eaea219760beed5
+Error = UNSUPPORTED_ALGORITHM
+
+PublicKey = RSA-2048-SPKI-PSS
+Input = 30820120300b06092a864886f70d01010a0382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
+Error = UNSUPPORTED_ALGORITHM
+
+# The same key but constrained to PSS, SHA-384, and salt length >= 48 (not supported)
+PrivateKey = RSA-2048-PSS-SHA384
+Input = 308204f0020100304106092a864886f70d01010a3034a00f300d06096086480165030402020500a11c301a06092a864886f70d010108300d06096086480165030402020500a203020130048204a6308204a20201000282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f02030100010282010060297ac7991b167a06d6b24758b8cbe208beb9b2d9ec9738bd80f90a2e35005dd7ce292d9e29ba885bd316fef1f20913bc0ac90d6b0808b2414d82104441d8624a33ce0233c8f780a48b375aff02d76712228a702484db3f9ebecccfbbee1709dba182800d949e9e4216e0bff3558388f8bd90da373a1d82743ec3fbdd1427fd16825a657a316912e8695365117ca2f845c909405fcac55f895fc15d20386c26ee78c9e99075029a178a6c1e4cf0c200e8a9cfb27e9d156f86e6c2adc22b1a84a1cd5ca5b2790875d79407c84b352395cb81cc3fed5bb043b69ede0c07204550025cee8c5f440170b6120bb48e0f747bcd8f522110850df043c428dfd187053102818100f6f961b47cbc035d3aedebc7de850a956b65ecdb9cf60764063f15aa48553c58d972fe6675056e35ddfdc37bf3b9f2f622ee271337256849c9bef2176fe8f7c3f8bb91ba374dd53baf3dec814d2bdec10c1fdc88cdd16876f26b1edfa3f094197edf4d42ff1fb2971103b898ca859c427287086a842ab410bb69cf2d35af6be302818100d47e724a7ff41048b270c2524a4101878b73159bb73d3dbc187b220e635b3534f96e243a184d93f860b6bfbb6b71c1ed9a1e1f458583023c301e96a692c1a08b53d0ec9ca910100d80451e3b7dc6a01bac4aecef8df798846bc235a08cbba2cf4c06804cc11219e95608c714e3f1430d491fadbba32a5751a04f97745834c9a502818021f2452bb9b95dfd028c914bf799f1ca77e89a95d50d3c16d384f8455f8bd7af9eb3dfa3d591d9842def235f7630a8e48c088ff6642e101794535a933e1e976fa8509fc728b2da0c4a1a08d7fcf37abaae1ff3001aca1dc1bbb05d9dffbaa1a09f7fb1eef38237d9ebccc722b9338436dde7119112798c26809c1a8dec4320610281801f7510aa62c2d8de4a3c53282781f41e02d0e8b402ae78432e449c48110161a11403f02d01880a8dcc938152d79721a4711a607ac4471ebf964810f95be47a45e60499e29f4c9773c83773404f606637728c2d0351bb03c326c8bb73a721e7fa5440ea2172bba1465fcc30dcb0d9f89930e815aa1f7f9729a857e00e0338dd590281804d1f0d756fe77e01099a652f50a88b7b685dc5bf00981d5d2376fd0c6fe29cd5b638734479305a73ad3c1599d39eae3bae035fbd6fed07c28de705933879a06e48e6a603686ed8e2560a5f6af1f2c24faf4aa960e382186f15eedce9a2491ae730680dd4cf778b70faa86826ab3223477cc91377b19a6d5a2eaea219760beed5
+Error = UNSUPPORTED_ALGORITHM
+
+PublicKey = RSA-2048-SPKI-PSS-SHA384
+Input = 30820156304106092a864886f70d01010a3034a00f300d06096086480165030402020500a11c301a06092a864886f70d010108300d06096086480165030402020500a2030201300382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
+Error = UNSUPPORTED_ALGORITHM
+
+# The same key but constrained to PSS, SHA-512, and salt length >= 64 (not supported)
+PrivateKey = RSA-2048-PSS-SHA512
+Input = 308204f0020100304106092a864886f70d01010a3034a00f300d06096086480165030402030500a11c301a06092a864886f70d010108300d06096086480165030402030500a203020140048204a6308204a20201000282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f02030100010282010060297ac7991b167a06d6b24758b8cbe208beb9b2d9ec9738bd80f90a2e35005dd7ce292d9e29ba885bd316fef1f20913bc0ac90d6b0808b2414d82104441d8624a33ce0233c8f780a48b375aff02d76712228a702484db3f9ebecccfbbee1709dba182800d949e9e4216e0bff3558388f8bd90da373a1d82743ec3fbdd1427fd16825a657a316912e8695365117ca2f845c909405fcac55f895fc15d20386c26ee78c9e99075029a178a6c1e4cf0c200e8a9cfb27e9d156f86e6c2adc22b1a84a1cd5ca5b2790875d79407c84b352395cb81cc3fed5bb043b69ede0c07204550025cee8c5f440170b6120bb48e0f747bcd8f522110850df043c428dfd187053102818100f6f961b47cbc035d3aedebc7de850a956b65ecdb9cf60764063f15aa48553c58d972fe6675056e35ddfdc37bf3b9f2f622ee271337256849c9bef2176fe8f7c3f8bb91ba374dd53baf3dec814d2bdec10c1fdc88cdd16876f26b1edfa3f094197edf4d42ff1fb2971103b898ca859c427287086a842ab410bb69cf2d35af6be302818100d47e724a7ff41048b270c2524a4101878b73159bb73d3dbc187b220e635b3534f96e243a184d93f860b6bfbb6b71c1ed9a1e1f458583023c301e96a692c1a08b53d0ec9ca910100d80451e3b7dc6a01bac4aecef8df798846bc235a08cbba2cf4c06804cc11219e95608c714e3f1430d491fadbba32a5751a04f97745834c9a502818021f2452bb9b95dfd028c914bf799f1ca77e89a95d50d3c16d384f8455f8bd7af9eb3dfa3d591d9842def235f7630a8e48c088ff6642e101794535a933e1e976fa8509fc728b2da0c4a1a08d7fcf37abaae1ff3001aca1dc1bbb05d9dffbaa1a09f7fb1eef38237d9ebccc722b9338436dde7119112798c26809c1a8dec4320610281801f7510aa62c2d8de4a3c53282781f41e02d0e8b402ae78432e449c48110161a11403f02d01880a8dcc938152d79721a4711a607ac4471ebf964810f95be47a45e60499e29f4c9773c83773404f606637728c2d0351bb03c326c8bb73a721e7fa5440ea2172bba1465fcc30dcb0d9f89930e815aa1f7f9729a857e00e0338dd590281804d1f0d756fe77e01099a652f50a88b7b685dc5bf00981d5d2376fd0c6fe29cd5b638734479305a73ad3c1599d39eae3bae035fbd6fed07c28de705933879a06e48e6a603686ed8e2560a5f6af1f2c24faf4aa960e382186f15eedce9a2491ae730680dd4cf778b70faa86826ab3223477cc91377b19a6d5a2eaea219760beed5
+Error = UNSUPPORTED_ALGORITHM
+
+PublicKey = RSA-2048-SPKI-PSS-SHA512
+Input = 30820156304106092a864886f70d01010a3034a00f300d06096086480165030402030500a11c301a06092a864886f70d010108300d06096086480165030402030500a2030201400382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
+Error = UNSUPPORTED_ALGORITHM
+
+# The PSS parameter parser should accept both explicit and missing NULL. See
+# Section 2.1 of RFC 4055. This encoding does not round-trip. When re-encoded,
+# we will put the NULLs in, to match the canonical PSS encoding.
+PublicKey = RSA-2048-SPKI-PSS-SHA256-NoNULL
+Algorithm = RSA-PSS-SHA-256
+Input = 30820152303d06092a864886f70d01010a3030a00d300b0609608648016503040201a11a301806092a864886f70d010108300b0609608648016503040201a2030201200382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
+Output = 30820156304106092a864886f70d01010a3034a00f300d06096086480165030402010500a11c301a06092a864886f70d010108300d06096086480165030402010500a2030201200382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
+
+# Although the defaults specify SHA-1, we do not allow SHA-1.
+PublicKey = RSA-2048-SPKI-PSS-SHA1-Defaults
+Input = 30820122300d06092a864886f70d01010a30000382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
+Error = BAD_ENCODING
+
+# PSS trailer field must be the default, and thus omitted.
+PublicKey = RSA-2048-SPKI-PSS-SHA256-TrailerField
+Input = 3082015b304606092a864886f70d01010a3039a00f300d06096086480165030402010500a11c301a06092a864886f70d010108300d06096086480165030402010500a203020120a3030201010382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
+Error = BAD_ENCODING
+
+# We do not support minimum salt lengths other than exactly the hash length.
+PublicKey = RSA-2048-SPKI-PSS-SHA256-Salt31
+Input = 30820156304106092a864886f70d01010a3034a00f300d06096086480165030402010500a11c301a06092a864886f70d010108300d06096086480165030402010500a20302011f0382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
+Error = BAD_ENCODING
+
+PublicKey = RSA-2048-SPKI-PSS-SHA256-Salt33
+Input = 30820156304106092a864886f70d01010a3034a00f300d06096086480165030402010500a11c301a06092a864886f70d010108300d06096086480165030402010500a2030201210382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
+Error = BAD_ENCODING
+
 # RSA 512 bit key.
 PrivateKey = RSA-512
 Algorithm = RSA
@@ -410,6 +483,65 @@
 Input = "0123456789ABCDEF0123456789ABCDEF"
 Output = 4065b284b0a6e98d4c41a8427007f878d8dd61599c87764fa79b8bf03f030c48127a4b1a5af5a6e0cf9055e57a1f47e5b0c0d8c600e78369cf1c39374899fac91a812692aa2216ba10900ce85a5cf7fddcafb726e4b83479c5bb7b3b84b08ffe183b4c2973aa3193ec7b7d4ea73bf1b579c6657b78ad7800e1975a4838c28ffe353fafef96be27b5c69677760a71b6f4df65ba6fe6b3565580a536f966928294c6e9ece807a90c1477779bcbfa3a250e98d685097c162c1c8c56ab02bd2e16eec7a019b51c067bdba7fa8cd5460796e22c607a8b6d12e1deb9be51c6943c46590f416800c48bb4cbb8c409d316573e59eadf7d3b9e6e5c2d0e570692e511e139
 
+# A constrained PSS key must use matching PSS parameters. The salt length
+# constraint is >=.
+Sign = RSA-2048-PSS-SHA256
+RSAPadding = PSS
+PSSSaltLength = 32
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+CheckVerify
+
+Sign = RSA-2048-PSS-SHA256
+RSAPadding = PSS
+PSSSaltLength = -1
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+CheckVerify
+
+Sign = RSA-2048-PSS-SHA256
+RSAPadding = PSS
+PSSSaltLength = 33
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+CheckVerify
+
+Sign = RSA-2048-PSS-SHA256
+RSAPadding = PSS
+PSSSaltLength = 31
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Error = INVALID_PSS_SALTLEN
+
+Sign = RSA-2048-PSS-SHA256
+Digest = SHA384
+Input = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"
+Error = INVALID_DIGEST_TYPE
+
+Sign = RSA-2048-PSS-SHA256
+Digest = SHA256
+MGF1Digest = SHA384
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Error = INVALID_MGF1_MD
+
+# A constrained PSS key defaults to its constraints.
+Sign = RSA-2048-PSS-SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+CheckVerify
+
+Verify = RSA-2048-SPKI-PSS-SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = 3a1ccf5115b006ba94515c69df540b4780eb0e80b8880359c363a1d2d9f12251f42c60770b55d5172091b082685c84f756e7e04effcc717046adc1418cb5c25755ef53f12d0ced5fa06922d9360968868eee8a2cec75122e553f71bac46e1ccd0aad29cc63ead7d4d6c092bf0e51aeaf70a994665769a56fe9176ddb89f8db22de1bc08d4a0865aaa789cca5056806b60d8bc36b508c645e0c4e9d10d6383498421ed5f42103e77e2f50d73156a0c4fca36fe62e7b7c418c33673158582672747e3e2a75bee287b8402f051cc93c748596f7925a69928b4bcacc90398da471c114369362df8c341e1a351dc9d7c393c556caf11855a3452b16a1df14798af199
+
+# A PSS key may not be used with RSASSA-PKCS1-v1_5, or encryption.
+Sign = RSA-2048-PSS-SHA256
+RSAPadding = PKCS1
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Error = ILLEGAL_OR_UNSUPPORTED_PADDING_MODE
+
+Decrypt = RSA-2048-PSS-SHA256
+Input = 550af55a2904e7b9762352f8fb7fa235a9cb053aacb2d5fcb8ca48453cb2ee3619746c701abf2d4cc67003471a187900b05aa812bd25ed05c675dfc8c97a24a7bf49bd6214992cad766d05a9a2b57b74f26a737e0237b8b76c45f1f226a836d7cfbc75ba999bdbe48dbc09227aa46c88f21dccba7840141ad5a5d71fd122e6bd6ac3e564780dfe623fc1ca9b995a6037bf0bbd43b205a84ac5444f34202c05ce9113087176432476576de6ffff9a52ea57c08be3ec2f49676cb8e12f762ac71fa3c321e00ac988910c85ff52f93825666ce0d40ffaa0592078919d4493f46d95ccf76364c6d57760dd0b64805f9afc76a2365a5575ca301d5103f0ea76cb9a78
+Error = OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE
 
 # RSA decrypt
 
diff --git a/crypto/fipsmodule/rsa/rsa.cc.inc b/crypto/fipsmodule/rsa/rsa.cc.inc
index f1ba37b..4c7c6ee 100644
--- a/crypto/fipsmodule/rsa/rsa.cc.inc
+++ b/crypto/fipsmodule/rsa/rsa.cc.inc
@@ -276,12 +276,6 @@
   }
 }
 
-const RSA_PSS_PARAMS *RSA_get0_pss_params(const RSA *rsa) {
-  // We do not support the id-RSASSA-PSS key encoding. If we add support later,
-  // the |maskHash| field should be filled in for OpenSSL compatibility.
-  return NULL;
-}
-
 void RSA_get0_crt_params(const RSA *rsa, const BIGNUM **out_dmp1,
                          const BIGNUM **out_dmq1, const BIGNUM **out_iqmp) {
   if (out_dmp1 != NULL) {
diff --git a/crypto/pem/pem_all.cc b/crypto/pem/pem_all.cc
index acd3b25..278f703 100644
--- a/crypto/pem/pem_all.cc
+++ b/crypto/pem/pem_all.cc
@@ -38,14 +38,17 @@
 // the relevant private key: this means can handle "traditional" and PKCS#8
 // formats transparently.
 static RSA *pkey_get_rsa(EVP_PKEY *key, RSA **rsa) {
-  RSA *rtmp;
   if (!key) {
-    return NULL;
+    return nullptr;
   }
-  rtmp = EVP_PKEY_get1_RSA(key);
-  EVP_PKEY_free(key);
+  if (EVP_PKEY_id(key) != EVP_PKEY_RSA) {
+    // Don't accept RSA-PSS keys in this function.
+    OPENSSL_PUT_ERROR(EVP, EVP_R_EXPECTING_AN_RSA_KEY);
+    return nullptr;
+  }
+  RSA *rtmp = EVP_PKEY_get1_RSA(key);
   if (!rtmp) {
-    return NULL;
+    return nullptr;
   }
   if (rsa) {
     RSA_free(*rsa);
@@ -56,15 +59,13 @@
 
 RSA *PEM_read_bio_RSAPrivateKey(BIO *bp, RSA **rsa, pem_password_cb *cb,
                                 void *u) {
-  EVP_PKEY *pktmp;
-  pktmp = PEM_read_bio_PrivateKey(bp, NULL, cb, u);
-  return pkey_get_rsa(pktmp, rsa);
+  bssl::UniquePtr<EVP_PKEY> pkey(PEM_read_bio_PrivateKey(bp, nullptr, cb, u));
+  return pkey_get_rsa(pkey.get(), rsa);
 }
 
 RSA *PEM_read_RSAPrivateKey(FILE *fp, RSA **rsa, pem_password_cb *cb, void *u) {
-  EVP_PKEY *pktmp;
-  pktmp = PEM_read_PrivateKey(fp, NULL, cb, u);
-  return pkey_get_rsa(pktmp, rsa);
+  bssl::UniquePtr<EVP_PKEY> pkey(PEM_read_PrivateKey(fp, nullptr, cb, u));
+  return pkey_get_rsa(pkey.get(), rsa);
 }
 
 IMPLEMENT_PEM_write_cb_const(RSAPrivateKey, RSA, PEM_STRING_RSA, RSAPrivateKey)
diff --git a/crypto/rsa/rsa_extra.cc b/crypto/rsa/rsa_extra.cc
index 1563720..58015bd 100644
--- a/crypto/rsa/rsa_extra.cc
+++ b/crypto/rsa/rsa_extra.cc
@@ -17,3 +17,14 @@
 int RSA_blinding_on(RSA *rsa, BN_CTX *ctx) { return 1; }
 
 void RSA_blinding_off(RSA *rsa) {}
+
+const RSA_PSS_PARAMS *RSA_get0_pss_params(const RSA *rsa) {
+  // We do not currently implement this function. By default, we will not parse
+  // |EVP_PKEY_RSA_PSS|. Callers that opt in with a BoringSSL-specific API are
+  // currently assumed to not need this function. Callers that need that opt-in
+  // and this functionality should contact the BoringSSL team.
+  //
+  // If we do add support later, the |maskHash| field should be filled in for
+  // OpenSSL compatibility.
+  return nullptr;
+}
diff --git a/include/openssl/evp.h b/include/openssl/evp.h
index 73ca73f..afc67e8 100644
--- a/include/openssl/evp.h
+++ b/include/openssl/evp.h
@@ -172,6 +172,43 @@
 // request |EVP_pkey_dsa|, we could change that.
 OPENSSL_EXPORT const EVP_PKEY_ALG *EVP_pkey_dsa(void);
 
+// EVP_pkey_rsa_pss_sha256 implements RSASSA-PSS keys, encoded as id-RSASSA-PSS
+// (RFC 4055, Section 3.1). The |EVP_PKEY_id| value is |EVP_PKEY_RSA_PSS|. This
+// |EVP_PKEY_ALG| only accepts keys whose parameters specify:
+//
+//  - A hashAlgorithm of SHA-256
+//  - A maskGenAlgorithm of MGF1 with SHA-256
+//  - A minimum saltLength of 32
+//  - A trailerField of one (must be omitted in the encoding)
+//
+// Keys of this type will only be usable with RSASSA-PSS with matching signature
+// parameters.
+//
+// This algorithm type is not recommended. The id-RSASSA-PSS key type is not
+// widely implemented. Using it negates any compatibility benefits of using RSA.
+// More modern algorithms like ECDSA are more performant and more compatible
+// than id-RSASSA-PSS keys. This key type also adds significant complexity to a
+// system. It has a wide range of possible parameter sets, so any uses must
+// ensure all components not only support id-RSASSA-PSS, but also the specific
+// parameters chosen.
+//
+// Note the id-RSASSA-PSS key type is distinct from the RSASSA-PSS signature
+// algorithm. The widely implemented id-rsaEncryption key type (|EVP_pkey_rsa|
+// and |EVP_PKEY_RSA|) also supports RSASSA-PSS signatures.
+//
+// WARNING: Any |EVP_PKEY|s produced by this algorithm will return a non-NULL
+// |RSA| object through |EVP_PKEY_get1_RSA| and |EVP_PKEY_get0_RSA|. This is
+// dangerous as existing code may assume a non-NULL return implies the more
+// common id-rsaEncryption key. Additionally, the operations on the underlying
+// |RSA| object will not capture the RSA-PSS constraints, so callers risk
+// misusing the key by calling these functions. Callers using this algorithm
+// must use |EVP_PKEY_id| to distinguish |EVP_PKEY_RSA| and |EVP_PKEY_RSA_PSS|.
+//
+// WARNING: BoringSSL does not currently implement |RSA_get0_pss_params| with
+// these keys. Callers that require this functionality should contact the
+// BoringSSL team.
+OPENSSL_EXPORT const EVP_PKEY_ALG *EVP_pkey_rsa_pss_sha256(void);
+
 
 // Getting and setting concrete key types.
 //
@@ -187,6 +224,18 @@
 // non-mutating for thread-safety purposes, but mutating functions on the
 // returned lower-level objects are considered to also mutate the |EVP_PKEY| and
 // may not be called concurrently with other operations on the |EVP_PKEY|.
+//
+// WARNING: Matching OpenSSL, the RSA functions behave non-uniformly.
+// |EVP_PKEY_set1_RSA| and |EVP_PKEY_assign_RSA| construct an |EVP_PKEY_RSA|
+// key, while the |EVP_PKEY_get0_RSA| and |EVP_PKEY_get1_RSA| will return
+// non-NULL for both |EVP_PKEY_RSA| and |EVP_PKEY_RSA_PSS|.
+//
+// This means callers risk misusing a key if they assume a non-NULL return from
+// |EVP_PKEY_get0_RSA| or |EVP_PKEY_get1_RSA| implies |EVP_PKEY_RSA|. Prefer
+// |EVP_PKEY_id| to check the type of a key. To reduce this risk, BoringSSL does
+// not make |EVP_PKEY_RSA_PSS| available by default, only when callers opt in
+// via |EVP_pkey_rsa_pss_sha256|. This differs from upstream OpenSSL, where
+// callers are exposed to |EVP_PKEY_RSA_PSS| by default.
 
 OPENSSL_EXPORT int EVP_PKEY_set1_RSA(EVP_PKEY *pkey, RSA *key);
 OPENSSL_EXPORT int EVP_PKEY_assign_RSA(EVP_PKEY *pkey, RSA *key);
diff --git a/include/openssl/rsa.h b/include/openssl/rsa.h
index d948b19..71ce444 100644
--- a/include/openssl/rsa.h
+++ b/include/openssl/rsa.h
@@ -763,8 +763,13 @@
 OPENSSL_EXPORT int RSA_print(BIO *bio, const RSA *rsa, int indent);
 
 // RSA_get0_pss_params returns NULL. In OpenSSL, this function retries RSA-PSS
-// parameters associated with |RSA| objects, but BoringSSL does not support
-// the id-RSASSA-PSS key encoding.
+// parameters associated with |RSA| objects, but BoringSSL does not enable the
+// id-RSASSA-PSS key encoding by default.
+//
+// WARNING: BoringSSL does support id-RSASSA-PSS parameters when callers opt in
+// (see |EVP_pkey_rsa_pss_sha256|). We currently assume such callers do not need
+// this function. Callers that opt into id-RSASSA-PSS support and require this
+// functionality should contact the BoringSSL team.
 OPENSSL_EXPORT const RSA_PSS_PARAMS *RSA_get0_pss_params(const RSA *rsa);
 
 // RSA_new_method_no_e returns a newly-allocated |RSA| object backed by