gnutls: drop the RSA-OAEP (SHA-1) OpenSSL fallback; just fail

GnuTLS/nettle cannot perform RSA-OAEP with SHA-1. The GnuTLS backend used to
delegate that case to OpenSSL when it was compiled in; now it simply rejects
plain RSA-OAEP (RSA-OAEP-256 remains native). This makes the GnuTLS backend's
behavior independent of whether OpenSSL is also built.

- gnutls/jwe.c: RSA-OAEP (SHA-1) encrypt/decrypt return failure instead of
  calling openssl_*_cek_rsa_pem.
- Remove the now-dead openssl_encrypt_cek_rsa_pem / openssl_decrypt_cek_rsa_pem
  helpers (and their declarations); they existed solely for this fallback. The
  shared rsa_oaep_{encrypt,decrypt}_pkey helpers stay (used by the OpenSSL ops);
  their comments are updated.
- tests/jwe_rsa.c: the GnuTLS backend never supports plain RSA-OAEP now,
  regardless of OpenSSL.
- README and doxygen/mainpage.dox: mark RSA-OAEP (SHA-1) unsupported under
  GnuTLS, drop the now-unused "OpenSSL fallback" legend entry, and update the
  note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Ben Collins <bcollins@libjwt.io>
diff --git a/README.md b/README.md
index a0f2a13..aeb2d3d 100644
--- a/README.md
+++ b/README.md
@@ -71,14 +71,13 @@
 the token. They also carry an optional shared unprotected header, per-recipient
 headers, and an application AAD member.
 
-Legend: :white_check_mark: native implementation &nbsp;·&nbsp;
-:large_blue_circle: supported, using OpenSSL as a fallback &nbsp;·&nbsp; :x: not supported
+Legend: :white_check_mark: native implementation &nbsp;·&nbsp; :x: not supported
 
 JWE key management ``alg``    | OpenSSL            | GnuTLS             | MbedTLS
 :---------------------------- | :----------------- | :----------------- | :-----------------
 ``dir`` (Direct Encryption)   | :white_check_mark: | :white_check_mark: | :white_check_mark:
 ``A128KW`` ``A192KW`` ``A256KW`` | :white_check_mark: | :white_check_mark: | :white_check_mark:
-``RSA-OAEP`` (SHA-1)          | :white_check_mark: | :large_blue_circle: | :white_check_mark:
+``RSA-OAEP`` (SHA-1)          | :white_check_mark: | :x:                | :white_check_mark:
 ``RSA-OAEP-256``              | :white_check_mark: | :white_check_mark: | :white_check_mark:
 ``ECDH-ES`` (+ ``+A128KW``/``+A192KW``/``+A256KW``) | :white_check_mark: | :white_check_mark: | :white_check_mark:
 
@@ -92,10 +91,9 @@
 > wrapping modes, on the EC curves P-256/384/521 and the OKP curves
 > X25519/X448, with optional ``apu``/``apv`` PartyInfo. ``RSA1_5`` and
 > ``zip`` (compression) are intentionally not supported. Each backend
-> implements JWE natively, with one exception: GnuTLS/Nettle cannot perform
-> RSA-OAEP with SHA-1, so under the GnuTLS backend plain ``RSA-OAEP`` falls
-> back to OpenSSL when it is compiled in, and is otherwise unsupported
-> (``RSA-OAEP-256`` is native).
+> implements JWE natively. GnuTLS/Nettle cannot perform RSA-OAEP with SHA-1,
+> so the GnuTLS backend does not support plain ``RSA-OAEP`` (``RSA-OAEP-256``
+> is native).
 
 ### Optional
 
diff --git a/doxygen/mainpage.dox b/doxygen/mainpage.dox
index c3ff2e9..82b92e7 100644
--- a/doxygen/mainpage.dox
+++ b/doxygen/mainpage.dox
@@ -54,14 +54,13 @@
 algorithm (``enc``).
 
 Legend: \emoji :white_check_mark: native implementation &middot;
-\emoji :large_blue_circle: supported, using OpenSSL as a fallback &middot;
 \emoji :x: not supported
 
 JWE key management ``alg``    | OpenSSL                   | GnuTLS                    | MbedTLS
 :---------------------------- | :------------------------ | :------------------------ | :------------------------
 ``dir``                       | \emoji :white_check_mark: | \emoji :white_check_mark: | \emoji :white_check_mark:
 ``A128KW`` ``A192KW`` ``A256KW`` | \emoji :white_check_mark: | \emoji :white_check_mark: | \emoji :white_check_mark:
