Skip to content

Commit a6b9fbc

Browse files
committed
fix(ssh): wrap agent signers to continue to next identity on signing failure
When an SSH agent signer fails to sign (e.g. user cancels a FIDO2/security-key user-presence prompt), golang.org/x/crypto/ssh treats the error as fatal and aborts the entire publickey authentication, preventing remaining identities from being tried. This deviates from OpenSSH behavior where a declined signature simply moves on to the next key. This change wraps agent-backed signers in a failoverSigner that returns a deliberately invalid signature on signing failure instead of propagating the error. The server rejects the invalid signature as a normal auth failure, allowing RetryableAuthMethod to continue with the next identity. Also adds conndebug logging for: - Agent socket dial failures - Agent key listing errors - Number of identities provided by the agent - Each agent identity being attempted - Per-key signing failures Fixes #3365
1 parent c99022c commit a6b9fbc

2 files changed

Lines changed: 63 additions & 1 deletion

File tree

pkg/remote/sshclient.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *wconfig.ConnK
310310
if len(*authSockSignersPtr) != 0 {
311311
authSockSigner := (*authSockSignersPtr)[0]
312312
*authSockSignersPtr = (*authSockSignersPtr)[1:]
313+
blocklogger.Infof(connCtx, "[conndebug] trying agent identity %s %s...\n",
314+
authSockSigner.PublicKey().Type(), ssh.FingerprintSHA256(authSockSigner.PublicKey()))
313315
return []ssh.Signer{authSockSigner}, nil
314316
}
315317

@@ -772,10 +774,19 @@ func createClientConfig(connCtx context.Context, sshKeywords *wconfig.ConnKeywor
772774
if !utilfn.SafeDeref(sshKeywords.SshIdentitiesOnly) && agentPath != "" {
773775
conn, err := dialIdentityAgent(agentPath)
774776
if err != nil {
777+
blocklogger.Infof(connCtx, "[conndebug] failed to open identity agent socket %q: %v\n", agentPath, err)
775778
log.Printf("Failed to open Identity Agent Socket %q: %v", agentPath, err)
776779
} else {
777780
agentClient = agent.NewClient(conn)
778-
authSockSigners, _ = agentClient.Signers()
781+
agentSigners, err := agentClient.Signers()
782+
if err != nil {
783+
blocklogger.Infof(connCtx, "[conndebug] failed to list identity agent keys: %v\n", err)
784+
log.Printf("Failed to list identity agent keys: %v", err)
785+
}
786+
blocklogger.Infof(connCtx, "[conndebug] identity agent provided %d identities\n", len(agentSigners))
787+
for _, agentSigner := range agentSigners {
788+
authSockSigners = append(authSockSigners, failoverSigner{signer: agentSigner, connCtx: connCtx})
789+
}
779790
}
780791
}
781792

pkg/remote/sshsigners.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2025, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package remote
5+
6+
import (
7+
"context"
8+
"io"
9+
10+
"github.com/wavetermdev/waveterm/pkg/blocklogger"
11+
"golang.org/x/crypto/ssh"
12+
)
13+
14+
type failoverSigner struct {
15+
signer ssh.Signer
16+
connCtx context.Context
17+
}
18+
19+
func (f failoverSigner) PublicKey() ssh.PublicKey {
20+
return f.signer.PublicKey()
21+
}
22+
23+
func (f failoverSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
24+
sig, err := f.signer.Sign(rand, data)
25+
if err == nil {
26+
return sig, nil
27+
}
28+
blocklogger.Infof(f.connCtx, "[conndebug] agent signing failed for key %s %s (%v); continuing with next identity\n",
29+
f.signer.PublicKey().Type(), ssh.FingerprintSHA256(f.signer.PublicKey()), err)
30+
return f.invalidSignature(), nil
31+
}
32+
33+
func (f failoverSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*ssh.Signature, error) {
34+
if as, ok := f.signer.(ssh.AlgorithmSigner); ok {
35+
sig, err := as.SignWithAlgorithm(rand, data, algorithm)
36+
if err == nil {
37+
return sig, nil
38+
}
39+
blocklogger.Infof(f.connCtx, "[conndebug] agent signing failed for key %s %s (%v); continuing with next identity\n",
40+
f.signer.PublicKey().Type(), ssh.FingerprintSHA256(f.signer.PublicKey()), err)
41+
return f.invalidSignature(), nil
42+
}
43+
return f.Sign(rand, data)
44+
}
45+
46+
func (f failoverSigner) invalidSignature() *ssh.Signature {
47+
return &ssh.Signature{
48+
Format: f.signer.PublicKey().Type(),
49+
Blob: []byte("invalid-signature-identity-skipped"),
50+
}
51+
}

0 commit comments

Comments
 (0)