From 0485079436ad11e6a4f403c3f7a259c7e51da691 Mon Sep 17 00:00:00 2001
From: Andre Noll <maan@tuebingen.mpg.de>
Date: Sun, 7 May 2023 17:49:58 +0200
Subject: [PATCH] openssl: Use the EVP library for RSA public encryption.

Many functions related to RSA have been deprecated in openssl-3. Users
of the deprecated API are expected to switch to the high-level
cryptographic functions of the EVP library which ships together
with openssl.

Since openssl-1.0 is still supported and even openssl-1.1 lacks some
of the features we need for EVP, for example OSSL_PARAM_construct_BN(),
we check for this symbol at configure time and use #ifdefs in openssl.c
to compile the code conditionally depending on the value of the new
HAVE_OSSL_PARAM preprocessor macro. The code should work with both
old and new openssl versions.

apc_get_pubkey() used to call RSA_size() to obtain the key size in
bytes for the return value, but RSA_size() is one of the functions
that got deprecated in openssl-3. So modify read_public_key() to
return the number of bits of the modulus (rather than the constant
one), and use 1/8 of this number as the return value.
---
 configure.ac |   5 ++
 openssl.c    | 134 +++++++++++++++++++++++++++++++++++++--------------
 2 files changed, 104 insertions(+), 35 deletions(-)

diff --git a/configure.ac b/configure.ac
index b9ac3d0d..d6796e56 100644
--- a/configure.ac
+++ b/configure.ac
@@ -110,6 +110,11 @@ if test $HAVE_OPENSSL = yes; then
 	will be removed in the next major paraslash release. Please upgrade
 	your openssl installation.])
 	fi
+
+	AC_CHECK_LIB([crypto], [OSSL_PARAM_construct_BN], [HAVE_OSSL_PARAM=yes],
+		[HAVE_OSSL_PARAM=no])
+	test $HAVE_OSSL_PARAM = yes &&
+		AC_DEFINE([HAVE_OSSL_PARAM], [1], [openssl >= 3.0])
 	HAVE_CRYPTO_CLEANUP_ALL_EX_DATA=yes
 	AC_CHECK_DECL([CRYPTO_cleanup_all_ex_data], [],
 		[HAVE_CRYPTO_CLEANUP_ALL_EX_DATA=no],
diff --git a/openssl.c b/openssl.c
index 5f981437..d30d8371 100644
--- a/openssl.c
+++ b/openssl.c
@@ -22,6 +22,8 @@
 
 struct asymmetric_key {
 	RSA *rsa;
+	EVP_PKEY *pkey;
+	EVP_PKEY_CTX *ctx;
 };
 
 static int openssl_perror(const char *pfx)
@@ -103,35 +105,78 @@ static int read_bignum(const unsigned char *buf, size_t len, BIGNUM **result)
 	return bnsize + 4;
 }
 
-static int read_public_key(const unsigned char *blob, int blen,
-		struct asymmetric_key *result)
+#ifdef HAVE_OSSL_PARAM /* openssl-3 */
+/*
+ * Convert bignumns e and n to a pkey and context.
+ */
+static int generate_public_pkey(struct asymmetric_key *pub,
+		const BIGNUM *e, const BIGNUM *n)
 {
-	int ret;
-	RSA *rsa;
-	BIGNUM *n, *e;
+	unsigned char *ebuf, *nbuf;
+	int ret, ebytes = BN_num_bytes(e), nbytes = BN_num_bytes(n);
+	OSSL_PARAM params[3];
+
+	/* Convert e and n to a buffer for OSSL_PARAM_construct_BN() */
+	ebuf = alloc(ebytes);
+	assert(BN_bn2nativepad(e, ebuf, ebytes) > 0);
+	nbuf = alloc(nbytes);
+	assert(BN_bn2nativepad(n, nbuf, nbytes) > 0);
+	/* Init params[] with {e,n}buf and create the pkey from it */
+	params[0] = OSSL_PARAM_construct_BN("e", ebuf, ebytes);
+	params[1] = OSSL_PARAM_construct_BN("n", nbuf, nbytes);
+	params[2] = OSSL_PARAM_construct_end();
+	pub->ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+	assert(pub->ctx);
+	assert(EVP_PKEY_fromdata_init(pub->ctx) > 0);
+	ret = EVP_PKEY_fromdata(pub->ctx, &pub->pkey, EVP_PKEY_PUBLIC_KEY,
+		params);
+	free(nbuf);
+	free(ebuf);
+	if (ret <= 0) {
+		EVP_PKEY_CTX_free(pub->ctx);
+		return openssl_perror("EVP_PKEY_fromdata()");
+	}
+	assert(pub->pkey);
+	return nbytes * 8;
+}
+#endif /* HAVE_OSSL_PARAM */
+
+static int read_public_key(const unsigned char *blob, size_t blen,
+		struct asymmetric_key *pub)
+{
+	int ret, bits;
 	const unsigned char *p = blob, *end = blob + blen;
+	BIGNUM *e, *n;
 
-	assert((rsa = RSA_new()));
 	ret = read_bignum(p, end - p, &e);
 	if (ret < 0)
-		goto free_rsa;
+		return ret;
 	p += ret;
 	ret = read_bignum(p, end - p, &n);
-	if (ret < 0)
-		goto free_e;
-#ifdef HAVE_RSA_SET0_KEY
-	RSA_set0_key(rsa, n, e, NULL);
-#else
-	rsa->n = n;
-	rsa->e = e;
-#endif
-	result->rsa = rsa;
-	return 1;
-free_e:
+	if (ret < 0) {
+		BN_free(e);
+		return ret;
+	}
+	bits = BN_num_bytes(n) * 8;
+	PARA_DEBUG_LOG("modulus: %d bits\n", bits);
+#ifdef HAVE_OSSL_PARAM /* openssl-3 */
+	ret = generate_public_pkey(pub, e, n);
 	BN_free(e);
-free_rsa:
-	RSA_free(rsa);
-	return ret;
+	BN_free(n);
+	if (ret < 0)
+		return ret;
+#else /* openssl < 3.0 */
+	pub->rsa = RSA_new();
+	assert(pub->rsa);
+	#if HAVE_RSA_SET0_KEY /* openssl-1.1 */
+		RSA_set0_key(pub->rsa, n, e, NULL);
+	#else /* openssl-1.0 */
+		pub->rsa->n = n;
+		pub->rsa->e = e;
+	#endif
+	/* e and n are now owned by openssl */
+#endif /* HAVE_OSSL_PARAM */
+	return bits;
 }
 
 static int read_pem_private_key(const char *path, struct asymmetric_key *priv)
