]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-dcrypt: Allow loading JWK RSA key with d only
authorAki Tuomi <aki.tuomi@open-xchange.com>
Wed, 27 Dec 2023 20:19:32 +0000 (22:19 +0200)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Wed, 17 Jan 2024 08:29:07 +0000 (10:29 +0200)
RFC7518 section 6.3 says that only mandatory parameters are n, e and d.

src/lib-dcrypt/dcrypt-openssl1.c
src/lib-dcrypt/dcrypt-openssl3.c
src/lib-dcrypt/test-crypto.c

index 6ea3adff9acd53d4dedcef2720259a1f9f9e0a38..52ba64ee8a4b0798c8935c178b108ef943d5db86 100644 (file)
@@ -1684,6 +1684,165 @@ static int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp)
 }
 #endif
 
+/* This function calculates missing parameters. The only required values
+ * are e, n, d. If p_r and q_r are provided, they can be used directly
+ * instead of deriving them. */
+static bool dcrypt_openssl_derive_rsa_param(BIGNUM *e, BIGNUM *n, BIGNUM *d,
+                                           BIGNUM *p_r, BIGNUM *q_r,
+                                           BIGNUM *dmp1_r, BIGNUM *dmq1_r,
+                                           BIGNUM *iqmp_r)
+{
+       BIGNUM *p = NULL, *q = NULL;
+       BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
+       BIGNUM *mphi;
+       BN_CTX *ctx;
+       BIGNUM *two, *a, *limit, *t;
+       BIGNUM *cand, *k, *n1, *tmp;
+
+       bool found = FALSE;
+       bool ret = FALSE;
+
+       ctx = BN_CTX_new();
+       if (ctx == NULL)
+               i_fatal_status(FATAL_OUTOFMEM, "Cannot allocate BN_CTX");
+       p = BN_secure_new();
+       BN_set_flags(p, BN_FLG_CONSTTIME);
+       q = BN_secure_new();
+       BN_set_flags(q, BN_FLG_CONSTTIME);
+       mphi = BN_secure_new();
+       BN_set_flags(mphi, BN_FLG_CONSTTIME);
+       t = BN_secure_new();
+       BN_set_flags(mphi, BN_FLG_CONSTTIME);
+       a = BN_new();
+       BN_set_flags(a, BN_FLG_CONSTTIME);
+       two = BN_new();
+       BN_set_flags(two, BN_FLG_CONSTTIME);
+       limit = BN_new();
+       k = BN_secure_new();
+       BN_set_flags(k, BN_FLG_CONSTTIME);
+       cand = BN_secure_new();
+       BN_set_flags(cand, BN_FLG_CONSTTIME);
+       n1 = BN_new();
+       BN_set_flags(n1, BN_FLG_CONSTTIME);
+       tmp = BN_secure_new();
+       BN_set_flags(tmp, BN_FLG_CONSTTIME);
+
+       BN_dec2bn(&two, "2");
+       BN_copy(a, two);
+       BN_dec2bn(&limit, "100");
+       BN_sub(n1, n, BN_value_one());
+
+       if (BN_is_zero(p_r) != 1 && BN_is_zero(q_r) != 1) {
+               /* Make sure n is actually related to p and q*/
+               if (BN_mul(tmp, p_r, q_r, ctx) != 1 ||
+                   BN_cmp(tmp, n) != 0) {
+                       ret = FALSE;
+                       goto finally;
+               }
+               /* Then check that d is related to p and q */
+               BIGNUM *p_1 = BN_secure_new();
+               BN_set_flags(p_1, BN_FLG_CONSTTIME);
+               BN_sub(p_1, p_r, BN_value_one());
+               BIGNUM *q_1 = BN_secure_new();
+               BN_set_flags(q_1, BN_FLG_CONSTTIME);
+               BN_sub(q_1, q_r, BN_value_one());
+               BN_mul(tmp, p_1, q_1, ctx);
+               BIGNUM *tmp2 = BN_secure_new();
+               BN_set_flags(tmp2, BN_FLG_CONSTTIME);
+               BN_mod_mul(tmp2, d, e, tmp, ctx);
+               ret = BN_cmp(tmp2, BN_value_one()) == 0;
+               BN_free(tmp2);
+               BN_free(p_1);
+               BN_free(q_1);
+               if (!ret)
+                       goto finally;
+               BN_copy(p, p_r);
+               BN_copy(q, q_r);
+               goto have_pq;
+       }
+
+       /* calculate e*d - 1 */
+       if (BN_mul(mphi, d, e, ctx) != 1)
+               goto finally;
+       if (BN_sub(mphi, mphi, BN_value_one()) != 1)
+               goto finally;
+
+       /* this is a multiple of phi(n), even */
+       if (BN_copy(t, mphi) == NULL)
+               goto finally;
+
+       while (BN_is_odd(t) != 1)
+               if (BN_rshift1(t, t) != 1)
+                       goto finally;
+
+       /* Go through all multiplicative inverses in Zn. */
+       for (; BN_cmp(a, limit) < 0; BN_add(a, a, two)) {
+               if (BN_copy(k, t) == NULL)
+                       goto finally;
+               while (BN_cmp(k, mphi) < 0) {
+                       if (BN_mod_exp(cand, a, k, n, ctx) != 1)
+                               goto finally;
+                       if (BN_cmp(cand, BN_value_one()) != 0 &&
+                           BN_cmp(cand, n1) != 0) {
+                               if (BN_mod_exp(tmp, cand, two, n, ctx) != 1)
+                                       goto finally;
+                               if (BN_cmp(tmp, BN_value_one()) == 0) {
+                                       if (BN_add(tmp, cand, BN_value_one()) !=
+                                                   1 ||
+                                           BN_gcd(q, tmp, n, ctx) != 1 ||
+                                           BN_div(p, tmp, n, q, ctx) != 1 ||
+                                           BN_is_zero(tmp) != 1) {
+                                               ret = FALSE;
+                                               goto finally;
+                                       }
+                                       found = TRUE;
+                                       break;
+                               }
+                       }
+                       if (BN_lshift1(k, k) != 1)
+                               goto finally;
+               }
+               if (found)
+                       break;
+       }
+
+       if (!found)
+               goto finally;
+have_pq:
+       dmp1 = BN_secure_new();
+       dmq1 = BN_secure_new();
+       iqmp = BN_secure_new();
+       if (BN_sub(tmp, p, BN_value_one()) == 1 &&
+           BN_mod(dmp1, d, tmp, ctx) == 1 &&
+           BN_sub(tmp, q, BN_value_one()) == 1 &&
+           BN_mod(dmq1, d, tmp, ctx) == 1 &&
+           BN_mod_inverse(iqmp, q, p, ctx) != NULL) {
+               BN_copy(p_r, p);
+               BN_copy(q_r, q);
+               BN_copy(dmp1_r, dmp1);
+               BN_copy(dmq1_r, dmq1);
+               BN_copy(iqmp_r, iqmp);
+               ret = TRUE;
+       }
+       BN_free(dmp1);
+       BN_free(dmq1);
+       BN_free(iqmp);
+finally:
+       BN_free(p);
+       BN_free(q);
+       BN_free(tmp);
+       BN_free(t);
+       BN_free(k);
+       BN_free(a);
+       BN_free(limit);
+       BN_free(cand);
+       BN_free(two);
+       BN_free(n1);
+       BN_free(mphi);
+       BN_CTX_free(ctx);
+       return ret;
+}
+
 /* Loads both public and private key */
 static bool load_jwk_rsa_key(EVP_PKEY **key_r, bool want_private_key,
                             const struct json_tree_node *root,
@@ -1691,8 +1850,8 @@ static bool load_jwk_rsa_key(EVP_PKEY **key_r, bool want_private_key,
                             struct dcrypt_private_key *dec_key ATTR_UNUSED,
                             const char **error_r)
 {
-       const char *n, *e, *d = NULL, *p = NULL, *q = NULL, *dp = NULL;
-       const char *dq = NULL, *qi = NULL;
+       const char *n, *e, *d = NULL, *p = NULL, *q = NULL;
+       const char *dp = NULL, *dq = NULL, *qi = NULL;
        const struct json_tree_node *node;
 
        /* n and e must be present */
@@ -1714,49 +1873,77 @@ static bool load_jwk_rsa_key(EVP_PKEY **key_r, bool want_private_key,
                        *error_r = "Missing d parameter";
                        return FALSE;
                }
-
-               if ((node = json_tree_node_get_member(root, "p")) == NULL ||
-                   (p = json_tree_node_get_str(node)) == NULL) {
-                       *error_r = "Missing p parameter";
+               if ((node = json_tree_node_get_member(root, "p")) != NULL)
+                       p = json_tree_node_get_str(node);
+               if ((node = json_tree_node_get_member(root, "q")) != NULL)
+                       q = json_tree_node_get_str(node);
+               if ((p != NULL && q == NULL) || (p == NULL && q != NULL)) {
+                       *error_r = "p and q have to be both present";
                        return FALSE;
                }
 
-               if ((node = json_tree_node_get_member(root, "q")) == NULL ||
-                   (q = json_tree_node_get_str(node)) == NULL) {
-                       *error_r = "Missing q parameter";
-                       return FALSE;
-               }
-
-               if ((node = json_tree_node_get_member(root, "dp")) == NULL ||
-                   (dp = json_tree_node_get_str(node)) == NULL) {
-                       *error_r = "Missing dp parameter";
-                       return FALSE;
-               }
-
-               if ((node = json_tree_node_get_member(root, "dq")) == NULL ||
-                   (dq = json_tree_node_get_str(node)) == NULL) {
-                       *error_r = "Missing dq parameter";
-                       return FALSE;
-               }
-
-               if ((node = json_tree_node_get_member(root, "qi")) == NULL ||
-                   (qi = json_tree_node_get_str(node)) == NULL) {
-                       *error_r = "Missing qi parameter";
+               if ((node = json_tree_node_get_member(root, "dp")) != NULL)
+                       dp = json_tree_node_get_str(node);
+               if ((node = json_tree_node_get_member(root, "dq")) != NULL)
+                       dq = json_tree_node_get_str(node);
+               if ((node = json_tree_node_get_member(root, "qi")) != NULL)
+                       qi = json_tree_node_get_str(node);
+               if ((dq != NULL || dp != NULL || qi != NULL) &&
+                   (dp == NULL || dq == NULL || qi == NULL)) {
+                       *error_r = "dp, dq, and qi must be present together";
                        return FALSE;
                }
        }
 
        /* convert into BIGNUMs */
-       BIGNUM *pn, *pe, *pd, *pp, *pq, *pdp, *pdq, *pqi;
+       BIGNUM *pn, *pe, *pd;
+       BIGNUM *pp = NULL, *pq = NULL, *pdp = NULL, *pdq = NULL, *pqi = NULL;
        buffer_t *bn = t_base64url_decode_str(n);
        buffer_t *be = t_base64url_decode_str(e);
        if (want_private_key) {
                pd = BN_secure_new();
+               BN_set_flags(pd, BN_FLG_CONSTTIME);
                buffer_t *bd = t_base64url_decode_str(d);
                if (BN_bin2bn(bd->data, bd->used, pd) == NULL) {
                        BN_free(pd);
                        return dcrypt_openssl_error(error_r);
                }
+               pp = BN_secure_new();
+               BN_set_flags(pp, BN_FLG_CONSTTIME);
+               pq = BN_secure_new();
+               BN_set_flags(pq, BN_FLG_CONSTTIME);
+               if (p != NULL) {
+                       buffer_t *bp = t_base64url_decode_str(p);
+                       buffer_t *bq = t_base64url_decode_str(q);
+
+                       if (BN_bin2bn(bp->data, bp->used, pp) == NULL ||
+                           BN_bin2bn(bq->data, bq->used, pq) == NULL) {
+                               BN_free(pd);
+                               BN_free(pp);
+                               BN_free(pq);
+                               return dcrypt_openssl_error(error_r);
+                       }
+               }
+               pdp = BN_secure_new();
+               pdq = BN_secure_new();
+               pqi = BN_secure_new();
+               if (dp != NULL) {
+                       buffer_t *bdp = t_base64url_decode_str(dp);
+                       buffer_t *bdq = t_base64url_decode_str(dq);
+                       buffer_t *bqi = t_base64url_decode_str(qi);
+
+                       if (BN_bin2bn(bdp->data, bdp->used, pdp) == NULL ||
+                           BN_bin2bn(bdq->data, bdq->used, pdq) == NULL ||
+                           BN_bin2bn(bqi->data, bqi->used, pqi) == NULL) {
+                               BN_free(pd);
+                               BN_free(pp);
+                               BN_free(pq);
+                               BN_free(pdp);
+                               BN_free(pdq);
+                               BN_free(pqi);
+                               return dcrypt_openssl_error(error_r);
+                       }
+               }
        } else {
                pd = NULL;
        }
@@ -1766,62 +1953,77 @@ static bool load_jwk_rsa_key(EVP_PKEY **key_r, bool want_private_key,
 
        if (BN_bin2bn(bn->data, bn->used, pn) == NULL ||
            BN_bin2bn(be->data, be->used, pe) == NULL) {
-               if (pd != NULL)
-                       BN_free(pd);
                BN_free(pn);
                BN_free(pe);
+               if (want_private_key) {
+                       BN_free(pd);
+                       BN_free(pp);
+                       BN_free(pq);
+                       BN_free(pdp);
+                       BN_free(pdq);
+                       BN_free(pqi);
+               }
                return dcrypt_openssl_error(error_r);
        }
 
        RSA *rsa_key = RSA_new();
        if (rsa_key == NULL) {
-               if (pd != NULL)
-                       BN_free(pd);
                BN_free(pn);
                BN_free(pe);
+               if (want_private_key) {
+                       BN_free(pd);
+                       BN_free(pp);
+                       BN_free(pq);
+                       BN_free(pdp);
+                       BN_free(pdq);
+                       BN_free(pqi);
+               }
                return dcrypt_openssl_error(error_r);
        }
 
        if (RSA_set0_key(rsa_key, pn, pe, pd) != 1) {
-               if (pd != NULL)
-                       BN_free(pd);
                BN_free(pn);
                BN_free(pe);
                RSA_free(rsa_key);
+               if (want_private_key) {
+                       BN_free(pd);
+                       BN_free(pp);
+                       BN_free(pq);
+                       BN_free(pdp);
+                       BN_free(pdq);
+                       BN_free(pqi);
+               }
                return dcrypt_openssl_error(error_r);
        }
 
        if (want_private_key) {
-               pp = BN_secure_new();
-               pq = BN_secure_new();
-               pdp = BN_secure_new();
-               pdq = BN_secure_new();
-               pqi = BN_secure_new();
-
-               buffer_t *bp = t_base64url_decode_str(p);
-               buffer_t *bq = t_base64url_decode_str(q);
-               buffer_t *bdp = t_base64url_decode_str(dp);
-               buffer_t *bdq = t_base64url_decode_str(dq);
-               buffer_t *bqi = t_base64url_decode_str(qi);
-
-               if (BN_bin2bn(bp->data, bp->used, pp) == NULL ||
-                   BN_bin2bn(bq->data, bq->used, pq) == NULL ||
-                   BN_bin2bn(bdp->data, bdp->used, pdp) == NULL ||
-                   BN_bin2bn(bdq->data, bdq->used, pdq) == NULL ||
-                   BN_bin2bn(bqi->data, bqi->used, pqi) == NULL ||
-                   RSA_set0_factors(rsa_key, pp, pq) != 1) {
+               if (dp == NULL &&
+                   !dcrypt_openssl_derive_rsa_param(pe, pn, pd, pp, pq, pdp,
+                                                    pdq, pqi)) {
+                       *error_r = "Cannot derive rsa primes";
+                       BN_free(pp);
+                       BN_free(pq);
+                       BN_free(pdp);
+                       BN_free(pdq);
+                       BN_free(pqi);
                        RSA_free(rsa_key);
+                       return FALSE;
+               } else if (RSA_set0_factors(rsa_key, pp, pq) != 1) {
                        BN_free(pp);
                        BN_free(pq);
                        BN_free(pdp);
                        BN_free(pdq);
                        BN_free(pqi);
+                       RSA_free(rsa_key);
                        return dcrypt_openssl_error(error_r);
                } else if (RSA_set0_crt_params(rsa_key, pdp, pdq, pqi) != 1) {
-                       RSA_free(rsa_key);
                        BN_free(pdp);
                        BN_free(pdq);
                        BN_free(pqi);
+                       RSA_free(rsa_key);
+                       return dcrypt_openssl_error(error_r);
+               } else if (RSA_check_key(rsa_key) != 1) {
+                       RSA_free(rsa_key);
                        return dcrypt_openssl_error(error_r);
                }
        }
@@ -2043,7 +2245,7 @@ static bool store_jwk_ec_key(EVP_PKEY *pkey, bool is_private_key,
                const BIGNUM *d = EC_KEY_get0_private_key(ec_key);
                if (d == NULL) {
                        *error_r = "No private key available";
-                       json_ostream_destroy(&joutput);
+                       json_ostream_nfinish_destroy(&joutput);
                        return FALSE;
                }
                str_truncate(b64url_temp, 0);
index 0feb41aa8e61fda25d494a15d9d9919f2ae56197..c71b2f6baddf045565031cb7accd505a615d28ee 100644 (file)
@@ -29,6 +29,7 @@
 #include <openssl/core.h>
 #include <openssl/core_names.h>
 #include <openssl/params.h>
+#include <openssl/param_build.h>
 #include <openssl/err.h>
 
 /**
@@ -1645,6 +1646,168 @@ static bool load_jwk_ec_key(EVP_PKEY **key_r, bool want_private_key,
        return res;
 }
 
+/* This function calculates missing parameters. The only required values
+ * are e, n, d. If p_r and q_r are provided, they can be used directly
+ * instead of deriving them.
+ *
+ * This functionality exists since OpenSSL 3.2, but it is hard to check
+ * in configure, so we implement this here ourselves.
+ */
+static bool dcrypt_openssl_derive_rsa_param(BIGNUM *e, BIGNUM *n, BIGNUM *d,
+                                           BIGNUM *p_r, BIGNUM *q_r,
+                                           BIGNUM *dmp1_r, BIGNUM *dmq1_r,
+                                           BIGNUM *iqmp_r)
+{
+       BIGNUM *p = NULL, *q = NULL;
+       BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
+       BIGNUM *mphi;
+       BN_CTX *ctx;
+       BIGNUM *two, *a, *limit, *t;
+       BIGNUM *cand, *k, *n1, *tmp;
+
+       bool found = FALSE;
+       bool ret = FALSE;
+
+       if ((ctx = BN_CTX_new()) == NULL)
+               return FALSE;
+
+       mphi = BN_secure_new();
+       BN_set_flags(mphi, BN_FLG_CONSTTIME);
+       t = BN_secure_new();
+       BN_set_flags(mphi, BN_FLG_CONSTTIME);
+       a = BN_new();
+       BN_set_flags(a, BN_FLG_CONSTTIME);
+       two = BN_new();
+       BN_set_flags(two, BN_FLG_CONSTTIME);
+       limit = BN_new();
+       k = BN_secure_new();
+       BN_set_flags(k, BN_FLG_CONSTTIME);
+       cand = BN_secure_new();
+       BN_set_flags(cand, BN_FLG_CONSTTIME);
+       n1 = BN_new();
+       BN_set_flags(n1, BN_FLG_CONSTTIME);
+       tmp = BN_secure_new();
+       BN_set_flags(tmp, BN_FLG_CONSTTIME);
+       p = BN_secure_new();
+       BN_set_flags(p, BN_FLG_CONSTTIME);
+       q = BN_secure_new();
+       BN_set_flags(q, BN_FLG_CONSTTIME);
+       BN_dec2bn(&two, "2");
+       BN_copy(a, two);
+       BN_dec2bn(&limit, "100");
+       BN_sub(n1, n, BN_value_one());
+
+       if (BN_is_zero(p_r) != 1 && BN_is_zero(q_r) != 1) {
+               /* Make sure n is actually related to p and q*/
+               if (BN_mul(tmp, p_r, q_r, ctx) != 1 ||
+                   BN_cmp(tmp, n) != 0) {
+                       ret = FALSE;
+                       goto finally;
+               }
+               /* Then check that d is related to p and q */
+               BIGNUM *p_1 = BN_secure_new();
+               BN_set_flags(p_1, BN_FLG_CONSTTIME);
+               BN_sub(p_1, p_r, BN_value_one());
+               BIGNUM *q_1 = BN_secure_new();
+               BN_set_flags(q_1, BN_FLG_CONSTTIME);
+               BN_sub(q_1, q_r, BN_value_one());
+               BN_mul(tmp, p_1, q_1, ctx);
+               BIGNUM *tmp2 = BN_secure_new();
+               BN_set_flags(tmp2, BN_FLG_CONSTTIME);
+               BN_mod_mul(tmp2, d, e, tmp, ctx);
+               ret = BN_cmp(tmp2, BN_value_one()) == 0;
+               BN_free(tmp2);
+               BN_free(p_1);
+               BN_free(q_1);
+               if (!ret)
+                       goto finally;
+               BN_copy(p, p_r);
+               BN_copy(q, q_r);
+               goto have_pq;
+       }
+
+       /* calculate e*d - 1 */
+       if (BN_mul(mphi, d, e, ctx) != 1)
+               goto finally;
+       if (BN_sub(mphi, mphi, BN_value_one()) != 1)
+               goto finally;
+
+       /* this is a multiple of phi(n), even */
+       if (BN_copy(t, mphi) == NULL)
+               goto finally;
+
+       while (BN_is_odd(t) != 1)
+               if (BN_rshift1(t, t) != 1)
+                       goto finally;
+
+       /* Go through all multiplicative inverses in Zn. */
+       for (; BN_cmp(a, limit) < 0; BN_add(a, a, two)) {
+               if (BN_copy(k, t) == NULL)
+                       goto finally;
+               while (BN_cmp(k, mphi) < 0) {
+                       if (BN_mod_exp(cand, a, k, n, ctx) != 1)
+                               goto finally;
+                       if (BN_cmp(cand, BN_value_one()) != 0 &&
+                           BN_cmp(cand, n1) != 0) {
+                               if (BN_mod_exp(tmp, cand, two, n, ctx) != 1)
+                                       goto finally;
+                               if (BN_cmp(tmp, BN_value_one()) == 0) {
+                                       if (BN_add(tmp, cand, BN_value_one()) !=
+                                                   1 ||
+                                           BN_gcd(q, tmp, n, ctx) != 1 ||
+                                           BN_div(p, tmp, n, q, ctx) != 1 ||
+                                           BN_is_zero(tmp) != 1) {
+                                               ret = FALSE;
+                                               goto finally;
+                                       }
+                                       found = TRUE;
+                                       break;
+                               }
+                       }
+                       if (BN_lshift1(k, k) != 1)
+                               goto finally;
+               }
+               if (found)
+                       break;
+       }
+
+       if (!found)
+               goto finally;
+have_pq:
+       dmp1 = BN_secure_new();
+       dmq1 = BN_secure_new();
+       iqmp = BN_secure_new();
+       if (BN_sub(tmp, p, BN_value_one()) == 1 &&
+           BN_mod(dmp1, d, tmp, ctx) == 1 &&
+           BN_sub(tmp, q, BN_value_one()) == 1 &&
+           BN_mod(dmq1, d, tmp, ctx) == 1 &&
+           BN_mod_inverse(iqmp, q, p, ctx) != NULL) {
+               BN_copy(p_r, p);
+               BN_copy(q_r, q);
+               BN_copy(dmp1_r, dmp1);
+               BN_copy(dmq1_r, dmq1);
+               BN_copy(iqmp_r, iqmp);
+               ret = TRUE;
+       }
+       BN_free(dmp1);
+       BN_free(dmq1);
+       BN_free(iqmp);
+finally:
+       BN_free(p);
+       BN_free(q);
+       BN_free(tmp);
+       BN_free(t);
+       BN_free(k);
+       BN_free(a);
+       BN_free(limit);
+       BN_free(cand);
+       BN_free(two);
+       BN_free(n1);
+       BN_free(mphi);
+       BN_CTX_free(ctx);
+       return ret;
+}
+
 /* Loads both public and private key */
 static bool load_jwk_rsa_key(EVP_PKEY **key_r, bool want_private_key,
                             const struct json_tree_node *root,
@@ -1652,8 +1815,8 @@ static bool load_jwk_rsa_key(EVP_PKEY **key_r, bool want_private_key,
                             struct dcrypt_private_key *dec_key ATTR_UNUSED,
                             const char **error_r)
 {
-       const char *n, *e, *d = NULL, *p = NULL, *q = NULL, *dp = NULL;
-       const char *dq = NULL, *qi = NULL;
+       const char *n, *e, *d = NULL, *p = NULL, *q = NULL;
+       const char *dp = NULL, *dq = NULL, *qi = NULL;
        const struct json_tree_node *node;
 
        /* n and e must be present */
@@ -1675,110 +1838,138 @@ static bool load_jwk_rsa_key(EVP_PKEY **key_r, bool want_private_key,
                        *error_r = "Missing d parameter";
                        return FALSE;
                }
-
-               if ((node = json_tree_node_get_member(root, "p")) == NULL ||
-                   (p = json_tree_node_get_str(node)) == NULL) {
-                       *error_r = "Missing p parameter";
-                       return FALSE;
-               }
-
-               if ((node = json_tree_node_get_member(root, "q")) == NULL ||
-                   (q = json_tree_node_get_str(node)) == NULL) {
-                       *error_r = "Missing q parameter";
-                       return FALSE;
-               }
-
-               if ((node = json_tree_node_get_member(root, "dp")) == NULL ||
-                   (dp = json_tree_node_get_str(node)) == NULL) {
-                       *error_r = "Missing dp parameter";
+               if ((node = json_tree_node_get_member(root, "p")) != NULL)
+                       p = json_tree_node_get_str(node);
+               if ((node = json_tree_node_get_member(root, "q")) != NULL)
+                       q = json_tree_node_get_str(node);
+               if ((p != NULL && q == NULL) || (p == NULL && q != NULL)) {
+                       *error_r = "p and q have to be both present";
                        return FALSE;
                }
 
-               if ((node = json_tree_node_get_member(root, "dq")) == NULL ||
-                   (dq = json_tree_node_get_str(node)) == NULL) {
-                       *error_r = "Missing dq parameter";
-                       return FALSE;
-               }
-
-               if ((node = json_tree_node_get_member(root, "qi")) == NULL ||
-                   (qi = json_tree_node_get_str(node)) == NULL) {
-                       *error_r = "Missing qi parameter";
+               if ((node = json_tree_node_get_member(root, "dp")) != NULL)
+                       dp = json_tree_node_get_str(node);
+               if ((node = json_tree_node_get_member(root, "dq")) != NULL)
+                       dq = json_tree_node_get_str(node);
+               if ((node = json_tree_node_get_member(root, "qi")) != NULL)
+                       qi = json_tree_node_get_str(node);
+               if ((dq != NULL || dp != NULL || qi != NULL) &&
+                   (dp == NULL || dq == NULL || qi == NULL)) {
+                       *error_r = "dp, dq, and qi must be present together";
                        return FALSE;
                }
        }
 
        /* convert into BIGNUMs */
-       BIGNUM *pn = NULL, *pe = NULL, *pd = NULL, *pp = NULL;
-       BIGNUM *pq = NULL, *pdp = NULL, *pdq = NULL, *pqi = NULL;
+       BIGNUM *pn, *pe, *pd;
+       BIGNUM *pp = NULL, *pq = NULL, *pdp = NULL, *pdq = NULL, *pqi = NULL;
        buffer_t *bn = t_base64url_decode_str(n);
        buffer_t *be = t_base64url_decode_str(e);
        if (want_private_key) {
                pd = BN_secure_new();
+               BN_set_flags(pd, BN_FLG_CONSTTIME);
                buffer_t *bd = t_base64url_decode_str(d);
                if (BN_bin2bn(bd->data, bd->used, pd) == NULL) {
                        BN_free(pd);
                        return dcrypt_openssl_error(error_r);
                }
+               pp = BN_secure_new();
+               BN_set_flags(pp, BN_FLG_CONSTTIME);
+               pq = BN_secure_new();
+               BN_set_flags(pq, BN_FLG_CONSTTIME);
+               if (p != NULL) {
+                       buffer_t *bp = t_base64url_decode_str(p);
+                       buffer_t *bq = t_base64url_decode_str(q);
+
+                       if (BN_bin2bn(bp->data, bp->used, pp) == NULL ||
+                           BN_bin2bn(bq->data, bq->used, pq) == NULL) {
+                               BN_free(pd);
+                               BN_free(pp);
+                               BN_free(pq);
+                               return dcrypt_openssl_error(error_r);
+                       }
+               }
+               pdp = BN_secure_new();
+               pdq = BN_secure_new();
+               pqi = BN_secure_new();
+               if (dp != NULL) {
+                       buffer_t *bdp = t_base64url_decode_str(dp);
+                       buffer_t *bdq = t_base64url_decode_str(dq);
+                       buffer_t *bqi = t_base64url_decode_str(qi);
+
+                       if (BN_bin2bn(bdp->data, bdp->used, pdp) == NULL ||
+                           BN_bin2bn(bdq->data, bdq->used, pdq) == NULL ||
+                           BN_bin2bn(bqi->data, bqi->used, pqi) == NULL) {
+                               BN_free(pd);
+                               BN_free(pp);
+                               BN_free(pq);
+                               BN_free(pdp);
+                               BN_free(pdq);
+                               BN_free(pqi);
+                               return dcrypt_openssl_error(error_r);
+                       }
+               }
        } else {
                pd = NULL;
        }
 
        pn = BN_new();
+       BN_set_flags(pn, BN_FLG_CONSTTIME);
        pe = BN_new();
+       BN_set_flags(pe, BN_FLG_CONSTTIME);
 
        if (BN_bin2bn(bn->data, bn->used, pn) == NULL ||
            BN_bin2bn(be->data, be->used, pe) == NULL) {
-               BN_free(pd);
                BN_free(pn);
                BN_free(pe);
+               if (want_private_key) {
+                       BN_free(pd);
+                       BN_free(pp);
+                       BN_free(pq);
+                       BN_free(pdp);
+                       BN_free(pdq);
+                       BN_free(pqi);
+               }
                return dcrypt_openssl_error(error_r);
        }
 
        if (want_private_key) {
-               pp = BN_secure_new();
-               pq = BN_secure_new();
-               pdp = BN_secure_new();
-               pdq = BN_secure_new();
-               pqi = BN_secure_new();
-
-               buffer_t *bp = t_base64url_decode_str(p);
-               buffer_t *bq = t_base64url_decode_str(q);
-               buffer_t *bdp = t_base64url_decode_str(dp);
-               buffer_t *bdq = t_base64url_decode_str(dq);
-               buffer_t *bqi = t_base64url_decode_str(qi);
-
-               if (BN_bin2bn(bp->data, bp->used, pp) == NULL ||
-                   BN_bin2bn(bq->data, bq->used, pq) == NULL ||
-                   BN_bin2bn(bdp->data, bdp->used, pdp) == NULL ||
-                   BN_bin2bn(bdq->data, bdq->used, pdq) == NULL ||
-                   BN_bin2bn(bqi->data, bqi->used, pqi) == NULL) {
+               if (dp == NULL &&
+                   !dcrypt_openssl_derive_rsa_param(pe, pn, pd, pp, pq, pdp,
+                                                    pdq, pqi)) {
                        BN_free(pn);
                        BN_free(pe);
+                       BN_free(pd);
                        BN_free(pp);
                        BN_free(pq);
                        BN_free(pdp);
                        BN_free(pdq);
                        BN_free(pqi);
-                       return dcrypt_openssl_error(error_r);
+                       *error_r = "Cannot derive rsa primes";
+                       return FALSE;
                }
        }
 
        /* create pkey */
-       OSSL_PARAM params[9];
-       params[0] = dcrypt_construct_param_BN(OSSL_PKEY_PARAM_RSA_N, pn);
-       params[1] = dcrypt_construct_param_BN(OSSL_PKEY_PARAM_RSA_E, pe);
+       OSSL_PARAM_BLD *build = OSSL_PARAM_BLD_new();
+
+       OSSL_PARAM_BLD_push_BN(build, OSSL_PKEY_PARAM_RSA_N, pn);
+       OSSL_PARAM_BLD_push_BN(build, OSSL_PKEY_PARAM_RSA_E, pe);
 
        if (want_private_key) {
-               params[2] = dcrypt_construct_param_BN(OSSL_PKEY_PARAM_RSA_FACTOR1, pp);
-               params[3] = dcrypt_construct_param_BN(OSSL_PKEY_PARAM_RSA_FACTOR2, pq);
-               params[4] = dcrypt_construct_param_BN(OSSL_PKEY_PARAM_RSA_EXPONENT1, pdp);
-               params[5] = dcrypt_construct_param_BN(OSSL_PKEY_PARAM_RSA_EXPONENT1, pdq);
-               params[6] = dcrypt_construct_param_BN(OSSL_PKEY_PARAM_RSA_COEFFICIENT1, pqi);
-               params[7] = OSSL_PARAM_construct_end();
-       } else {
-               params[2] = OSSL_PARAM_construct_end();
+               OSSL_PARAM_BLD_push_BN(build, OSSL_PKEY_PARAM_RSA_D, pd);
+               OSSL_PARAM_BLD_push_BN(build, OSSL_PKEY_PARAM_RSA_FACTOR1, pp);
+               OSSL_PARAM_BLD_push_BN(build, OSSL_PKEY_PARAM_RSA_FACTOR2, pq);
+               OSSL_PARAM_BLD_push_BN(build, OSSL_PKEY_PARAM_RSA_EXPONENT1,
+                                      pdp);
+               OSSL_PARAM_BLD_push_BN(build, OSSL_PKEY_PARAM_RSA_EXPONENT2,
+                                      pdq);
+               OSSL_PARAM_BLD_push_BN(build, OSSL_PKEY_PARAM_RSA_COEFFICIENT1,
+                                      pqi);
        }
 
+       OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(build);
+
        /* then load the key */
        EVP_PKEY_CTX *ctx =
            EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
@@ -1790,6 +1981,7 @@ static bool load_jwk_rsa_key(EVP_PKEY **key_r, bool want_private_key,
                /* pass */
        }
        EVP_PKEY_CTX_free(ctx);
+       OSSL_PARAM_BLD_free(build);
        BN_free(pn);
        BN_free(pe);
 
@@ -1799,6 +1991,7 @@ static bool load_jwk_rsa_key(EVP_PKEY **key_r, bool want_private_key,
                BN_free(pdp);
                BN_free(pdq);
                BN_free(pqi);
+               BN_free(pd);
        }
 
        if (ec != 1) {
@@ -2024,7 +2217,7 @@ static bool store_jwk_ec_key(EVP_PKEY *pkey, bool is_private_key,
                EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &d);
                if (d == NULL) {
                        *error_r = "No private key available";
-                       json_ostream_destroy(&joutput);
+                       json_ostream_nfinish_destroy(&joutput);
                        return FALSE;
                }
                str_truncate(b64url_temp, 0);
index e93afd6c2fb6733da56bd07a74418090ec9e53c5..232878a01f82919d56f1e79f1323698247de8ea7 100644 (file)
@@ -1073,13 +1073,95 @@ static void test_load_invalid_keys(void)
                        .error = "point is not on curve",
                        .public = FALSE,
                },
+               /* JWT RSA: Missing d */
+               {
+                       .key = "{" \
+                               "\"p\": \"0Il4JCQvWtDxyVEHd18rqxhXzdzIaJ3Xq5311ppIXs-oNCe2G2eTAE-CRiePOE0aQ0rl0fjkEeL8kRZZa17npQ\"," \
+                               "\"kty\": \"RSA\"," \
+                               "\"q\": \"stp6wLoE3XI3oITZO73DkhpDpuNpZ4uMHCg8GCcj784xhLtlPF_hiPgNMgT7tS4JFHFwn7V5GEG3Rk8ThDVvPQ\"," \
+                               "\"e\": \"AQAB\"," \
+                               "\"use\": \"sig\"," \
+                               "\"n\": \"kbGOl_HS6aYs8Ya2Y-OMlK8YcaGldcLanU6wF8nCI0WnedR_DnzZllDhWr7o8h0J5BKuL7Hop_8rn5zSEva213_Zpy3cE5DdrWtdpGyyz9cTceuhukvFSBfw_D4HOQdigRYwerl8Oq6kqCYDL5ui-TmYDLbL_oVdXshfMsU2vVE\"" \
+                       "}",
+                       .error = "key is not private",
+                       .public = FALSE,
+               },
+               /* JWT RSA: Missing e */
+               {
+                       .key = "{" \
+                               "\"p\": \"0Il4JCQvWtDxyVEHd18rqxhXzdzIaJ3Xq5311ppIXs-oNCe2G2eTAE-CRiePOE0aQ0rl0fjkEeL8kRZZa17npQ\"," \
+                               "\"kty\": \"RSA\"," \
+                               "\"q\": \"stp6wLoE3XI3oITZO73DkhpDpuNpZ4uMHCg8GCcj784xhLtlPF_hiPgNMgT7tS4JFHFwn7V5GEG3Rk8ThDVvPQ\"," \
+                               "\"d\": \"CqxINudXPRiYWEU3HVAxHz9IeiKOXXcdzsJR8hwsparXnvwrJqOMyQ85ww0TQZFRBS09J29QDOaLipDRbuQ19q0c7k0ek_sIrrzx9iulSCPdrbhdw0LS48HfsJxoD5xFg8E5BtDAjnd0P3eUrtG3R1rZXpvnlMd6-kLW-WAyGnE\"," \
+                               "\"use\": \"sig\"," \
+                               "\"n\": \"kbGOl_HS6aYs8Ya2Y-OMlK8YcaGldcLanU6wF8nCI0WnedR_DnzZllDhWr7o8h0J5BKuL7Hop_8rn5zSEva213_Zpy3cE5DdrWtdpGyyz9cTceuhukvFSBfw_D4HOQdigRYwerl8Oq6kqCYDL5ui-TmYDLbL_oVdXshfMsU2vVE\"" \
+                       "}",
+                       .error = "Missing e parameter",
+                       .public = FALSE,
+               },
+               /* JWT RSA: Missing n */
+               {
+                       .key = "{" \
+                               "\"p\": \"0Il4JCQvWtDxyVEHd18rqxhXzdzIaJ3Xq5311ppIXs-oNCe2G2eTAE-CRiePOE0aQ0rl0fjkEeL8kRZZa17npQ\"," \
+                               "\"kty\": \"RSA\"," \
+                               "\"q\": \"stp6wLoE3XI3oITZO73DkhpDpuNpZ4uMHCg8GCcj784xhLtlPF_hiPgNMgT7tS4JFHFwn7V5GEG3Rk8ThDVvPQ\"," \
+                               "\"d\": \"CqxINudXPRiYWEU3HVAxHz9IeiKOXXcdzsJR8hwsparXnvwrJqOMyQ85ww0TQZFRBS09J29QDOaLipDRbuQ19q0c7k0ek_sIrrzx9iulSCPdrbhdw0LS48HfsJxoD5xFg8E5BtDAjnd0P3eUrtG3R1rZXpvnlMd6-kLW-WAyGnE\"," \
+                               "\"e\": \"AQAB\"," \
+                               "\"use\": \"sig\"" \
+                       "}",
+                       .error = "Missing n parameter",
+                       .public = FALSE,
+               },
+               /* JWT RSA: Mismatch p,q and d */
+               {
+                       .key = "{" \
+                               "\"p\": \"7v5Z9uz-SES2yQ1lWTvYIqxW7B6avGJcI6W_t5c5JDzLxmT3OQ-kw9oJV7oYvFVagIVgmTgP1ymvbcfYzDSKKQ\"," \
+                               "\"kty\": \"RSA\"," \
+                               "\"q\": \"sdZWMrIiOa8XJFtRkHKZE5sp6PTvTH1H52Zcr7O14j39zMmR-i9GL1-uI_EQGBA0TK-zxnZ-incUm2cWirwVbw\"," \
+                               "\"d\": \"CqxINudXPRiYWEU3HVAxHz9IeiKOXXcdzsJR8hwsparXnvwrJqOMyQ85ww0TQZFRBS09J29QDOaLipDRbuQ19q0c7k0ek_sIrrzx9iulSCPdrbhdw0LS48HfsJxoD5xFg8E5BtDAjnd0P3eUrtG3R1rZXpvnlMd6-kLW-WAyGnE\"," \
+                               "\"e\": \"AQAB\"," \
+                               "\"use\": \"sig\"," \
+                               "\"n\": \"kbGOl_HS6aYs8Ya2Y-OMlK8YcaGldcLanU6wF8nCI0WnedR_DnzZllDhWr7o8h0J5BKuL7Hop_8rn5zSEva213_Zpy3cE5DdrWtdpGyyz9cTceuhukvFSBfw_D4HOQdigRYwerl8Oq6kqCYDL5ui-TmYDLbL_oVdXshfMsU2vVE\"" \
+                       "}",
+                       .error = "Cannot derive rsa primes",
+                       .public = FALSE,
+               },
+               /* JWT RSA: Mismatch n and p,q,d */
+               {
+                       .key = "{" \
+                               "\"p\": \"0Il4JCQvWtDxyVEHd18rqxhXzdzIaJ3Xq5311ppIXs-oNCe2G2eTAE-CRiePOE0aQ0rl0fjkEeL8kRZZa17npQ\"," \
+                               "\"kty\": \"RSA\"," \
+                               "\"q\": \"stp6wLoE3XI3oITZO73DkhpDpuNpZ4uMHCg8GCcj784xhLtlPF_hiPgNMgT7tS4JFHFwn7V5GEG3Rk8ThDVvPQ\"," \
+                               "\"d\": \"CqxINudXPRiYWEU3HVAxHz9IeiKOXXcdzsJR8hwsparXnvwrJqOMyQ85ww0TQZFRBS09J29QDOaLipDRbuQ19q0c7k0ek_sIrrzx9iulSCPdrbhdw0LS48HfsJxoD5xFg8E5BtDAjnd0P3eUrtG3R1rZXpvnlMd6-kLW-WAyGnE\"," \
+                               "\"e\": \"AQAB\"," \
+                               "\"use\": \"sig\"," \
+                               "\"n\": \"pgX1S7R5QI9c2Y7X6KXgUyJdOLJP3DZWBR-NR7w96rHOayKeAHKPiyg9vyGzV8rB_uePbmrnhBAZ42IDlXW_AieJGhPLYkH34d4FX8cC7mmWWXXjolajZWoW5pCg2Ilkk47R1osXGkS6Ta97ODtAvzw7PAJq1jFFPjYXVmo5RMc\"" \
+                       "}",
+                       .error = "Cannot derive rsa primes",
+                       .public = FALSE,
+               },
+               /* JWT RSA: Mismatch e */
+               {
+                       .key = "{" \
+                               "\"p\": \"0Il4JCQvWtDxyVEHd18rqxhXzdzIaJ3Xq5311ppIXs-oNCe2G2eTAE-CRiePOE0aQ0rl0fjkEeL8kRZZa17npQ\"," \
+                               "\"kty\": \"RSA\"," \
+                               "\"q\": \"stp6wLoE3XI3oITZO73DkhpDpuNpZ4uMHCg8GCcj784xhLtlPF_hiPgNMgT7tS4JFHFwn7V5GEG3Rk8ThDVvPQ\"," \
+                               "\"d\": \"CqxINudXPRiYWEU3HVAxHz9IeiKOXXcdzsJR8hwsparXnvwrJqOMyQ85ww0TQZFRBS09J29QDOaLipDRbuQ19q0c7k0ek_sIrrzx9iulSCPdrbhdw0LS48HfsJxoD5xFg8E5BtDAjnd0P3eUrtG3R1rZXpvnlMd6-kLW-WAyGnE\"," \
+                               "\"e\": \"AQAC\"," \
+                               "\"use\": \"sig\"," \
+                               "\"n\": \"kbGOl_HS6aYs8Ya2Y-OMlK8YcaGldcLanU6wF8nCI0WnedR_DnzZllDhWr7o8h0J5BKuL7Hop_8rn5zSEva213_Zpy3cE5DdrWtdpGyyz9cTceuhukvFSBfw_D4HOQdigRYwerl8Oq6kqCYDL5ui-TmYDLbL_oVdXshfMsU2vVE\"" \
+                       "}",
+                       .error = "Cannot derive rsa primes",
+                       .public = FALSE,
+               },
+
        };
 
        for (size_t i = 0; i < N_ELEMENTS(invalid_keys); i++) {
                struct dcrypt_keypair pair;
                i_zero(&pair);
                bool ret;
-               const char *error;
+               const char *error = NULL;
                if (invalid_keys[i].public) {
                        ret = dcrypt_key_load_public(
                                &pair.pub, invalid_keys[i].key, &error);
@@ -1090,7 +1172,7 @@ static void test_load_invalid_keys(void)
                }
                test_assert_idx(ret == FALSE, i);
                test_assert_idx(error != NULL, i);
-               test_assert_idx(strstr(error, invalid_keys[i].error) != NULL,
+               test_assert_idx(error != NULL && strstr(error, invalid_keys[i].error) != NULL,
                                i);
                if (ret)
                        dcrypt_keypair_unref(&pair);
@@ -1423,6 +1505,42 @@ static void test_jwk_keys(void)
 
        dcrypt_keypair_unref(&pair);
 
+       /* try loading RSA keys */
+
+       jwk_key_json = "{" \
+"\"p\": \"0Il4JCQvWtDxyVEHd18rqxhXzdzIaJ3Xq5311ppIXs-oNCe2G2eTAE-CRiePOE0aQ0rl0fjkEeL8kRZZa17npQ\"," \
+"\"kty\": \"RSA\"," \
+"\"q\": \"stp6wLoE3XI3oITZO73DkhpDpuNpZ4uMHCg8GCcj784xhLtlPF_hiPgNMgT7tS4JFHFwn7V5GEG3Rk8ThDVvPQ\"," \
+"\"d\": \"CqxINudXPRiYWEU3HVAxHz9IeiKOXXcdzsJR8hwsparXnvwrJqOMyQ85ww0TQZFRBS09J29QDOaLipDRbuQ19q0c7k0ek_sIrrzx9iulSCPdrbhdw0LS48HfsJxoD5xFg8E5BtDAjnd0P3eUrtG3R1rZXpvnlMd6-kLW-WAyGnE\"," \
+"\"e\": \"AQAB\"," \
+"\"use\": \"sig\"," \
+"\"qi\": \"PuEkPmG12QTnyYd0DXcNJTD-aq8CCl3alpSn_ra4V-2p3r__auYVm-Z_DvxFmGQNPWf6ENA7i4ETTXJ29V8O1A\"," \
+"\"dp\": \"fKJ8tJUP3GZe6-RK4xR1Y_fTmo0nFieoW8C3yoBBWrEfpbRKScy4dgoPIWDJX2vtk2RzQPBRV0Njkk9aOCrrpQ\"," \
+"\"dq\": \"Ugph8HHjtWilF56Yvwym3yfpDG6YdQTP-kKCflnF5ERi9o23PGCG5ftSojUi-NLrG1OF49ysdXH_jeLMAwM3yQ\"," \
+"\"n\": \"kbGOl_HS6aYs8Ya2Y-OMlK8YcaGldcLanU6wF8nCI0WnedR_DnzZllDhWr7o8h0J5BKuL7Hop_8rn5zSEva213_Zpy3cE5DdrWtdpGyyz9cTceuhukvFSBfw_D4HOQdigRYwerl8Oq6kqCYDL5ui-TmYDLbL_oVdXshfMsU2vVE\"" \
+"}";
+
+       test_assert(dcrypt_key_load_private(&pair.priv, jwk_key_json, NULL, NULL, &error));
+       dcrypt_keypair_unref(&pair);
+
+       jwk_key_json = "{" \
+"\"kty\": \"RSA\"," \
+"\"d\": \"CqxINudXPRiYWEU3HVAxHz9IeiKOXXcdzsJR8hwsparXnvwrJqOMyQ85ww0TQZFRBS09J29QDOaLipDRbuQ19q0c7k0ek_sIrrzx9iulSCPdrbhdw0LS48HfsJxoD5xFg8E5BtDAjnd0P3eUrtG3R1rZXpvnlMd6-kLW-WAyGnE\"," \
+"\"e\": \"AQAB\"," \
+"\"use\": \"sig\"," \
+"\"n\": \"kbGOl_HS6aYs8Ya2Y-OMlK8YcaGldcLanU6wF8nCI0WnedR_DnzZllDhWr7o8h0J5BKuL7Hop_8rn5zSEva213_Zpy3cE5DdrWtdpGyyz9cTceuhukvFSBfw_D4HOQdigRYwerl8Oq6kqCYDL5ui-TmYDLbL_oVdXshfMsU2vVE\"" \
+"}";
+
+       test_assert(dcrypt_key_load_private(&pair.priv, jwk_key_json, NULL, NULL, &error));
+       jwk_key_json = "{" \
+"\"kty\": \"RSA\"," \
+"\"e\": \"AQAB\"," \
+"\"use\": \"sig\"," \
+"\"n\": \"kbGOl_HS6aYs8Ya2Y-OMlK8YcaGldcLanU6wF8nCI0WnedR_DnzZllDhWr7o8h0J5BKuL7Hop_8rn5zSEva213_Zpy3cE5DdrWtdpGyyz9cTceuhukvFSBfw_D4HOQdigRYwerl8Oq6kqCYDL5ui-TmYDLbL_oVdXshfMsU2vVE\"" \
+"}";
+       test_assert(dcrypt_key_load_public(&pair.pub, jwk_key_json, &error));
+       dcrypt_keypair_unref(&pair);
+
        test_end();
 }