aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Jelen <jjelen@redhat.com>2018-10-08 13:24:49 +0200
committerAndreas Schneider <asn@cryptomilk.org>2018-10-09 12:05:40 +0200
commit46090facbae8c8292d2775546082ccbd5e56fbd1 (patch)
tree3e6875a6e353d9d496972f0756f220446f082db2
parent777786d76c83b017ce6936bd15aa3413fd65bcca (diff)
downloadlibssh-46090facbae8c8292d2775546082ccbd5e56fbd1.tar.gz
libssh-46090facbae8c8292d2775546082ccbd5e56fbd1.tar.xz
libssh-46090facbae8c8292d2775546082ccbd5e56fbd1.zip
libcrypto: Implement OpenSSH-compatible AES-GCM ciphers using OpenSSL
The commit also propares the internals throughout the code base for the inclusion of a new AEAD cipher, because previously, the source code counted only with chacha20-poly1305 cipher, which is very specific in many cases. The SSH_HMAC_AEAD_GCM mac algorithm is not actually used, but the name needed to be defined so we can match in the algorithms selection per OpenSSH specification (MACs are ignored in case GCM is select as a cipher [1]). If the provided OpenSSL does not provide EVP_aes_128_gcm() function, the AES-GCM ciphers will not be compiled in. [1] https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD Signed-off-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
-rw-r--r--ConfigureChecks.cmake4
-rw-r--r--config.h.cmake3
-rw-r--r--include/libssh/crypto.h8
-rw-r--r--include/libssh/wrapper.h3
-rw-r--r--src/chachapoly.c1
-rw-r--r--src/kex.c9
-rw-r--r--src/libcrypto.c247
-rw-r--r--src/packet_crypt.c5
-rw-r--r--src/wrapper.c27
9 files changed, 297 insertions, 10 deletions
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index 2ab140db..d0868bab 100644
--- a/ConfigureChecks.cmake
+++ b/ConfigureChecks.cmake
@@ -110,6 +110,10 @@ if (OPENSSL_FOUND)
set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR})
set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY})
+ check_function_exists(EVP_aes_128_gcm HAVE_OPENSSL_EVP_AES_GCM)
+
+ set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR})
+ set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY})
check_function_exists(CRYPTO_THREADID_set_callback HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK)
set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR})
diff --git a/config.h.cmake b/config.h.cmake
index d7adb154..e5f92043 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -100,6 +100,9 @@
/* Define to 1 if you have the `EVP_aes128_cbc' function. */
#cmakedefine HAVE_OPENSSL_EVP_AES_CBC 1
+/* Define to 1 if you have the `EVP_aes128_gcm' function. */
+#cmakedefine HAVE_OPENSSL_EVP_AES_GCM 1
+
/* Define to 1 if you have the `CRYPTO_THREADID_set_callback' function. */
#cmakedefine HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK 1
diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h
index 52518d3c..fc375a4f 100644
--- a/include/libssh/crypto.h
+++ b/include/libssh/crypto.h
@@ -48,6 +48,9 @@
#define DIGEST_MAX_LEN 64
+#define AES_GCM_TAGLEN 16
+#define AES_GCM_IVLEN 12
+
enum ssh_key_exchange_e {
/* diffie-hellman-group1-sha1 */
SSH_KEX_DH_GROUP1_SHA1=1,
@@ -78,7 +81,10 @@ enum ssh_cipher_e {
SSH_AES256_CBC,
SSH_AES128_CTR,
SSH_AES192_CTR,
- SSH_AES256_CTR
+ SSH_AES256_CTR,
+ SSH_AEAD_AES128_GCM,
+ SSH_AEAD_AES256_GCM,
+ SSH_AEAD_CHACHA20_POLY1305
};
struct ssh_crypto_struct {
diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h
index 23d98afc..5dc44094 100644
--- a/include/libssh/wrapper.h
+++ b/include/libssh/wrapper.h
@@ -47,7 +47,8 @@ enum ssh_hmac_e {
SSH_HMAC_SHA384,
SSH_HMAC_SHA512,
SSH_HMAC_MD5,
- SSH_HMAC_AEAD_POLY1305
+ SSH_HMAC_AEAD_POLY1305,
+ SSH_HMAC_AEAD_GCM
};
enum ssh_des_e {
diff --git a/src/chachapoly.c b/src/chachapoly.c
index 904303ef..caa694cb 100644
--- a/src/chachapoly.c
+++ b/src/chachapoly.c
@@ -192,6 +192,7 @@ static void chacha20_cleanup(struct ssh_cipher_struct *cipher) {
}
const struct ssh_cipher_struct chacha20poly1305_cipher = {
+ .ciphertype = SSH_AEAD_CHACHA20_POLY1305,
.name = "chacha20-poly1305@openssh.com",
.blocksize = 8,
.lenfield_blocksize = 4,
diff --git a/src/kex.c b/src/kex.c
index a59374c5..e0fd5680 100644
--- a/src/kex.c
+++ b/src/kex.c
@@ -59,10 +59,15 @@
# endif /* HAVE_OPENSSL_BLOWFISH_H */
# ifdef HAVE_OPENSSL_AES_H
+# ifdef HAVE_OPENSSL_EVP_AES_GCM
+# define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com,"
+# else
+# define GCM ""
+# endif /* HAVE_OPENSSL_EVP_AES_GCM */
# ifdef BROKEN_AES_CTR
-# define AES "aes256-cbc,aes192-cbc,aes128-cbc,"
+# define AES GCM "aes256-cbc,aes192-cbc,aes128-cbc,"
# else /* BROKEN_AES_CTR */
-# define AES "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,"
+# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,"
# endif /* BROKEN_AES_CTR */
# else /* HAVE_OPENSSL_AES_H */
# define AES ""
diff --git a/src/libcrypto.c b/src/libcrypto.c
index ffbb1171..35b73b9f 100644
--- a/src/libcrypto.c
+++ b/src/libcrypto.c
@@ -492,6 +492,19 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) {
SSH_LOG(SSH_LOG_WARNING, "This cipher is not available in evp_cipher_init");
break;
#endif
+#ifdef HAVE_OPENSSL_EVP_AES_GCM
+ case SSH_AEAD_AES128_GCM:
+ cipher->cipher = EVP_aes_128_gcm();
+ break;
+ case SSH_AEAD_AES256_GCM:
+ cipher->cipher = EVP_aes_256_gcm();
+ break;
+#else
+ case SSH_AEAD_AES128_GCM:
+ case SSH_AEAD_AES256_GCM:
+ SSH_LOG(SSH_LOG_WARNING, "This cipher is not available in evp_cipher_init");
+ break;
+#endif /* HAVE_OPENSSL_EVP_AES_GCM */
case SSH_3DES_CBC:
cipher->cipher = EVP_des_ede3_cbc();
break;
@@ -499,6 +512,9 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) {
cipher->cipher = EVP_bf_cbc();
break;
/* ciphers not using EVP */
+ case SSH_AEAD_CHACHA20_POLY1305:
+ SSH_LOG(SSH_LOG_WARNING, "The ChaCha cipher can not be handled here");
+ break;
case SSH_NO_CIPHER:
SSH_LOG(SSH_LOG_WARNING, "No valid ciphertype found");
break;
@@ -518,6 +534,22 @@ static int evp_cipher_set_encrypt_key(struct ssh_cipher_struct *cipher,
SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptInit_ex failed");
return SSH_ERROR;
}
+
+#ifdef HAVE_OPENSSL_EVP_AES_GCM
+ /* For AES-GCM we need to set IV in specific way */
+ if (cipher->ciphertype == SSH_AEAD_AES128_GCM ||
+ cipher->ciphertype == SSH_AEAD_AES256_GCM) {
+ rc = EVP_CIPHER_CTX_ctrl(cipher->ctx,
+ EVP_CTRL_GCM_SET_IV_FIXED,
+ -1,
+ (u_char *)IV);
+ if (rc != 1) {
+ SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_IV_FIXED failed");
+ return SSH_ERROR;
+ }
+ }
+#endif /* HAVE_OPENSSL_EVP_AES_GCM */
+
EVP_CIPHER_CTX_set_padding(cipher->ctx, 0);
return SSH_OK;
@@ -535,6 +567,22 @@ static int evp_cipher_set_decrypt_key(struct ssh_cipher_struct *cipher,
SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptInit_ex failed");
return SSH_ERROR;
}
+
+#ifdef HAVE_OPENSSL_EVP_AES_GCM
+ /* For AES-GCM we need to set IV in specific way */
+ if (cipher->ciphertype == SSH_AEAD_AES128_GCM ||
+ cipher->ciphertype == SSH_AEAD_AES256_GCM) {
+ rc = EVP_CIPHER_CTX_ctrl(cipher->ctx,
+ EVP_CTRL_GCM_SET_IV_FIXED,
+ -1,
+ (u_char *)IV);
+ if (rc != 1) {
+ SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_IV_FIXED failed");
+ return SSH_ERROR;
+ }
+ }
+#endif /* HAVE_OPENSSL_EVP_AES_GCM */
+
EVP_CIPHER_CTX_set_padding(cipher->ctx, 0);
return SSH_OK;
@@ -643,6 +691,175 @@ static void aes_ctr_cleanup(struct ssh_cipher_struct *cipher){
#endif /* HAVE_OPENSSL_EVP_AES_CTR */
+#ifdef HAVE_OPENSSL_EVP_AES_GCM
+static int
+evp_cipher_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;
+}
+
+static void
+evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher,
+ void *in,
+ void *out,
+ size_t len,
+ uint8_t *tag,
+ uint64_t seq)
+{
+ size_t authlen, aadlen;
+ u_char lastiv[1];
+ int outlen = 0;
+ int rc;
+
+ (void) seq;
+
+ aadlen = cipher->lenfield_blocksize;
+ authlen = cipher->tag_size;
+
+ /* increment IV */
+ rc = EVP_CIPHER_CTX_ctrl(cipher->ctx,
+ EVP_CTRL_GCM_IV_GEN,
+ 1,
+ lastiv);
+ if (rc == 0) {
+ SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_IV_GEN failed");
+ return;
+ }
+
+ /* Pass over the authenticated data (not encrypted) */
+ rc = EVP_EncryptUpdate(cipher->ctx,
+ NULL,
+ &outlen,
+ (unsigned char *)in,
+ aadlen);
+ if (rc == 0 || outlen != aadlen) {
+ SSH_LOG(SSH_LOG_WARNING, "Failed to pass authenticated data");
+ return;
+ }
+ memcpy(out, in, aadlen);
+
+ /* Encrypt the rest of the data */
+ rc = EVP_EncryptUpdate(cipher->ctx,
+ (unsigned char *)out + aadlen,
+ &outlen,
+ (unsigned char *)in + aadlen,
+ len - aadlen);
+ if (rc != 1 || outlen != len - aadlen) {
+ SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptUpdate failed");
+ return;
+ }
+
+ /* compute tag */
+ rc = EVP_EncryptFinal(cipher->ctx,
+ NULL,
+ &outlen);
+ if (rc < 0) {
+ SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptFinal failed: Failed to create a tag");
+ return;
+ }
+
+ rc = EVP_CIPHER_CTX_ctrl(cipher->ctx,
+ EVP_CTRL_GCM_GET_TAG,
+ authlen,
+ (unsigned char *)tag);
+ if (rc != 1) {
+ SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_GET_TAG failed");
+ return;
+ }
+}
+
+static int
+evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher,
+ void *complete_packet,
+ uint8_t *out,
+ size_t encrypted_size,
+ uint64_t seq)
+{
+ size_t authlen, aadlen;
+ u_char lastiv[1];
+ int outlen = 0;
+ int rc = 0;
+
+ (void)seq;
+
+ aadlen = cipher->lenfield_blocksize;
+ authlen = cipher->tag_size;
+
+ /* increment IV */
+ rc = EVP_CIPHER_CTX_ctrl(cipher->ctx,
+ EVP_CTRL_GCM_IV_GEN,
+ 1,
+ lastiv);
+ if (rc == 0) {
+ SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_IV_GEN failed");
+ return SSH_ERROR;
+ }
+
+ /* set tag for authentication */
+ rc = EVP_CIPHER_CTX_ctrl(cipher->ctx,
+ EVP_CTRL_GCM_SET_TAG,
+ authlen,
+ (unsigned char *)complete_packet + aadlen + encrypted_size);
+ if (rc == 0) {
+ SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_TAG failed");
+ return SSH_ERROR;
+ }
+
+ /* Pass over the authenticated data (not encrypted) */
+ rc = EVP_DecryptUpdate(cipher->ctx,
+ NULL,
+ &outlen,
+ (unsigned char *)complete_packet,
+ aadlen);
+ if (rc == 0) {
+ SSH_LOG(SSH_LOG_WARNING, "Failed to pass authenticated data");
+ return SSH_ERROR;
+ }
+ /* Do not copy the length to the target buffer, because it is already processed */
+ //memcpy(out, complete_packet, aadlen);
+
+ /* Decrypt the rest of the data */
+ rc = EVP_DecryptUpdate(cipher->ctx,
+ (unsigned char *)out,
+ &outlen,
+ (unsigned char *)complete_packet + aadlen,
+ encrypted_size /* already substracted aadlen*/);
+ if (rc != 1) {
+ SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptUpdate failed");
+ return SSH_ERROR;
+ }
+
+ if (outlen != (int)encrypted_size) {
+ SSH_LOG(SSH_LOG_WARNING,
+ "EVP_DecryptUpdate: output size %d for %zd in",
+ outlen,
+ encrypted_size);
+ return SSH_ERROR;
+ }
+
+ /* verify tag */
+ rc = EVP_DecryptFinal(cipher->ctx,
+ NULL,
+ &outlen);
+ if (rc < 0) {
+ SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptFinal failed: Failed authentication");
+ return SSH_ERROR;
+ }
+
+ return SSH_OK;
+}
+
+#endif /* HAVE_OPENSSL_EVP_AES_GCM */
+
/*
* The table of supported ciphers
*/
@@ -766,6 +983,36 @@ static struct ssh_cipher_struct ssh_ciphertab[] = {
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup
},
+#ifdef HAVE_OPENSSL_EVP_AES_GCM
+ {
+ .name = "aes128-gcm@openssh.com",
+ .blocksize = AES_BLOCK_SIZE,
+ .lenfield_blocksize = 4, /* not encrypted, but authenticated */
+ .ciphertype = SSH_AEAD_AES128_GCM,
+ .keysize = 128,
+ .tag_size = AES_GCM_TAGLEN,
+ .set_encrypt_key = evp_cipher_set_encrypt_key,
+ .set_decrypt_key = evp_cipher_set_decrypt_key,
+ .aead_encrypt = evp_cipher_aead_encrypt,
+ .aead_decrypt_length = evp_cipher_aead_get_length,
+ .aead_decrypt = evp_cipher_aead_decrypt,
+ .cleanup = evp_cipher_cleanup
+ },
+ {
+ .name = "aes256-gcm@openssh.com",
+ .blocksize = AES_BLOCK_SIZE,
+ .lenfield_blocksize = 4, /* not encrypted, but authenticated */
+ .ciphertype = SSH_AEAD_AES256_GCM,
+ .keysize = 256,
+ .tag_size = AES_GCM_TAGLEN,
+ .set_encrypt_key = evp_cipher_set_encrypt_key,
+ .set_decrypt_key = evp_cipher_set_decrypt_key,
+ .aead_encrypt = evp_cipher_aead_encrypt,
+ .aead_decrypt_length = evp_cipher_aead_get_length,
+ .aead_decrypt = evp_cipher_aead_decrypt,
+ .cleanup = evp_cipher_cleanup
+ },
+#endif /* HAVE_OPENSSL_EVP_AES_GCM */
#endif /* HAS_AES */
#ifdef HAS_DES
{
diff --git a/src/packet_crypt.c b/src/packet_crypt.c
index 7306e4b3..bdc0e5c8 100644
--- a/src/packet_crypt.c
+++ b/src/packet_crypt.c
@@ -198,8 +198,9 @@ int ssh_packet_hmac_verify(ssh_session session,
unsigned int len;
uint32_t seq;
- /* AEAD type have no mac checking */
- if (type == SSH_HMAC_AEAD_POLY1305) {
+ /* AEAD types have no mac checking */
+ if (type == SSH_HMAC_AEAD_POLY1305 ||
+ type == SSH_HMAC_AEAD_GCM) {
return SSH_OK;
}
diff --git a/src/wrapper.c b/src/wrapper.c
index 354bc0de..f7377b97 100644
--- a/src/wrapper.c
+++ b/src/wrapper.c
@@ -56,6 +56,7 @@ static struct ssh_hmac_struct ssh_hmac_tab[] = {
{ "hmac-sha2-512", SSH_HMAC_SHA512 },
{ "hmac-md5", SSH_HMAC_MD5 },
{ "aead-poly1305", SSH_HMAC_AEAD_POLY1305 },
+ { "aead-gcm", SSH_HMAC_AEAD_GCM },
{ NULL, 0}
};
@@ -77,6 +78,8 @@ size_t hmac_digest_len(enum ssh_hmac_e type) {
return MD5_DIGEST_LEN;
case SSH_HMAC_AEAD_POLY1305:
return POLY1305_TAGLEN;
+ case SSH_HMAC_AEAD_GCM:
+ return AES_GCM_TAGLEN;
default:
return 0;
}
@@ -256,7 +259,11 @@ static int crypt_set_algorithms2(ssh_session session){
if (session->next_crypto->out_cipher->aead_encrypt != NULL){
/* this cipher has integrated MAC */
- wanted = "aead-poly1305";
+ if (session->next_crypto->out_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) {
+ wanted = "aead-poly1305";
+ } else {
+ wanted = "aead-gcm";
+ }
} else {
/*
* We must scan the kex entries to find hmac algorithms and set their
@@ -310,7 +317,11 @@ static int crypt_set_algorithms2(ssh_session session){
if (session->next_crypto->in_cipher->aead_encrypt != NULL){
/* this cipher has integrated MAC */
- wanted = "aead-poly1305";
+ if (session->next_crypto->in_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) {
+ wanted = "aead-poly1305";
+ } else {
+ wanted = "aead-gcm";
+ }
} else {
/* we must scan the kex entries to find hmac algorithms and set their appropriate structure */
wanted = session->next_crypto->kex_methods[SSH_MAC_S_C];
@@ -398,7 +409,11 @@ int crypt_set_algorithms_server(ssh_session session){
i=0;
if (session->next_crypto->out_cipher->aead_encrypt != NULL){
/* this cipher has integrated MAC */
- method = "aead-poly1305";
+ if (session->next_crypto->out_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) {
+ method = "aead-poly1305";
+ } else {
+ method = "aead-gcm";
+ }
} else {
/* we must scan the kex entries to find hmac algorithms and set their appropriate structure */
/* out */
@@ -449,7 +464,11 @@ int crypt_set_algorithms_server(ssh_session session){
if (session->next_crypto->in_cipher->aead_encrypt != NULL){
/* this cipher has integrated MAC */
- method = "aead-poly1305";
+ if (session->next_crypto->in_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) {
+ method = "aead-poly1305";
+ } else {
+ method = "aead-gcm";
+ }
} else {
/* we must scan the kex entries to find hmac algorithms and set their appropriate structure */
method = session->next_crypto->kex_methods[SSH_MAC_C_S];