From 07f571f1c0db615987de462fb709b5d090da2469 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 15 Jan 2020 17:00:19 +0100 Subject: [PATCH] Implement chacha20-poly1305 in mbedTLS Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/libmbedcrypto.c | 346 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 345 insertions(+), 1 deletion(-) diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c index 755c5ebe..2db0f3ea 100644 --- a/src/libmbedcrypto.c +++ b/src/libmbedcrypto.c @@ -27,6 +27,12 @@ #include "libssh/crypto.h" #include "libssh/priv.h" #include "libssh/misc.h" +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) +#include "libssh/bytearray.h" +#include "libssh/chacha20-poly1305-common.h" +#include +#include +#endif #ifdef HAVE_LIBMBEDCRYPTO #include @@ -881,6 +887,326 @@ cipher_decrypt_gcm(struct ssh_cipher_struct *cipher, } #endif /* MBEDTLS_GCM_C */ +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) + +struct chacha20_poly1305_keysched { + bool initialized; + /* cipher handle used for encrypting the packets */ + mbedtls_chacha20_context main_ctx; + /* cipher handle used for encrypting the length field */ + mbedtls_chacha20_context header_ctx; + /* Poly1305 key */ + mbedtls_poly1305_context poly_ctx; +}; + +static void +chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + + if (cipher->chacha20_schedule == NULL) { + return; + } + + ctx = cipher->chacha20_schedule; + + if (ctx->initialized) { + mbedtls_chacha20_free(&ctx->main_ctx); + mbedtls_chacha20_free(&ctx->header_ctx); + mbedtls_poly1305_free(&ctx->poly_ctx); + ctx->initialized = false; + } + + SAFE_FREE(cipher->chacha20_schedule); +} + +static int +chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, + void *key, + UNUSED_PARAM(void *IV)) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + uint8_t *u8key = key; + int ret = SSH_ERROR, rv; + + if (cipher->chacha20_schedule == NULL) { + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return -1; + } + cipher->chacha20_schedule = ctx; + } else { + ctx = cipher->chacha20_schedule; + } + + if (!ctx->initialized) { + mbedtls_chacha20_init(&ctx->main_ctx); + mbedtls_chacha20_init(&ctx->header_ctx); + mbedtls_poly1305_init(&ctx->poly_ctx); + ctx->initialized = true; + } + + /* ChaCha20 keys initialization */ + /* K2 uses the first half of the key */ + rv = mbedtls_chacha20_setkey(&ctx->main_ctx, u8key); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_setkey(main_ctx) failed"); + goto out; + } + + /* K1 uses the second half of the key */ + rv = mbedtls_chacha20_setkey(&ctx->header_ctx, u8key + CHACHA20_KEYLEN); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_setkey(header_ctx) failed"); + goto out; + } + + ret = SSH_OK; +out: + if (ret != SSH_OK) { + chacha20_poly1305_cleanup(cipher); + } + return ret; +} + +static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0}; + +static int +chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t seqbuf[12] = {0}; + int ret; + + /* The nonce in mbedTLS is 96 b long. The counter is passed through separate + * parameter of 32 b size. + * Encode the seqence number into the last 8 bytes. + */ + PUSH_BE_U64(seqbuf, 4, seq); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("seqbuf (chacha20 IV)", seqbuf, sizeof(seqbuf)); +#endif /* DEBUG_CRYPTO */ + + ret = mbedtls_chacha20_starts(&ctx->header_ctx, seqbuf, 0); + if (ret != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_starts(header_ctx) failed"); + return SSH_ERROR; + } + + ret = mbedtls_chacha20_starts(&ctx->header_ctx, seqbuf, 0); + if (ret != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_starts(header_ctx) failed"); + return SSH_ERROR; + } + + return SSH_OK; +} + +static int +chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, + uint64_t seq, + int do_encrypt) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t poly_key[CHACHA20_BLOCKSIZE]; + int ret = SSH_ERROR, rv; + + /* The initialization for decrypt was already done with the length block */ + if (do_encrypt) { + rv = chacha20_poly1305_set_iv(cipher, seq); + if (rv != SSH_OK) { + return SSH_ERROR; + } + } + + /* Output full ChaCha block so that counter increases by one for + * next step. */ + rv = mbedtls_chacha20_update(&ctx->main_ctx, sizeof(zero_block), + zero_block, poly_key); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + goto out; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("poly_key", poly_key, POLY1305_KEYLEN); +#endif /* DEBUG_CRYPTO */ + + /* Set the Poly1305 key */ + rv = mbedtls_poly1305_starts(&ctx->poly_ctx, poly_key); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_starts failed"); + goto out; + } + + ret = SSH_OK; +out: + explicit_bzero(poly_key, sizeof(poly_key)); + return ret; +} + +static int +chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + int rv; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", (uint8_t *)in, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + /* Set IV for the header context */ + rv = chacha20_poly1305_set_iv(cipher, seq); + if (rv != SSH_OK) { + return SSH_ERROR; + } + + rv = mbedtls_chacha20_update(&ctx->header_ctx, sizeof(uint32_t), in, out); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("deciphered length", out, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + return SSH_OK; +} + +static int +chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) + + encrypted_size; + uint8_t tag[POLY1305_TAGLEN] = {0}; + int ret = SSH_ERROR; + int rv, cmp = 0; + + /* Prepare the Poly1305 key */ + rv = chacha20_poly1305_packet_setup(cipher, seq, 0); + if (rv != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("received mac", mac, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Calculate MAC of received data */ + rv = mbedtls_poly1305_update(&ctx->poly_ctx, complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_update failed"); + goto out; + } + + rv = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_finish failed"); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("calculated mac", tag, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Verify the calculated MAC matches the attached MAC */ + cmp = memcmp(tag, mac, POLY1305_TAGLEN); + if (cmp != 0) { + /* mac error */ + SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); + return SSH_ERROR; + } + + /* Decrypt the message */ + rv = mbedtls_chacha20_update(&ctx->main_ctx, encrypted_size, + (uint8_t *)complete_packet + sizeof(uint32_t), + out); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + goto out; + } + + ret = SSH_OK; +out: + return ret; +} + +static void +chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + int ret; + + /* Prepare the Poly1305 key */ + ret = chacha20_poly1305_packet_setup(cipher, seq, 1); + if (ret != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + return; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("plaintext length", + (unsigned char *)&in_packet->length, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + /* step 2, encrypt length field */ + ret = mbedtls_chacha20_update(&ctx->header_ctx, sizeof(uint32_t), + (unsigned char *)&in_packet->length, + (unsigned char *)&out_packet->length); + if (ret != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + return; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", + (unsigned char *)&out_packet->length, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + /* step 3, encrypt packet payload (main_ctx counter == 1) */ + /* We already did encrypt one block so the counter should be in the correct position */ + ret = mbedtls_chacha20_update(&ctx->main_ctx, len - sizeof(uint32_t), + in_packet->payload, out_packet->payload); + if (ret != 1) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + return; + } + + /* step 4, compute the MAC */ + ret = mbedtls_poly1305_update(&ctx->poly_ctx, (const unsigned char *)out_packet, len); + if (ret != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_update failed"); + return; + } + ret = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); + if (ret != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_finish failed"); + return; + } +} +#endif /* defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) */ + + static void cipher_cleanup(struct ssh_cipher_struct *cipher) { mbedtls_cipher_free(&cipher->encrypt_ctx); @@ -1012,7 +1338,23 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .cleanup = cipher_cleanup }, { +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 2 * CHACHA20_KEYLEN * 8, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_poly1305_set_key, + .set_decrypt_key = chacha20_poly1305_set_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_poly1305_cleanup +#else .name = "chacha20-poly1305@openssh.com" +#endif }, { .name = NULL, @@ -1033,7 +1375,7 @@ struct ssh_cipher_struct *ssh_get_ciphertab(void) int ssh_crypto_init(void) { - size_t i; + UNUSED_VAR(size_t i); int rc; if (libmbedcrypto_initialized) { @@ -1049,6 +1391,7 @@ int ssh_crypto_init(void) mbedtls_ctr_drbg_free(&ssh_mbedtls_ctr_drbg); } +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) for (i = 0; ssh_ciphertab[i].name != NULL; i++) { int cmp; @@ -1060,6 +1403,7 @@ int ssh_crypto_init(void) break; } } +#endif libmbedcrypto_initialized = 1;