diff options
Diffstat (limited to 'src/lib/crypt.c')
| -rw-r--r-- | src/lib/crypt.c | 455 |
1 files changed, 455 insertions, 0 deletions
diff --git a/src/lib/crypt.c b/src/lib/crypt.c new file mode 100644 index 00000000..ad679501 --- /dev/null +++ b/src/lib/crypt.c @@ -0,0 +1,455 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Elliptic curve Diffie-Hellman key exchange and + * AES encryption for flows using OpenSSL + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ +#include <config.h> + +#include <ouroboros/crypt.h> +#include <ouroboros/errno.h> + +#include <assert.h> +#include <string.h> + +#ifdef HAVE_OPENSSL + +#include <ouroboros/hash.h> +#include <ouroboros/random.h> + +#include <openssl/evp.h> +#include <openssl/ec.h> +#include <openssl/pem.h> + +#include <openssl/bio.h> + +#define IVSZ 16 +/* SYMMKEYSZ defined in dev.c */ + +/* + * Derive the common secret from + * your public key pair (kp) + * the remote public key (pub). + * Store it in a preallocated buffer (s). + */ +static int __openssl_ecdh_derive_secret(EVP_PKEY * kp, + EVP_PKEY * pub, + uint8_t * s) +{ + EVP_PKEY_CTX * ctx; + int ret; + uint8_t * secret; + size_t secret_len; + + ctx = EVP_PKEY_CTX_new(kp, NULL); + if (ctx == NULL) + goto fail_new; + + ret = EVP_PKEY_derive_init(ctx); + if (ret != 1) + goto fail_ctx; + + ret = EVP_PKEY_derive_set_peer(ctx, pub); + if (ret != 1) + goto fail_ctx; + + ret = EVP_PKEY_derive(ctx, NULL, &secret_len); + if (ret != 1) + goto fail_ctx; + + if (secret_len < SYMMKEYSZ) + goto fail_ctx; + + secret = OPENSSL_malloc(secret_len); + if (secret == NULL) + goto fail_ctx; + + ret = EVP_PKEY_derive(ctx, secret, &secret_len); + if (ret != 1) + goto fail_derive; + + /* Hash the secret for use as AES key. */ + mem_hash(HASH_SHA3_256, s, secret, secret_len); + + OPENSSL_free(secret); + EVP_PKEY_CTX_free(ctx); + + return 0; + + fail_derive: + OPENSSL_free(secret); + fail_ctx: + EVP_PKEY_CTX_free(ctx); + fail_new: + return -ECRYPT; +} + +static int __openssl_ecdh_gen_key(void ** kp) +{ + EVP_PKEY_CTX * ctx = NULL; + EVP_PKEY_CTX * kctx = NULL; + EVP_PKEY * params = NULL; + int ret; + + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + if (ctx == NULL) + goto fail_new_id; + + ret = EVP_PKEY_paramgen_init(ctx); + if (ret != 1) + goto fail_paramgen; + + ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, NID_X9_62_prime256v1); + if (ret != 1) + goto fail_paramgen; + + ret = EVP_PKEY_paramgen(ctx, ¶ms); + if (ret != 1) + goto fail_paramgen; + + kctx = EVP_PKEY_CTX_new(params, NULL); + if (kctx == NULL) + goto fail_keygen_init; + + ret = EVP_PKEY_keygen_init(kctx); + if (ret != 1) + goto fail_keygen; + + ret = EVP_PKEY_keygen(kctx, (EVP_PKEY **) kp); + if (ret != 1) + goto fail_keygen; + + EVP_PKEY_free(params); + EVP_PKEY_CTX_free(kctx); + EVP_PKEY_CTX_free(ctx); + + return 0; + + fail_keygen: + EVP_PKEY_CTX_free(kctx); + fail_keygen_init: + EVP_PKEY_free(params); + fail_paramgen: + EVP_PKEY_CTX_free(ctx); + fail_new_id: + return -ECRYPT; +} + +static ssize_t openssl_ecdh_pkp_create(void ** pkp, + uint8_t * pk) +{ + uint8_t * pos; + ssize_t len; + + assert(pkp != NULL); + assert(*pkp == NULL); + assert(pk != NULL); + + if (__openssl_ecdh_gen_key(pkp) < 0) + return -ECRYPT; + + assert(*pkp != NULL); + + pos = pk; /* i2d_PUBKEY increments the pointer, don't use buf! */ + len = i2d_PUBKEY(*pkp, &pos); + if (len < 0) { + EVP_PKEY_free(*pkp); + return -ECRYPT; + } + + return len; +} + +static void openssl_ecdh_pkp_destroy(void * pkp) +{ + EVP_PKEY_free((EVP_PKEY *) pkp); +} + +static int openssl_ecdh_derive(void * pkp, + buffer_t pk, + uint8_t * s) +{ + uint8_t * pos; + EVP_PKEY * pub; + + pos = pk.data; /* d2i_PUBKEY increments the pointer, don't use key ptr! */ + pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len); + if (pub == NULL) + return -ECRYPT; + + if (__openssl_ecdh_derive_secret(pkp, pub, s) < 0) { + EVP_PKEY_free(pub); + return -ECRYPT; + } + + EVP_PKEY_free(pub); + + return 0; +} + +/* + * AES encryption calls. If FRCT is disabled, we should generate a + * 128-bit random IV and append it to the packet. If the flow is + * reliable, we could initialize the context once, and consider the + * stream a single encrypted message to avoid initializing the + * encryption context for each packet. + */ + +static int openssl_encrypt(void * ctx, + uint8_t * key, + struct shm_du_buff * sdb) +{ + uint8_t * out; + uint8_t * in; + uint8_t * head; + uint8_t iv[IVSZ]; + int in_sz; + int out_sz; + int tmp_sz; + int ret; + + in = shm_du_buff_head(sdb); + in_sz = shm_du_buff_tail(sdb) - in; + + assert(in_sz > 0); + + if (random_buffer(iv, IVSZ) < 0) + goto fail_iv; + + out = malloc(in_sz + EVP_MAX_BLOCK_LENGTH); + if (out == NULL) + goto fail_iv; + + EVP_CIPHER_CTX_reset(ctx); + + ret = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv); + if (ret != 1) + goto fail_encrypt_init; + + ret = EVP_EncryptUpdate(ctx, out, &tmp_sz, in, in_sz); + if (ret != 1) + goto fail_encrypt; + + out_sz = tmp_sz; + ret = EVP_EncryptFinal_ex(ctx, out + tmp_sz, &tmp_sz); + if (ret != 1) + goto fail_encrypt; + + out_sz += tmp_sz; + + EVP_CIPHER_CTX_cleanup(ctx); + + assert(out_sz >= in_sz); + + head = shm_du_buff_head_alloc(sdb, IVSZ); + if (head == NULL) + goto fail_encrypt; + + if (shm_du_buff_tail_alloc(sdb, out_sz - in_sz) == NULL) + goto fail_tail_alloc; + + memcpy(head, iv, IVSZ); + memcpy(in, out, out_sz); + + free(out); + + return 0; + + fail_tail_alloc: + shm_du_buff_head_release(sdb, IVSZ); + fail_encrypt: + EVP_CIPHER_CTX_cleanup(ctx); + fail_encrypt_init: + free(out); + fail_iv: + return -ECRYPT; +} + +static int openssl_decrypt(void * ctx, + uint8_t * key, + struct shm_du_buff * sdb) +{ + uint8_t * in; + uint8_t * out; + uint8_t iv[IVSZ]; + int ret; + int out_sz; + int in_sz; + int tmp_sz; + + in_sz = shm_du_buff_len(sdb); + if (in_sz < IVSZ) + return -ECRYPT; + + in = shm_du_buff_head_release(sdb, IVSZ); + + memcpy(iv, in, IVSZ); + + in = shm_du_buff_head(sdb); + in_sz = shm_du_buff_tail(sdb) - in; + + out = malloc(in_sz); + if (out == NULL) + goto fail_malloc; + + EVP_CIPHER_CTX_reset(ctx); + + ret = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv); + if (ret != 1) + goto fail_decrypt_init; + + ret = EVP_DecryptUpdate(ctx, out, &tmp_sz, in, in_sz); + if (ret != 1) + goto fail_decrypt; + + out_sz = tmp_sz; + + ret = EVP_DecryptFinal_ex(ctx, out + tmp_sz, &tmp_sz); + if (ret != 1) + goto fail_decrypt; + + out_sz += tmp_sz; + + assert(out_sz <= in_sz); + + shm_du_buff_tail_release(sdb, in_sz - out_sz); + + memcpy(in, out, out_sz); + + free(out); + + return 0; + + fail_decrypt: + EVP_CIPHER_CTX_cleanup(ctx); + fail_decrypt_init: + free(out); + fail_malloc: + return -ECRYPT; + +} + +static int openssl_crypt_init(void ** ctx) +{ + *ctx = EVP_CIPHER_CTX_new(); + if (*ctx == NULL) + return -ECRYPT; + + return 0; +} + +static void openssl_crypt_fini(void * ctx) +{ + EVP_CIPHER_CTX_free(ctx); +} + +#endif /* HAVE_OPENSSL */ + +int crypt_dh_pkp_create(void ** pkp, + uint8_t * pk) +{ +#ifdef HAVE_OPENSSL + assert(pkp != NULL); + *pkp = NULL; + return openssl_ecdh_pkp_create(pkp, pk); +#else + (void) pkp; + (void) pk; + + *pkp = NULL; + + return 0; +#endif +} + +void crypt_dh_pkp_destroy(void * pkp) +{ +#ifdef HAVE_OPENSSL + openssl_ecdh_pkp_destroy(pkp); +#else + (void) pkp; + return; +#endif +} + +int crypt_dh_derive(void * pkp, + buffer_t pk, + uint8_t * s) +{ +#ifdef HAVE_OPENSSL + return openssl_ecdh_derive(pkp, pk, s); +#else + (void) pkp; + (void) pk; + + memset(s, 0, SYMMKEYSZ); + + return -ECRYPT; +#endif +} + +int crypt_encrypt(struct crypt_info * info, + struct shm_du_buff * sdb) +{ + if (info->flags == 0) + return 0; + +#ifdef HAVE_OPENSSL + return openssl_encrypt(info->ctx, info->key, sdb); +#else + (void) sdb; + + return 0; +#endif +} + +int crypt_decrypt(struct crypt_info * info, + struct shm_du_buff * sdb) +{ + if (info->flags == 0) + return 0; + +#ifdef HAVE_OPENSSL + return openssl_decrypt(info->ctx, info->key, sdb); +#else + (void) sdb; + + return -ECRYPT; +#endif +} + +int crypt_init(struct crypt_info * info) +{ +#ifdef HAVE_OPENSSL + return openssl_crypt_init(&info->ctx); +#else + info->ctx = NULL; + return 0; +#endif +} + +void crypt_fini(struct crypt_info * info) +{ +#ifdef HAVE_OPENSSL + openssl_crypt_fini(info->ctx); +#else + (void) info; + assert(info->ctx == NULL); +#endif +} |
