]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge topic branch t/openssl-3 into pu
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 17 Mar 2024 11:36:38 +0000 (12:36 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 17 Mar 2024 11:36:38 +0000 (12:36 +0100)
Started on 2023-04-29.

The series starts with a few cleanups and crypto-backend tweaks to
beat the openssl specific code into shape for the main objective:
the switch of the RSA encryption and decryption routines to the
high-level EVP API. This has become necessary because the old RSA
API functions have been deprecated in openssl-3.

<!--

- The openssl RSA code has been converted to EVP and no longer uses
  any deprecated openssl API functions.

-->

* refs/heads/t/openssl-3:
  openssl: Reactivate openssl warnings.
  openssl: Use the EVP library for RSA private decryption.
  openssl: Use the EVP library for RSA public encryption.
  apc_priv_decrypt: Let the callee allocate the buffer.
  apc_pub_encrypt: Let the callee allocate the buffer.
  openssl: Assume that openssl allocation functions functions succeed.
  openssl: Introduce openssl_perror().
  openssl: Don't pass pointers to RSA structures around.
  openssl: Kill rsa coefficient computations.
  client: Check buffer size returned by apc_priv_decrypt().

# Conflicts:
# configure.ac

Makefile.real
client_common.c
command.c
configure.ac
crypt.h
error.h
gcrypt.c
openssl.c

index 92a5724b3cc6543553dcaf8dbdf6385e4169e41e..21d5fc03477145f087be31a319838d636d3df2b2 100644 (file)
@@ -475,7 +475,6 @@ $(call OD, afs aft attribute blob mood playlist score server vss command \
        CPPFLAGS += $(osl_cppflags)
 
 $(call OD, compress_filter): CFLAGS += -O3
-$(call OD, openssl): CFLAGS += -Wno-deprecated-declarations
 
 $(object_dir)/%.o: %.c | $(object_dir) $(dep_dir) $(lsg_h) $(yy_h)
 define CC_CMD
index fe8234f98fe90f6ce74ea5e22699fbfc0059a042..396fd88c894cda20e4f55a7955a9a4fe77e9633d 100644 (file)
@@ -324,7 +324,7 @@ static int client_post_monitor(struct sched *s, void *context)
                 */
                {
                /* decrypted challenge/session key buffer */
-               unsigned char crypt_buf[1024];
+               unsigned char *crypt_buf;
                struct sb_buffer sbb;
 
                ret = recv_sb(ct, &sbb);
@@ -337,11 +337,16 @@ static int client_post_monitor(struct sched *s, void *context)
                }
                n = sbb.iov.iov_len;
                PARA_INFO_LOG("<-- [challenge] (%zu bytes)\n", n);
-               ret = apc_priv_decrypt(ct->key_file, crypt_buf,
+               ret = apc_priv_decrypt(ct->key_file, &crypt_buf,
                        sbb.iov.iov_base, n);
                free(sbb.iov.iov_base);
                if (ret < 0)
                        goto out;
+               if (ret != APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN) {
+                       free(crypt_buf);
+                       ret = -E_DECRYPT;
+                       goto out;
+               }
                ct->challenge_hash = alloc(HASH2_SIZE);
                if (has_feature("sha256", ct)) {
                        hash2_function((char *)crypt_buf, APC_CHALLENGE_SIZE,
@@ -356,6 +361,7 @@ static int client_post_monitor(struct sched *s, void *context)
                         SESSION_KEY_LEN);
                ct->scc.recv = sc_new(crypt_buf + APC_CHALLENGE_SIZE
                        + SESSION_KEY_LEN, SESSION_KEY_LEN);
+               free(crypt_buf);
                PARA_INFO_LOG("--> %s\n", buf);
                ct->status = CL_RECEIVED_CHALLENGE;
                return 0;
index 60c2aeba4300124c51af13d03c522fe2c2bbb1f1..42152ca8cbfff616df68abf050816a3a3e670fef 100644 (file)
--- a/command.c
+++ b/command.c
@@ -917,7 +917,8 @@ int handle_connect(int fd)
        int ret;
        unsigned char rand_buf[APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
        unsigned char challenge_hash[HASH2_SIZE];
-       char *command = NULL, *buf = alloc(HANDSHAKE_BUFSIZE) /* must be on the heap */;
+       char *command = NULL, *buf = NULL, hsbuf[HANDSHAKE_BUFSIZE];
+       unsigned char *crypt_buf;
        size_t numbytes;
        struct command_context cc_struct = {.u = NULL}, *cc = &cc_struct;
        struct iovec iov;
@@ -937,16 +938,16 @@ int handle_connect(int fd)
        if (ret < 0)
                goto net_err;
        /* recv auth request line */
-       ret = recv_buffer(fd, buf, HANDSHAKE_BUFSIZE);
+       ret = recv_buffer(fd, hsbuf, HANDSHAKE_BUFSIZE);
        if (ret < 0)
                goto net_err;
-       ret = parse_auth_request(buf, ret, &cc->u, &cf);
+       ret = parse_auth_request(hsbuf, ret, &cc->u, &cf);
        if (ret < 0)
                goto net_err;
        if (cc->u) {
                get_random_bytes_or_die(rand_buf, sizeof(rand_buf));
                ret = apc_pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf),
-                       (unsigned char *)buf);
+                       &crypt_buf);
                if (ret < 0)
                        goto net_err;
                numbytes = ret;
@@ -957,12 +958,12 @@ int handle_connect(int fd)
                 * fail the authentication later.
                 */
                numbytes = 256;
-               get_random_bytes_or_die((unsigned char *)buf, numbytes);
+               crypt_buf = alloc(numbytes);
+               get_random_bytes_or_die(crypt_buf, numbytes);
        }
        PARA_DEBUG_LOG("sending %d byte challenge + session key (%zu bytes)\n",
                APC_CHALLENGE_SIZE, numbytes);
-       ret = send_sb(&cc->scc, buf, numbytes, SBD_CHALLENGE, false);
-       buf = NULL;
+       ret = send_sb(&cc->scc, crypt_buf, numbytes, SBD_CHALLENGE, false);
        if (ret < 0)
                goto net_err;
        ret = recv_sb(&cc->scc, SBD_CHALLENGE_RESPONSE,
index 34dcd060a5891a07dc6ea8c62ca53a7209f2a376..2bba8129b92a834a998a1aea84fee9b475e3cdde 100644 (file)
@@ -109,6 +109,10 @@ 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_OPENSSL_THREAD_STOP=yes
        AC_CHECK_DECL([OPENSSL_thread_stop], [],
                [HAVE_OPENSSL_THREAD_STOP=no],
diff --git a/crypt.h b/crypt.h
index 5578cd563fae4dc24d673e653dcf21329eba90bc..2e094ced9ee760ced8a7647217a5046bc58e0c20 100644 (file)
--- a/crypt.h
+++ b/crypt.h
@@ -20,18 +20,18 @@ struct asymmetric_key;
  * \param pub: The public key.
  * \param inbuf The input buffer.
  * \param len The length of \a inbuf.
- * \param outbuf The output buffer.
+ * \param outbuf The output buffer will be allocated by the callee.
  *
  * \return The size of the encrypted data on success, negative on errors.
  */
 int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
-               unsigned len, unsigned char *outbuf);
+               unsigned len, unsigned char **outbuf);
 
 /**
  * Decrypt a buffer using a private key.
  *
  * \param key_file Full path of the key.
- * \param outbuf The output buffer.
+ * \param outbuf The output buffer is allocated by the callee.
  * \param inbuf The encrypted input buffer.
  * \param inlen The length of \a inbuf.
  *
@@ -39,7 +39,7 @@ int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
  *
  * \return The size of the recovered plaintext on success, negative on errors.
  */
-int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
+int apc_priv_decrypt(const char *key_file, unsigned char **outbuf,
                unsigned char *inbuf, int inlen);
 
 /**
diff --git a/error.h b/error.h
index 8805c9c7a4c0de6fe958e1523df883ad47effaad..971d3e7f9b7df5a1c96744cc003f93343cd0d6c9 100644 (file)
--- a/error.h
+++ b/error.h
        PARA_ERROR(OGG_PACKET_IN, "ogg_stream_packetin() failed"), \
        PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \
        PARA_ERROR(OPENSSH_PARSE, "could not parse openssh private key"), \
+       PARA_ERROR(OPENSSL, "openssl error"), \
        PARA_ERROR(OPUS_COMMENT, "invalid or corrupted opus comment"), \
        PARA_ERROR(OPUS_DECODE, "opus decode error"), \
        PARA_ERROR(OPUS_HEADER, "invalid opus header"), \
        PARA_ERROR(PATH_FOUND, ""), /* not really an error */ \
        PARA_ERROR(PLAYLIST_EMPTY, "attempted to load empty playlist"), \
        PARA_ERROR(PREBUFFER_SUCCESS, "prebuffering complete"), \
-       PARA_ERROR(PRIVATE_KEY, "can not read private key"), \
        PARA_ERROR(QUEUE, "packet queue overrun"), \
        PARA_ERROR(READ_PATTERN, "did not read expected pattern"), \
        PARA_ERROR(RECVMSG, "recvmsg() failed"), \
index b46f8f9555824eb86b7bbf600b4352558ed2db26..e5f64688cbbc6ef5aa907fb7336675bdc9818e61 100644 (file)
--- a/gcrypt.c
+++ b/gcrypt.c
@@ -114,6 +114,7 @@ void crypt_shutdown(void)
 
 struct asymmetric_key {
        gcry_sexp_t sexp;
+       int bits;
 };
 
 static const char *gcrypt_strerror(gcry_error_t gret)
@@ -457,6 +458,7 @@ int apc_get_pubkey(const char *key_file, struct asymmetric_key **result)
        PARA_INFO_LOG("successfully read %u bit ssh public key\n", bits);
        key = alloc(sizeof(*key));
        key->sexp = sexp;
+       key->bits = bits;
        *result = key;
        ret = bits / 8;
 release_n:
@@ -476,17 +478,20 @@ void apc_free_pubkey(struct asymmetric_key *key)
        free(key);
 }
 
-static int decode_rsa(gcry_sexp_t sexp, unsigned char *outbuf, size_t *nbytes)
+static int decode_rsa(gcry_sexp_t sexp, unsigned char **outbuf, size_t *nbytes)
 {
        const char *p = gcry_sexp_nth_data(sexp, 1, nbytes);
 
-       if (!p)
+       if (!p) {
+               *outbuf = NULL;
                return -E_RSA_DECODE;
-       memcpy(outbuf, p, *nbytes);
+       }
+       *outbuf = alloc(*nbytes);
+       memcpy(*outbuf, p, *nbytes);
        return 1;
 }
 
-int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
+int apc_priv_decrypt(const char *key_file, unsigned char **outbuf,
                unsigned char *inbuf, int inlen)
 {
        gcry_error_t gret;
@@ -496,6 +501,7 @@ int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
        gcry_sexp_t in, out, priv_key;
        size_t nbytes;
 
+       *outbuf = NULL;
        ret = check_private_key_file(key_file);
        if (ret < 0)
                return ret;
@@ -554,7 +560,7 @@ free_key:
 }
 
 int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
-               unsigned len, unsigned char *outbuf)
+               unsigned len, unsigned char **outbuf)
 {
        gcry_error_t gret;
        gcry_sexp_t pub_key, in, out, out_a;
@@ -562,6 +568,7 @@ int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
        size_t nbytes;
        int ret;
 
+       *outbuf = NULL;
        /* get pub key */
        pub_key = gcry_sexp_find_token(pub->sexp, "public-key", 0);
        if (!pub_key)
@@ -590,14 +597,18 @@ int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
                ret = -E_SEXP_FIND;
                goto out_a_release;
        }
