mirror of
https://github.com/libssh2/libssh2.git
synced 2026-10-24 03:48:58 +03:00
Add support for AES-GCM crypto protocols
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.
This commit is contained in:
@@ -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.
|
||||
|
||||
42
src/crypt.c
42
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
11
src/kex.c
11
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
29
src/mac.c
29
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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
|
||||
128
src/openssl.c
128
src/openssl.c
@@ -42,6 +42,7 @@
|
||||
|
||||
#ifdef LIBSSH2_OPENSSL /* compile only if we build with openssl */
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
12
src/pem.c
12
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;
|
||||
|
||||
178
src/transport.c
178
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 */
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user