deniz.in

Markets

Weather

Loading weather

· via Hacker News – Front Page (native)

LogRaker forks swift-nio-ssh to add RSA support for legacy EC2 keys

LogRaker published a fork of Apple's swift-nio-ssh that adds RSA key support for pre-2021 EC2 key pairs, and documented the subtle SSH authentication detail that made it necessary.

LogRaker forks swift-nio-ssh to add RSA support for legacy EC2 keys

LogRaker, a log-viewing product that reaches machines over SSH, has documented why it forked Apple's swift-nio-ssh library to add RSA key support — a capability upstream leaves out on purpose, preferring modern primitives, with ed25519 the better default for new deployments.

The constraint, according to LogRaker, is that clients rarely get to choose their key format. Amazon EC2 issued only PKCS#1 RSA .pem key pairs until ed25519 support arrived in 2021, so every older key pair is RSA, and the instances those keys unlock are often still running. A tool that cannot authenticate with the key already sitting in ~/.ssh simply cannot reach those machines.

No extension point

Adding a key type from outside the module is not possible. LogRaker explains that NIOSSHPrivateKey stores its key material in an internal enum, that the memberwise initializer is private, and that the only public initializers cover one algorithm each. With no protocol to conform to and no case to extend, supporting RSA means editing the module itself — a fork, in effect.

The change touched eight files against upstream 0.15.0: Package.swift, to pull RSA signing in from swift-crypto's CryptoExtras product; five files under Sources/NIOSSH/; and one upstream test, which had to change because it used ssh-rsa as its stand-in for an unrecognized algorithm.

Two strings that must agree

The subtlest part involves the publickey userauth request, which carries a public key blob plus an algorithm name. For ed25519 and the ECDSA curves those strings are identical, so upstream writes the key's own tag into the algorithm field without a second thought. RSA is the exception: the key blob keeps the ssh-rsa tag, while the algorithm field must name the signature algorithm — rsa-sha2-256 or rsa-sha2-512 under RFC 8332. The bare ssh-rsa label means RSA with SHA-1, which OpenSSH 8.8 disabled by default in 2021, so current servers reject it outright no matter how sound the key is.

LogRaker's fork adds a signatureAlgorithmName property alongside keyPrefix, but the real difficulty is that the algorithm name is written in two places: SSHMessages.swift, which builds the wire message, and UserAuthSignablePayload.swift, which builds the buffer being signed. One string is what the client claims; the other is what it signs. If they diverge, the message can be well-formed at every byte and carry a valid RSA signature, and the server will still refuse it — and because the rejection looks identical to a wrong key from the server's side, nothing in the error points at the mismatch. A new test pins both write sites together, so a future change to either produces a failure rather than a debugging session.

A fixed choice, not a lazy one

The fork always offers rsa-sha2-512 and never negotiates. That is forced rather than convenient: swift-nio-ssh does not parse SSH_MSG_EXT_INFO, so the server's server-sig-algs list never reaches code that could act on it. The default is safe anyway, LogRaker argues, since rsa-sha2-512 has been accepted since OpenSSH 7.2 in 2016 — older than anything a log viewer would realistically connect to. One related wrinkle: the fork's list of recognized algorithms must include the key tag alongside both signature algorithm names, because that list is matched against incoming userauth requests and PK_OK replies.

The price of vendoring

LogRaker lists three habits that keep the fork honest. Every edit is marked with a LOGRAKER FORK comment, making the full diff against upstream a single grep and turning the markers into a rebase checklist. A serialization test compares the generated public key blob byte-for-byte with ssh-keygen's output for the same key, on the theory that mistakes in hand-written wire encoding can look plausible and still slip through review, with OpenSSH the only arbiter that counts. And security advisories will not arrive on their own: a locally pinned Swift package never reports upstream updates, so checking the swift-nio-ssh advisories has to become a routine someone actually performs.

The fork is public under Apache 2.0 at lograker/swift-nio-ssh, but its README is blunt about the limits: it exists to serve one app, is pinned to 0.15.0 and has not been audited. LogRaker's own verdict for anyone weighing the same path is probably don't — unless RSA keys guarding machines you must reach leave no alternative. The fork can be deleted the day upstream ships RSA support of its own.

Why it matters

A small fork captures a recurring problem: security libraries retire old algorithms faster than the world retires the credentials that depend on them. Cloud providers issued RSA keys for years, and those keys still open production machines, so tooling that supports only modern primitives strands real workloads. The post is also a candid accounting of what forking a security-critical dependency costs — silent advisories, permanent rebase duty, unaudited code — and a useful primer on an SSH detail, key blob tags versus RFC 8332 signature algorithm names and the SHA-1 deprecation behind it, that would trip up any implementer approaching RSA fresh.

  • #swift
  • #ssh
  • #open-source
  • #cryptography
  • #apple

Related posts