-       gret = gcry_mpi_print(GCRYMPI_FMT_USG, outbuf, 512 /* FIXME */, &nbytes, out_mpi);
+       *outbuf = alloc(pub->bits);
+       gret = gcry_mpi_print(GCRYMPI_FMT_USG, *outbuf, pub->bits, &nbytes,
+               out_mpi);
        if (gret) {
+               free(*outbuf);
+               *outbuf = NULL;
                PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret));
                ret = -E_SEXP_ENCRYPT;
                goto out_mpi_release;
        }
        PARA_INFO_LOG("encrypted buffer is %zu bytes\n", nbytes);
-       dump_buffer("enc buf", outbuf, nbytes);
+       dump_buffer("enc buf", *outbuf, nbytes);
        ret = nbytes;
 
 out_mpi_release:
index 13550e7a9f34e1771932d05ef7fea5fed8c109d6..a5a6a17576989ed51fbe36c116effb3ae0679f1a 100644 (file)
--- a/openssl.c
+++ b/openssl.c
 
 struct asymmetric_key {
        RSA *rsa;
+       EVP_PKEY *pkey;
+       EVP_PKEY_CTX *ctx;
 };
 
+static int openssl_perror(const char *pfx)
+{
+       unsigned long err = ERR_get_error();
+       PARA_ERROR_LOG("%s: \"%s\"\n", pfx, ERR_reason_error_string(err));
+       return -E_OPENSSL;
+}
+
 void get_random_bytes_or_die(unsigned char *buf, int num)
 {
-       unsigned long err;
+       int ret;
 
-       /* RAND_bytes() returns 1 on success, 0 otherwise. */
-       if (RAND_bytes(buf, num) == 1)
+       if (RAND_bytes(buf, num) == 1) /* success */
                return;
-       err = ERR_get_error();
-       PARA_EMERG_LOG("%s\n", ERR_reason_error_string(err));
+       ret = openssl_perror("RAND_bytes");
+       PARA_EMERG_LOG("%s\n", strerror(-ret));
        exit(EXIT_FAILURE);
 }
 
