From d038c4dee76ff8dff93101f5807a78f21064824c Mon Sep 17 00:00:00 2001 From: Aris Adamantiadis Date: Wed, 28 Feb 2018 10:24:53 -0600 Subject: [PATCH] chacha: packet encryption Signed-off-by: Aris Adamantiadis Reviewed-by: Andreas Schneider --- include/libssh/crypto.h | 6 ++ include/libssh/libcrypto.h | 1 + include/libssh/wrapper.h | 3 +- src/CMakeLists.txt | 1 + src/chachapoly.c | 126 +++++++++++++++++++++++++++++++++++++ src/dh.c | 3 + src/kex.c | 4 +- src/libcrypto.c | 21 ++++++- src/packet.c | 43 ++++++++----- src/packet_crypt.c | 46 ++++++++------ src/wrapper.c | 96 ++++++++++++++++++++-------- 11 files changed, 288 insertions(+), 62 deletions(-) create mode 100644 src/chachapoly.c diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h index 3a1af62d..f41b7249 100644 --- a/include/libssh/crypto.h +++ b/include/libssh/crypto.h @@ -128,10 +128,12 @@ struct ssh_cipher_struct { const char *name; /* ssh name of the algorithm */ unsigned int blocksize; /* blocksize of the algo */ enum ssh_cipher_e ciphertype; + uint32_t lenfield_blocksize; /* blocksize of the packet length field */ #ifdef HAVE_LIBGCRYPT size_t keylen; /* length of the key structure */ gcry_cipher_hd_t *key; #elif defined HAVE_LIBCRYPTO + size_t keylen; /* length of the key structure */ struct ssh_3des_key_schedule *des3_key; struct ssh_aes_key_schedule *aes_key; const EVP_CIPHER *cipher; @@ -141,7 +143,9 @@ struct ssh_cipher_struct { mbedtls_cipher_context_t decrypt_ctx; mbedtls_cipher_type_t type; #endif + struct chacha20_poly1305_keysched *chacha20_schedule; unsigned int keysize; /* bytes of key used. != keylen */ + size_t tag_size; /* overhead required for tag */ /* sets the new key for immediate use */ int (*set_encrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); int (*set_decrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); @@ -149,6 +153,8 @@ struct ssh_cipher_struct { unsigned long len); void (*decrypt)(struct ssh_cipher_struct *cipher, void *in, void *out, unsigned long len); + void (*aead_encrypt)(struct ssh_cipher_struct *cipher, void *in, void *out, + size_t len, uint8_t *mac, uint64_t seq); void (*cleanup)(struct ssh_cipher_struct *cipher); }; diff --git a/include/libssh/libcrypto.h b/include/libssh/libcrypto.h index 6a08837a..4b8e5414 100644 --- a/include/libssh/libcrypto.h +++ b/include/libssh/libcrypto.h @@ -95,6 +95,7 @@ SHA512CTX sha512_init(void); void sha512_update(SHA512CTX c, const void *data, unsigned long len); void sha512_final(unsigned char *md, SHA512CTX c); +void libcrypto_init(void); struct ssh_cipher_struct *ssh_get_ciphertab(void); #endif /* HAVE_LIBCRYPTO */ diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h index 6b6cf0b1..c23c9061 100644 --- a/include/libssh/wrapper.h +++ b/include/libssh/wrapper.h @@ -39,7 +39,8 @@ enum ssh_hmac_e { SSH_HMAC_SHA256, SSH_HMAC_SHA384, SSH_HMAC_SHA512, - SSH_HMAC_MD5 + SSH_HMAC_MD5, + SSH_HMAC_AEAD_POLY1305 }; enum ssh_des_e { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index edbf3520..90e44253 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -122,6 +122,7 @@ set(libssh_SRCS bignum.c buffer.c callbacks.c + chachapoly.c channels.c client.c config.c diff --git a/src/chachapoly.c b/src/chachapoly.c new file mode 100644 index 00000000..16032179 --- /dev/null +++ b/src/chachapoly.c @@ -0,0 +1,126 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2015 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH 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 the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/libssh.h" +#include "libssh/crypto.h" +#include "libssh/chacha.h" +#include "libssh/poly1305.h" +#include "libssh/misc.h" + +/* size of the keys k1 and k2 as defined in specs */ +#define CHACHA20_KEYLEN 32 +struct chacha20_poly1305_keysched { + /* key used for encrypting the length field*/ + struct chacha_ctx k1; + /* key used for encrypting the packets */ + struct chacha_ctx k2; +}; + +#pragma pack(push, 1) +struct ssh_packet_header { + uint32_t length; + uint8_t payload[]; +}; +#pragma pack(pop) + +const uint8_t zero_block_counter[8] = {0, 0, 0, 0, 0, 0, 0, 0}; +const uint8_t payload_block_counter[8] = {1, 0, 0, 0, 0, 0, 0, 0}; + +static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + struct chacha20_poly1305_keysched *sched; + uint8_t *u8key = key; + (void)IV; + + if (cipher->chacha20_schedule == NULL) { + sched = malloc(sizeof *sched); + if (sched == NULL){ + return -1; + } + } else { + sched = cipher->chacha20_schedule; + } + + chacha_keysetup(&sched->k2, u8key, CHACHA20_KEYLEN * 8); + chacha_keysetup(&sched->k1, u8key + CHACHA20_KEYLEN, CHACHA20_KEYLEN * 8); + cipher->chacha20_schedule = sched; + + return 0; +} + +/** + * @internal + * + * @brief encrypts an outgoing packet with chacha20 and authenticate it + * with poly1305. + */ +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; + uint8_t poly1305_ctx[POLY1305_KEYLEN] = {0}; + struct chacha20_poly1305_keysched *keys = cipher->chacha20_schedule; + + seq = htonll(seq); + /* step 1, prepare the poly1305 key */ + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k2, + poly1305_ctx, + poly1305_ctx, + POLY1305_KEYLEN); + + /* step 2, encrypt length field */ + chacha_ivsetup(&keys->k1, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k1, + (uint8_t *)&in_packet->length, + (uint8_t *)&out_packet->length, + sizeof(uint32_t)); + + /* step 3, encrypt packet payload */ + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, payload_block_counter); + chacha_encrypt_bytes(&keys->k2, + in_packet->payload, + out_packet->payload, + len - sizeof(uint32_t)); + + /* step 4, compute the MAC */ + poly1305_auth(tag, (uint8_t *)out_packet, len, poly1305_ctx); +} + +const struct ssh_cipher_struct chacha20poly1305_cipher = { + .name = "chacha20-poly1305@openssh.com", + .blocksize = 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 512, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_set_encrypt_key, + .set_decrypt_key = chacha20_set_encrypt_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, +}; diff --git a/src/dh.c b/src/dh.c index 90eda09b..02935795 100644 --- a/src/dh.c +++ b/src/dh.c @@ -68,6 +68,7 @@ #include #include #include +#include "libssh/libcrypto.h" #endif static unsigned char p_group1_value[] = { @@ -210,6 +211,8 @@ int ssh_crypto_init(void) { bignum_bin2bn(p_group14_value, P_GROUP14_LEN, p_group14); OpenSSL_add_all_algorithms(); + + libcrypto_init(); #elif defined HAVE_LIBMBEDCRYPTO p_group1 = bignum_new(); bignum_bin2bn(p_group1_value, P_GROUP1_LEN, p_group1); diff --git a/src/kex.c b/src/kex.c index b658ed44..20044c32 100644 --- a/src/kex.c +++ b/src/kex.c @@ -95,6 +95,8 @@ #define ECDH "" #endif +#define CHACHA20 "chacha20-poly1305@openssh.com," + #define KEY_EXCHANGE CURVE25519 ECDH "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" #define KEX_METHODS_SIZE 10 @@ -117,7 +119,7 @@ static const char *default_methods[] = { static const char *supported_methods[] = { KEY_EXCHANGE, HOSTKEYS, - AES BLOWFISH DES_SUPPORTED, + CHACHA20 AES BLOWFISH DES_SUPPORTED, AES BLOWFISH DES_SUPPORTED, "hmac-sha2-256,hmac-sha2-512,hmac-sha1", "hmac-sha2-256,hmac-sha2-512,hmac-sha1", diff --git a/src/libcrypto.c b/src/libcrypto.c index 66453666..982c9c84 100644 --- a/src/libcrypto.c +++ b/src/libcrypto.c @@ -60,6 +60,7 @@ #include "libssh/crypto.h" +extern const struct ssh_cipher_struct chacha20poly1305_cipher; struct ssh_mac_ctx_struct { enum ssh_mac_e mac_type; union { @@ -860,11 +861,30 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .cleanup = des_cleanup }, #endif /* HAS_DES */ + { + .name = "chacha20-poly1305@openssh.com" + }, { .name = NULL } }; +void libcrypto_init(void) +{ + size_t i; + + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + int cmp; + + cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com"); + if (cmp == 0) { + memcpy(&ssh_ciphertab[i], + &chacha20poly1305_cipher, + sizeof(struct ssh_cipher_struct)); + break; + } + } +} struct ssh_cipher_struct *ssh_get_ciphertab(void) { @@ -872,4 +892,3 @@ struct ssh_cipher_struct *ssh_get_ciphertab(void) } #endif /* LIBCRYPTO */ - diff --git a/src/packet.c b/src/packet.c index b66e3d22..f6fb6bff 100644 --- a/src/packet.c +++ b/src/packet.c @@ -555,6 +555,8 @@ static int ssh_packet_write(ssh_session session) { static int packet_send2(ssh_session session) { unsigned int blocksize = (session->current_crypto ? session->current_crypto->out_cipher->blocksize : 8); + unsigned int lenfield_blocksize = (session->current_crypto ? + session->current_crypto->out_cipher->lenfield_blocksize : 0); enum ssh_hmac_e hmac_type = (session->current_crypto ? session->current_crypto->out_hmac : session->next_crypto->out_hmac); uint32_t currentlen = ssh_buffer_get_len(session->out_buffer); @@ -563,8 +565,7 @@ static int packet_send2(ssh_session session) { int rc = SSH_ERROR; uint32_t finallen,payloadsize,compsize; uint8_t padding; - - uint8_t header[sizeof(padding) + sizeof(finallen)] = { 0 }; + ssh_buffer header_buffer = ssh_buffer_new(); payloadsize = currentlen; #ifdef WITH_ZLIB @@ -578,20 +579,30 @@ static int packet_send2(ssh_session session) { } #endif /* WITH_ZLIB */ compsize = currentlen; - padding = (blocksize - ((currentlen +5) % blocksize)); + /* compressed payload + packet len (4) + padding len (1) */ + /* totallen - lenfield_blocksize must be equal to 0 (mod blocksize) */ + padding = (blocksize - ((blocksize - lenfield_blocksize + currentlen + 5) % blocksize)); if(padding < 4) { padding += blocksize; } - if (session->current_crypto) { + if (session->current_crypto != NULL) { ssh_get_random(padstring, padding, 0); } - finallen = htonl(currentlen + padding + 1); + if (header_buffer == NULL){ + ssh_set_error_oom(session); + goto error; + } + finallen = currentlen + padding + 1; + rc = ssh_buffer_pack(header_buffer, "db", finallen, padding); + if (rc == SSH_ERROR){ + goto error; + } - memcpy(&header[0], &finallen, sizeof(finallen)); - header[sizeof(finallen)] = padding; - rc = ssh_buffer_prepend_data(session->out_buffer, &header, sizeof(header)); + rc = ssh_buffer_prepend_data(session->out_buffer, + ssh_buffer_get(header_buffer), + ssh_buffer_get_len(header_buffer)); if (rc < 0) { goto error; } @@ -600,10 +611,12 @@ static int packet_send2(ssh_session session) { goto error; } #ifdef WITH_PCAP - if(session->pcap_ctx){ - ssh_pcap_context_write(session->pcap_ctx,SSH_PCAP_DIR_OUT, - ssh_buffer_get(session->out_buffer),ssh_buffer_get_len(session->out_buffer) - ,ssh_buffer_get_len(session->out_buffer)); + if (session->pcap_ctx) { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_OUT, + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); } #endif hmac = ssh_packet_encrypt(session, ssh_buffer_get(session->out_buffer), @@ -624,12 +637,14 @@ static int packet_send2(ssh_session session) { SSH_LOG(SSH_LOG_PACKET, "packet: wrote [len=%d,padding=%hhd,comp=%d,payload=%d]", - ntohl(finallen), padding, compsize, payloadsize); + finallen, padding, compsize, payloadsize); if (ssh_buffer_reinit(session->out_buffer) < 0) { rc = SSH_ERROR; } error: - + if (header_buffer != NULL) { + ssh_buffer_free(header_buffer); + } return rc; /* SSH_OK, AGAIN or ERROR */ } diff --git a/src/packet_crypt.c b/src/packet_crypt.c index 7a30e661..5bcb6d65 100644 --- a/src/packet_crypt.c +++ b/src/packet_crypt.c @@ -93,7 +93,7 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) if (!session->current_crypto) { return NULL; /* nothing to do here */ } - if(len % session->current_crypto->in_cipher->blocksize != 0){ + if((len - session->current_crypto->out_cipher->lenfield_blocksize) % session->current_crypto->out_cipher->blocksize != 0){ ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be set on at least one blocksize (received %d)",len); return NULL; } @@ -106,26 +106,36 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) seq = ntohl(session->send_seq); crypto = session->current_crypto->out_cipher; - if (session->version == 2) { - ctx = hmac_init(session->current_crypto->encryptMAC, hmac_digest_len(type), type); - if (ctx == NULL) { - SAFE_FREE(out); - return NULL; - } - hmac_update(ctx,(unsigned char *)&seq,sizeof(uint32_t)); - hmac_update(ctx,data,len); - hmac_final(ctx,session->current_crypto->hmacbuf,&finallen); + if (crypto->aead_encrypt != NULL) { + crypto->aead_encrypt(crypto, data, out, len, + session->current_crypto->hmacbuf, session->send_seq); + } else { + if (session->version == 2) { + ctx = hmac_init(session->current_crypto->encryptMAC, hmac_digest_len(type), type); + if (ctx == NULL) { + SAFE_FREE(out); + return NULL; + } + hmac_update(ctx,(unsigned char *)&seq,sizeof(uint32_t)); + hmac_update(ctx,data,len); + hmac_final(ctx,session->current_crypto->hmacbuf,&finallen); + + if (crypto->set_encrypt_key(crypto, session->current_crypto->encryptkey, + session->current_crypto->encryptIV) < 0) { + SAFE_FREE(out); + return NULL; + } + #ifdef DEBUG_CRYPTO - ssh_print_hexa("mac: ",data,hmac_digest_len(type)); - if (finallen != hmac_digest_len(type)) { - printf("Final len is %d\n",finallen); - } - ssh_print_hexa("Packet hmac", session->current_crypto->hmacbuf, hmac_digest_len(type)); + ssh_print_hexa("mac: ",data,hmac_digest_len(type)); + if (finallen != hmac_digest_len(type)) { + printf("Final len is %d\n",finallen); + } + ssh_print_hexa("Packet hmac", session->current_crypto->hmacbuf, hmac_digest_len(type)); #endif - } - + } crypto->encrypt(crypto, data, out, len); - + } memcpy(data, out, len); explicit_bzero(out, len); SAFE_FREE(out); diff --git a/src/wrapper.c b/src/wrapper.c index b27d7ca8..ccab7b1c 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -47,6 +47,7 @@ #include "libssh/crypto.h" #include "libssh/wrapper.h" #include "libssh/pki.h" +#include "libssh/poly1305.h" static struct ssh_hmac_struct ssh_hmac_tab[] = { { "hmac-sha1", SSH_HMAC_SHA1 }, @@ -54,6 +55,7 @@ static struct ssh_hmac_struct ssh_hmac_tab[] = { { "hmac-sha2-384", SSH_HMAC_SHA384 }, { "hmac-sha2-512", SSH_HMAC_SHA512 }, { "hmac-md5", SSH_HMAC_MD5 }, + { "aead-poly1305", SSH_HMAC_AEAD_POLY1305 }, { NULL, 0} }; @@ -73,6 +75,8 @@ size_t hmac_digest_len(enum ssh_hmac_e type) { return SHA512_DIGEST_LEN; case SSH_HMAC_MD5: return MD5_DIGEST_LEN; + case SSH_HMAC_AEAD_POLY1305: + return POLY1305_TAGLEN; default: return 0; } @@ -124,6 +128,9 @@ void ssh_cipher_clear(struct ssh_cipher_struct *cipher){ if (cipher->cleanup != NULL) { cipher->cleanup(cipher); } + if (cipher->chacha20_schedule != NULL){ + SAFE_FREE(cipher->chacha20_schedule); + } } static void cipher_free(struct ssh_cipher_struct *cipher) { @@ -247,9 +254,14 @@ static int crypt_set_algorithms2(ssh_session session){ } i = 0; - /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ - /* out */ - wanted = session->next_crypto->kex_methods[SSH_MAC_C_S]; + if (session->next_crypto->out_cipher->aead_encrypt != NULL){ + /* this cipher has integrated MAC */ + wanted = "aead-poly1305"; + } else { + /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ + /* out */ + wanted = session->next_crypto->kex_methods[SSH_MAC_C_S]; + } while (ssh_hmactab[i].name && strcmp(wanted, ssh_hmactab[i].name)) { i++; } @@ -357,7 +369,7 @@ int crypt_set_algorithms(ssh_session session, enum ssh_des_e des_type) { #ifdef WITH_SERVER int crypt_set_algorithms_server(ssh_session session){ - char *method = NULL; + const char *method = NULL; int i = 0; struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); @@ -372,9 +384,17 @@ int crypt_set_algorithms_server(ssh_session session){ */ /* out */ method = session->next_crypto->kex_methods[SSH_CRYPT_S_C]; - while(ssh_ciphertab[i].name && strcmp(method,ssh_ciphertab[i].name)) - i++; - if(!ssh_ciphertab[i].name){ + + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + int cmp; + + cmp = strcmp(method, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { ssh_set_error(session,SSH_FATAL,"crypt_set_algorithms_server : " "no crypto algorithm function found for %s",method); return SSH_ERROR; @@ -387,26 +407,16 @@ int crypt_set_algorithms_server(ssh_session session){ return SSH_ERROR; } i=0; - /* in */ - method = session->next_crypto->kex_methods[SSH_CRYPT_C_S]; - while(ssh_ciphertab[i].name && strcmp(method,ssh_ciphertab[i].name)) - i++; - if(!ssh_ciphertab[i].name){ - ssh_set_error(session,SSH_FATAL,"Crypt_set_algorithms_server :" - "no crypto algorithm function found for %s",method); - return SSH_ERROR; + if (session->next_crypto->out_cipher->aead_encrypt != NULL){ + /* this cipher has integrated MAC */ + method = "aead-poly1305"; + } else { + /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ + /* out */ + method = session->next_crypto->kex_methods[SSH_MAC_S_C]; } - SSH_LOG(SSH_LOG_PACKET,"Set input algorithm %s",method); - - session->next_crypto->in_cipher = cipher_new(i); - if (session->next_crypto->in_cipher == NULL) { - ssh_set_error_oom(session); - return SSH_ERROR; - } - i=0; - /* HMAC algorithm selection */ - method = session->next_crypto->kex_methods[SSH_MAC_S_C]; + while (ssh_hmactab[i].name && strcmp(method, ssh_hmactab[i].name)) { i++; } @@ -420,11 +430,43 @@ int crypt_set_algorithms_server(ssh_session session){ SSH_LOG(SSH_LOG_PACKET, "Set HMAC output algorithm to %s", method); session->next_crypto->out_hmac = ssh_hmactab[i].hmac_type; + + /* in */ + i=0; + method = session->next_crypto->kex_methods[SSH_CRYPT_C_S]; + + for (i = 0; ssh_ciphertab[i].name; i++) { + int cmp; + + cmp = strcmp(method, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session,SSH_FATAL,"Crypt_set_algorithms_server :" + "no crypto algorithm function found for %s",method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET,"Set input algorithm %s",method); + + session->next_crypto->in_cipher = cipher_new(i); + if (session->next_crypto->in_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } i=0; method = session->next_crypto->kex_methods[SSH_MAC_C_S]; - while (ssh_hmactab[i].name && strcmp(method, ssh_hmactab[i].name)) { - i++; + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + int cmp; + + cmp = strcmp(method, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } } if (ssh_hmactab[i].name == NULL) {