]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: jws: implement a JWK public key converter
authorWilliam Lallemand <wlallemand@haproxy.com>
Fri, 28 Feb 2025 15:11:08 +0000 (16:11 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Mon, 3 Mar 2025 11:43:32 +0000 (12:43 +0100)
Implement a converter which takes an EVP_PKEY and converts it to a
public JWK key. This is the first step of the JWS implementation.

It supports both EC and RSA keys.

Know to work with:

- LibreSSL
- AWS-LC
- OpenSSL > 1.1.1

Makefile
include/haproxy/jws.h [new file with mode: 0644]
include/haproxy/openssl-compat.h
src/jws.c [new file with mode: 0644]

index d98c87999fa44384bc115604e1ad00cfbae43547..5486f07d539d905fb31110fd968e4ac1e89a2e64 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -627,7 +627,9 @@ ifneq ($(USE_OPENSSL:0=),)
     SSL_LDFLAGS   := $(if $(SSL_LIB),-L$(SSL_LIB)) -lssl -lcrypto
   endif
   USE_SSL         := $(if $(USE_SSL:0=),$(USE_SSL:0=),implicit)
-  OPTIONS_OBJS += src/ssl_sock.o src/ssl_ckch.o src/ssl_ocsp.o src/ssl_crtlist.o src/ssl_sample.o src/cfgparse-ssl.o src/ssl_gencert.o src/ssl_utils.o src/jwt.o src/ssl_clienthello.o
+  OPTIONS_OBJS += src/ssl_sock.o src/ssl_ckch.o src/ssl_ocsp.o src/ssl_crtlist.o     \
+                  src/ssl_sample.o src/cfgparse-ssl.o src/ssl_gencert.o              \
+                  src/ssl_utils.o src/jwt.o src/ssl_clienthello.o src/jws.o
 endif
 
 ifneq ($(USE_ENGINE:0=),)
diff --git a/include/haproxy/jws.h b/include/haproxy/jws.h
new file mode 100644 (file)
index 0000000..5f3c613
--- /dev/null
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef _HAPROXY_JWK_H_
+#define _HAPROXY_JWK_H_
+
+int bn2base64url(const BIGNUM *bn, char *dst, size_t dsize);
+int EVP_PKEY_to_pub_jwk(EVP_PKEY *pkey, char *dst, size_t dsize);
+
+#endif /* ! _HAPROXY_JWK_H_ */
index f98aa79497a05a6d441feca8caea73e3f6b86e9b..360cf771657619035d89a1d63f68a2cf20e3b5e5 100644 (file)
 #define HAVE_SSL_SET_SECURITY_LEVEL
 #endif
 
+#if ((defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x3030600L)) || (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) || defined(OPENSSL_IS_AWSLC)) && !defined(USE_OPENSSL_WOLFSSL)
+#define HAVE_JWS
+#endif
+
 #if !defined(HAVE_SSL_SET_SECURITY_LEVEL)
 /* define a nope function for set_security_level */
 #define SSL_CTX_set_security_level(ctx, level) ({})
diff --git a/src/jws.c b/src/jws.c
new file mode 100644 (file)
index 0000000..e0cc23a
--- /dev/null
+++ b/src/jws.c
@@ -0,0 +1,198 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <stdio.h>
+
+#include <haproxy/base64.h>
+#include <haproxy/chunk.h>
+#include <haproxy/openssl-compat.h>
+
+#if defined(HAVE_JWS)
+
+/*
+ * Convert an OpenSSL BIGNUM to a base64url representation
+ * Fill a buffer <dst> of <dsize> max size
+ *
+ * Return the size of the data dumped in <dst>
+ */
+int bn2base64url(const BIGNUM *bn, char *dst, size_t dsize)
+{
+       struct buffer *bin;
+       int binlen;
+       int ret = 0;
+
+       if ((bin = get_trash_chunk()) == NULL)
+               goto out;
+
+       binlen = BN_num_bytes(bn);
+       if (binlen > bin->size)
+               goto out;
+
+       if (BN_bn2bin(bn, (unsigned char *)bin->area) != binlen)
+               goto out;
+
+       ret = a2base64url(bin->area, binlen, dst, dsize);
+out:
+       return ret;
+}
+
+/*
+ * Convert a EC <pkey> to a public key JWK
+ * Fill a buffer <dst> of <dsize> max size
+ *
+ * Return the size of the data or 0
+ */
+static int EVP_PKEY_EC_to_pub_jwk(EVP_PKEY *pkey, char *dst, size_t dsize)
+{
+       BIGNUM *x = NULL, *y = NULL;
+       struct buffer *str_x = NULL, *str_y = NULL;
+       int ret = 0;
+
+#if HA_OPENSSL_VERSION_NUMBER > 0x30000000L
+       char crv[32];
+       size_t crvlen;
+
+       EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &x);
+       EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &y);
+
+       if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, crv, sizeof(crv), &crvlen) == 0)
+               goto out;
+#else
+       const char *crv = NULL;
+       const EC_KEY *ec = NULL;
+       const EC_GROUP *ec_group = NULL;
+       const EC_POINT *ec_point = NULL;
+
+       /* get EC from EVP */
+       if ((ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL)
+               goto out;
+       if ((ec_group = EC_KEY_get0_group(ec)) == NULL)
+               goto out;
+       if ((ec_point = EC_KEY_get0_public_key(ec)) == NULL)
+               goto out;
+
+       /* get group, point, x, y */
+       if ((x = BN_new()) == NULL)
+               goto out;
+       if ((y = BN_new()) == NULL)
+               goto out;
+       if ((EC_POINT_get_affine_coordinates(ec_group, ec_point, x, y, NULL)) == 0)
+               goto out;
+       if ((crv = EC_curve_nid2nist(EC_GROUP_get_curve_name(ec_group))) == NULL)
+               goto out;
+#endif
+
+       /* allocate trash */
+       if ((str_x = alloc_trash_chunk()) == NULL)
+               goto out;
+       if ((str_y = alloc_trash_chunk()) == NULL)
+               goto out;
+
+       /* convert x, y to base64url */
+       str_x->data = bn2base64url(x, str_x->area, str_x->size);
+       str_y->data = bn2base64url(y, str_y->area, str_y->size);
+       if (str_x->data == 0 || str_y->data == 0)
+               goto out;
+
+       ret = snprintf(dst, dsize, "{\n"
+                       "    \"kty\": \"%s\",\n"
+                       "    \"crv\": \"%s\",\n"
+                       "    \"x\":   \"%s\",\n"
+                       "    \"y\":   \"%s\"\n"
+                       "}\n",
+                       "EC", crv, str_x->area, str_y->area);
+       if (ret >= dsize)
+               ret = 0;
+
+out:
+       free_trash_chunk(str_x);
+       free_trash_chunk(str_y);
+
+       BN_free(x);
+       BN_free(y);
+
+       return ret;
+}
+
+/*
+ * Convert a RSA <pkey> to a public key JWK
+ * Fill a buffer <dst> of <dsize> max size
+ *
+ * Return the size of the data or 0
+ */
+static int EVP_PKEY_RSA_to_pub_jwk(EVP_PKEY *pkey, char *dst, size_t dsize)
+{
+       BIGNUM *n = NULL, *e = NULL;
+       struct buffer *str_n = NULL, *str_e = NULL;
+       int ret = 0;
+
+#if HA_OPENSSL_VERSION_NUMBER > 0x30000000L
+
+       if ((EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n)) == 0)
+               goto out;
+       if ((EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e)) == 0)
+               goto out;
+#else
+       const RSA *rsa;
+
+       if ((rsa = EVP_PKEY_get0_RSA(pkey)) == NULL)
+               goto out;
+       if ((n = (BIGNUM *)BN_dup(RSA_get0_n(rsa))) == NULL)
+               goto out;
+       if ((e = (BIGNUM *)BN_dup(RSA_get0_e(rsa))) == NULL)
+               goto out;
+#endif
+
+       /* allocate trash */
+       if ((str_n = alloc_trash_chunk()) == NULL)
+               goto out;
+       if ((str_e = alloc_trash_chunk()) == NULL)
+               goto out;
+
+       /* convert n, e to base64url */
+       str_n->data = bn2base64url(n, str_n->area, str_n->size);
+       str_e->data = bn2base64url(e, str_e->area, str_e->size);
+       if (str_n->data == 0 || str_e->data == 0)
+               goto out;
+
+       ret = snprintf(dst, dsize, "{\n"
+                       "    \"kty\": \"%s\",\n"
+                       "    \"n\":   \"%s\",\n"
+                       "    \"e\":   \"%s\"\n"
+                       "}\n",
+                       "RSA", str_n->area, str_e->area);
+       if (ret >= dsize)
+               ret = 0;
+
+out:
+       BN_free(n);
+       BN_free(e);
+       free_trash_chunk(str_n);
+       free_trash_chunk(str_e);
+
+       return ret;
+}
+
+/* Convert an EVP_PKEY to a public key JWK
+ * Fill a buffer <dst> of <dsize> max size
+ *
+ * Return the size of the data or 0
+ */
+int EVP_PKEY_to_pub_jwk(EVP_PKEY *pkey, char *dst, size_t dsize)
+{
+       int ret = 0;
+
+       switch (EVP_PKEY_base_id(pkey)) {
+               case EVP_PKEY_RSA:
+                       ret = EVP_PKEY_RSA_to_pub_jwk(pkey, dst, dsize);
+                       break;
+               case EVP_PKEY_EC:
+                       ret = EVP_PKEY_EC_to_pub_jwk(pkey, dst, dsize);
+                       break;
+               default:
+                       break;
+       }
+       return ret;
+}
+
+#endif /* HAVE_JWS */
+