@@ -94,88 +102,155 @@ 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, RSA **result)
+#ifdef HAVE_OSSL_PARAM /* openssl-3 */
+
+static int generate_private_pkey(struct asymmetric_key *priv,
+               const BIGNUM *n, const BIGNUM *e, const BIGNUM *d,
+               const BIGNUM *p, const BIGNUM *q)
 {
-       int ret;
-       RSA *rsa;
-       BIGNUM *n, *e;
+       const BIGNUM *bignums[] = {n, e, d, p, q};
+       const char *strings[] = {"n", "e", "d", "p", "q"};
+       int ret, bytes[ARRAY_SIZE(bignums)];
+       unsigned char *bufs[ARRAY_SIZE(bignums)];
+       OSSL_PARAM params[ARRAY_SIZE(bignums) + 1];
+       /*
+        * Convert bignums to buffers for OSSL_PARAM_construct_BN() and init
+        * params[].
+        */
+       for (int i = 0; i < ARRAY_SIZE(bignums); i++) {
+               bytes[i] = BN_num_bytes(bignums[i]);
+               PARA_DEBUG_LOG("%s: %d bits\n", strings[i], bytes[i] * 8);
+               bufs[i] = alloc(bytes[i]);
+               assert(BN_bn2nativepad(bignums[i], bufs[i], bytes[i]) > 0);
+               params[i] = OSSL_PARAM_construct_BN(strings[i], bufs[i],
+                       bytes[i]);
+       }
+       params[ARRAY_SIZE(bignums)] = OSSL_PARAM_construct_end();
+       /* Transfer buffers to openssl to create the pkey from it */
+       priv->ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+       assert(priv->ctx);
+       assert(EVP_PKEY_fromdata_init(priv->ctx) > 0);
+       ret = EVP_PKEY_fromdata(priv->ctx, &priv->pkey,
+               EVP_PKEY_KEYPAIR, params);
+       for (int i = 0; i < ARRAY_SIZE(bignums); i++)
+               free(bufs[i]);
+       if (ret <= 0) {
+               EVP_PKEY_CTX_free(priv->ctx);
+               return openssl_perror("EVP_PKEY_fromdata()");
+       }
+       assert(priv->pkey);
+       return BN_num_bytes(n) * 8;
+}
+
+/*
+ * 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)
+{
+       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;
 
-       rsa = RSA_new();
-       if (!rsa)
-               return -E_BIGNUM;
        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;
-       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, RSA **rsa)
+static int read_pem_private_key(const char *path, struct asymmetric_key *priv)
 {
-       EVP_PKEY *pkey;
-       BIO *bio = BIO_new(BIO_s_file());
-
-       *rsa = NULL;
-       if (!bio)
-               return -E_PRIVATE_KEY;
-       if (BIO_read_filename(bio, path) <= 0)
-               goto bio_free;
-       pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
-       if (!pkey)
-               goto bio_free;
-       *rsa = EVP_PKEY_get1_RSA(pkey);
-       EVP_PKEY_free(pkey);
-bio_free:
+       BIO *bio;
+       int ret;
+
+       assert((bio = BIO_new(BIO_s_file())));
+       ret = BIO_read_filename(bio, path);
+       if (ret <= 0) {
+               priv->pkey = NULL;
+               ret = openssl_perror("BIO_read_filename");
+               goto free_bio;
+       }
+       priv->pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
+       if (!priv->pkey) {
+               ret = openssl_perror("PEM_read_bio_PrivateKey");
+               goto free_bio;
+       }
+#ifndef HAVE_OSSL_PARAM /* openssl-1 */
+       priv->rsa = EVP_PKEY_get1_RSA(priv->pkey);
+#endif
+free_bio:
        BIO_free(bio);
