diff --git a/CHANGELOG.md b/CHANGELOG.md index f7d0b7f..772ed6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [2.18.0] - 2026-05-18 +- **BREAKING**: `SSHHostkeyVerifyHandler` now receives an OpenSSH-style `SHA256:` host key fingerprint instead of the previous raw MD5 digest, so host key pinning code must be updated accordingly [#162]. Thanks [@thyssentishman]. + ## [2.17.1] - 2026-04-12 - Made `SSHPem.decode` accept CRLF (`\r\n`) line endings in addition to LF when parsing PEM content [#157]. Thanks [@gkc]. diff --git a/lib/src/ssh_transport.dart b/lib/src/ssh_transport.dart index d66a245..815885e 100644 --- a/lib/src/ssh_transport.dart +++ b/lib/src/ssh_transport.dart @@ -31,12 +31,19 @@ typedef SSHPrintHandler = void Function(String?); /// Function called when host key is received. /// [type] is the type of the host key, For example 'ssh-rsa', -/// [fingerprint] md5 fingerprint of the host key. +/// [fingerprint] OpenSSH-style SHA256 fingerprint of the host key, +/// UTF-8 encoded as `SHA256:`. typedef SSHHostkeyVerifyHandler = FutureOr Function( String type, Uint8List fingerprint, ); +Uint8List _hostkeyFingerprint(Uint8List hostkey) { + final fingerprint = SHA256Digest().process(hostkey); + final encoded = base64.encode(fingerprint).replaceAll('=', ''); + return Uint8List.fromList(utf8.encode('SHA256:$encoded')); +} + typedef SSHTransportReadyHandler = void Function(); typedef SSHPacketHandler = void Function(Uint8List payload); @@ -1180,7 +1187,7 @@ class SSHTransport { _sessionId ??= exchangeHash; _sharedSecret = sharedSecret; - final fingerprint = MD5Digest().process(hostkey); + final fingerprint = _hostkeyFingerprint(hostkey); if (_hostkeyVerified) { _sendNewKeys(); diff --git a/pubspec.yaml b/pubspec.yaml index b48b3a4..8b15ed9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: dartssh2 -version: 2.17.1 +version: 2.18.0 description: SSH and SFTP client written in pure Dart, aiming to be feature-rich as well as easy to use. homepage: https://github.com/TerminalStudio/dartssh2 diff --git a/test/src/ssh_transport_fingerprint_test.dart b/test/src/ssh_transport_fingerprint_test.dart new file mode 100644 index 0000000..9415010 --- /dev/null +++ b/test/src/ssh_transport_fingerprint_test.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; +import 'dart:mirrors'; +import 'dart:typed_data'; + +import 'package:dartssh2/dartssh2.dart'; +import 'package:pointycastle/export.dart'; +import 'package:test/test.dart'; + +void main() { + final transportLibrary = reflectClass(SSHTransport).owner as LibraryMirror; + + Uint8List invokeFingerprint(Uint8List hostkey) { + final symbol = + MirrorSystem.getSymbol('_hostkeyFingerprint', transportLibrary); + return transportLibrary.invoke(symbol, [hostkey]).reflectee as Uint8List; + } + + test('formats host key fingerprints using OpenSSH SHA256 style', () { + final hostkey = + Uint8List.fromList(List.generate(32, (index) => index)); + + final fingerprint = utf8.decode(invokeFingerprint(hostkey)); + final expectedDigest = SHA256Digest().process(hostkey); + final expected = + 'SHA256:${base64.encode(expectedDigest).replaceAll('=', '')}'; + + expect(fingerprint, equals(expected)); + }); +}