Skip to content

mbedtls: add GCM support#1764

Open
AdrianMoranMontes wants to merge 13 commits into
libssh2:masterfrom
AdrianMoranMontes:fix-mbedtls-gcm
Open

mbedtls: add GCM support#1764
AdrianMoranMontes wants to merge 13 commits into
libssh2:masterfrom
AdrianMoranMontes:fix-mbedtls-gcm

Conversation

@AdrianMoranMontes
Copy link
Copy Markdown
Contributor

Add AES-GCM support for mbedTLS backend

Summary

This PR implements AES-128-GCM and AES-256-GCM cipher support for the mbedTLS backend, enabling connections to SSH servers that require GCM encryption algorithms.

Problem

The mbedTLS backend had GCM support disabled (LIBSSH2_AES_GCM = 0) even though mbedTLS provides full GCM implementation through its mbedtls_gcm_* API.

Changes

mbedtls.h

  • Enabled GCM support: #define LIBSSH2_AES_GCM 1
  • Added #include <mbedtls/gcm.h>
  • Defined algorithm mappings for _libssh2_cipher_aes128gcm and _libssh2_cipher_aes256gcm

mbedtls.c

  • Created unified cipher context structure _libssh2_mbedtls_cipher_ctx with union to handle both regular ciphers and GCM
  • Modified _libssh2_mbedtls_cipher_init() to initialize GCM contexts with proper key setup
  • Implemented GCM operations in _libssh2_mbedtls_cipher_crypt():
    • AAD (Additional Authenticated Data) processing for SSH packet length
    • Encryption/decryption with proper IV management
    • Authentication tag generation and verification (16 bytes)
    • IV increment after each packet (last 8 bytes of 12-byte IV)
  • Updated _libssh2_mbedtls_cipher_dtor() to properly free GCM contexts

Technical Details

  • GCM uses mbedTLS native API (mbedtls_gcm_starts/update_ad/update/finish) rather than generic cipher API
  • IV structure: 4 bytes fixed + 8 bytes invocation counter (incremented per packet)
  • SSH GCM packet format: 4-byte AAD + encrypted payload + 16-byte auth tag

Testing

Tested successfully with SFTP servers requiring GCM-only encryption.

Related

This PR has been built including bugfix of #1758

@vszakats vszakats changed the title Add GCM support on mbedTLS. mbedtls: add GCM support Dec 25, 2025
Comment thread src/mbedtls.c Outdated
@willco007
Copy link
Copy Markdown
Member

@AdrianMoranMontes Sorry for the delay on this, could you please pull to master and we'll get it integrated. Thanks!

@AdrianMoranMontes AdrianMoranMontes force-pushed the fix-mbedtls-gcm branch 2 times, most recently from 9ce7aaa to 3061d4b Compare April 13, 2026 08:42
@AdrianMoranMontes
Copy link
Copy Markdown
Contributor Author

@willco007 Done!

Comment thread src/mbedtls.c Outdated
} gcm;
#endif
} ctx;
} _libssh2_mbedtls_cipher_ctx;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest converting the typedef to a struct type and referring to it
as struct <name> throughout the code.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to add AES-128-GCM and AES-256-GCM cipher support to the mbedTLS crypto backend so libssh2 can negotiate and use aes{128,256}-gcm@openssh.com with servers requiring GCM.