-       return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY;
+       return ret;
 }
 
 static int read_openssh_private_key(const unsigned char *blob,
-               const unsigned char *end, RSA **result)
+               const unsigned char *end, struct asymmetric_key *priv)
 {
        int ret;
-       RSA *rsa;
-       BN_CTX *ctx;
        BIGNUM *n, *e, *d, *iqmp, *p, *q; /* stored in the key file */
-       BIGNUM *dmp1, *dmq1; /* these will be computed */
-       BIGNUM *tmp;
        const unsigned char *cp = blob;
 
-       rsa = RSA_new();
-       if (!rsa)
-               return -E_BIGNUM;
-       ret = -E_BIGNUM;
-       tmp = BN_new();
-       if (!tmp)
-               goto free_rsa;
-       ctx = BN_CTX_new();
-       if (!ctx)
-               goto free_tmp;
-       dmp1 = BN_new();
-       if (!dmp1)
-               goto free_ctx;
-       dmq1 = BN_new();
-       if (!dmq1)
-               goto free_dmp1;
        ret = read_bignum(cp, end - cp, &n);
        if (ret < 0)
-               goto free_dmq1;
+               return ret;
        cp += ret;
        ret = read_bignum(cp, end - cp, &e);
        if (ret < 0)
@@ -196,33 +271,29 @@ static int read_openssh_private_key(const unsigned char *blob,
        ret = read_bignum(cp, end - cp, &q);
        if (ret < 0)
                goto free_p;
-       ret = -E_BIGNUM;
-       if (!BN_sub(tmp, q, BN_value_one()))
-               goto free_q;
-       if (!BN_mod(dmp1, d, tmp, ctx))
-               goto free_q;
-       if (!BN_sub(tmp, q, BN_value_one()))
-               goto free_q;
-       if (!BN_mod(dmq1, d, tmp, ctx))
-               goto free_q;
-#ifdef HAVE_RSA_SET0_KEY
-       RSA_set0_key(rsa, n, e, d);
-       RSA_set0_factors(rsa, p, q);
-       RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp);
+#ifdef HAVE_OSSL_PARAM /* openssl-3 */
+       /*
+        * Ignore iqmp, the coefficient for Chinese remainder theorem. It is
+        * dispensable because it can be derived from the other values. Passing
+        * it to the EVP API results in a memory leak.
+        */
+       ret = generate_private_pkey(priv, n, e, d, p, q);
 #else
-       rsa->n = n;
-       rsa->e = e;
-       rsa->d = d;
-       rsa->iqmp = iqmp;
-       rsa->p = p;
-       rsa->q = q;
-       rsa->dmp1 = dmp1;
-       rsa->dmq1 = dmq1;
-#endif
-       *result = rsa;
-       ret = 1;
-       goto free_ctx;
-free_q:
+       assert((priv->rsa = RSA_new()));
+       #ifdef HAVE_RSA_SET0_KEY
+               RSA_set0_key(priv->rsa, n, e, d);
+               RSA_set0_factors(priv->rsa, p, q);
+               RSA_set0_crt_params(priv->rsa, NULL, NULL, iqmp);
+       #else
+               priv->rsa->n = n;
+               priv->rsa->e = e;
+               priv->rsa->d = d;
+               priv->rsa->iqmp = iqmp;
+               priv->rsa->p = p;
+               priv->rsa->q = q;
+       #endif
+       return 1;
+#endif /* HAVE_OSSL_PARAM */
        BN_clear_free(q);
 free_p:
        BN_clear_free(p);
@@ -234,27 +305,15 @@ free_e:
        BN_free(e);
 free_n:
        BN_free(n);
-free_dmq1:
-       BN_clear_free(dmq1);
-free_dmp1:
-       BN_clear_free(dmp1);
-free_ctx:
-       BN_CTX_free(ctx);
-free_tmp:
-       BN_clear_free(tmp);
-free_rsa:
-       if (ret < 0)
-               RSA_free(rsa);
        return ret;
 }
 
