mirror of
https://github.com/libssh2/libssh2.git
synced 2025-08-05 20:55:47 +03:00
Add support for AES-GCM crypto protocols (#797)
Add support for aes256-gcm@openssh.com and aes128-gcm@openssh.com ciphers, which are the OpenSSH implementations of AES-GCM cryptography. It is similar to RFC5647 but has changes to the MAC protocol negotiation. These are implemented for recent versions of OpenSSL only. The ciphers work differently than most previous ones in two big areas: the cipher includes its own integrated MAC, and the packet length field in the SSH frame is left unencrypted. The code changes necessary are gated by flags in the LIBSSH2_CRYPT_METHOD configuration structure. These differences mean that both the first and last parts of a block require special handling during encryption. The first part is where the packet length field is, which must be kept out of the encryption path but in the authenticated part (as AAD). The last part is where the Authentication Tag is found, which is calculated and appended during encryption or removed and validated on decryption. As encryption/ decryption is performed on each packet in a loop, one block at a time, flags indicating when the first and last blocks are being processed are passed down to the encryption layers. The strict block-by-block encryption that occurs with other protocols is inappropriate for AES-GCM, since the packet length shifts the first encrypted byte 4 bytes into the block. Additionally, the final part of the block must contain the AES-GCM's Authentication Tag, so it must be presented to the lower encryption layer whole. These requirements mean added code to consolidate blocks as they are passed down. When AES-GCM is negotiated as the cipher, its built-in MAC is automatically used as the SSH MAC so further MAC negotiation is not necessary. The SSH negotiation is skipped when _libssh2_mac_override() indicates that such a cipher is in use. The virtual MAC configuration block mac_method_hmac_aesgcm is then used as the MAC placeholder. This work was sponsored by Anders Borum. Integration-patches-by: Viktor Szakats * fix checksrc errors * fix openssl.c warning * fix transport.c warnings * switch to `LIBSSH2_MIN/MAX()` from `MIN()`/`MAX()` * fix indent * fix libgcrypt unused warning * fix mbedtls unused warning * fix wincng unused warning * fix old openssl unused variable warnings * delete blank lines * updates to help merging with the ETM patch
This commit is contained in:
@@ -323,7 +323,8 @@ int _libssh2_cipher_crypt(_libssh2_cipher_ctx *ctx,
|
||||
_libssh2_cipher_type(algo),
|
||||
int encrypt,
|
||||
unsigned char *block,
|
||||
size_t blocksize);
|
||||
size_t blocksize,
|
||||
int firstlast);
|
||||
Encrypt or decrypt in-place data at (block, blocksize) using the given
|
||||
context and/or algorithm.
|
||||
Return 0 if OK, else -1.
|
||||
|
41
src/crypt.c
41
src/crypt.c
@@ -54,7 +54,7 @@
|
||||
*/
|
||||
static int
|
||||
crypt_none_crypt(LIBSSH2_SESSION * session, unsigned char *buf,
|
||||
void **abstract)
|
||||
void **abstract, int firstlast)
|
||||
{
|
||||
/* Do nothing to the data! */
|
||||
return 0;
|
||||
@@ -106,12 +106,12 @@ crypt_init(LIBSSH2_SESSION * session,
|
||||
|
||||
static int
|
||||
crypt_encrypt(LIBSSH2_SESSION * session, unsigned char *block,
|
||||
size_t blocksize, void **abstract)
|
||||
size_t blocksize, void **abstract, int firstlast)
|
||||
{
|
||||
struct crypt_ctx *cctx = *(struct crypt_ctx **) abstract;
|
||||
(void) session;
|
||||
return _libssh2_cipher_crypt(&cctx->h, cctx->algo, cctx->encrypt, block,
|
||||
blocksize);
|
||||
blocksize, firstlast);
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -126,6 +126,34 @@ crypt_dtor(LIBSSH2_SESSION * session, void **abstract)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if LIBSSH2_AES_GCM
|
||||
static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes256_gcm = {
|
||||
"aes256-gcm@openssh.com",
|
||||
"",
|
||||
16, /* blocksize */
|
||||
12, /* initial value length */
|
||||
32, /* secret length -- 32*8 == 256bit */
|
||||
LIBSSH2_CRYPT_FLAG_INTEGRATED_MAC | LIBSSH2_CRYPT_FLAG_PKTLEN_AAD,
|
||||
&crypt_init,
|
||||
&crypt_encrypt,
|
||||
&crypt_dtor,
|
||||
_libssh2_cipher_aes256gcm
|
||||
};
|
||||
|
||||
static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes128_gcm = {
|
||||
"aes128-gcm@openssh.com",
|
||||
"",
|
||||
16, /* blocksize */
|
||||
12, /* initial value length */
|
||||
16, /* secret length -- 16*8 == 128bit */
|
||||
LIBSSH2_CRYPT_FLAG_INTEGRATED_MAC | LIBSSH2_CRYPT_FLAG_PKTLEN_AAD,
|
||||
&crypt_init,
|
||||
&crypt_encrypt,
|
||||
&crypt_dtor,
|
||||
_libssh2_cipher_aes128gcm
|
||||
};
|
||||
#endif
|
||||
|
||||
#if LIBSSH2_AES_CTR
|
||||
static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_aes128_ctr = {
|
||||
"aes128-ctr",
|
||||
@@ -269,7 +297,8 @@ crypt_init_arcfour128(LIBSSH2_SESSION * session,
|
||||
size_t discard = 1536;
|
||||
for(; discard; discard -= 8)
|
||||
_libssh2_cipher_crypt(&cctx->h, cctx->algo, cctx->encrypt, block,
|
||||
method->blocksize);
|
||||
method->blocksize, MIDDLE_BLOCK);
|
||||
/* Not all middle, but here it doesn't matter */
|
||||
}
|
||||
|
||||
return rc;
|
||||
@@ -322,6 +351,10 @@ static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_3des_cbc = {
|
||||
/* These are the crypt methods that are available to be negotiated. Methods
|
||||
towards the start are chosen in preference to ones further down the list. */
|
||||
static const LIBSSH2_CRYPT_METHOD *_libssh2_crypt_methods[] = {
|
||||
#if LIBSSH2_AES_GCM
|
||||
&libssh2_crypt_method_aes256_gcm,
|
||||
&libssh2_crypt_method_aes128_gcm,
|
||||
#endif /* LIBSSH2_AES_GCM */
|
||||
#if LIBSSH2_AES_CTR
|
||||
&libssh2_crypt_method_aes256_ctr,
|
||||
&libssh2_crypt_method_aes192_ctr,
|
||||
|
@@ -332,7 +332,8 @@ int _libssh2_cipher_init(_libssh2_cipher_ctx * h,
|
||||
|
||||
int _libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx,
|
||||
_libssh2_cipher_type(algo),
|
||||
int encrypt, unsigned char *block, size_t blocksize);
|
||||
int encrypt, unsigned char *block, size_t blocksize,
|
||||
int firstlast);
|
||||
|
||||
int _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session,
|
||||
unsigned char **method,
|
||||
|
11
src/kex.c
11
src/kex.c
@@ -3582,9 +3582,18 @@ static int kex_agree_mac(LIBSSH2_SESSION * session,
|
||||
size_t mac_len)
|
||||
{
|
||||
const LIBSSH2_MAC_METHOD **macp = _libssh2_mac_methods();
|
||||
const LIBSSH2_MAC_METHOD *override;
|
||||
unsigned char *s;
|
||||
(void)session;
|
||||
|
||||
override = _libssh2_mac_override(endpoint->crypt);
|
||||
if(override) {
|
||||
/* This crypto method has its own hmac method built-in, so a separate
|
||||
* negotiation (and use) of a separate hmac method is unnecessary */
|
||||
endpoint->mac = override;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(endpoint->mac_prefs) {
|
||||
s = (unsigned char *) endpoint->mac_prefs;
|
||||
|
||||
@@ -3747,6 +3756,8 @@ static int kex_agree_methods(LIBSSH2_SESSION * session, unsigned char *data,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* This must happen after kex_agree_crypt since some MACs depend on the
|
||||
negotiated crypto method */
|
||||
if(kex_agree_mac(session, &session->local, mac_cs, mac_cs_len) ||
|
||||
kex_agree_mac(session, &session->remote, mac_sc, mac_sc_len)) {
|
||||
return -1;
|
||||
|
@@ -607,11 +607,13 @@ _libssh2_cipher_init(_libssh2_cipher_ctx * h,
|
||||
int
|
||||
_libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx,
|
||||
_libssh2_cipher_type(algo),
|
||||
int encrypt, unsigned char *block, size_t blklen)
|
||||
int encrypt, unsigned char *block, size_t blklen,
|
||||
int firstlast)
|
||||
{
|
||||
int ret;
|
||||
|
||||
(void)algo;
|
||||
(void)firstlast;
|
||||
|
||||
if(encrypt) {
|
||||
ret = gcry_cipher_encrypt(*ctx, block, blklen, block, blklen);
|
||||
|
@@ -51,6 +51,7 @@
|
||||
|
||||
#define LIBSSH2_AES 1
|
||||
#define LIBSSH2_AES_CTR 1
|
||||
#define LIBSSH2_AES_GCM 0
|
||||
#define LIBSSH2_BLOWFISH 1
|
||||
#define LIBSSH2_RC4 1
|
||||
#define LIBSSH2_CAST 1
|
||||
|
@@ -948,12 +948,36 @@ struct _LIBSSH2_CRYPT_METHOD
|
||||
int *free_iv, unsigned char *secret, int *free_secret,
|
||||
int encrypt, void **abstract);
|
||||
int (*crypt) (LIBSSH2_SESSION * session, unsigned char *block,
|
||||
size_t blocksize, void **abstract);
|
||||
size_t blocksize, void **abstract, int firstlast);
|
||||
int (*dtor) (LIBSSH2_SESSION * session, void **abstract);
|
||||
|
||||
_libssh2_cipher_type(algo);
|
||||
};
|
||||
|
||||
/* Bit flags for _LIBSSH2_CRYPT_METHOD */
|
||||
|
||||
/* Crypto method has integrated message authentication */
|
||||
#define LIBSSH2_CRYPT_FLAG_INTEGRATED_MAC 1
|
||||
/* Crypto method does not encrypt the packet length */
|
||||
#define LIBSSH2_CRYPT_FLAG_PKTLEN_AAD 2
|
||||
|
||||
/* Convenience macros for accessing crypt flags */
|
||||
/* Local crypto flags */
|
||||
#define CRYPT_FLAG_L(session, flag) ((session)->local.crypt && \
|
||||
((session)->local.crypt->flags & LIBSSH2_CRYPT_FLAG_##flag))
|
||||
/* Remote crypto flags */
|
||||
#define CRYPT_FLAG_R(session, flag) ((session)->remote.crypt && \
|
||||
((session)->remote.crypt->flags & LIBSSH2_CRYPT_FLAG_##flag))
|
||||
|
||||
/* Values for firstlast */
|
||||
#define FIRST_BLOCK 1
|
||||
#define MIDDLE_BLOCK 0
|
||||
#define LAST_BLOCK 2
|
||||
|
||||
/* Convenience macros for accessing firstlast */
|
||||
#define IS_FIRST(firstlast) (firstlast & FIRST_BLOCK)
|
||||
#define IS_LAST(firstlast) (firstlast & LAST_BLOCK)
|
||||
|
||||
struct _LIBSSH2_COMP_METHOD
|
||||
{
|
||||
const char *name;
|
||||
|
29
src/mac.c
29
src/mac.c
@@ -423,3 +423,32 @@ _libssh2_mac_methods(void)
|
||||
{
|
||||
return mac_methods;
|
||||
}
|
||||
|
||||
#if LIBSSH2_AES_GCM
|
||||
/* Stub for aes256-gcm@openssh.com crypto type, which has an integrated
|
||||
HMAC method. This must not be added to mac_methods[] since it cannot be
|
||||
negotiated separately. */
|
||||
static const LIBSSH2_MAC_METHOD mac_method_hmac_aesgcm = {
|
||||
"INTEGRATED-AES-GCM", /* made up name for display only */
|
||||
16,
|
||||
16,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
#endif /* LIBSSH2_AES_GCM */
|
||||
|
||||
/* See if the negotiated crypto method has its own authentication scheme that
|
||||
* obviates the need for a separate negotiated hmac method */
|
||||
const LIBSSH2_MAC_METHOD *
|
||||
_libssh2_mac_override(const LIBSSH2_CRYPT_METHOD *crypt)
|
||||
{
|
||||
#if LIBSSH2_AES_GCM
|
||||
if(!strcmp(crypt->name, "aes256-gcm@openssh.com") ||
|
||||
!strcmp(crypt->name, "aes128-gcm@openssh.com"))
|
||||
return &mac_method_hmac_aesgcm;
|
||||
#else
|
||||
(void) crypt;
|
||||
#endif /* LIBSSH2_AES_GCM */
|
||||
return NULL;
|
||||
}
|
||||
|
@@ -62,5 +62,7 @@ struct _LIBSSH2_MAC_METHOD
|
||||
typedef struct _LIBSSH2_MAC_METHOD LIBSSH2_MAC_METHOD;
|
||||
|
||||
const LIBSSH2_MAC_METHOD **_libssh2_mac_methods(void);
|
||||
const LIBSSH2_MAC_METHOD *_libssh2_mac_override(
|
||||
const LIBSSH2_CRYPT_METHOD *crypt);
|
||||
|
||||
#endif /* __LIBSSH2_MAC_H */
|
||||
|
@@ -139,7 +139,7 @@ _libssh2_mbedtls_cipher_crypt(_libssh2_cipher_ctx *ctx,
|
||||
_libssh2_cipher_type(algo),
|
||||
int encrypt,
|
||||
unsigned char *block,
|
||||
size_t blocklen)
|
||||
size_t blocklen, int firstlast)
|
||||
{
|
||||
int ret;
|
||||
unsigned char *output;
|
||||
@@ -147,6 +147,7 @@ _libssh2_mbedtls_cipher_crypt(_libssh2_cipher_ctx *ctx,
|
||||
|
||||
(void)encrypt;
|
||||
(void)algo;
|
||||
(void)firstlast;
|
||||
|
||||
osize = blocklen + mbedtls_cipher_get_block_size(ctx);
|
||||
|
||||
|
@@ -67,6 +67,7 @@
|
||||
|
||||
#define LIBSSH2_AES 1
|
||||
#define LIBSSH2_AES_CTR 1
|
||||
#define LIBSSH2_AES_GCM 0
|
||||
#ifdef MBEDTLS_CIPHER_BLOWFISH_CBC
|
||||
# define LIBSSH2_BLOWFISH 1
|
||||
#else
|
||||
@@ -390,8 +391,8 @@ typedef enum {
|
||||
|
||||
#define _libssh2_cipher_init(ctx, type, iv, secret, encrypt) \
|
||||
_libssh2_mbedtls_cipher_init(ctx, type, iv, secret, encrypt)
|
||||
#define _libssh2_cipher_crypt(ctx, type, encrypt, block, blocklen) \
|
||||
_libssh2_mbedtls_cipher_crypt(ctx, type, encrypt, block, blocklen)
|
||||
#define _libssh2_cipher_crypt(ctx, type, encrypt, block, blocklen, fl) \
|
||||
_libssh2_mbedtls_cipher_crypt(ctx, type, encrypt, block, blocklen, fl)
|
||||
#define _libssh2_cipher_dtor(ctx) \
|
||||
_libssh2_mbedtls_cipher_dtor(ctx)
|
||||
|
||||
@@ -472,7 +473,7 @@ _libssh2_mbedtls_cipher_crypt(_libssh2_cipher_ctx *ctx,
|
||||
_libssh2_cipher_type(type),
|
||||
int encrypt,
|
||||
unsigned char *block,
|
||||
size_t blocklen);
|
||||
size_t blocklen, int firstlast);
|
||||
void
|
||||
_libssh2_mbedtls_cipher_dtor(_libssh2_cipher_ctx *ctx);
|
||||
|
||||
|
121
src/openssl.c
121
src/openssl.c
@@ -40,6 +40,7 @@
|
||||
|
||||
#ifdef LIBSSH2_CRYPTO_C /* Compile this via crypto.c */
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef EVP_MAX_BLOCK_LENGTH
|
||||
@@ -481,9 +482,26 @@ _libssh2_cipher_init(_libssh2_cipher_ctx * h,
|
||||
unsigned char *iv, unsigned char *secret, int encrypt)
|
||||
{
|
||||
#ifdef HAVE_OPAQUE_STRUCTS
|
||||
#if LIBSSH2_AES_GCM
|
||||
const int is_aesgcm = (algo == EVP_aes_128_gcm) ||
|
||||
(algo == EVP_aes_256_gcm);
|
||||
#endif /* LIBSSH2_AES_GCM */
|
||||
int rc;
|
||||
|
||||
*h = EVP_CIPHER_CTX_new();
|
||||
return !EVP_CipherInit(*h, algo(), secret, iv, encrypt);
|
||||
rc = !EVP_CipherInit(*h, algo(), secret, iv, encrypt);
|
||||
#if LIBSSH2_AES_GCM
|
||||
if(is_aesgcm) {
|
||||
/* Sets both fixed and invocation_counter parts of IV */
|
||||
rc |= !EVP_CIPHER_CTX_ctrl(*h, EVP_CTRL_AEAD_SET_IV_FIXED, -1, iv);
|
||||
}
|
||||
#endif /* LIBSSH2_AES_GCM */
|
||||
|
||||
return rc;
|
||||
#else
|
||||
# if LIBSSH2_AES_GCM
|
||||
# error AES-GCM is only supported with opaque structs in use
|
||||
# endif /* LIBSSH2_AES_GCM */
|
||||
EVP_CIPHER_CTX_init(h);
|
||||
return !EVP_CipherInit(h, algo(), secret, iv, encrypt);
|
||||
#endif
|
||||
@@ -492,32 +510,113 @@ _libssh2_cipher_init(_libssh2_cipher_ctx * h,
|
||||
int
|
||||
_libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx,
|
||||
_libssh2_cipher_type(algo),
|
||||
int encrypt, unsigned char *block, size_t blocksize)
|
||||
int encrypt, unsigned char *block, size_t blocksize,
|
||||
int firstlast)
|
||||
{
|
||||
unsigned char buf[EVP_MAX_BLOCK_LENGTH];
|
||||
int ret;
|
||||
int ret = 1;
|
||||
int rc = 1;
|
||||
|
||||
(void)algo;
|
||||
(void)encrypt;
|
||||
|
||||
#ifdef HAVE_OPAQUE_STRUCTS
|
||||
ret = EVP_Cipher(*ctx, buf, block, (unsigned int) blocksize);
|
||||
#if LIBSSH2_AES_GCM
|
||||
const int is_aesgcm = (algo == EVP_aes_128_gcm) ||
|
||||
(algo == EVP_aes_256_gcm);
|
||||
char lastiv[1];
|
||||
#else
|
||||
ret = EVP_Cipher(ctx, buf, block, (unsigned int) blocksize);
|
||||
const int is_aesgcm = 0;
|
||||
#endif /* LIBSSH2_AES_GCM */
|
||||
/* length of AES-GCM Authentication Tag */
|
||||
const int authlen = is_aesgcm ? 16 : 0;
|
||||
/* length of AAD, only on the first block */
|
||||
const int aadlen = (is_aesgcm && IS_FIRST(firstlast)) ? 4 : 0;
|
||||
/* size of AT, if present */
|
||||
const int authenticationtag = IS_LAST(firstlast) ? authlen : 0;
|
||||
/* length to encrypt */
|
||||
const int cryptlen = (unsigned int)blocksize - aadlen - authenticationtag;
|
||||
|
||||
(void)algo;
|
||||
|
||||
assert(blocksize <= sizeof(buf));
|
||||
assert(cryptlen >= 0);
|
||||
|
||||
#if LIBSSH2_AES_GCM
|
||||
/* First block */
|
||||
if(IS_FIRST(firstlast)) {
|
||||
/* Increments invocation_counter portion of IV */
|
||||
if(is_aesgcm) {
|
||||
ret = EVP_CIPHER_CTX_ctrl(*ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv);
|
||||
}
|
||||
|
||||
if(aadlen) {
|
||||
/* Include the 4 byte packet length as AAD */
|
||||
ret = EVP_Cipher(*ctx, NULL, block, aadlen);
|
||||
}
|
||||
}
|
||||
|
||||
/* Last portion of block to encrypt/decrypt */
|
||||
if(IS_LAST(firstlast)) {
|
||||
if(is_aesgcm && !encrypt) {
|
||||
/* set tag on decryption */
|
||||
ret = EVP_CIPHER_CTX_ctrl(*ctx, EVP_CTRL_GCM_SET_TAG, authlen,
|
||||
block + blocksize - authlen);
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)encrypt;
|
||||
(void)firstlast;
|
||||
#endif /* LIBSSH2_AES_GCM */
|
||||
|
||||
if(cryptlen > 0) {
|
||||
#ifdef HAVE_OPAQUE_STRUCTS
|
||||
ret = EVP_Cipher(*ctx, buf + aadlen, block + aadlen, cryptlen);
|
||||
#else
|
||||
ret = EVP_Cipher(ctx, buf + aadlen, block + aadlen, cryptlen);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if (defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3) || \
|
||||
defined(LIBSSH2_WOLFSSL)
|
||||
if(ret != -1)
|
||||
#else
|
||||
if(ret == 1)
|
||||
if(ret >= 1)
|
||||
#endif
|
||||
{
|
||||
rc = 0;
|
||||
memcpy(block, buf, blocksize);
|
||||
if(IS_LAST(firstlast)) {
|
||||
/* This is the last block.
|
||||
encrypt: compute tag, if applicable
|
||||
decrypt: verify tag, if applicable
|
||||
in!=NULL is equivalent to EVP_CipherUpdate
|
||||
in==NULL is equivalent to EVP_CipherFinal */
|
||||
#ifdef HAVE_OPAQUE_STRUCTS
|
||||
ret = EVP_Cipher(*ctx, NULL, NULL, 0); /* final */
|
||||
#else
|
||||
ret = EVP_Cipher(ctx, NULL, NULL, 0); /* final */
|
||||
#endif
|
||||
if(ret < 0) {
|
||||
ret = 0;
|
||||
}
|
||||
else {
|
||||
ret = 1;
|
||||
#if LIBSSH2_AES_GCM
|
||||
if(is_aesgcm && encrypt) {
|
||||
/* write the Authentication Tag a.k.a. MAC at the end
|
||||
of the block */
|
||||
assert(authenticationtag == authlen);
|
||||
ret = EVP_CIPHER_CTX_ctrl(*ctx, EVP_CTRL_GCM_GET_TAG,
|
||||
authlen, block + blocksize - authenticationtag);
|
||||
}
|
||||
#endif /* LIBSSH2_AES_GCM */
|
||||
}
|
||||
}
|
||||
/* Copy en/decrypted data back to the caller.
|
||||
The first aadlen should not be touched because they weren't
|
||||
encrypted and are unmodified. */
|
||||
memcpy(block + aadlen, buf + aadlen, cryptlen);
|
||||
rc = !ret;
|
||||
}
|
||||
|
||||
/* TODO: the return code should distinguish between decryption errors and
|
||||
invalid MACs */
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
@@ -159,6 +159,12 @@
|
||||
# define LIBSSH2_AES 0
|
||||
#endif
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x01010100fL && !defined(OPENSSL_NO_AES))
|
||||
# define LIBSSH2_AES_GCM 1
|
||||
#else
|
||||
# define LIBSSH2_AES_GCM 0
|
||||
#endif
|
||||
|
||||
#ifdef OPENSSL_NO_BF
|
||||
# define LIBSSH2_BLOWFISH 0
|
||||
#else
|
||||
@@ -395,6 +401,9 @@ libssh2_curve_type;
|
||||
#define _libssh2_cipher_ctx EVP_CIPHER_CTX
|
||||
#endif
|
||||
|
||||
#define _libssh2_cipher_aes256gcm EVP_aes_256_gcm
|
||||
#define _libssh2_cipher_aes128gcm EVP_aes_128_gcm
|
||||
|
||||
#define _libssh2_cipher_aes256 EVP_aes_256_cbc
|
||||
#define _libssh2_cipher_aes192 EVP_aes_192_cbc
|
||||
#define _libssh2_cipher_aes128 EVP_aes_128_cbc
|
||||
|
@@ -1097,7 +1097,8 @@ _libssh2_cipher_init(_libssh2_cipher_ctx *h, _libssh2_cipher_type(algo),
|
||||
int
|
||||
_libssh2_cipher_crypt(_libssh2_cipher_ctx *ctx,
|
||||
_libssh2_cipher_type(algo),
|
||||
int encrypt, unsigned char *block, size_t blocksize)
|
||||
int encrypt, unsigned char *block, size_t blocksize,
|
||||
int firstlast)
|
||||
{
|
||||
Qus_EC_t errcode;
|
||||
int outlen;
|
||||
|
@@ -171,6 +171,7 @@
|
||||
|
||||
#define LIBSSH2_AES 1
|
||||
#define LIBSSH2_AES_CTR 1
|
||||
#define LIBSSH2_AES_GCM 0
|
||||
#define LIBSSH2_BLOWFISH 0
|
||||
#define LIBSSH2_RC4 1
|
||||
#define LIBSSH2_CAST 0
|
||||
|
12
src/pem.c
12
src/pem.c
@@ -260,7 +260,11 @@ _libssh2_pem_parse(LIBSSH2_SESSION * session,
|
||||
|
||||
while(len_decrypted <= (int)*datalen - blocksize) {
|
||||
if(method->crypt(session, *data + len_decrypted, blocksize,
|
||||
&abstract)) {
|
||||
&abstract,
|
||||
len_decrypted == 0 ? FIRST_BLOCK :
|
||||
((len_decrypted == (int)*datalen - blocksize) ?
|
||||
LAST_BLOCK : MIDDLE_BLOCK)
|
||||
)) {
|
||||
ret = LIBSSH2_ERROR_DECRYPT;
|
||||
_libssh2_explicit_zero((char *)secret, sizeof(secret));
|
||||
method->dtor(session, &abstract);
|
||||
@@ -589,7 +593,11 @@ _libssh2_openssh_pem_parse_data(LIBSSH2_SESSION * session,
|
||||
while((size_t)len_decrypted <= decrypted.len - blocksize) {
|
||||
if(method->crypt(session, decrypted.data + len_decrypted,
|
||||
blocksize,
|
||||
&abstract)) {
|
||||
&abstract,
|
||||
len_decrypted == 0 ? FIRST_BLOCK : (
|
||||
((size_t)len_decrypted == decrypted.len - blocksize) ?
|
||||
LAST_BLOCK : MIDDLE_BLOCK)
|
||||
)) {
|
||||
ret = LIBSSH2_ERROR_DECRYPT;
|
||||
method->dtor(session, &abstract);
|
||||
goto out;
|
||||
|
141
src/transport.c
141
src/transport.c
@@ -130,18 +130,38 @@ debugdump(LIBSSH2_SESSION * session,
|
||||
|
||||
static int
|
||||
decrypt(LIBSSH2_SESSION * session, unsigned char *source,
|
||||
unsigned char *dest, ssize_t len)
|
||||
unsigned char *dest, ssize_t len, int firstlast)
|
||||
{
|
||||
struct transportpacket *p = &session->packet;
|
||||
int blocksize = session->remote.crypt->blocksize;
|
||||
|
||||
/* if we get called with a len that isn't an even number of blocksizes
|
||||
we risk losing those extra bytes */
|
||||
we risk losing those extra bytes. AAD is an exception, since those first
|
||||
few bytes aren't encrypted so it throws off the rest of the count. */
|
||||
if(!CRYPT_FLAG_L(session, PKTLEN_AAD))
|
||||
assert((len % blocksize) == 0);
|
||||
|
||||
while(len >= blocksize) {
|
||||
if(session->remote.crypt->crypt(session, source, blocksize,
|
||||
&session->remote.crypt_abstract)) {
|
||||
while(len > 0) {
|
||||
/* normally decrypt up to blocksize bytes at a time */
|
||||
ssize_t decryptlen = LIBSSH2_MIN(blocksize, len);
|
||||
/* The first block is special (since it needs to be decoded to get the
|
||||
length of the remainder of the block) and takes priority. When the
|
||||
length finally gets to the last blocksize bytes, and there's no
|
||||
more data to come, it's the end. */
|
||||
int lowerfirstlast = IS_FIRST(firstlast) ? FIRST_BLOCK :
|
||||
((len <= blocksize) ? firstlast : MIDDLE_BLOCK);
|
||||
/* If the last block would be less than a whole blocksize, combine it
|
||||
with the previous block to make it larger. This ensures that the
|
||||
whole MAC is included in a single decrypt call. */
|
||||
if(CRYPT_FLAG_L(session, PKTLEN_AAD) && IS_LAST(firstlast)
|
||||
&& (len < blocksize*2)) {
|
||||
decryptlen = len;
|
||||
lowerfirstlast = LAST_BLOCK;
|
||||
}
|
||||
|
||||
if(session->remote.crypt->crypt(session, source, decryptlen,
|
||||
&session->remote.crypt_abstract,
|
||||
lowerfirstlast)) {
|
||||
LIBSSH2_FREE(session, p->payload);
|
||||
return LIBSSH2_ERROR_DECRYPT;
|
||||
}
|
||||
@@ -149,11 +169,11 @@ decrypt(LIBSSH2_SESSION * session, unsigned char *source,
|
||||
/* if the crypt() function would write to a given address it
|
||||
wouldn't have to memcpy() and we could avoid this memcpy()
|
||||
too */
|
||||
memcpy(dest, source, blocksize);
|
||||
memcpy(dest, source, decryptlen);
|
||||
|
||||
len -= blocksize; /* less bytes left */
|
||||
dest += blocksize; /* advance write pointer */
|
||||
source += blocksize; /* advance read pointer */
|
||||
len -= decryptlen; /* less bytes left */
|
||||
dest += decryptlen; /* advance write pointer */
|
||||
source += decryptlen; /* advance read pointer */
|
||||
}
|
||||
return LIBSSH2_ERROR_NONE; /* all is fine */
|
||||
}
|
||||
@@ -174,7 +194,7 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ )
|
||||
session->fullpacket_macstate = LIBSSH2_MAC_CONFIRMED;
|
||||
session->fullpacket_payload_len = p->packet_length - 1;
|
||||
|
||||
if(encrypted) {
|
||||
if(encrypted && !CRYPT_FLAG_L(session, INTEGRATED_MAC)) {
|
||||
|
||||
/* Calculate MAC hash */
|
||||
session->remote.mac->hash(session, macbuf, /* store hash here */
|
||||
@@ -286,6 +306,7 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session)
|
||||
int blocksize; /* minimum number of bytes we need before we can
|
||||
use them */
|
||||
int encrypted = 1; /* whether the packet is encrypted or not */
|
||||
int firstlast = FIRST_BLOCK; /* if the first or last block to decrypt */
|
||||
|
||||
/* default clear the bit */
|
||||
session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_INBOUND;
|
||||
@@ -423,12 +444,15 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session)
|
||||
}
|
||||
|
||||
if(encrypted) {
|
||||
rc = decrypt(session, &p->buf[p->readidx], block, blocksize);
|
||||
/* first decrypted block */
|
||||
rc = decrypt(session, &p->buf[p->readidx],
|
||||
block, blocksize, FIRST_BLOCK);
|
||||
if(rc != LIBSSH2_ERROR_NONE) {
|
||||
return rc;
|
||||
}
|
||||
/* save the first 5 bytes of the decrypted package, to be
|
||||
used in the hash calculation later down. */
|
||||
/* Save the first 5 bytes of the decrypted package, to be
|
||||
used in the hash calculation later down.
|
||||
This is ignored in the INTEGRATED_MAC case. */
|
||||
memcpy(p->init, block, 5);
|
||||
}
|
||||
else {
|
||||
@@ -451,12 +475,15 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session)
|
||||
return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
|
||||
}
|
||||
|
||||
/* padding_length has not been authenticated yet, but it won't
|
||||
actually be used (except for the sanity check immediately
|
||||
following) until after the entire packet is authenticated,
|
||||
so this is safe. */
|
||||
p->padding_length = block[4];
|
||||
if(p->padding_length > p->packet_length - 1) {
|
||||
return LIBSSH2_ERROR_DECRYPT;
|
||||
}
|
||||
|
||||
|
||||
/* total_num is the number of bytes following the initial
|
||||
(5 bytes) packet length and padding length fields */
|
||||
total_num = p->packet_length - 1 +
|
||||
@@ -524,35 +551,53 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session)
|
||||
since it is used for the hash later on. */
|
||||
int skip = session->remote.mac->mac_len;
|
||||
|
||||
if(CRYPT_FLAG_R(session, INTEGRATED_MAC))
|
||||
/* This crypto method DOES need the MAC to go through
|
||||
decryption so it can be authenticated. */
|
||||
skip = 0;
|
||||
|
||||
/* if what we have plus numbytes is bigger than the
|
||||
total minus the skip margin, we should lower the
|
||||
amount to decrypt even more */
|
||||
if((p->data_num + numbytes) > (p->total_num - skip)) {
|
||||
numdecrypt = (p->total_num - skip) - p->data_num;
|
||||
if((p->data_num + numbytes) >= (p->total_num - skip)) {
|
||||
/* decrypt the entire rest of the package */
|
||||
numdecrypt = LIBSSH2_MAX(0,
|
||||
(int)(p->total_num - skip) - (int)p->data_num);
|
||||
firstlast = LAST_BLOCK;
|
||||
}
|
||||
else {
|
||||
ssize_t frac;
|
||||
numdecrypt = numbytes;
|
||||
frac = numdecrypt % blocksize;
|
||||
if(frac) {
|
||||
/* not an aligned amount of blocks,
|
||||
align it */
|
||||
/* not an aligned amount of blocks, align it by reducing
|
||||
the number of bytes processed this loop */
|
||||
numdecrypt -= frac;
|
||||
/* and make it no unencrypted data
|
||||
after it */
|
||||
numbytes = 0;
|
||||
}
|
||||
if(CRYPT_FLAG_R(session, INTEGRATED_MAC)) {
|
||||
/* Make sure that we save enough bytes to make the last
|
||||
* block large enough to hold the entire integrated MAC */
|
||||
numdecrypt = LIBSSH2_MIN(numdecrypt,
|
||||
(int)(p->total_num - skip - blocksize - p->data_num));
|
||||
numbytes = 0;
|
||||
}
|
||||
firstlast = MIDDLE_BLOCK;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* unencrypted data should not be decrypted at all */
|
||||
numdecrypt = 0;
|
||||
}
|
||||
assert(numdecrypt >= 0);
|
||||
|
||||
/* if there are bytes to decrypt, do that */
|
||||
if(numdecrypt > 0) {
|
||||
/* now decrypt the lot */
|
||||
rc = decrypt(session, &p->buf[p->readidx], p->wptr, numdecrypt);
|
||||
rc = decrypt(session, &p->buf[p->readidx], p->wptr, numdecrypt,
|
||||
firstlast);
|
||||
if(rc != LIBSSH2_ERROR_NONE) {
|
||||
p->total_num = 0; /* no packet buffer available */
|
||||
return rc;
|
||||
@@ -731,6 +776,7 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session,
|
||||
int rc;
|
||||
const unsigned char *orgdata = data;
|
||||
size_t orgdata_len = data_len;
|
||||
size_t crypt_offset;
|
||||
|
||||
/*
|
||||
* If the last read operation was interrupted in the middle of a key
|
||||
@@ -829,12 +875,14 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session,
|
||||
|
||||
packet_length = data_len + 1 + 4; /* 1 is for padding_length field
|
||||
4 for the packet_length field */
|
||||
/* subtract 4 bytes of the packet_length field when padding AES-GCM */
|
||||
crypt_offset = (encrypted && CRYPT_FLAG_R(session, PKTLEN_AAD)) ? 4 : 0;
|
||||
|
||||
/* at this point we have it all except the padding */
|
||||
|
||||
/* first figure out our minimum padding amount to make it an even
|
||||
block size */
|
||||
padding_length = blocksize - (packet_length % blocksize);
|
||||
padding_length = blocksize - ((packet_length - crypt_offset) % blocksize);
|
||||
|
||||
/* if the padding becomes too small we add another blocksize worth
|
||||
of it (taken from the original libssh2 where it didn't have any
|
||||
@@ -877,19 +925,62 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session,
|
||||
/* Calculate MAC hash. Put the output at index packet_length,
|
||||
since that size includes the whole packet. The MAC is
|
||||
calculated on the entire unencrypted packet, including all
|
||||
fields except the MAC field itself. */
|
||||
fields except the MAC field itself. This is skipped in the
|
||||
INTEGRATED_MAC case, where the crypto algorithm also does its
|
||||
own hash. */
|
||||
if(!CRYPT_FLAG_R(session, INTEGRATED_MAC)) {
|
||||
session->local.mac->hash(session, p->outbuf + packet_length,
|
||||
session->local.seqno, p->outbuf,
|
||||
packet_length, NULL, 0,
|
||||
&session->local.mac_abstract);
|
||||
}
|
||||
|
||||
/* Encrypt the whole packet data, one block size at a time.
|
||||
The MAC field is not encrypted. */
|
||||
for(i = 0; i < packet_length; i += session->local.crypt->blocksize) {
|
||||
The MAC field is not encrypted unless INTEGRATED_MAC. */
|
||||
/* Some crypto back-ends could handle a single crypt() call for
|
||||
encryption, but (presumably) others cannot, so break it up
|
||||
into blocksize-sized chunks to satisfy them all. */
|
||||
for(i = 0; i < packet_length;
|
||||
i += session->local.crypt->blocksize) {
|
||||
unsigned char *ptr = &p->outbuf[i];
|
||||
size_t bsize = LIBSSH2_MIN(session->local.crypt->blocksize,
|
||||
(int)(packet_length-i));
|
||||
/* The INTEGRATED_MAC case always has an extra call below,
|
||||
so it will never be LAST_BLOCK up here. */
|
||||
int firstlast = i == 0 ? FIRST_BLOCK :
|
||||
(!CRYPT_FLAG_L(session, INTEGRATED_MAC)
|
||||
&& (i == packet_length - session->local.crypt->blocksize)
|
||||
? LAST_BLOCK: MIDDLE_BLOCK);
|
||||
/* In the AAD case, the last block would be only 4 bytes
|
||||
because everything is offset by 4 since the initial
|
||||
packet_length isn't encrypted. In this case, combine that
|
||||
last short packet with the previous one since AES-GCM
|
||||
crypt() assumes that the entire MAC is available in that
|
||||
packet so it can set that to the authentication tag. */
|
||||
if(!CRYPT_FLAG_L(session, INTEGRATED_MAC))
|
||||
if(i > packet_length - 2*bsize) {
|
||||
/* increase the final block size */
|
||||
bsize = packet_length - i;
|
||||
/* advance the loop counter by the extra amount */
|
||||
i += bsize - session->local.crypt->blocksize;
|
||||
}
|
||||
if(session->local.crypt->crypt(session, ptr,
|
||||
session->local.crypt->blocksize,
|
||||
&session->local.crypt_abstract))
|
||||
bsize,
|
||||
&session->local.crypt_abstract,
|
||||
firstlast))
|
||||
return LIBSSH2_ERROR_ENCRYPT; /* encryption failure */
|
||||
}
|
||||
/* Call crypt() one last time so it can be filled in with
|
||||
the MAC */
|
||||
if(CRYPT_FLAG_L(session, INTEGRATED_MAC)) {
|
||||
int authlen = session->local.mac->mac_len;
|
||||
assert((size_t)total_length <=
|
||||
packet_length + session->local.crypt->blocksize);
|
||||
if(session->local.crypt->crypt(session,
|
||||
&p->outbuf[packet_length],
|
||||
authlen,
|
||||
&session->local.crypt_abstract,
|
||||
LAST_BLOCK))
|
||||
return LIBSSH2_ERROR_ENCRYPT; /* encryption failure */
|
||||
}
|
||||
}
|
||||
|
@@ -2010,13 +2010,14 @@ _libssh2_wincng_cipher_crypt(_libssh2_cipher_ctx *ctx,
|
||||
_libssh2_cipher_type(type),
|
||||
int encrypt,
|
||||
unsigned char *block,
|
||||
size_t blocklen)
|
||||
size_t blocklen, int firstlast)
|
||||
{
|
||||
unsigned char *pbOutput, *pbInput;
|
||||
unsigned long cbOutput, cbInput;
|
||||
int ret;
|
||||
|
||||
(void)type;
|
||||
(void)firstlast;
|
||||
|
||||
cbInput = (unsigned long)blocklen;
|
||||
|
||||
|
@@ -59,6 +59,7 @@
|
||||
|
||||
#define LIBSSH2_AES 1
|
||||
#define LIBSSH2_AES_CTR 1
|
||||
#define LIBSSH2_AES_GCM 0
|
||||
#define LIBSSH2_BLOWFISH 0
|
||||
#define LIBSSH2_RC4 1
|
||||
#define LIBSSH2_CAST 0
|
||||
@@ -377,8 +378,8 @@ struct _libssh2_wincng_cipher_type {
|
||||
|
||||
#define _libssh2_cipher_init(ctx, type, iv, secret, encrypt) \
|
||||
_libssh2_wincng_cipher_init(ctx, type, iv, secret, encrypt)
|
||||
#define _libssh2_cipher_crypt(ctx, type, encrypt, block, blocklen) \
|
||||
_libssh2_wincng_cipher_crypt(ctx, type, encrypt, block, blocklen)
|
||||
#define _libssh2_cipher_crypt(ctx, type, encrypt, block, blocklen, fl) \
|
||||
_libssh2_wincng_cipher_crypt(ctx, type, encrypt, block, blocklen, fl)
|
||||
#define _libssh2_cipher_dtor(ctx) \
|
||||
_libssh2_wincng_cipher_dtor(ctx)
|
||||
|
||||
@@ -606,7 +607,7 @@ _libssh2_wincng_cipher_crypt(_libssh2_cipher_ctx *ctx,
|
||||
_libssh2_cipher_type(type),
|
||||
int encrypt,
|
||||
unsigned char *block,
|
||||
size_t blocklen);
|
||||
size_t blocklen, int firstlast);
|
||||
void
|
||||
_libssh2_wincng_cipher_dtor(_libssh2_cipher_ctx *ctx);
|
||||
|
||||
|
Reference in New Issue
Block a user