@@ -244,33 +289,35 @@ int apc_get_pubkey(const char *key_file, struct asymmetric_key **result)
 	unsigned char *blob;
 	size_t decoded_size;
 	int ret;
-	struct asymmetric_key *pub = alloc(sizeof(*pub));
+	struct asymmetric_key *pub;
 
 	ret = decode_public_key(key_file, &blob, &decoded_size);
 	if (ret < 0)
-		goto out;
+		return ret;
+	pub = zalloc(sizeof(*pub)); /* ->pkey needs to start out zeroed */
 	ret = read_public_key(blob + ret, decoded_size - ret, pub);
-	if (ret < 0)
-		goto free_blob;
-	ret = RSA_size(pub->rsa);
-	assert(ret > 0);
-	*result = pub;
-free_blob:
 	free(blob);
-out:
 	if (ret < 0) {
 		free(pub);
 		*result = NULL;
 		PARA_ERROR_LOG("can not load key %s\n", key_file);
+		return ret;
 	}
-	return ret;
+	PARA_NOTICE_LOG("loaded %d bit key from %s\n", ret, key_file);
+	*result = pub;
+	return ret / 8;
 }
 
 void apc_free_pubkey(struct asymmetric_key *pub)
 {
 	if (!pub)
 		return;
+#ifdef HAVE_OSSL_PARAM /* openssl-3 */
+	EVP_PKEY_CTX_free(pub->ctx);
+	EVP_PKEY_free(pub->pkey);
+#else
 	RSA_free(pub->rsa);
+#endif
 	free(pub);
 }
 
@@ -317,13 +364,29 @@ out:
 int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
 		unsigned len, unsigned char **outbuf)
 {
-	int ret, flen = len; /* RSA_public_encrypt expects a signed int */
+	int ret;
+#ifdef HAVE_OSSL_PARAM /* openssl-3 */
+	EVP_PKEY_CTX *ctx;
+	size_t outlen;
 
 	*outbuf = NULL;
-	if (flen < 0)
-		return -E_ENCRYPT;
+	assert((ctx = EVP_PKEY_CTX_new(pub->pkey, NULL)));
+	assert((EVP_PKEY_encrypt_init(ctx) > 0));
+	assert((EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) > 0));
+	if (EVP_PKEY_encrypt(ctx, NULL, &outlen, inbuf, len) <= 0) {
+		ret = openssl_perror("EVP_PKEY_encrypt()");
+		goto free_ctx;
+	}
+	*outbuf = alloc(outlen);
+	assert((EVP_PKEY_encrypt(ctx, *outbuf, &outlen, inbuf, len) > 0));
+	PARA_INFO_LOG("wrote %zu encrypted data bytes\n", outlen);
+	ret = outlen;
+free_ctx:
+	EVP_PKEY_CTX_free(ctx);
+	return ret;
+#else /* openssl < 3.0 */
 	*outbuf = alloc(RSA_size(pub->rsa));
-	ret = RSA_public_encrypt(flen, inbuf, *outbuf, pub->rsa,
+	ret = RSA_public_encrypt((int)len, inbuf, *outbuf, pub->rsa,
 		RSA_PKCS1_OAEP_PADDING);
 	if (ret < 0) {
 		free(*outbuf);
@@ -331,6 +394,7 @@ int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
 		return -E_ENCRYPT;
 	}
 	return ret;
+#endif /* HAVE_OSSL_PARAM */
 }
 
 struct stream_cipher {
-- 
2.39.5