-static int get_private_key(const char *path, RSA **rsa)
+static int get_private_key(const char *path, struct asymmetric_key *priv)
 {
        int ret;
        unsigned char *blob, *end;
        size_t blob_size;
 
-       *rsa = NULL;
        ret = decode_private_key(path, &blob, &blob_size);
        if (ret < 0)
                return ret;
@@ -264,9 +323,9 @@ static int get_private_key(const char *path, RSA **rsa)
                if (ret < 0)
                        goto free_blob;
                PARA_INFO_LOG("reading RSA params at offset %d\n", ret);
-               ret = read_openssh_private_key(blob + ret, end, rsa);
+               ret = read_openssh_private_key(blob + ret, end, priv);
        } else
-               ret = read_pem_private_key(path, rsa);
+               ret = read_pem_private_key(path, priv);
 free_blob:
        free(blob);
        return ret;
@@ -277,53 +336,84 @@ 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;
-       ret = read_public_key(blob + ret, decoded_size - ret, &pub->rsa);
-       if (ret < 0)
-               goto free_blob;
-       ret = RSA_size(pub->rsa);
-       assert(ret > 0);
-       *result = pub;
-free_blob:
+               return ret;
+       pub = zalloc(sizeof(*pub)); /* ->pkey needs to start out zeroed */
+       ret = read_public_key(blob + ret, decoded_size - ret, pub);
        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);
 }
 
