1
0
mirror of https://github.com/libssh2/libssh2.git synced 2026-10-24 03:48:58 +03:00

mbedtls: add support for AES-GCM ciphers

In addition to the original patch:
- simplify cipher object. drop the allocation and double redirection,
  drop redundant `encrypt` member, shorten/dedupe names, replace `algo`
  with `is_gcm`.
- replace heap alloc with stack buffer for GCM in `ssh2_cipher_crypt()`.
- use the algo argument instead of the context copy.
  (for speed and to match OpenSSL sibling code.)
- fix returned error value on GCM auth fail.
- increment IV on decrypt auth fail. Copilot suggestion. I cannot
  confirm this, so erring on the side caution to avoid a potential IV
  reuse.
- support mbedTLS builds without GCM built-in, via `MBEDTLS_GCM_C`.
- retrofit bounds check applied to OpenSSL backend earlier.
  Follow-up to a2ed82d409 #2401
- misc tidy-ups.

Original-patch-by: Adrian Moran
Closes #1764
Closes #2505
This commit is contained in:
Viktor Szakats
2026-08-04 01:01:08 +02:00
parent 84cffeaf11
commit d5ba5bc77b
2 changed files with 166 additions and 22 deletions

View File

@@ -102,14 +102,48 @@ int ssh2_cipher_init(ssh2_cipher_ctx *ctx, SSH2_CIPHER_T(algo),
if(!ctx)
return -1;
memset(ctx, 0, sizeof(*ctx));
#if LIBSSH2_AES_GCM
/* Check if this is a GCM algorithm */
if(algo == MBEDTLS_CIPHER_AES_128_GCM ||
algo == MBEDTLS_CIPHER_AES_256_GCM) {
unsigned int keybits;
ctx->is_aesgcm = 1;
/* Store the initial IV (will be incremented per packet) */
memcpy(ctx->u.gcm.iv, iv, sizeof(ctx->u.gcm.iv));
/* Initialize GCM context */
mbedtls_gcm_init(&ctx->u.gcm.ctx);
/* Set key length based on algorithm */
if(algo == MBEDTLS_CIPHER_AES_128_GCM)
keybits = 128;
else /* MBEDTLS_CIPHER_AES_256_GCM */
keybits = 256;
/* Setup GCM with AES cipher and key */
if(mbedtls_gcm_setkey(&ctx->u.gcm.ctx, MBEDTLS_CIPHER_ID_AES,
secret, keybits)) {
mbedtls_gcm_free(&ctx->u.gcm.ctx);
return -1;
}
return 0;
}
#endif
/* Non-GCM algorithms: use cipher API */
op = encrypt ? MBEDTLS_ENCRYPT : MBEDTLS_DECRYPT;
cipher_info = mbedtls_cipher_info_from_type(algo);
if(!cipher_info)
return -1;
mbedtls_cipher_init(ctx);
ret = mbedtls_cipher_setup(ctx, cipher_info);
mbedtls_cipher_init(&ctx->u.cipher);
ret = mbedtls_cipher_setup(&ctx->u.cipher, cipher_info);
/* libssh2 computes and adds SSH packet padding itself, so for CBC
tell mbedTLS to expect no padding on the cipher layer. Only call
@@ -124,18 +158,21 @@ int ssh2_cipher_init(ssh2_cipher_ctx *ctx, SSH2_CIPHER_T(algo),
#endif
)
) {
ret = mbedtls_cipher_set_padding_mode(ctx, MBEDTLS_PADDING_NONE);
ret = mbedtls_cipher_set_padding_mode(&ctx->u.cipher,
MBEDTLS_PADDING_NONE);
}
if(!ret)
ret = mbedtls_cipher_setkey(ctx,
secret,
ret = mbedtls_cipher_setkey(&ctx->u.cipher, secret,
(int)mbedtls_cipher_info_get_key_bitlen(cipher_info), op);
if(!ret)
ret = mbedtls_cipher_set_iv(ctx, iv,
ret = mbedtls_cipher_set_iv(&ctx->u.cipher, iv,
mbedtls_cipher_info_get_iv_size(cipher_info));
if(ret)
mbedtls_cipher_free(&ctx->u.cipher);
return ret == 0 ? 0 : -1;
}
@@ -145,26 +182,99 @@ int ssh2_cipher_crypt(ssh2_cipher_ctx *ctx, SSH2_CIPHER_T(algo),
{
int ret;
unsigned char *output;
size_t osize;
size_t osize, olen, finish_olen;
if(!ctx)
return -1;
(void)algo;
#if LIBSSH2_AES_GCM
if(ctx->is_aesgcm) {
unsigned char buf[32];
unsigned char tag[16]; /* GCM authentication tag */
const int authlen = sizeof(tag);
const int aadlen = IS_FIRST(firstlast) ? 4 : 0;
const int authenticationtag = IS_LAST(firstlast) ? authlen : 0;
size_t cryptlen; /* length of encrypt */
if(blocksize > sizeof(buf) ||
blocksize < (size_t)(aadlen + authenticationtag))
return -1;
cryptlen = blocksize - aadlen - authenticationtag;
/* First block: start GCM operation */
if(IS_FIRST(firstlast)) {
if(mbedtls_gcm_starts(&ctx->u.gcm.ctx,
encrypt ? MBEDTLS_GCM_ENCRYPT
: MBEDTLS_GCM_DECRYPT,
ctx->u.gcm.iv, sizeof(ctx->u.gcm.iv)))
return -1;
/* Process AAD (Additional Authenticated Data) */
if(aadlen && mbedtls_gcm_update_ad(&ctx->u.gcm.ctx, block, aadlen))
return -1;
}
/* Process payload */
if(cryptlen > 0) {
olen = 0;
if(mbedtls_gcm_update(&ctx->u.gcm.ctx,
block + aadlen, cryptlen,
buf, cryptlen, &olen))
return -1;
memcpy(block + aadlen, buf, olen);
}
ret = 0;
/* Last block: finalize and handle authentication tag */
if(IS_LAST(firstlast)) {
unsigned char finish_buf[16];
int i;
finish_olen = 0;
if(mbedtls_gcm_finish(&ctx->u.gcm.ctx,
finish_buf, sizeof(finish_buf),
&finish_olen, tag, authlen))
return -1;
if(encrypt)
memcpy(block + blocksize - authlen, tag, authlen);
else if(ssh2_timingsafe_bcmp(tag, block + blocksize - authlen,
authlen))
ret = -1; /* GCM auth failed */
/* Increment IV for next packet */
for(i = 11; i >= 4; i--)
if(++ctx->u.gcm.iv[i])
break;
}
return ret;
}
#else
(void)encrypt;
(void)algo;
(void)firstlast;
#endif
osize = blocksize + mbedtls_cipher_get_block_size(ctx);
/* Non-GCM algorithms: use standard cipher API */
osize = blocksize + mbedtls_cipher_get_block_size(&ctx->u.cipher);
output = mbedtls_calloc(osize, sizeof(char));
if(output) {
size_t olen = 0, finish_olen = 0;
ret = mbedtls_cipher_reset(ctx);
olen = 0;
finish_olen = 0;
ret = mbedtls_cipher_reset(&ctx->u.cipher);
if(!ret)
ret = mbedtls_cipher_update(ctx, block, blocksize, output, &olen);
ret = mbedtls_cipher_update(&ctx->u.cipher, block,
blocksize, output, &olen);
if(!ret)
ret = mbedtls_cipher_finish(ctx, output + olen, &finish_olen);
ret = mbedtls_cipher_finish(&ctx->u.cipher,
output + olen, &finish_olen);
if(!ret) {
olen += finish_olen;
memcpy(block, output, olen);
@@ -178,6 +288,21 @@ int ssh2_cipher_crypt(ssh2_cipher_ctx *ctx, SSH2_CIPHER_T(algo),
return ret == 0 ? 0 : -1;
}
void ssh2_cipher_dtor(ssh2_cipher_ctx *ctx)
{
if(!ctx)
return;
#if LIBSSH2_AES_GCM
if(ctx->is_aesgcm) {
mbedtls_gcm_free(&ctx->u.gcm.ctx);
return;
}
#endif
mbedtls_cipher_free(&ctx->u.cipher);
}
int ssh2_hash_init(ssh2_hash_ctx *ctx, ssh2_hash_alg alg)
{
*ctx = psa_hash_operation_init();

View File

@@ -43,6 +43,9 @@
#include <mbedtls/rsa.h>
#include <mbedtls/bignum.h>
#include <mbedtls/cipher.h>
#ifdef MBEDTLS_GCM_C
# include <mbedtls/gcm.h>
#endif
#ifdef MBEDTLS_ECDH_C
# include <mbedtls/ecdh.h>
#endif
@@ -79,7 +82,11 @@
#define LIBSSH2_AES_CBC 1
#define LIBSSH2_AES_CTR 1
#ifdef MBEDTLS_GCM_C
#define LIBSSH2_AES_GCM 1
#else
#define LIBSSH2_AES_GCM 0
#endif
#define LIBSSH2_BLOWFISH 0
#define LIBSSH2_RC4 0
#define LIBSSH2_CAST 0
@@ -180,7 +187,23 @@ typedef enum {
* mbedTLS backend: Cipher Context structure
*/
#define ssh2_cipher_ctx mbedtls_cipher_context_t
/* Unified cipher context structure.
Handles both regular ciphers and AEAD ciphers (GCM)
using a union to optimize memory usage. */
struct ssh2_mbed_cipher_ctx {
int is_aesgcm;
union {
mbedtls_cipher_context_t cipher;
#if LIBSSH2_AES_GCM
struct {
mbedtls_gcm_context ctx;
unsigned char iv[12];
} gcm;
#endif
} u;
};
#define ssh2_cipher_ctx struct ssh2_mbed_cipher_ctx
#define SSH2_CIPHER_T(algo) mbedtls_cipher_type_t algo
@@ -193,14 +216,10 @@ typedef enum {
#if LIBSSH2_3DES
#define ssh2_cipher_3des MBEDTLS_CIPHER_DES_EDE3_CBC
#endif
#define ssh2_cipher_aes128gcm MBEDTLS_CIPHER_AES_128_GCM
#define ssh2_cipher_aes256gcm MBEDTLS_CIPHER_AES_256_GCM
#define ssh2_cipher_chacha20 MBEDTLS_CIPHER_CHACHA20_POLY1305
/*******************************************************************/
/*
* mbedTLS backend: Cipher functions
*/
#define ssh2_cipher_dtor(ctx) mbedtls_cipher_free(ctx)
/*******************************************************************/
/*
* mbedTLS backend: BigNumber Support