From e6667a6b8cec1a28ac04aff9375a0231f1330e84 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sun, 21 Dec 2025 08:43:18 -0500 Subject: [PATCH] Pass client SSH key fingerprint in upstream username When using public key auth, include the client's key fingerprint in the upstream username with format: fp:FINGERPRINT.originaluser This allows upstream servers (like maldoror) to identify users by their original SSH key even when sshpiper uses its own key for upstream authentication. --- libplugin/skel/skel.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/libplugin/skel/skel.go b/libplugin/skel/skel.go index c6e11738..3ab9bf1b 100644 --- a/libplugin/skel/skel.go +++ b/libplugin/skel/skel.go @@ -2,10 +2,13 @@ package skel import ( "bytes" + "crypto/sha256" "crypto/subtle" + "encoding/base64" "fmt" "io" "net" + "strings" "time" "github.com/patrickmn/go-cache" @@ -253,7 +256,8 @@ func (p *SkelPlugin) PublicKeyCallback(conn libplugin.ConnMetadata, publicKey [] return nil, err } - u, err := p.createUpstream(conn, to, nil) + // Pass the client's public key so their fingerprint can be included in the upstream username + u, err := p.createUpstreamWithClientKey(conn, to, nil, publicKey) if err != nil { return nil, err } @@ -262,6 +266,10 @@ func (p *SkelPlugin) PublicKeyCallback(conn libplugin.ConnMetadata, publicKey [] } func (p *SkelPlugin) createUpstream(conn libplugin.ConnMetadata, to SkelPipeTo, originalPassword []byte) (*libplugin.Upstream, error) { + return p.createUpstreamWithClientKey(conn, to, originalPassword, nil) +} + +func (p *SkelPlugin) createUpstreamWithClientKey(conn libplugin.ConnMetadata, to SkelPipeTo, originalPassword []byte, clientPublicKey []byte) (*libplugin.Upstream, error) { host, port, err := libplugin.SplitHostPortForSSH(to.Host(conn)) if err != nil { return nil, err @@ -272,6 +280,17 @@ func (p *SkelPlugin) createUpstream(conn libplugin.ConnMetadata, to SkelPipeTo, user = conn.User() } + // If client public key is provided, prepend fingerprint to username + // Format: "SHA256:xxxxx.originaluser" - maldoror/upstream can parse this + if clientPublicKey != nil { + hash := sha256.Sum256(clientPublicKey) + fingerprint := base64.RawStdEncoding.EncodeToString(hash[:]) + // Replace any dots in fingerprint with underscores to avoid parsing issues + fingerprint = strings.ReplaceAll(fingerprint, ".", "_") + user = "fp:" + fingerprint + "." + user + log.Debugf("passing client fingerprint in username: %s", user) + } + p.cache.SetDefault(conn.UniqueID(), to) u := &libplugin.Upstream{