aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Kivilinna <jussi.kivilinna@iki.fi>2019-11-25 23:04:09 +0200
committerJussi Kivilinna <jussi.kivilinna@iki.fi>2019-12-16 22:31:54 +0200
commitde4b8f88a292d2bf1491e174d4498b7a28c769ea (patch)
tree8c3e2c6a36de8754b3587b4584e8e37615f37499
parentaf5de2d37e7c2069a3baac85e4341e69f3353a86 (diff)
downloadlibssh-de4b8f88a292d2bf1491e174d4498b7a28c769ea.tar.gz
libssh-de4b8f88a292d2bf1491e174d4498b7a28c769ea.tar.xz
libssh-de4b8f88a292d2bf1491e174d4498b7a28c769ea.zip
libgcrypt: Implement chacha20-poly1305@openssh.com cipher using libgcrypt
Libgcrypt has supported ChaCha20 and Poly1305 since 1.7.0 version and provides fast assembler implementations. v3: - initialize pointers to NULL - use 'bool' for chacha20_poly1305_keysched.initialized - pass error codes from libgcrypt calls to variable - add SSH_LOG on error paths v2: - use braces for one-line blocks - use UNUSED_PARAM/UNUSED_VAR instead of cast to void - use calloc instead of malloc+memset Signed-off-by: Jussi Kivilinna <jussi.kivilinna@iki.fi> Reviewed-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
-rw-r--r--ConfigureChecks.cmake3
-rw-r--r--config.h.cmake3
-rw-r--r--src/libgcrypt.c343
3 files changed, 348 insertions, 1 deletions
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index c8bb2aa0..a99278f7 100644
--- a/ConfigureChecks.cmake
+++ b/ConfigureChecks.cmake
@@ -278,6 +278,9 @@ if (GCRYPT_FOUND)
set(HAVE_GCRYPT_ECC 1)
set(HAVE_ECC 1)
endif (GCRYPT_VERSION VERSION_GREATER "1.4.6")
+ if (NOT GCRYPT_VERSION VERSION_LESS "1.7.0")
+ set(HAVE_GCRYPT_CHACHA_POLY 1)
+ endif (NOT GCRYPT_VERSION VERSION_LESS "1.7.0")
endif (GCRYPT_FOUND)
if (MBEDTLS_FOUND)
diff --git a/config.h.cmake b/config.h.cmake
index 98a72f65..847fc579 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -103,6 +103,9 @@
/* Define to 1 if you have OpenSSL with X25519 support */
#cmakedefine HAVE_OPENSSL_X25519 1
+/* Define to 1 if you have gcrypt with ChaCha20/Poly1305 support */
+#cmakedefine HAVE_GCRYPT_CHACHA_POLY 1
+
/*************************** FUNCTIONS ***************************/
/* Define to 1 if you have the `EVP_aes128_ctr' function. */
diff --git a/src/libgcrypt.c b/src/libgcrypt.c
index 17d544ea..06decc29 100644
--- a/src/libgcrypt.c
+++ b/src/libgcrypt.c
@@ -25,6 +25,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <stdbool.h>
#include "libssh/priv.h"
#include "libssh/session.h"
@@ -36,6 +37,34 @@
#ifdef HAVE_LIBGCRYPT
#include <gcrypt.h>
+#ifdef HAVE_GCRYPT_CHACHA_POLY
+
+#define CHACHA20_BLOCKSIZE 64
+#define CHACHA20_KEYLEN 32
+
+#define POLY1305_TAGLEN 16
+#define POLY1305_KEYLEN 32
+
+struct chacha20_poly1305_keysched {
+ bool initialized;
+ /* cipher handle used for encrypting the packets */
+ gcry_cipher_hd_t main_hd;
+ /* cipher handle used for encrypting the length field */
+ gcry_cipher_hd_t header_hd;
+ /* mac handle used for authenticating the packets */
+ gcry_mac_hd_t mac_hd;
+};
+
+#pragma pack(push, 1)
+struct ssh_packet_header {
+ uint32_t length;
+ uint8_t payload[];
+};
+#pragma pack(pop)
+
+static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0};
+#endif /* HAVE_GCRYPT_CHACHA_POLY */
+
static int libgcrypt_initialized = 0;
static int alloc_key(struct ssh_cipher_struct *cipher) {
@@ -558,6 +587,300 @@ static void des3_decrypt(struct ssh_cipher_struct *cipher, void *in,
gcry_cipher_decrypt(cipher->key[0], out, len, in, len);
}
+#ifdef HAVE_GCRYPT_CHACHA_POLY
+static void chacha20_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) {
+ gcry_cipher_close(ctx->main_hd);
+ gcry_cipher_close(ctx->header_hd);
+ gcry_mac_close(ctx->mac_hd);
+ ctx->initialized = false;
+ }
+
+ SAFE_FREE(cipher->chacha20_schedule);
+}
+
+static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher,
+ void *key,
+ UNUSED_PARAM(void *IV))
+{
+ struct chacha20_poly1305_keysched *ctx = NULL;
+ uint8_t *u8key = key;
+ gpg_error_t err;
+
+ 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) {
+ /* Open cipher/mac handles. */
+ err = gcry_cipher_open(&ctx->main_hd, GCRY_CIPHER_CHACHA20,
+ GCRY_CIPHER_MODE_STREAM, 0);
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_open failed: %s",
+ gpg_strerror(err));
+ SAFE_FREE(cipher->chacha20_schedule);
+ return -1;
+ }
+ err = gcry_cipher_open(&ctx->header_hd, GCRY_CIPHER_CHACHA20,
+ GCRY_CIPHER_MODE_STREAM, 0);
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_open failed: %s",
+ gpg_strerror(err));
+ gcry_cipher_close(ctx->main_hd);
+ SAFE_FREE(cipher->chacha20_schedule);
+ return -1;
+ }
+ err = gcry_mac_open(&ctx->mac_hd, GCRY_MAC_POLY1305, 0, NULL);
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_mac_open failed: %s",
+ gpg_strerror(err));
+ gcry_cipher_close(ctx->main_hd);
+ gcry_cipher_close(ctx->header_hd);
+ SAFE_FREE(cipher->chacha20_schedule);
+ return -1;
+ }
+
+ ctx->initialized = true;
+ }
+
+ err = gcry_cipher_setkey(ctx->main_hd, u8key, CHACHA20_KEYLEN);
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setkey failed: %s",
+ gpg_strerror(err));
+ chacha20_cleanup(cipher);
+ return -1;
+ }
+
+ err = gcry_cipher_setkey(ctx->header_hd, u8key + CHACHA20_KEYLEN,
+ CHACHA20_KEYLEN);
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setkey failed: %s",
+ gpg_strerror(err));
+ chacha20_cleanup(cipher);
+ return -1;
+ }
+
+ return 0;
+}
+
+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;
+ uint8_t poly_key[CHACHA20_BLOCKSIZE];
+ size_t taglen = POLY1305_TAGLEN;
+ gpg_error_t err;
+
+ seq = htonll(seq);
+
+ /* step 1, prepare the poly1305 key */
+ err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq));
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+ /* Output full ChaCha block so that counter increases by one for
+ * payload encryption step. */
+ err = gcry_cipher_encrypt(ctx->main_hd,
+ poly_key,
+ sizeof(poly_key),
+ zero_block,
+ sizeof(zero_block));
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+ err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN);
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_mac_setkey failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+
+ /* step 2, encrypt length field */
+ err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq));
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+ err = gcry_cipher_encrypt(ctx->header_hd,
+ (uint8_t *)&out_packet->length,
+ sizeof(uint32_t),
+ (uint8_t *)&in_packet->length,
+ sizeof(uint32_t));
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+
+ /* step 3, encrypt packet payload (main_hd counter == 1) */
+ err = gcry_cipher_encrypt(ctx->main_hd,
+ out_packet->payload,
+ len - sizeof(uint32_t),
+ in_packet->payload,
+ len - sizeof(uint32_t));
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+
+ /* step 4, compute the MAC */
+ err = gcry_mac_write(ctx->mac_hd, (uint8_t *)out_packet, len);
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_mac_write failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+ err = gcry_mac_read(ctx->mac_hd, tag, &taglen);
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_mac_read failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+
+out:
+ explicit_bzero(poly_key, sizeof(poly_key));
+}
+
+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;
+ gpg_error_t err;
+
+ if (len < sizeof(uint32_t)) {
+ return SSH_ERROR;
+ }
+ seq = htonll(seq);
+
+ err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq));
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
+ gpg_strerror(err));
+ return SSH_ERROR;
+ }
+ err = gcry_cipher_decrypt(ctx->header_hd,
+ out,
+ sizeof(uint32_t),
+ in,
+ sizeof(uint32_t));
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s",
+ gpg_strerror(err));
+ return SSH_ERROR;
+ }
+
+ 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 poly_key[CHACHA20_BLOCKSIZE];
+ int ret = SSH_ERROR;
+ gpg_error_t err;
+
+ seq = htonll(seq);
+
+ /* step 1, prepare the poly1305 key */
+ err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq));
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+ /* Output full ChaCha block so that counter increases by one for
+ * decryption step. */
+ err = gcry_cipher_encrypt(ctx->main_hd,
+ poly_key,
+ sizeof(poly_key),
+ zero_block,
+ sizeof(zero_block));
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+ err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN);
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_mac_setkey failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+
+ /* step 2, check MAC */
+ err = gcry_mac_write(ctx->mac_hd, (uint8_t *)complete_packet,
+ encrypted_size + sizeof(uint32_t));
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_mac_write failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+ err = gcry_mac_verify(ctx->mac_hd, mac, POLY1305_TAGLEN);
+ if (gpg_err_code(err) == GPG_ERR_CHECKSUM) {
+ SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error");
+ goto out;
+ } else if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_mac_verify failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+
+ /* step 3, decrypt packet payload (main_hd counter == 1) */
+ err = gcry_cipher_decrypt(ctx->main_hd,
+ out,
+ encrypted_size,
+ (uint8_t *)complete_packet + sizeof(uint32_t),
+ encrypted_size);
+ if (err != 0) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s",
+ gpg_strerror(err));
+ goto out;
+ }
+
+ ret = SSH_OK;
+
+out:
+ explicit_bzero(poly_key, sizeof(poly_key));
+ return ret;
+}
+#endif /* HAVE_GCRYPT_CHACHA_POLY */
+
/* the table of supported ciphers */
static struct ssh_cipher_struct ssh_ciphertab[] = {
#ifdef WITH_BLOWFISH_CIPHER
@@ -679,7 +1002,23 @@ static struct ssh_cipher_struct ssh_ciphertab[] = {
.decrypt = des3_decrypt
},
{
+#ifdef HAVE_GCRYPT_CHACHA_POLY
+ .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_set_encrypt_key,
+ .set_decrypt_key = chacha20_set_encrypt_key,
+ .aead_encrypt = chacha20_poly1305_aead_encrypt,
+ .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length,
+ .aead_decrypt = chacha20_poly1305_aead_decrypt,
+ .cleanup = chacha20_cleanup
+#else
.name = "chacha20-poly1305@openssh.com"
+#endif
},
{
.name = NULL,
@@ -757,7 +1096,7 @@ fail:
*/
int ssh_crypto_init(void)
{
- size_t i;
+ UNUSED_VAR(size_t i);
if (libgcrypt_initialized) {
return SSH_OK;
@@ -776,6 +1115,7 @@ int ssh_crypto_init(void)
/* Re-enable warning */
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
+#ifndef HAVE_GCRYPT_CHACHA_POLY
for (i = 0; ssh_ciphertab[i].name != NULL; i++) {
int cmp;
cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com");
@@ -786,6 +1126,7 @@ int ssh_crypto_init(void)
break;
}
}
+#endif
libgcrypt_initialized = 1;