diff --git a/docs/HACKING-CRYPTO b/docs/HACKING-CRYPTO index 94bba5ab..b8519832 100644 --- a/docs/HACKING-CRYPTO +++ b/docs/HACKING-CRYPTO @@ -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. diff --git a/src/crypt.c b/src/crypt.c index 87d80518..fb47e19c 100644 --- a/src/crypt.c +++ b/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, diff --git a/src/crypto.h b/src/crypto.h index 2c1fb740..b517b16f 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -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, diff --git a/src/kex.c b/src/kex.c index 2a73c5b8..886a4568 100644 --- a/src/kex.c +++ b/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; diff --git a/src/libgcrypt.c b/src/libgcrypt.c index 59b57ad0..ccee5834 100644 --- a/src/libgcrypt.c +++ b/src/libgcrypt.c @@ -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); diff --git a/src/libgcrypt.h b/src/libgcrypt.h index de2389f2..f750f9b4 100644 --- a/src/libgcrypt.h +++ b/src/libgcrypt.h @@ -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 diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h index e04181dc..55e1a4ab 100644 --- a/src/libssh2_priv.h +++ b/src/libssh2_priv.h @@ -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; diff --git a/src/mac.c b/src/mac.c index ca0a4151..06ca396a 100644 --- a/src/mac.c +++ b/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; +} diff --git a/src/mac.h b/src/mac.h index c6e48bba..7d21a3af 100644 --- a/src/mac.h +++ b/src/mac.h @@ -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 */ diff --git a/src/mbedtls.c b/src/mbedtls.c index e8d4f652..c7d0b56b 100644 --- a/src/mbedtls.c +++ b/src/mbedtls.c @@ -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); diff --git a/src/mbedtls.h b/src/mbedtls.h index b9654b70..5053b101 100644 --- a/src/mbedtls.h +++ b/src/mbedtls.h @@ -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); diff --git a/src/openssl.c b/src/openssl.c index 9e4a03a4..99d1e123 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -40,6 +40,7 @@ #ifdef LIBSSH2_CRYPTO_C /* Compile this via crypto.c */ +#include #include #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; } diff --git a/src/openssl.h b/src/openssl.h index 13535aa5..3cd0deb0 100644 --- a/src/openssl.h +++ b/src/openssl.h @@ -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 diff --git a/src/os400qc3.c b/src/os400qc3.c index 1af7710b..870b5100 100644 --- a/src/os400qc3.c +++ b/src/os400qc3.c @@ -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; diff --git a/src/os400qc3.h b/src/os400qc3.h index 990daefb..297503b1 100644 --- a/src/os400qc3.h +++ b/src/os400qc3.h @@ -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 diff --git a/src/pem.c b/src/pem.c index 16717b73..255539a1 100644 --- a/src/pem.c +++ b/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; diff --git a/src/transport.c b/src/transport.c index 19ad6ec2..b2f64610 100644 --- a/src/transport.c +++ b/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 */ - 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 */ } } diff --git a/src/wincng.c b/src/wincng.c index 46536105..3c842bc1 100644 --- a/src/wincng.c +++ b/src/wincng.c @@ -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; diff --git a/src/wincng.h b/src/wincng.h index df346c9e..558adda6 100644 --- a/src/wincng.h +++ b/src/wincng.h @@ -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);