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>
Этот коммит содержится в:
родитель
777786d76c
Коммит
46090facba
@ -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 ""
|
||||
|
247
src/libcrypto.c
247
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
|
||||
{
|
||||
|
@ -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];
|
||||
|
Загрузка…
Ссылка в новой задаче
Block a user