Skip to content

Commit e87bdef

Browse files
authored
Fix private keys encrypted with aes-gcm methods (libssh2#1133)
libssh2 1.11.0 fails to decrypt private keys encrypted with [email protected] and [email protected] ciphers. To reproduce the issue, you can create a test key with a command like the following: ```bash ssh-keygen -Z [email protected] -f id_aes256-gcm ``` If you attempt to use this key for authentication, libssh2 returns the not-so-helpful error message "Wrong passphrase or invalid/unrecognized private key file format". The problem is that OpenSSH encrypts keys differently than packets. It does not include the length as AAD, and the 16 byte authentication tag is appended after the encrypted key. The length of the authentication tag is not included in the encrypted key length. I have not found any documentation for this behaviour -- I discovered it by looking at the OpenSSH source. See the `private2_decrypt` function in <https://github.com/openssh/openssh-portable/blob/master/sshkey.c>. This patch fixes the code for reading OpenSSH private keys encrypted with AES-GCM methods.
1 parent 6265ffd commit e87bdef

File tree

1 file changed

+28
-4
lines changed

1 file changed

+28
-4
lines changed

src/pem.c

+28-4
Original file line numberDiff line numberDiff line change
@@ -599,13 +599,17 @@ _libssh2_openssh_pem_parse_data(LIBSSH2_SESSION * session,
599599
}
600600

601601
while((size_t)len_decrypted <= decrypted.len - blocksize) {
602+
/* We always pass MIDDLE_BLOCK here because OpenSSH Key Files
603+
* do not use AAD to authenticate the length.
604+
* Furthermore, the authentication tag is appended after the
605+
* encrypted key, and the length of the authentication tag is
606+
* not included in the key length, so we check it after the
607+
* loop.
608+
*/
602609
if(method->crypt(session, decrypted.data + len_decrypted,
603610
blocksize,
604611
&abstract,
605-
len_decrypted == 0 ? FIRST_BLOCK : (
606-
((size_t)len_decrypted == decrypted.len - blocksize) ?
607-
LAST_BLOCK : MIDDLE_BLOCK)
608-
)) {
612+
MIDDLE_BLOCK)) {
609613
ret = LIBSSH2_ERROR_DECRYPT;
610614
method->dtor(session, &abstract);
611615
goto out;
@@ -616,6 +620,26 @@ _libssh2_openssh_pem_parse_data(LIBSSH2_SESSION * session,
616620

617621
/* No padding */
618622

623+
/* for the AES GCM methods, the 16 byte authentication tag is
624+
* appended to the encrypted key */
625+
if(strcmp(method->name, "[email protected]") == 0 ||
626+
strcmp(method->name, "[email protected]") == 0) {
627+
if(!_libssh2_check_length(&decoded, 16)) {
628+
ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO,
629+
"GCM auth tag missing");
630+
method->dtor(session, &abstract);
631+
goto out;
632+
}
633+
if(method->crypt(session, decoded.dataptr, 16, &abstract,
634+
LAST_BLOCK)) {
635+
ret = _libssh2_error(session, LIBSSH2_ERROR_DECRYPT,
636+
"GCM auth tag invalid");
637+
method->dtor(session, &abstract);
638+
goto out;
639+
}
640+
decoded.dataptr += 16;
641+
}
642+
619643
method->dtor(session, &abstract);
620644
}
621645

0 commit comments

Comments
 (0)