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 ·
-:large_blue_circle: supported, using OpenSSL as a fallback · :x: not supported
+Legend: :white_check_mark: native implementation · :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 ·
-\emoji :large_blue_circle: supported, using OpenSSL as a fallback ·
\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)