-``RSA-OAEP`` (SHA-1)          | \emoji :white_check_mark: | \emoji :large_blue_circle: | \emoji :white_check_mark:
+``RSA-OAEP`` (SHA-1)          | \emoji :white_check_mark: | \emoji :x:                | \emoji :white_check_mark:
 ``RSA-OAEP-256``              | \emoji :white_check_mark: | \emoji :white_check_mark: | \emoji :white_check_mark:
 ``ECDH-ES`` (+ ``+A128KW``/``+A192KW``/``+A256KW``) | \emoji :white_check_mark: | \emoji :white_check_mark: | \emoji :white_check_mark:
 
@@ -73,10 +72,9 @@
 @note ``ECDH-ES`` supports both Direct Key Agreement and ``+A*KW`` key
 wrapping, on the EC curves P-256/384/521 and the OKP curves X25519/X448, with
 optional ``apu``/``apv`` PartyInfo. ``RSA1_5`` and ``zip`` are intentionally
-not supported. Each backend implements JWE natively except that GnuTLS/Nettle
-cannot perform RSA-OAEP with SHA-1, so under the GnuTLS backend plain
-``RSA-OAEP`` falls back to OpenSSL when it is compiled in, and is otherwise
-unsupported (``RSA-OAEP-256`` is native).
+not supported. Each backend implements JWE natively. GnuTLS/Nettle cannot
+perform RSA-OAEP with SHA-1, so the GnuTLS backend does not support plain
+``RSA-OAEP`` (``RSA-OAEP-256`` is native).
 
 @subsection optional Optional
 
diff --git a/libjwt/gnutls/jwe.c b/libjwt/gnutls/jwe.c
index 37e681e..a9f79c7 100644
--- a/libjwt/gnutls/jwe.c
+++ b/libjwt/gnutls/jwe.c
@@ -654,17 +654,10 @@
 	if (jk == NULL || jk->kty != JWK_KEY_TYPE_RSA)
 		return 1; // LCOV_EXCL_LINE
 
-	/* SHA-1 RSA-OAEP: GnuTLS/nettle has no SHA-1 OAEP. */
-	if (dig == GNUTLS_DIG_UNKNOWN) {
-#ifdef HAVE_OPENSSL
-		/* Delegate to OpenSSL via the PEM when that backend is present. */
-		return openssl_encrypt_cek_rsa_pem(alg, jwks_item_pem(key), cek,
-						   cek_len, out, out_len);
-#else
-		/* No OpenSSL backend: RSA-OAEP (SHA-1) is not supported here. */
+	/* SHA-1 RSA-OAEP: GnuTLS/nettle has no SHA-1 OAEP, so the GnuTLS backend
+	 * does not support plain RSA-OAEP (RSA-OAEP-256 is native). */
+	if (dig == GNUTLS_DIG_UNKNOWN)
 		return 1;
-#endif
-	}
 
 	if (gnutls_x509_spki_init(&spki))
 		return 1; // LCOV_EXCL_LINE
@@ -726,17 +719,10 @@
 	if (jk == NULL || jk->kty != JWK_KEY_TYPE_RSA || jk->priv == NULL)
 		return 1; // LCOV_EXCL_LINE
 