Changes:

  • Refactors the mbedTLS cipher handling to use a unified cipher context structure intended to support both classic ciphers and AES-GCM.
  • Adds an AES-GCM encrypt/decrypt path using mbedtls_gcm_* APIs (currently guarded by #if LIBSSH2_AES_GCM).
  • Updates the mbedTLS cipher init/crypt/dtor plumbing to use the new unified context.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
src/mbedtls.h Adjusts the cipher-init macro signature (but does not currently enable/define the advertised GCM support).
src/mbedtls.c Introduces a unified cipher context and implements a GCM code path alongside existing cipher handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/mbedtls.c Outdated
Comment on lines +381 to +385
_libssh2_mbedtls_cipher_ctx *cctx = *(_libssh2_mbedtls_cipher_ctx **)ctx;

if(!cctx)
return;

Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_libssh2_mbedtls_cipher_dtor() also assumes _libssh2_cipher_ctx is a pointer-like container (*(_libssh2_mbedtls_cipher_ctx **)ctx). This has the same undefined-behavior/ABI problem as init/crypt. Once _libssh2_cipher_ctx is corrected for mbedTLS, make sure the dtor reads the context in a type-correct way and clears the stored pointer (e.g., set it to NULL) to avoid accidental double-free on repeated teardown.

Copilot uses AI. Check for mistakes.
Comment thread src/mbedtls.h
Comment on lines +364 to +365
#define _libssh2_cipher_init(h, type, iv, secret, encrypt) \
_libssh2_mbedtls_cipher_init(h, type, iv, secret, encrypt)
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says AES-GCM is enabled for the mbedTLS backend, but in the current codebase LIBSSH2_AES_GCM is still defined as 0 in src/mbedtls.h and there are no _libssh2_cipher_aes{128,256}gcm mappings for mbedTLS. As a result, all the new GCM code in mbedtls.c is compiled out and GCM won’t actually be negotiable/usable. Please flip LIBSSH2_AES_GCM to 1, add the required mbedtls/gcm.h include, and add the cipher type mappings (consistent with how openssl.h exposes _libssh2_cipher_aes128gcm/_libssh2_cipher_aes256gcm).

Copilot uses AI. Check for mistakes.
Comment thread src/mbedtls.c
Comment on lines +179 to 183

/* Store the context pointer */
*(void **)h = cctx;
return 0;
}
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_libssh2_mbedtls_cipher_init() stores cctx via *(void **)h = cctx;, but in the mbedTLS backend _libssh2_cipher_ctx is defined as mbedtls_cipher_context_t (a struct), not a pointer type. This is undefined behavior (strict aliasing/alignment) and makes the _libssh2_cipher_ctx ABI inconsistent. Fix by redefining _libssh2_cipher_ctx for mbedTLS to be a pointer type (and updating init/crypt/dtor accordingly) or by embedding the unified context directly in _libssh2_cipher_ctx.

Copilot uses AI. Check for mistakes.
Comment thread src/mbedtls.c Outdated
Comment on lines +248 to +250
cctx = *(_libssh2_mbedtls_cipher_ctx **)ctx;
if(!cctx)
return -1;
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_libssh2_mbedtls_cipher_crypt() assumes ctx points to a pointer (cctx = *(_libssh2_mbedtls_cipher_ctx **)ctx;). This depends on _libssh2_cipher_ctx being pointer-compatible storage; with the current #define _libssh2_cipher_ctx mbedtls_cipher_context_t, this is undefined behavior and can break under optimization. Fix this together with the _libssh2_cipher_ctx type issue in _libssh2_mbedtls_cipher_init().

Copilot uses AI. Check for mistakes.
Comment thread src/mbedtls.c Outdated
memcpy(block + blocksize - authlen, tag, authlen);
}
else {
if(memcmp(tag, block + blocksize - authlen, authlen) != 0)
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the GCM decrypt path, the authentication tag is compared with memcmp(), which is not timing-safe. This codebase already has _libssh2_timingsafe_bcmp() for MAC/tag verification; use it here as well to avoid timing side-channels on tag checks.

Suggested change
if(memcmp(tag, block + blocksize - authlen, authlen) != 0)
if(_libssh2_timingsafe_bcmp(tag, block + blocksize - authlen,
authlen) != 0)

Copilot uses AI. Check for mistakes.
Comment thread src/mbedtls.c
Comment on lines +267 to +271
if(cryptlen > 0) {
buf = (unsigned char *)mbedtls_calloc(cryptlen, 1);
if(!buf)
return -1;
}
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The temporary buf in the GCM path can hold plaintext during decrypt, but it’s freed with mbedtls_free() (and on some error paths too) without being wiped. Use _libssh2_mbedtls_safe_free(buf, cryptlen) (or equivalent explicit zeroization) for all frees of this buffer to avoid leaving plaintext in heap memory.

Copilot uses AI. Check for mistakes.
@AdrianMoranMontes
Copy link
Copy Markdown
Contributor Author

@vszakats I think all your comments are now fullfiled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants