Add SSH certificate authentication support#1495
Open
labros-mediaalpha wants to merge 1 commit intomscdex:masterfrom
Open
Add SSH certificate authentication support#1495labros-mediaalpha wants to merge 1 commit intomscdex:masterfrom
labros-mediaalpha wants to merge 1 commit intomscdex:masterfrom
Conversation
SSH certificates (e.g. ssh-ed25519-cert-v01@openssh.com) are widely used
in enterprise environments where a CA signs user keys instead of managing
authorized_keys files. This change adds support for authenticating with
them.
Three components are needed:
keyParser: Add Cert_Public class
- Parses certificate public key files (id_xxx-cert.pub) and raw certificate
blobs returned by SSH agents into a Cert_Public object.
- Cert_Public stores the full certificate blob and returns it from
getPublicSSH(), which is what the server needs to verify the cert against
its trusted CA.
- The existing OpenSSH_Public.parse already matched cert types via regex;
it now routes them to Cert_Public instead of parseDER (which only handles
plain public keys and would discard the cert fields).
- Binary key parsing (used for SSH agent identity blobs) similarly detects
cert types and creates Cert_Public objects rather than failing.
Protocol: Fix signature algorithm for cert auth
- authPK was writing the certificate type name (e.g.
"ssh-ed25519-cert-v01@openssh.com") as the signature algorithm in the
signed USERAUTH_REQUEST. The SSH protocol requires the underlying key
algorithm ("ssh-ed25519") in that field. The server rejects the
authentication if these don't match.
Client: Support publicKey option and pre-parsed key objects
- Accepts a publicKey config option (cert file path/Buffer/parsed key) to
use for identification when it differs from privateKey (e.g. when you
have id_ed25519 + id_ed25519-cert.pub as separate files).
- Also accepts already-parsed key objects as privateKey/publicKey, so
callers that pre-parse keys (e.g. to auto-detect certs alongside keys)
can pass them directly without the client re-parsing.
- The USERAUTH_PK_OK handler signs challenges with privateKey so the
signing key is always the real private key regardless of whether a
certificate is being used for identification.
Usage:
client.connect({
host: 'example.com',
username: 'user',
privateKey: fs.readFileSync('~/.ssh/id_ed25519'),
publicKey: fs.readFileSync('~/.ssh/id_ed25519-cert.pub'), // optional
});
When publicKey is omitted and privateKey is a cert-less key, behaviour
is identical to before. SSH agents that return cert-type identities are
now handled correctly too.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Transparency note: 100% Authored by Claude Sonnet 4.6
Problem
SSH certificates (
ssh-ed25519-cert-v01@openssh.comand equivalent types for RSA/ECDSA) are widely used in enterprise environments where a CA signs user keys. This PR adds support for authenticating with them. Currently all three layers silently fail:ssh-ed25519) and rejects the attemptprivateKey, making it impossible to pass a cert key constructed outside the libraryChanges
lib/protocol/keyParser.jsCert_Publicclass that stores the full certificate blob and returns it fromgetPublicSSH(). This is what the server needs to verify the certificate against its trusted CA.OpenSSH_Public.parsealready matched cert types via its regex; it now routes them toCert_Publicinstead ofparseDER(which only handles plain public keys and would silently discard the certificate fields).Cert_Publicobjects rather than returning an error, so agent-held certificates are no longer silently filtered.lib/protocol/Protocol.jsauthPKwas writing the certificate type name (e.g.ssh-ed25519-cert-v01@openssh.com) as the signature algorithm inside the signedSSH_MSG_USERAUTH_REQUEST. Per the SSH certificate protocol the signature algorithm must be the underlying key type (ssh-ed25519). Servers reject the auth attempt when these do not match.lib/client.jspublicKeyconfig option (cert file as string/Buffer, or pre-parsed key) to use for identification when it differs fromprivateKey— the common case ofid_ed25519+id_ed25519-cert.pubas separate files.privateKey/publicKeyso callers that pre-parse keys can pass them directly.USERAUTH_PK_OKsigning callback always usesprivateKey(the real private key) regardless of whether a certificate is being used for identification.Usage
When
publicKeyis omitted and no cert-type keys are involved, behaviour is identical to before this change.Relation to #808
PR #808 proposed a similar
publicKeyoption but targeted the old v0.x stream-based API. This PR targets the current v1.x API, adds the agent fix, and corrects the signature algorithm bug that would have caused #808 to fail against a real server even if merged.