]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: jwt: Convert an RSA JWK into an EVP_PKEY
authorRemi Tricot-Le Breton <rlebreton@haproxy.com>
Tue, 10 Feb 2026 14:29:59 +0000 (15:29 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Thu, 12 Feb 2026 15:31:12 +0000 (16:31 +0100)
Add helper functions that take a JWK (JSON representation of an RSA
private key) into an EVP_PKEY (containing the private key).
Those functions are not used yet, they will be used in the upcoming
'jwt_decrypt_jwk' converter.

src/jwe.c

index 2ebf2ebe249d28db05c03afab5a743ab1f95cd45..3d0f3abaf687bb9bfb9cac999530c47f050f6a63 100644 (file)
--- a/src/jwe.c
+++ b/src/jwe.c
@@ -652,33 +652,20 @@ end:
 }
 
 
-static int decrypt_cek_rsa(struct buffer *cek, struct buffer *decrypted_cek,
-                           struct buffer *cert, jwe_alg crypt_alg)
+/*
+ * Decrypt the content of <cek> buffer into <decrypted_cek> buffer thanks to the
+ * private key <pkey> using algorithm <crypt_alg> (RSA).
+ * Returns 0 in case of success, 1 otherwise.
+ */
+static int do_decrypt_cek_rsa(struct buffer *cek, struct buffer *decrypted_cek,
+                              EVP_PKEY *pkey, jwe_alg crypt_alg)
 {
        EVP_PKEY_CTX *ctx = NULL;
        const EVP_MD *md = NULL;
-       EVP_PKEY *pkey = NULL;
-       int retval = 0;
+       int retval = 1;
        int pad = 0;
        size_t outl = b_size(decrypted_cek);
 
-       struct ckch_store *store = NULL;
-
-       if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
-               goto end;
-
-       store = ckchs_lookup(b_orig(cert));
-       if (!store || !store->data->key || !store->conf.jwt) {
-               HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
-               goto end;
-       }
-
-       pkey = store->data->key;
-
-       EVP_PKEY_up_ref(pkey);
-
-       HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
-
        switch(crypt_alg) {
        case JWE_ALG_RSA1_5:
                pad = RSA_PKCS1_PADDING;
@@ -721,10 +708,48 @@ static int decrypt_cek_rsa(struct buffer *cek, struct buffer *decrypted_cek,
 
        decrypted_cek->data = outl;
 
-       retval = 1;
+       retval = 0;
 
 end:
        EVP_PKEY_CTX_free(ctx);
+       return retval;
+}
+
+
+/*
+ * Look for <cert> in the ckch_store tree and use its private key to decrypt
+ * <cek> into <decrypted_cek> using <crypt_alg> algorithm (of the RSA alg
+ * family).
+ * Returns 0 in case of success, 1 otherwise.
+ */
+static int decrypt_cek_rsa(struct buffer *cek, struct buffer *decrypted_cek,
+                           struct buffer *cert, jwe_alg crypt_alg)
+{
+       EVP_PKEY *pkey = NULL;
+       int retval = 1;
+
+       struct ckch_store *store = NULL;
+
+       if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
+               goto end;
+
+       store = ckchs_lookup(b_orig(cert));
+       if (!store || !store->data->key || !store->conf.jwt) {
+               HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+               goto end;
+       }
+
+       pkey = store->data->key;
+
+       EVP_PKEY_up_ref(pkey);
+
+       HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
+
+       if (do_decrypt_cek_rsa(cek, decrypted_cek, pkey, crypt_alg))
+               goto end;
+
+       retval = 0;
+end:
        EVP_PKEY_free(pkey);
        return retval;
 }
@@ -821,7 +846,7 @@ static int sample_conv_jwt_decrypt_cert(const struct arg *args, struct sample *s
        }
        (*cek)->data = size;
 
-       if (rsa && !decrypt_cek_rsa(*cek, decrypted_cek, cert, alg))
+       if (rsa && decrypt_cek_rsa(*cek, decrypted_cek, cert, alg))
                goto end;
 
        if (decrypt_ciphertext(enc, items, decoded_items, decrypted_cek, &out))
@@ -894,11 +919,209 @@ static int sample_conv_jwt_decrypt_secret_check(struct arg *args, struct sample_
 }
 
 
+/*
+ * Convert a base64url encoded buffer into a BIGNUM.
+ */
+static BIGNUM *base64url_to_BIGNUM(struct buffer *b64url_buf)
+{
+       BIGNUM *bn = NULL;
+       struct buffer *decoded = get_trash_chunk();
+       int size = 0;
+
+       if (!b64url_buf)
+               return NULL;
+
+       size = base64urldec(b_orig(b64url_buf), b_data(b64url_buf),
+                           b_orig(decoded), b_size(decoded));
+       if (size < 0)
+               return NULL;
+       decoded->data = size;
+
+       bn = BN_bin2bn((const unsigned char *)b_orig(decoded), b_data(decoded), NULL);
+
+       return bn;
+}
+
+/*
+ * Extract a field named <field> of type string out of the <jwk> JSON buffer and
+ * dump its value in <out>.
+ * Return 0 in case of success, 1 in case of error (JSON parsing error or value
+ * not found).
+ */
+static int get_jwk_field(struct buffer *jwk, const char *field, struct buffer *out)
+{
+       int size = 0;
+
+       chunk_reset(out);
+
+       size = mjson_get_string(b_orig(jwk), b_data(jwk), field,
+                               b_orig(out), b_size(out));
+       if (size == -1)
+               return 1;
+
+       out->data = size;
+       return 0;
+}
+
+enum {
+       RSA_BIGNUM_N,
+       RSA_BIGNUM_E,
+       RSA_BIGNUM_D,
+       RSA_BIGNUM_P,
+       RSA_BIGNUM_Q,
+       RSA_BIGNUM_DP,
+       RSA_BIGNUM_DQ,
+       RSA_BIGNUM_QI,
+
+       RSA_BIGNUM_COUNT
+};
+
+
+/*
+ * Build the EVP_PKEY out of the BIGNUMs parsed by the caller.
+ * The RSA_set0_ functions were deprecated in OpenSSL3, hence the two different
+ * code blocks.
+ * Returns 0 in case of success, 1 otherwise.
+ */
+static int do_build_RSA_PKEY(BIGNUM *nums[RSA_BIGNUM_COUNT], EVP_PKEY **pkey)
+#if HA_OPENSSL_VERSION_NUMBER >= 0x30000000L
+{
+       int retval = 1;
+       OSSL_PARAM *params = NULL;
+       OSSL_PARAM_BLD *param_bld = NULL;
+       EVP_PKEY_CTX *pctx = NULL;
+
+       param_bld = OSSL_PARAM_BLD_new();
+
+       if (!OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, nums[RSA_BIGNUM_N]) ||
+           !OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, nums[RSA_BIGNUM_E]) ||
+           !OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_D, nums[RSA_BIGNUM_D]) ||
+           !OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR1, nums[RSA_BIGNUM_P]) ||
+           !OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR2, nums[RSA_BIGNUM_Q]) ||
+           !OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, nums[RSA_BIGNUM_DP]) ||
+           !OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, nums[RSA_BIGNUM_DQ]) ||
+           !OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, nums[RSA_BIGNUM_QI]))
+               goto end;
+
+       params = OSSL_PARAM_BLD_to_param(param_bld);
+
+       if (!params)
+               goto end;
+
+       pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+       if (!pctx)
+               goto end;
+       if (EVP_PKEY_fromdata_init(pctx) != 1)
+               goto end;
+
+       if (EVP_PKEY_fromdata(pctx, pkey, EVP_PKEY_KEYPAIR, params) != 1)
+               goto end;
+
+       retval = 0;
+end:
+       OSSL_PARAM_BLD_free(param_bld);
+       OSSL_PARAM_free(params);
+       EVP_PKEY_CTX_free(pctx);
+
+       return retval;
+}
+#else /* HA_OPENSSL_VERSION_NUMBER < 0x30000000L */
+{
+       int retval = 1;
+       RSA *rsa = NULL;
+
+       rsa = RSA_new();
+       if (!rsa)
+               goto end;
+
+       if (RSA_set0_key(rsa, nums[RSA_BIGNUM_N], nums[RSA_BIGNUM_E], nums[RSA_BIGNUM_D]) != 1 ||
+           RSA_set0_factors(rsa, nums[RSA_BIGNUM_P], nums[RSA_BIGNUM_Q]) != 1 ||
+           RSA_set0_crt_params(rsa, nums[RSA_BIGNUM_DP], nums[RSA_BIGNUM_DQ], nums[RSA_BIGNUM_QI]) != 1)
+               goto end;
+
+       *pkey = EVP_PKEY_new();
+       if (!*pkey)
+               goto end;
+
+       if (EVP_PKEY_set1_RSA(*pkey, rsa) != 1)
+               goto end;
+
+       retval = 0;
+end:
+       RSA_free(rsa);
+       return retval;
+}
+#endif
+
+static inline void clear_bignums(BIGNUM *nums[RSA_BIGNUM_COUNT])
+{
+       int idx = 0;
+
+       while (idx < RSA_BIGNUM_COUNT)
+               BN_free(nums[idx++]);
+}
+
+/*
+ * Build an EVP_PKEY that contains an RSA private key out of a JWK buffer that
+ * must have an "RSA" key type ("kty" field).
+ * Return 0 in case of success, 1 otherwise.
+ */
+static int build_RSA_PKEY_from_buf(struct buffer *jwk, EVP_PKEY **pkey)
+{
+       BIGNUM *nums[RSA_BIGNUM_COUNT] = {};
+       int retval = 1;
+
+       struct buffer *tmpbuf = alloc_trash_chunk();
+
+       if (!tmpbuf)
+               goto end;
+
+       /*
+        * Extract all the mandatory fields that all represent BIGNUMs out of
+        * the JWK buffer.
+        */
+       if (get_jwk_field(jwk, "$.n", tmpbuf) || (nums[RSA_BIGNUM_N] = base64url_to_BIGNUM(tmpbuf)) == NULL)
+               goto end;
+       if (get_jwk_field(jwk, "$.e", tmpbuf) || (nums[RSA_BIGNUM_E] = base64url_to_BIGNUM(tmpbuf)) == NULL)
+               goto end;
+       if (get_jwk_field(jwk, "$.d", tmpbuf) || (nums[RSA_BIGNUM_D] = base64url_to_BIGNUM(tmpbuf)) == NULL)
+               goto end;
+       if (get_jwk_field(jwk, "$.p", tmpbuf) || (nums[RSA_BIGNUM_P] = base64url_to_BIGNUM(tmpbuf)) == NULL)
+               goto end;
+       if (get_jwk_field(jwk, "$.q", tmpbuf) || (nums[RSA_BIGNUM_Q] = base64url_to_BIGNUM(tmpbuf)) == NULL)
+               goto end;
+       if (get_jwk_field(jwk, "$.dp", tmpbuf) || (nums[RSA_BIGNUM_DP] = base64url_to_BIGNUM(tmpbuf)) == NULL)
+               goto end;
+       if (get_jwk_field(jwk, "$.dq", tmpbuf) || (nums[RSA_BIGNUM_DQ] = base64url_to_BIGNUM(tmpbuf)) == NULL)
+               goto end;
+       if (get_jwk_field(jwk, "$.qi", tmpbuf) || (nums[RSA_BIGNUM_QI] = base64url_to_BIGNUM(tmpbuf)) == NULL)
+               goto end;
+
+       retval = do_build_RSA_PKEY(nums, pkey);
+
+end:
+       /* The bignums are duplicated with OpenSSL3+ but not with the older API */
+#if HA_OPENSSL_VERSION_NUMBER >= 0x30000000L
+       clear_bignums(nums);
+#else
+       if (retval)
+               clear_bignums(nums);
+#endif
+
+       free_trash_chunk(tmpbuf);
+       if (retval) {
+               EVP_PKEY_free(*pkey);
+               *pkey = NULL;
+       }
+
+       return retval;
+}
+
 
 static struct sample_conv_kw_list sample_conv_kws = {ILH, {
        /* JSON Web Token converters */
        { "jwt_decrypt_secret",    sample_conv_jwt_decrypt_secret, ARG1(1,STR), sample_conv_jwt_decrypt_secret_check, SMP_T_BIN, SMP_T_BIN },
-       { "jwt_decrypt_cert",      sample_conv_jwt_decrypt_cert,   ARG1(1,STR), sample_conv_jwt_decrypt_cert_check,    SMP_T_BIN, SMP_T_BIN },
+       { "jwt_decrypt_cert",      sample_conv_jwt_decrypt_cert,   ARG1(1,STR), sample_conv_jwt_decrypt_cert_check,   SMP_T_BIN, SMP_T_BIN },
        { NULL, NULL, 0, 0, 0 },
 
 }};