/* SPDX-License-Identifier: GPL-2.0 */ /** \file openssl.c Openssl-based encryption/decryption routines. */ #include #include #include #include #include #include #include #include #include #include "para.h" #include "error.h" #include "string.h" #include "crypt.h" #include "crypt_backend.h" #include "portable_io.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) { int ret; if (RAND_bytes(buf, num) == 1) /* success */ return; 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. Then seed the * PRNG used by random(3) with a random seed obtained from SSL. */ void crypt_init(void) { int seed, ret = RAND_load_file("/dev/urandom", 64); if (ret != 64) { PARA_EMERG_LOG("could not seed PRNG (ret = %d)\n", ret); exit(EXIT_FAILURE); } get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed)); srandom(seed); } void crypt_shutdown(void) { OPENSSL_thread_stop(); EVP_cleanup(); } /* * The public key loading functions below were inspired by corresponding code * of openssh-5.2p1, Copyright (c) 1995 Tatu Ylonen , Espoo, * Finland. However, not much of the original code remains. */ static int read_bignum(const unsigned char *buf, size_t len, BIGNUM **result) { const unsigned char *p = buf, *end = buf + len; uint32_t bnsize; BIGNUM *bn; if (p + 4 < p) return -E_BIGNUM; if (p + 4 > end) return -E_BIGNUM; bnsize = read_u32_be(p); PARA_DEBUG_LOG("bnsize: %u\n", bnsize); p += 4; if (p + bnsize < p) return -E_BIGNUM; if (p + bnsize > end) return -E_BIGNUM; if (bnsize > 8192) return -E_BIGNUM; bn = BN_bin2bn(p, bnsize, NULL); if (!bn) return -E_BIGNUM; *result = bn; return bnsize + 4; } #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) { 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; ret = read_bignum(p, end - p, &e); if (ret < 0) 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); RSA_set0_key(pub->rsa, n, e, NULL); /* e and n are now owned by openssl */ #endif /* HAVE_OSSL_PARAM */ return bits; } 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; 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 the 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 assert((priv->rsa = RSA_new())); RSA_set0_key(priv->rsa, n, e, d); RSA_set0_factors(priv->rsa, p, q); RSA_set0_crt_params(priv->rsa, NULL, NULL, iqmp); BN_clear_free(iqmp); /* This is not freed by RSA_free() */ 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_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; 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); free_blob: free(blob); return ret; } 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; ret = decode_public_key(key_file, &blob, &decoded_size); if (ret < 0) return ret; pub = zalloc(sizeof(*pub)); /* ->pkey needs to start out zeroed */ ret = read_public_key(blob + ret, decoded_size - ret, pub); free(blob); if (ret < 0) { free(pub); *result = NULL; PARA_ERROR_LOG("can not load key %s\n", key_file); 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); } #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 = 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; *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) { 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) { 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); if (ret < 0) { free(*outbuf); *outbuf = NULL; return -E_ENCRYPT; } return ret; #endif /* HAVE_OSSL_PARAM */ } struct stream_cipher { EVP_CIPHER_CTX *aes; }; 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); 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; } void sc_free(struct stream_cipher *sc) { if (!sc) return; EVP_CIPHER_CTX_free(sc->aes); free(sc); } static void aes_ctr128_crypt(EVP_CIPHER_CTX *ctx, struct iovec *src, struct iovec *dst) { int ret, inlen = src->iov_len, outlen, tmplen; *dst = (typeof(*dst)) { /* Add one for the terminating zero byte. */ .iov_base = alloc(inlen + 1), .iov_len = inlen }; ret = EVP_EncryptUpdate(ctx, dst->iov_base, &outlen, src->iov_base, inlen); assert(ret != 0); ret = EVP_EncryptFinal_ex(ctx, dst->iov_base + outlen, &tmplen); assert(ret != 0); outlen += tmplen; ((char *)dst->iov_base)[outlen] = '\0'; dst->iov_len = outlen; } void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) { return aes_ctr128_crypt(sc->aes, src, dst); } void hash_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); }