diff --git a/docs/HACKING-CRYPTO b/docs/HACKING-CRYPTO index d0173553..ca6316b0 100644 --- a/docs/HACKING-CRYPTO +++ b/docs/HACKING-CRYPTO @@ -317,7 +317,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. diff --git a/src/crypt.c b/src/crypt.c index 8d493b48..ca9ca039 100644 --- a/src/crypt.c +++ b/src/crypt.c @@ -45,7 +45,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; @@ -97,12 +97,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 @@ -117,6 +117,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, /*flag*/ + &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, /*flag*/ + &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", @@ -260,7 +288,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; @@ -311,6 +340,11 @@ static const LIBSSH2_CRYPT_METHOD libssh2_crypt_method_3des_cbc = { #endif 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_aes128_ctr, &libssh2_crypt_method_aes192_ctr, diff --git a/src/crypto.h b/src/crypto.h index efaf1564..2c63876a 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -239,7 +239,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, diff --git a/src/kex.c b/src/kex.c index e3975246..76af7588 100644 --- a/src/kex.c +++ b/src/kex.c @@ -3584,9 +3584,18 @@ static int kex_agree_mac(LIBSSH2_SESSION * session, unsigned long 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; @@ -3749,6 +3758,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; diff --git a/src/libgcrypt.c b/src/libgcrypt.c index 0aff176a..6d637b52 100644 --- a/src/libgcrypt.c +++ b/src/libgcrypt.c @@ -583,7 +583,8 @@ _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 cipher = _libssh2_gcry_cipher(algo); int ret; diff --git a/src/libgcrypt.h b/src/libgcrypt.h index 95876b96..b65096e9 100644 --- a/src/libgcrypt.h +++ b/src/libgcrypt.h @@ -49,6 +49,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 diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h index ee029e11..3c8a3759 100644 --- a/src/libssh2_priv.h +++ b/src/libssh2_priv.h @@ -949,12 +949,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; diff --git a/src/mac.c b/src/mac.c index afe28ee5..f1b4cb00 100644 --- a/src/mac.c +++ b/src/mac.c @@ -454,3 +454,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; +} diff --git a/src/mac.h b/src/mac.h index 6b11cbf5..55eb1a2f 100644 --- a/src/mac.h +++ b/src/mac.h @@ -64,5 +64,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 */ diff --git a/src/mbedtls.h b/src/mbedtls.h index 0450113f..ebdee505 100644 --- a/src/mbedtls.h +++ b/src/mbedtls.h @@ -65,8 +65,17 @@ #define LIBSSH2_AES 1 #define LIBSSH2_AES_CTR 1 -#define LIBSSH2_BLOWFISH 1 -#define LIBSSH2_RC4 1 +#define LIBSSH2_AES_GCM 0 +#ifdef MBEDTLS_CIPHER_BLOWFISH_CBC +# define LIBSSH2_BLOWFISH 1 +#else +# define LIBSSH2_BLOWFISH 0 +#endif +#ifdef MBEDTLS_CIPHER_ARC4_128 +# define LIBSSH2_RC4 1 +#else +# define LIBSSH2_RC4 0 +#endif #define LIBSSH2_CAST 0 #define LIBSSH2_3DES 1 @@ -365,7 +374,7 @@ 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) \ +#define _libssh2_cipher_crypt(ctx, type, encrypt, block, blocklen, fl) \ _libssh2_mbedtls_cipher_crypt(ctx, type, encrypt, block, blocklen) #define _libssh2_cipher_dtor(ctx) \ _libssh2_mbedtls_cipher_dtor(ctx) diff --git a/src/openssl.c b/src/openssl.c index 6237da20..cec73c07 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -42,6 +42,7 @@ #ifdef LIBSSH2_OPENSSL /* compile only if we build with openssl */ +#include #include #include "misc.h" @@ -439,10 +440,27 @@ _libssh2_cipher_init(_libssh2_cipher_ctx * h, _libssh2_cipher_type(algo), unsigned char *iv, unsigned char *secret, int encrypt) { +#if LIBSSH2_AES_GCM + const int is_aesgcm = (algo == EVP_aes_128_gcm) || + (algo == EVP_aes_256_gcm); +#endif /* LIBSSH2_AES_GCM */ + int rc; + #ifdef HAVE_OPAQUE_STRUCTS *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 @@ -451,26 +469,106 @@ _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; +#if LIBSSH2_AES_GCM + const int is_aesgcm = (algo == EVP_aes_128_gcm) || + (algo == EVP_aes_256_gcm); + char lastiv[1]; +#else + 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 = blocksize - aadlen - authenticationtag; (void) algo; - (void) encrypt; -#ifdef HAVE_OPAQUE_STRUCTS - ret = EVP_Cipher(*ctx, buf, block, blocksize); -#else - ret = EVP_Cipher(ctx, buf, block, blocksize); -#endif -#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 - if(ret != -1) { -#else - if(ret == 1) { -#endif - memcpy(block, buf, blocksize); + assert(blocksize <= sizeof(buf)); + + /* First block */ + if(IS_FIRST(firstlast)) { +#if LIBSSH2_AES_GCM + /* 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); + } +#endif /* LIBSSH2_AES_GCM */ } + /* Last portion of block to encrypt/decrypt */ +#if LIBSSH2_AES_GCM + 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); + } + } +#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) { + ret = 1; +#endif + 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); + } + + /* TODO: the return code should distinguish between decryption errors and + invalid MACs */ #if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 return ret != -1 ? 0 : 1; #else diff --git a/src/openssl.h b/src/openssl.h index 2a002b41..c5070ace 100644 --- a/src/openssl.h +++ b/src/openssl.h @@ -113,6 +113,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 @@ -338,6 +344,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 diff --git a/src/os400qc3.c b/src/os400qc3.c index f05bfabf..634b9680 100644 --- a/src/os400qc3.c +++ b/src/os400qc3.c @@ -1099,7 +1099,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; diff --git a/src/os400qc3.h b/src/os400qc3.h index 7bcef233..1cfa4b41 100644 --- a/src/os400qc3.h +++ b/src/os400qc3.h @@ -169,6 +169,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 diff --git a/src/pem.c b/src/pem.c index 3416bd52..4f7f7521 100644 --- a/src/pem.c +++ b/src/pem.c @@ -258,7 +258,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); @@ -587,7 +591,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; diff --git a/src/transport.c b/src/transport.c index ba418b80..441a7016 100644 --- a/src/transport.c +++ b/src/transport.c @@ -51,9 +51,14 @@ #include "transport.h" #include "mac.h" +#ifndef MIN +#define MIN(x,y) ((x)<(y)?(x):(y)) +#endif + #define MAX_BLOCKSIZE 32 /* MUST fit biggest crypto block size we use/get */ #define MAX_MACSIZE 64 /* MUST fit biggest MAC length we support */ + #ifdef LIBSSH2DEBUG #define UNPRINTABLE_CHAR '.' static void @@ -129,18 +134,37 @@ debugdump(LIBSSH2_SESSION * session, static int decrypt(LIBSSH2_SESSION * session, unsigned char *source, - unsigned char *dest, int len) + unsigned char *dest, int 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 */ + int decryptlen = 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, it's the end. */ + int lowerfirstlast = IS_FIRST(firstlast) ? FIRST_BLOCK : + ((len <= blocksize) ? LAST_BLOCK : 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_FIRST(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; } @@ -148,11 +172,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 */ } @@ -173,7 +197,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 */ int etm = session->remote.mac->etm; @@ -210,7 +234,7 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) avoiding moving a larger block of memory one byte. */ unsigned char first_block[MAX_BLOCKSIZE]; int blocksize = session->remote.crypt->blocksize; - int rc = decrypt(session, p->payload + 4, first_block, blocksize); + int rc = decrypt(session, p->payload + 4, first_block, blocksize, FIRST_BLOCK); if(rc != 0) { return rc; } @@ -232,7 +256,8 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) if(blocksize < decrypt_size) { rc = decrypt(session, p->payload + blocksize + 4, decrypt_buffer + blocksize - 1, - decrypt_size - blocksize); + decrypt_size - blocksize, + LAST_BLOCK); if(rc != 0) { LIBSSH2_FREE(session, decrypt_buffer); return rc; @@ -325,13 +350,18 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) { int rc; struct transportpacket *p = &session->packet; - int remainbuf; - int remainpack; - int numbytes; - int numdecrypt; - unsigned char block[MAX_BLOCKSIZE]; - int blocksize; - int encrypted = 1; + int remainpack; /* how much there is left to add to the current payload + package */ + int remainbuf; /* how much data there is remaining in the buffer to deal + with before we should read more from the network */ + int numbytes; /* how much data to deal with from the buffer on this + iteration through the loop */ + int numdecrypt; /* number of bytes to decrypt this iteration */ + unsigned char block[MAX_BLOCKSIZE]; /* working block buffer */ + 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; @@ -477,17 +507,20 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) p->packet_length = _libssh2_ntohu32(&p->buf[p->readidx]); } else { 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 { /* the data is plain, just copy it verbatim to - the working block buffer */ + the working block buffer */ memcpy(block, &p->buf[p->readidx], blocksize); } @@ -512,8 +545,11 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) packet length field that we run MAC over */ total_num = 4 + p->packet_length + session->remote.mac->mac_len; - } else { + /* 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; @@ -591,25 +627,34 @@ 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)) { + if((p->data_num + numbytes) >= (p->total_num - skip)) { + /* decrypt the entire rest of the package */ numdecrypt = (p->total_num - skip) - p->data_num; + firstlast = LAST_BLOCK; } else { int 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; } + firstlast = MIDDLE_BLOCK; } + } else { /* unencrypted data should not be decrypted at all */ @@ -619,7 +664,8 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) /* 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; @@ -785,7 +831,7 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, (session->state & LIBSSH2_STATE_NEWKEYS) ? session->local.crypt->blocksize : 8; int padding_length; - size_t packet_length; + size_t packet_length, crypt_offset, etm_crypt_offset; int total_length; #ifdef RANDOM_PADDING int rand_max; @@ -899,12 +945,16 @@ 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 */ - size_t crypt_offset = etm ? 4 : 0; /* length field is not encrypted with etm */ + /* subtract 4 bytes of the packet_length field when padding AES-GCM + or with ETM */ + crypt_offset = etm || (encrypted && CRYPT_FLAG_R(session, PKTLEN_AAD)) ? 4 : 0; + etm_crypt_offset = etm ? 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 - crypt_offset) % blocksize); /* if the padding becomes too small we add another blocksize worth @@ -945,27 +995,63 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, if(encrypted) { size_t i; - if(!etm) { - /* 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); - } + /* 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. This is skipped in the + INTEGRATED_MAC case, where the crypto algorithm also does its + own hash. */ + if(!etm && !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. */ - size_t blocksize = session->local.crypt->blocksize; - for(i = crypt_offset; i < packet_length; i += 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 = etm_crypt_offset; i < packet_length; i += session->local.crypt->blocksize) { unsigned char *ptr = &p->outbuf[i]; + size_t bsize = 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; + } _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, "crypting bytes %d-%d", i, i + session->local.crypt->blocksize - 1); 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 */ } diff --git a/src/wincng.h b/src/wincng.h index 538cc431..70a0ab78 100644 --- a/src/wincng.h +++ b/src/wincng.h @@ -57,6 +57,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 @@ -349,7 +350,7 @@ 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) \ +#define _libssh2_cipher_crypt(ctx, type, encrypt, block, blocklen, fl) \ _libssh2_wincng_cipher_crypt(ctx, type, encrypt, block, blocklen) #define _libssh2_cipher_dtor(ctx) \ _libssh2_wincng_cipher_dtor(ctx)