-	/* SHA-1 RSA-OAEP: GnuTLS/nettle has no SHA-1 OAEP. */
-	if (oaep_dig(alg) == GNUTLS_DIG_UNKNOWN) {
-#ifdef HAVE_OPENSSL
-		/* Delegate to OpenSSL via the PEM when that backend is present. */
-		return openssl_decrypt_cek_rsa_pem(alg, jwks_item_pem(key), in,
-						   in_len, cek, cek_len);
-#else
-		/* No OpenSSL backend: RSA-OAEP (SHA-1) is not supported here. */
+	/* SHA-1 RSA-OAEP: GnuTLS/nettle has no SHA-1 OAEP, so the GnuTLS backend
+	 * does not support plain RSA-OAEP (RSA-OAEP-256 is native). */
+	if (oaep_dig(alg) == GNUTLS_DIG_UNKNOWN)
 		return 1;
-#endif
-	}
 
 	/* The RSA-OAEP-256 SPKI was attached to jk->priv once at parse time
 	 * (gnutls_jwk_rsa_set_oaep), so decrypt does not mutate the shared key
diff --git a/libjwt/gnutls/jwt-gnutls.h b/libjwt/gnutls/jwt-gnutls.h
index 2ea69ad..5fe0023 100644
--- a/libjwt/gnutls/jwt-gnutls.h
+++ b/libjwt/gnutls/jwt-gnutls.h
@@ -36,20 +36,6 @@
 JWT_NO_EXPORT
 int gnutls_key2jwk_params(const char *key, size_t len, jwk_export_t *out);
 
-#ifdef HAVE_OPENSSL
-/* GnuTLS/nettle has no SHA-1 RSA-OAEP, so plain RSA-OAEP (SHA-1) is delegated
- * to OpenSSL when that backend is compiled in (RSA-OAEP-256 is native, and
- * without OpenSSL the SHA-1 variant is rejected cleanly). */
-JWT_NO_EXPORT
-int openssl_encrypt_cek_rsa_pem(jwe_key_alg_t alg, const char *pem,
-	const unsigned char *cek, size_t cek_len,
-	unsigned char **out, size_t *out_len);
-JWT_NO_EXPORT
-int openssl_decrypt_cek_rsa_pem(jwe_key_alg_t alg, const char *pem,
-	const unsigned char *in, size_t in_len,
-	unsigned char **cek, size_t *cek_len);
-#endif /* HAVE_OPENSSL */
-
 /* JWE (RFC 7516/7518) — native GnuTLS implementations. Backend internals
  * reached only through the jwt_crypto_ops table; keep out of ABI. */
 JWT_NO_EXPORT
diff --git a/libjwt/openssl/jwe.c b/libjwt/openssl/jwe.c
index a2e9847..e6e86d0 100644
--- a/libjwt/openssl/jwe.c
+++ b/libjwt/openssl/jwe.c
@@ -551,10 +551,8 @@
 	return 0;
 }
 
-/* @rfc{7518,4.3} RSAES-OAEP encrypt the CEK to a recipient EVP_PKEY. Shared by
- * the OpenSSL op (which uses the EVP_PKEY on the JWK) and the GnuTLS fallback
- * (GnuTLS cannot OAEP-encrypt with a public-only key, so it builds an EVP_PKEY
- * from the JWK's public PEM and calls this). */
+/* @rfc{7518,4.3} RSAES-OAEP encrypt the CEK to a recipient EVP_PKEY (the
+ * EVP_PKEY parsed from the JWK). */
 static int rsa_oaep_encrypt_pkey(jwe_key_alg_t alg, EVP_PKEY *pkey,
 				 const unsigned char *cek, size_t cek_len,
 				 unsigned char **out, size_t *out_len)
@@ -606,47 +604,8 @@
 				     cek, cek_len, out, out_len);
 }
 
-/* RSAES-OAEP encryption from a PEM-encoded RSA public key. Used by the GnuTLS
- * backend, whose pubkey path cannot OAEP-encrypt natively. */
-int openssl_encrypt_cek_rsa_pem(jwe_key_alg_t alg, const char *pem,
-				const unsigned char *cek, size_t cek_len,
-				unsigned char **out, size_t *out_len)
-{
-	EVP_PKEY *pkey = NULL;
-	BIO *bio = NULL;
-	int ret = 1;
-
-	if (pem == NULL)
-		return 1; // LCOV_EXCL_LINE
-
-	bio = BIO_new_mem_buf(pem, -1);
-	if (bio == NULL)
-		return 1; // LCOV_EXCL_LINE
-
-	/* The convenience PEM is a public key for a public-only JWK and a
-	 * private key for a private JWK; both carry the public part needed to
-	 * encrypt. Try public first, then private. */
-	pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
-	if (pkey == NULL) {
-		BIO_free(bio);
-		bio = BIO_new_mem_buf(pem, -1);
-		if (bio == NULL)
-			return 1; // LCOV_EXCL_LINE
-		pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
-	}
-
-	if (pkey != NULL)
-		ret = rsa_oaep_encrypt_pkey(alg, pkey, cek, cek_len, out,
-					    out_len);
-
-	EVP_PKEY_free(pkey);
-	BIO_free(bio);
-
-	return ret;
-}
-
-/* @rfc{7518,4.3} RSAES-OAEP decrypt with a recipient private EVP_PKEY. Shared
- * by the OpenSSL op and the GnuTLS fallback (see rsa_oaep_encrypt_pkey). */
+/* @rfc{7518,4.3} RSAES-OAEP decrypt with a recipient private EVP_PKEY (the
+ * EVP_PKEY parsed from the JWK). */
 static int rsa_oaep_decrypt_pkey(jwe_key_alg_t alg, EVP_PKEY *pkey,
 				 const unsigned char *in, size_t in_len,
 				 unsigned char **cek, size_t *cek_len)