-int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
+#ifdef HAVE_OSSL_PARAM /* openssl-3 */
+static int pkey_priv_decrypt(const struct asymmetric_key *priv,
+               unsigned char **outbuf, unsigned char *inbuf, int inlen)
+{
+       EVP_PKEY_CTX *ctx;
+       size_t outlen;
+
+       assert((ctx = EVP_PKEY_CTX_new(priv->pkey, NULL)));
+       assert((EVP_PKEY_decrypt_init(ctx) > 0));
+       assert(EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) > 0);
+       if (EVP_PKEY_decrypt(ctx, NULL, &outlen, inbuf, inlen) <= 0) {
+               *outbuf = NULL;
+               EVP_PKEY_CTX_free(ctx);
+               return openssl_perror("EVP_PKEY_encrypt()");
+       }
+       *outbuf = alloc(outlen);
+       assert((EVP_PKEY_decrypt(ctx, *outbuf, &outlen, inbuf, inlen) > 0));
+       EVP_PKEY_CTX_free(ctx);
+       PARA_INFO_LOG("wrote %zu decrypted data bytes\n", outlen);
+       return outlen;
+}
+#endif /* HAVE_OSSL_PARAM */
+
+int apc_priv_decrypt(const char *key_file, unsigned char **outbuf,
                unsigned char *inbuf, int inlen)
 {
        struct asymmetric_key *priv;
        int ret;
 
+       *outbuf = NULL;
        ret = check_private_key_file(key_file);
        if (ret < 0)
                return ret;
        if (inlen < 0)
                return -E_RSA;
-       priv = alloc(sizeof(*priv));
-       ret = get_private_key(key_file, &priv->rsa);
+       priv = zalloc(sizeof(*priv)); /* ->pkey needs to start out zeroed */
+       ret = get_private_key(key_file, priv);
        if (ret < 0) {
                free(priv);
                return ret;
        }
+#ifdef HAVE_OSSL_PARAM /* openssl-3 */
+       ret = pkey_priv_decrypt(priv, outbuf, inbuf, inlen);
+       EVP_PKEY_CTX_free(priv->ctx);
+       EVP_PKEY_free(priv->pkey);
+#else
        /*
         * RSA is vulnerable to timing attacks. Generate a random blinding
         * factor to protect against this kind of attack.
@@ -331,27 +421,56 @@ int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
        ret = -E_BLINDING;
        if (RSA_blinding_on(priv->rsa, NULL) == 0)
                goto out;
-       ret = RSA_private_decrypt(inlen, inbuf, outbuf, priv->rsa,
+       *outbuf = alloc(RSA_size(priv->rsa));
+       ret = RSA_private_decrypt(inlen, inbuf, *outbuf, priv->rsa,
                RSA_PKCS1_OAEP_PADDING);
        RSA_blinding_off(priv->rsa);
-       if (ret <= 0)
+       if (ret <= 0) {
+               free(*outbuf);
+               *outbuf = NULL;
                ret = -E_DECRYPT;
+       }
 out:
        RSA_free(priv->rsa);
+#endif
        free(priv);
        return ret;
 }
 
 int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
-               unsigned len, unsigned char *outbuf)
+               unsigned len, unsigned char **outbuf)
 {
-       int ret, flen = len; /* RSA_public_encrypt expects a signed int */
-
-       if (flen < 0)
-               return -E_ENCRYPT;
-       ret = RSA_public_encrypt(flen, inbuf, outbuf, pub->rsa,
+       int ret;
+#ifdef HAVE_OSSL_PARAM /* openssl-3 */
+       EVP_PKEY_CTX *ctx;
+       size_t outlen;
+
+       *outbuf = NULL;
+       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((int)len, inbuf, *outbuf, pub->rsa,
                RSA_PKCS1_OAEP_PADDING);
-       return ret < 0? -E_ENCRYPT : ret;
+       if (ret < 0) {
+               free(*outbuf);
+               *outbuf = NULL;
+               return -E_ENCRYPT;
+       }
+       return ret;
+#endif /* HAVE_OSSL_PARAM */
 }
 
 struct stream_cipher {
@@ -363,7 +482,7 @@ struct stream_cipher *sc_new(const unsigned char *data, int len)
        struct stream_cipher *sc = alloc(sizeof(*sc));
 
        assert(len >= 2 * AES_CRT128_BLOCK_SIZE);
-       sc->aes = EVP_CIPHER_CTX_new();
+       assert((sc->aes = EVP_CIPHER_CTX_new()));
        EVP_EncryptInit_ex(sc->aes, EVP_aes_128_ctr(), NULL, data,
                data + AES_CRT128_BLOCK_SIZE);
        return sc;
@@ -403,8 +522,11 @@ void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst)
 
 void hash_function(const char *data, unsigned long len, unsigned char *hash)
 {
-       EVP_MD_CTX *c = EVP_MD_CTX_new();
-       int ret = EVP_DigestInit_ex(c, EVP_sha1(), NULL);
+       int ret;
+       EVP_MD_CTX *c;
+
+       assert((c = EVP_MD_CTX_new()));
+       ret = EVP_DigestInit_ex(c, EVP_sha1(), NULL);
        assert(ret != 0);
        ret = EVP_DigestUpdate(c, data, len);
        assert(ret != 0);
@@ -415,8 +537,11 @@ void hash_function(const char *data, unsigned long len, unsigned char *hash)
 
 void hash2_function(const char *data, unsigned long len, unsigned char *hash)
 {
-       EVP_MD_CTX *c = EVP_MD_CTX_new();
-       int ret = EVP_DigestInit_ex(c, EVP_sha256(), NULL);
+       int ret;
+       EVP_MD_CTX *c;
+
+       assert((c = EVP_MD_CTX_new()));
+       ret = EVP_DigestInit_ex(c, EVP_sha256(), NULL);
        assert(ret != 0);
        ret = EVP_DigestUpdate(c, data, len);
        assert(ret != 0);