1
1

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>
Этот коммит содержится в:
Jakub Jelen 2018-10-08 13:24:49 +02:00 коммит произвёл Andreas Schneider
родитель 777786d76c
Коммит 46090facba
9 изменённых файлов: 297 добавлений и 10 удалений

Просмотреть файл

@ -108,6 +108,10 @@ if (OPENSSL_FOUND)
set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY})
check_function_exists(EVP_aes_128_cbc HAVE_OPENSSL_EVP_AES_CBC)
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)

Просмотреть файл

@ -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

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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 ""

Просмотреть файл

@ -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
{

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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];