Skip to content

Repository files navigation

sshdispatcher

A transparent ssh wrapper that routes connections to legacy DSA-only hosts through PuTTY's plink, while leaving all other hosts to the real ssh.

Problem

ssh-dss (DSA 1024-bit) host keys were deprecated in OpenSSH 7.0 (2015), disabled by default in OpenSSH 9.7, and completely removed from OpenSSH 9.8/10.0 (2024–2025). Any host that has not migrated to a modern host key type (RSA 3072+, ECDSA, or Ed25519) is unreachable from current OpenSSH clients — the algorithm name is unknown to the parser, so even HostKeyAlgorithms +ssh-dss in ~/.ssh/config causes the file to fail to load.

This affects any device where the SSH implementation is fixed and cannot be upgraded: managed network switches, embedded appliances, serial console servers, legacy VMs — anything where the firmware is end-of-life or the vendor simply hasn't shipped a fix. TP-Link JetStream switches are a good example: common in home and small-office networks, only offering ssh-dss, with no firmware update in sight.

PuTTY's plink still supports DSA and is available via Homebrew. sshdispatcher makes the handoff invisible.

Quickstart

# 1. Install dependencies
brew install putty

# 2. Install sshdispatcher
git clone https://github.com/your/sshdispatcher ~/Projects/sshdispatcher
cd ~/Projects/sshdispatcher
./install.sh          # needs sudo for /usr/local symlinks

# 3. Add to ~/.zshrc
echo 'alias ssh=sshdispatcher' >> ~/.zshrc
source ~/.zshrc

# 4. Convert any keys to PuTTY format (one-time per key)
puttygen ~/.ssh/mykey -O private -o ~/.ssh/mykey.ppk

Configuration

Mark a host stanza as legacy by adding the commented line # HostKeyAlgorithms +ssh-dss. The comment form is required — a live HostKeyAlgorithms +ssh-dss directive causes OpenSSH 10+ to reject the entire ~/.ssh/config because ssh-dss is no longer a recognised algorithm token.

Host switch switch.local 172.17.17.12 switch.fi
    HostName 172.17.17.12
    User admin
    IdentityFile ~/.ssh/switch_key
    # HostKeyAlgorithms +ssh-dss   ← triggers dispatcher
    KexAlgorithms +diffie-hellman-group1-sha1
    Ciphers aes256-cbc

Host poeswitch poeswitch.local 172.17.17.2 poeswitch.fi
    HostName 172.17.17.2
    User admin
    IdentityFile ~/.ssh/poeswitch_key
    # HostKeyAlgorithms +ssh-dss
    KexAlgorithms +diffie-hellman-group1-sha1
    Ciphers aes256-cbc

All aliases on the Host line are detected. Both stanzas above yield eight legacy hostnames total.

Usage

After adding the alias, ssh works as normal:

ssh switch            # → plink (legacy DSA host)
ssh poeswitch.local   # → plink (legacy DSA host)
ssh myserver          # → real ssh (unaffected)
ssh -p 2222 switch    # → plink with -P 2222
ssh admin@switch      # → plink as admin@switch

On the first connection to a new legacy host, plink will prompt you to verify and accept its host key. That key is cached in ~/.putty/sshhostkeys and the prompt does not reappear. All other connections are silent.

PuTTY session profile (sshdispatcher.putty)

Without additional configuration, plink produces two interactive prompts on every connection to a legacy host — not just the first:

  • KEX warning: "The first key-exchange algorithm supported by the server is diffie-hellman-group1-sha1, which is below the configured warning threshold. Continue with connection? (y/n)" — plink's default algorithm preference list places dh-group1-sha1 below a warn marker, so it flags it every time regardless of whether you've connected before.
  • Anti-spoofing pause: "Access granted. Press Return to begin session." — a PuTTY security feature that pauses after authentication to prevent a rogue server from mimicking a shell prompt before auth completes.

Both are suppressed by the dispatcher. The KEX warning is handled via sshdispatcher.putty, a PuTTY session profile that reconfigures the algorithm preference list to place dh-group1-sha1 before the warn marker. The dispatcher loads it on every plink invocation with plink -load sshdispatcher.putty. The anti-spoofing pause is suppressed with plink's -no-antispoof flag.

install.sh symlinks sshdispatcher.putty from the repository into ~/.putty/sessions/sshdispatcher.putty, where plink can find it by name. If you skip the installer, the dispatcher writes the file itself on first use.

Because it is a standard PuTTY session file, any other PuTTY setting that has no plink CLI equivalent — keepalive intervals, terminal type, bell behaviour, etc. — can be added here to apply to all legacy connections.

Key conversion

PuTTY uses .ppk format instead of OpenSSH format. Convert once per key:

puttygen ~/.ssh/mykey -O private -o ~/.ssh/mykey.ppk

The original key is unchanged. Both files represent the same keypair. sshdispatcher detects the .ppk sibling automatically — if it exists, plink uses it; if not, plink falls back to a password prompt.

Trade-offs

Aspect Detail
Scope Only ssh sessions are dispatched. scp, sftp, and rsync are not handled.
KexAlgorithms / Ciphers plink negotiates its own key exchange and cipher suite. The KexAlgorithms and Ciphers values in ~/.ssh/config are not forwarded to it.
Host-key verification plink handles this interactively on first connection and caches keys in ~/.putty/sshhostkeys. There is no fingerprint pinning in the dispatcher.
Other ssh flags Non-legacy hosts get the full original argv. Legacy hosts get a reconstructed minimal command line (-p, -l, -i only).
Scripts & tools The alias only applies to interactive shells. git, VS Code Remote-SSH, and other tools invoke /usr/bin/ssh or Homebrew's ssh directly and are unaffected.

Man page

man sshdispatcher

Running the tests

brew install bats-core   # if not already installed
bats tests/

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages