Skip to content

Commit 3b57dcd

Browse files
authored
Merge pull request #155 from TerminalStudio/feat/issue-26-aead-groundwork
feat: start AEAD support with AES-GCM groundwork
2 parents a2f57cf + ea6766f commit 3b57dcd

6 files changed

Lines changed: 864 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## [2.17.0] - yyyy-mm-dd
22
- Improved Web/WASM compatibility by updating `SSHSocket` conditional imports so web runtimes consistently use the web socket shim and avoid incorrect native socket selection [#88]. Thanks [@vicajilau].
33
- Added local dynamic forwarding (`SSHClient.forwardDynamic`) with SOCKS5 `NO AUTH` + `CONNECT`, including configurable handshake/connect timeouts and connection limits.
4+
- Added AES-GCM (`aes128-gcm@openssh.com`, `aes256-gcm@openssh.com`) AEAD groundwork in transport and cipher negotiation; currently opt-in (not enabled by default yet). `chacha20-poly1305@openssh.com` remains pending [#26]. Thanks [@vicajilau].
45

56
## [2.16.0] - 2026-03-24
67
- **BREAKING**: Changed `SSHChannelController.sendEnv()` from `void` to `Future<bool>` to properly await environment variable setup responses and avoid race conditions with PTY requests [#102]. Thanks [@itzhoujun] and [@vicajilau].
@@ -208,6 +209,7 @@
208209
[#124]: https://github.com/TerminalStudio/dartssh2/issues/124
209210
[#95]: https://github.com/TerminalStudio/dartssh2/issues/95
210211
[#88]: https://github.com/TerminalStudio/dartssh2/issues/88
212+
[#26]: https://github.com/TerminalStudio/dartssh2/issues/26
211213
[#139]: https://github.com/TerminalStudio/dartssh2/pull/139
212214
[#132]: https://github.com/TerminalStudio/dartssh2/pull/132
213215
[#133]: https://github.com/TerminalStudio/dartssh2/pull/133

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,9 +625,39 @@ void main() async {
625625
- `diffie-hellman-group1-sha1 `
626626

627627
**Cipher**:
628+
- `aes[128|256]-gcm@openssh.com`
628629
- `aes[128|192|256]-ctr`
629630
- `aes[128|192|256]-cbc`
630631

632+
AES-GCM is currently available as opt-in via `SSHAlgorithms(cipher: ...)`, and is not enabled in the default cipher preference list yet.
633+
634+
Example (opt-in AES-GCM with explicit fallback ciphers):
635+
636+
```dart
637+
void main() async {
638+
final client = SSHClient(
639+
await SSHSocket.connect('localhost', 22),
640+
username: '<username>',
641+
onPasswordRequest: () => '<password>',
642+
algorithms: const SSHAlgorithms(
643+
cipher: [
644+
SSHCipherType.aes256gcm,
645+
SSHCipherType.aes128gcm,
646+
SSHCipherType.aes256ctr,
647+
SSHCipherType.aes128ctr,
648+
SSHCipherType.aes256cbc,
649+
SSHCipherType.aes128cbc,
650+
],
651+
),
652+
);
653+
654+
// Use the client...
655+
client.close();
656+
}
657+
```
658+
659+
`chacha20-poly1305@openssh.com` is not supported yet.
660+
631661
**Integrity**:
632662
- `hmac-md5`
633663
- `hmac-sha1`

lib/src/algorithm/ssh_cipher_type.dart

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import 'package:pointycastle/export.dart';
55

66
class SSHCipherType extends SSHAlgorithm {
77
static const values = [
8+
aes128gcm,
9+
aes256gcm,
810
aes128cbc,
911
aes192cbc,
1012
aes256cbc,
@@ -31,6 +33,24 @@ class SSHCipherType extends SSHAlgorithm {
3133
cipherFactory: _aesCtrFactory,
3234
);
3335

36+
static const aes128gcm = SSHCipherType._(
37+
name: 'aes128-gcm@openssh.com',
38+
keySize: 16,
39+
isAead: true,
40+
ivSize: 12,
41+
blockSize: 16,
42+
aeadTagSize: 16,
43+
);
44+
45+
static const aes256gcm = SSHCipherType._(
46+
name: 'aes256-gcm@openssh.com',
47+
keySize: 32,
48+
isAead: true,
49+
ivSize: 12,
50+
blockSize: 16,
51+
aeadTagSize: 16,
52+
);
53+
3454
static const aes128cbc = SSHCipherType._(
3555
name: 'aes128-cbc',
3656
keySize: 16,
@@ -61,7 +81,11 @@ class SSHCipherType extends SSHAlgorithm {
6181
const SSHCipherType._({
6282
required this.name,
6383
required this.keySize,
64-
required this.cipherFactory,
84+
this.cipherFactory,
85+
this.isAead = false,
86+
this.aeadTagSize = 0,
87+
this.ivSize = 16,
88+
this.blockSize = 16,
6589
});
6690

6791
/// The name of the algorithm. For example, `"aes256-ctr`"`.
@@ -70,17 +94,29 @@ class SSHCipherType extends SSHAlgorithm {
7094

7195
final int keySize;
7296

73-
final int ivSize = 16;
97+
/// Indicates whether this cipher is an AEAD mode (e.g. AES-GCM).
98+
final bool isAead;
7499

75-
final int blockSize = 16;
100+
/// Authentication tag size for AEAD ciphers.
101+
final int aeadTagSize;
76102

77-
final BlockCipher Function() cipherFactory;
103+
final int ivSize;
104+
105+
final int blockSize;
106+
107+
final BlockCipher Function()? cipherFactory;
78108

79109
BlockCipher createCipher(
80110
Uint8List key,
81111
Uint8List iv, {
82112
required bool forEncryption,
83113
}) {
114+
if (isAead) {
115+
throw UnsupportedError(
116+
'AEAD ciphers are packet-level and do not expose BlockCipher',
117+
);
118+
}
119+
84120
if (key.length != keySize) {
85121
throw ArgumentError.value(key, 'key', 'Key must be $keySize bytes long');
86122
}
@@ -89,7 +125,11 @@ class SSHCipherType extends SSHAlgorithm {
89125
throw ArgumentError.value(iv, 'iv', 'IV must be $ivSize bytes long');
90126
}
91127

92-
final cipher = cipherFactory();
128+
final factory = cipherFactory;
129+
if (factory == null) {
130+
throw StateError('No block cipher factory configured for $name');
131+
}
132+
final cipher = factory();
93133
cipher.init(forEncryption, ParametersWithIV(KeyParameter(key), iv));
94134
return cipher;
95135
}

0 commit comments

Comments
 (0)