From: William Lallemand Date: Fri, 28 Feb 2025 15:11:08 +0000 (+0100) Subject: MINOR: jws: implement a JWK public key converter X-Git-Tag: v3.2-dev7~48 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4dc0ba233ef7fcdd77dba6edb12a32b8347c7acb;p=thirdparty%2Fhaproxy.git MINOR: jws: implement a JWK public key converter 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 --- diff --git a/Makefile b/Makefile index d98c87999..5486f07d5 100644 --- 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 index 000000000..5f3c613b5 --- /dev/null +++ b/include/haproxy/jws.h @@ -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_ */ diff --git a/include/haproxy/openssl-compat.h b/include/haproxy/openssl-compat.h index f98aa7949..360cf7716 100644 --- a/include/haproxy/openssl-compat.h +++ b/include/haproxy/openssl-compat.h @@ -128,6 +128,10 @@ #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 index 000000000..e0cc23a82 --- /dev/null +++ b/src/jws.c @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include +#include +#include + +#if defined(HAVE_JWS) + +/* + * Convert an OpenSSL BIGNUM to a base64url representation + * Fill a buffer of max size + * + * Return the size of the data dumped in + */ +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 to a public key JWK + * Fill a buffer of 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 to a public key JWK + * Fill a buffer of 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 of 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 */ +