1
0
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:
Dan Fandrich
2023-04-20 06:46:44 -07:00
committed by GitHub
parent d09ca26563
commit 3c953c05d6
19 changed files with 377 additions and 60 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 */
assert((len % blocksize) == 0);
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. */
session->local.mac->hash(session, p->outbuf + packet_length,
session->local.seqno, p->outbuf,
packet_length, NULL, 0,
&session->local.mac_abstract);
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 */
}
}

View File

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

View File

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