@@ -700,33 +659,6 @@
 				     in, in_len, cek, cek_len);
 }
 
-/* RSAES-OAEP decryption from a PEM-encoded RSA private key. Used by the GnuTLS
- * backend, whose native OAEP decrypt is unreliable on the supported versions. */
-int openssl_decrypt_cek_rsa_pem(jwe_key_alg_t alg, const char *pem,
-				const unsigned char *in, size_t in_len,
-				unsigned char **cek, size_t *cek_len)
-{
-	EVP_PKEY *pkey = NULL;
-	BIO *bio = NULL;
-	int ret = 1;
-
-	if (pem == NULL)
-		return 1; // LCOV_EXCL_LINE
-
-	bio = BIO_new_mem_buf(pem, -1);
-	if (bio == NULL)
-		return 1; // LCOV_EXCL_LINE
-
-	pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
-	if (pkey != NULL)
-		ret = rsa_oaep_decrypt_pkey(alg, pkey, in, in_len, cek, cek_len);
-
-	EVP_PKEY_free(pkey);
-	BIO_free(bio);
-
-	return ret;
-}
-
 /* ======================== ECDH-ES (RFC 7518 4.6) ======================== */
 
 /* The derived-key length (octets) and the ASCII AlgorithmID for the Concat
diff --git a/libjwt/openssl/jwt-openssl.h b/libjwt/openssl/jwt-openssl.h
index 4c6070a..e493624 100644
--- a/libjwt/openssl/jwt-openssl.h
+++ b/libjwt/openssl/jwt-openssl.h
@@ -67,18 +67,10 @@
 	const unsigned char *cek, size_t cek_len,
 	unsigned char **out, size_t *out_len);
 JWT_NO_EXPORT
-int openssl_encrypt_cek_rsa_pem(jwe_key_alg_t alg, const char *pem,
-	const unsigned char *cek, size_t cek_len,
-	unsigned char **out, size_t *out_len);
-JWT_NO_EXPORT
 int openssl_decrypt_cek_rsa(jwe_key_alg_t alg, const jwk_item_t *key,
 	const unsigned char *in, size_t in_len,
 	unsigned char **cek, size_t *cek_len);
 JWT_NO_EXPORT
-int openssl_decrypt_cek_rsa_pem(jwe_key_alg_t alg, const char *pem,
-	const unsigned char *in, size_t in_len,
-	unsigned char **cek, size_t *cek_len);
-JWT_NO_EXPORT
 int openssl_ecdh_derive(jwe_key_alg_t alg, jwe_enc_t enc,
 	const jwk_item_t *key, int for_encrypt, jwt_json_t *hdr,
 	unsigned char **dk, size_t *dk_len);
diff --git a/tests/jwe_rsa.c b/tests/jwe_rsa.c
index d03c1f6..ba6f789 100644
--- a/tests/jwe_rsa.c
+++ b/tests/jwe_rsa.c
@@ -8,17 +8,12 @@
 
 static const char PT[] = "{\"sub\":\"1234567890\",\"name\":\"Jane Doe\"}";
 
-/* GnuTLS/nettle has no SHA-1 RSA-OAEP, so the GnuTLS backend can only do plain
- * RSA-OAEP (JWE_ALG_RSA_OAEP) via the OpenSSL fallback. In a build without the
- * OpenSSL backend it is unsupported. OpenSSL and MbedTLS do it natively. */
+/* GnuTLS/nettle has no SHA-1 RSA-OAEP, and the GnuTLS backend does not fall
+ * back to OpenSSL, so it never supports plain RSA-OAEP (JWE_ALG_RSA_OAEP).
+ * OpenSSL and MbedTLS do it natively. */
 static int rsa_oaep_sha1_supported(jwt_crypto_provider_t type)
 {
-#ifdef HAVE_OPENSSL
-	(void)type;
-	return 1;
-#else
 	return type != JWT_CRYPTO_OPS_GNUTLS;
-#endif
 }
 
 static void roundtrip(jwe_key_alg_t alg, jwe_enc_t enc)