#include <openssl/sha.h>
#include <openssl/bn.h>
#include <openssl/aes.h>
+#include <openssl/evp.h>
#include "para.h"
#include "error.h"
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);
}
/*
- * Read 64 bytes from /dev/urandom and add them to the SSL PRNG. Seed the PRNG
- * used by random(3) with a random seed obtained from SSL. If /dev/urandom is
- * not readable, the function calls exit().
- *
- * \sa RAND_load_file(3), \ref get_random_bytes_or_die(), srandom(3),
- * random(3), \ref para_random().
+ * Read 64 bytes from /dev/urandom and add them to the SSL PRNG. Then seed the
+ * PRNG used by random(3) with a random seed obtained from SSL.
*/
void crypt_init(void)
{
void crypt_shutdown(void)
{
- CRYPTO_cleanup_all_ex_data();
-}
-
-static int get_private_key(const char *path, RSA **rsa)
-{
- 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_free(bio);
- return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY;
+#ifdef HAVE_OPENSSL_THREAD_STOP /* openssl-1.1 or later */
+ OPENSSL_thread_stop();
+#else /* openssl-1.0 */
+ ERR_remove_thread_state(NULL);
+#endif
+ EVP_cleanup();
}
/*
return bnsize + 4;
}
-static int read_rsa_bignums(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) {
+ 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);
+ 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)
+{
+ 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 ret;
+}
+
+static int read_openssh_private_key(const unsigned char *blob,
+ const unsigned char *end, struct asymmetric_key *priv)
+{
+ int ret;
+ BIGNUM *n, *e, *d, *iqmp, *p, *q; /* stored in the key file */
+ const unsigned char *cp = blob;
+
+ ret = read_bignum(cp, end - cp, &n);
+ if (ret < 0)
+ return ret;
+ cp += ret;
+ ret = read_bignum(cp, end - cp, &e);
+ if (ret < 0)
+ goto free_n;
+ cp += ret;
+ ret = read_bignum(cp, end - cp, &d);
if (ret < 0)
goto free_e;
-#ifdef HAVE_RSA_SET0_KEY
- RSA_set0_key(rsa, n, e, NULL);
+ cp += ret;
+ ret = read_bignum(cp, end - cp, &iqmp);
+ if (ret < 0)
+ goto free_d;
+ cp += ret;
+ ret = read_bignum(cp, end - cp, &p);
+ if (ret < 0)
+ goto free_iqmp;
+ cp += ret;
+ ret = read_bignum(cp, end - cp, &q);
+ if (ret < 0)
+ goto free_p;
+#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;
-#endif
- *result = rsa;
+ 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);
+free_iqmp:
+ BN_clear_free(iqmp);
+free_d:
+ BN_clear_free(d);
free_e:
BN_free(e);
-free_rsa:
- RSA_free(rsa);
+free_n:
+ BN_free(n);
+ return ret;
+}
+
+static int get_private_key(const char *path, struct asymmetric_key *priv)
+{
+ int ret;
+ unsigned char *blob, *end;
+ size_t blob_size;
+
+ ret = decode_private_key(path, &blob, &blob_size);
+ if (ret < 0)
+ return ret;
+ end = blob + blob_size;
+ if (ret == PKT_OPENSSH) {
+ ret = find_openssh_bignum_offset(blob, blob_size);
+ 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, priv);
+ } else
+ ret = read_pem_private_key(path, priv);
+free_blob:
+ free(blob);
return ret;
}
unsigned char *blob;
size_t decoded_size;
int ret;
- struct asymmetric_key *key = para_malloc(sizeof(*key));
+ struct asymmetric_key *pub;
- ret = decode_ssh_key(key_file, &blob, &decoded_size);
- if (ret < 0)
- goto out;
- ret = read_rsa_bignums(blob + ret, decoded_size - ret, &key->rsa);
+ ret = decode_public_key(key_file, &blob, &decoded_size);
if (ret < 0)
- goto free_blob;
- ret = RSA_size(key->rsa);
- assert(ret > 0);
- *result = key;
-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(key);
+ 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 *key)
+void apc_free_pubkey(struct asymmetric_key *pub)
{
- if (!key)
+ if (!pub)
return;
- RSA_free(key->rsa);
- free(key);
+#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);
+}
+
+#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,
+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 = para_malloc(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.
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 */
+ int ret;
+#ifdef HAVE_OSSL_PARAM /* openssl-3 */
+ EVP_PKEY_CTX *ctx;
+ size_t outlen;
- if (flen < 0)
- return -E_ENCRYPT;
- ret = RSA_public_encrypt(flen, inbuf, outbuf, pub->rsa,
+ *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 {
struct stream_cipher *sc_new(const unsigned char *data, int len)
{
- struct stream_cipher *sc = para_malloc(sizeof(*sc));
+ 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;
*dst = (typeof(*dst)) {
/* Add one for the terminating zero byte. */
- .iov_base = para_malloc(inlen + 1),
+ .iov_base = alloc(inlen + 1),
.iov_len = inlen
};
ret = EVP_EncryptUpdate(ctx, dst->iov_base, &outlen, src->iov_base, inlen);
void hash_function(const char *data, unsigned long len, unsigned char *hash)
{
- SHA_CTX c;
- SHA1_Init(&c);
- SHA1_Update(&c, data, len);
- SHA1_Final(hash, &c);
+ 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);
+ ret = EVP_DigestFinal_ex(c, hash, NULL);
+ assert(ret != 0);
+ EVP_MD_CTX_free(c);
+}
+
+void hash2_function(const char *data, unsigned long len, unsigned char *hash)
+{
+ 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);
+ ret = EVP_DigestFinal_ex(c, hash, NULL);
+ assert(ret != 0);
+ EVP_MD_CTX_free(c);
}