diff options
-rw-r--r-- | ConfigureChecks.cmake | 4 | ||||
-rw-r--r-- | config.h.cmake | 3 | ||||
-rw-r--r-- | include/libssh/crypto.h | 8 | ||||
-rw-r--r-- | include/libssh/wrapper.h | 3 | ||||
-rw-r--r-- | src/chachapoly.c | 1 | ||||
-rw-r--r-- | src/kex.c | 9 | ||||
-rw-r--r-- | src/libcrypto.c | 247 | ||||
-rw-r--r-- | src/packet_crypt.c | 5 | ||||
-rw-r--r-- | src/wrapper.c | 27 |
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, @@ -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]; |