aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJakub Jelen <jjelen@redhat.com>2018-10-01 14:32:05 +0200
committerAndreas Schneider <asn@cryptomilk.org>2018-10-09 13:05:38 +0200
commit5790036a2305d5610ac55adb5382ea55d043998f (patch)
tree66ab807fe1df456856066e771a584db114dae415 /src
parent032f486f27c51f78318a2f26486ccdcabe139d5e (diff)
downloadlibssh-5790036a2305d5610ac55adb5382ea55d043998f.tar.gz
libssh-5790036a2305d5610ac55adb5382ea55d043998f.tar.xz
libssh-5790036a2305d5610ac55adb5382ea55d043998f.zip
libgcrypt: Implement OpenSSH-compatible AES-GCM ciphers using libgcrypt
Signed-off-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
Diffstat (limited to 'src')
-rw-r--r--src/kex.c4
-rw-r--r--src/libgcrypt.c201
2 files changed, 204 insertions, 1 deletions
diff --git a/src/kex.c b/src/kex.c
index e0fd5680..382d88fb 100644
--- a/src/kex.c
+++ b/src/kex.c
@@ -40,7 +40,9 @@
#ifdef HAVE_LIBGCRYPT
# define BLOWFISH "blowfish-cbc,"
-# define AES "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,"
+# define AES "aes256-gcm@openssh.com,aes128-gcm@openssh.com," \
+ "aes256-ctr,aes192-ctr,aes128-ctr," \
+ "aes256-cbc,aes192-cbc,aes128-cbc,"
# define DES "3des-cbc"
# define DES_SUPPORTED "3des-cbc"
diff --git a/src/libgcrypt.c b/src/libgcrypt.c
index f004ffe2..7160bb1c 100644
--- a/src/libgcrypt.c
+++ b/src/libgcrypt.c
@@ -353,6 +353,8 @@ static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) {
}
if(strstr(cipher->name,"-ctr"))
mode=GCRY_CIPHER_MODE_CTR;
+ if (strstr(cipher->name, "-gcm"))
+ mode = GCRY_CIPHER_MODE_GCM;
switch (cipher->keysize) {
case 128:
if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES128,
@@ -386,6 +388,11 @@ static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) {
SAFE_FREE(cipher->key);
return -1;
}
+ } else if (mode == GCRY_CIPHER_MODE_GCM) {
+ /* Store the IV so we can handle the packet counter increments later
+ * The IV is passed to the cipher context later.
+ */
+ memcpy(cipher->last_iv, IV, AES_GCM_IVLEN);
} else {
if(gcry_cipher_setctr(cipher->key[0],IV,16)){
SAFE_FREE(cipher->key);
@@ -407,6 +414,172 @@ static void aes_decrypt(struct ssh_cipher_struct *cipher, void *in, void *out,
gcry_cipher_decrypt(cipher->key[0], out, len, in, len);
}
+static int
+aes_aead_get_length(struct ssh_cipher_struct *cipher,
+ void *in,
+ uint8_t *out,
+ size_t len,
+ uint64_t seq)
+{
+ (void)seq;
+
+ /* The length is not encrypted: Copy it to the result buffer */
+ memcpy(out, in, len);
+
+ return SSH_OK;
+}
+
+/* Increment 64b integer in network byte order */
+static void
+uint64_inc(unsigned char *counter)
+{
+ int i;
+
+ for (i = 7; i >= 0; i--) {
+ counter[i]++;
+ if (counter[i])
+ return;
+ }
+}
+
+static void
+aes_gcm_encrypt(struct ssh_cipher_struct *cipher,
+ void *in,
+ void *out,
+ size_t len,
+ uint8_t *tag,
+ uint64_t seq)
+{
+ gpg_error_t err;
+ size_t aadlen, authlen;
+
+ (void)seq;
+
+ aadlen = cipher->lenfield_blocksize;
+ authlen = cipher->tag_size;
+
+ /* increment IV */
+ err = gcry_cipher_setiv(cipher->key[0],
+ cipher->last_iv,
+ AES_GCM_IVLEN);
+ /* This actualy does not increment the packet counter for the
+ * current encryption operation, but for the next one. The first
+ * operation needs to be completed with the derived IV.
+ *
+ * The IV buffer has the following structure:
+ * [ 4B static IV ][ 8B packet counter ][ 4B block counter ]
+ */
+ uint64_inc(cipher->last_iv + 4);
+ if (err) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
+ gpg_strerror(err));
+ return;
+ }
+
+ /* Pass the authenticated data (packet_length) */
+ err = gcry_cipher_authenticate(cipher->key[0], in, aadlen);
+ if (err) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_authenticate failed: %s",
+ gpg_strerror(err));
+ return;
+ }
+ memcpy(out, in, aadlen);
+
+ /* Encrypt the rest of the data */
+ err = gcry_cipher_encrypt(cipher->key[0],
+ (unsigned char *)out + aadlen,
+ len - aadlen,
+ (unsigned char *)in + aadlen,
+ len - aadlen);
+ if (err) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s",
+ gpg_strerror(err));
+ return;
+ }
+
+ /* Calculate the tag */
+ err = gcry_cipher_gettag(cipher->key[0],
+ (void *)tag,
+ authlen);
+ if (err) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_gettag failed: %s",
+ gpg_strerror(err));
+ return;
+ }
+}
+
+static int
+aes_gcm_decrypt(struct ssh_cipher_struct *cipher,
+ void *complete_packet,
+ uint8_t *out,
+ size_t encrypted_size,
+ uint64_t seq)
+{
+ gpg_error_t err;
+ size_t aadlen, authlen;
+
+ (void)seq;
+
+ aadlen = cipher->lenfield_blocksize;
+ authlen = cipher->tag_size;
+
+ /* increment IV */
+ err = gcry_cipher_setiv(cipher->key[0],
+ cipher->last_iv,
+ AES_GCM_IVLEN);
+ /* This actualy does not increment the packet counter for the
+ * current encryption operation, but for the next one. The first
+ * operation needs to be completed with the derived IV.
+ *
+ * The IV buffer has the following structure:
+ * [ 4B static IV ][ 8B packet counter ][ 4B block counter ]
+ */
+ uint64_inc(cipher->last_iv + 4);
+ if (err) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
+ gpg_strerror(err));
+ return SSH_ERROR;
+ }
+
+ /* Pass the authenticated data (packet_length) */
+ err = gcry_cipher_authenticate(cipher->key[0],
+ complete_packet,
+ aadlen);
+ if (err) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_authenticate failed: %s",
+ gpg_strerror(err));
+ return SSH_ERROR;
+ }
+ /* Do not copy the length to the target buffer, because it is already processed */
+ //memcpy(out, complete_packet, aadlen);
+
+ /* Encrypt the rest of the data */
+ err = gcry_cipher_decrypt(cipher->key[0],
+ out,
+ encrypted_size,
+ (unsigned char *)complete_packet + aadlen,
+ encrypted_size);
+ if (err) {
+ SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s",
+ gpg_strerror(err));
+ return SSH_ERROR;
+ }
+
+ /* Check the tag */
+ err = gcry_cipher_checktag(cipher->key[0],
+ (unsigned char *)complete_packet + aadlen + encrypted_size,
+ authlen);
+ if (gpg_err_code(err) == GPG_ERR_CHECKSUM) {
+ SSH_LOG(SSH_LOG_WARNING, "The authentication tag does not match");
+ return SSH_ERROR;
+ } else if (err != GPG_ERR_NO_ERROR) {
+ SSH_LOG(SSH_LOG_WARNING, "General error while decryption: %s",
+ gpg_strerror(err));
+ return SSH_ERROR;
+ }
+ return SSH_OK;
+}
+
static int des3_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) {
if (cipher->key == NULL) {
if (alloc_key(cipher) < 0) {
@@ -520,6 +693,34 @@ static struct ssh_cipher_struct ssh_ciphertab[] = {
.decrypt = aes_decrypt
},
{
+ .name = "aes128-gcm@openssh.com",
+ .blocksize = 16,
+ .lenfield_blocksize = 4, /* not encrypted, but authenticated */
+ .keylen = sizeof(gcry_cipher_hd_t),
+ .key = NULL,
+ .keysize = 128,
+ .tag_size = AES_GCM_TAGLEN,
+ .set_encrypt_key = aes_set_key,
+ .set_decrypt_key = aes_set_key,
+ .aead_encrypt = aes_gcm_encrypt,
+ .aead_decrypt_length = aes_aead_get_length,
+ .aead_decrypt = aes_gcm_decrypt,
+ },
+ {
+ .name = "aes256-gcm@openssh.com",
+ .blocksize = 16,
+ .lenfield_blocksize = 4, /* not encrypted, but authenticated */
+ .keylen = sizeof(gcry_cipher_hd_t),
+ .key = NULL,
+ .keysize = 256,
+ .tag_size = AES_GCM_TAGLEN,
+ .set_encrypt_key = aes_set_key,
+ .set_decrypt_key = aes_set_key,
+ .aead_encrypt = aes_gcm_encrypt,
+ .aead_decrypt_length = aes_aead_get_length,
+ .aead_decrypt = aes_gcm_decrypt,
+ },
+ {
.name = "3des-cbc",
.blocksize = 8,
.keylen = sizeof(gcry_cipher_hd_t),