Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion pkg/remote/sshclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *wconfig.ConnK
if len(*authSockSignersPtr) != 0 {
authSockSigner := (*authSockSignersPtr)[0]
*authSockSignersPtr = (*authSockSignersPtr)[1:]
blocklogger.Infof(connCtx, "[conndebug] trying agent identity %s %s...\n",
authSockSigner.PublicKey().Type(), ssh.FingerprintSHA256(authSockSigner.PublicKey()))
return []ssh.Signer{authSockSigner}, nil
}

Expand Down Expand Up @@ -772,10 +774,19 @@ func createClientConfig(connCtx context.Context, sshKeywords *wconfig.ConnKeywor
if !utilfn.SafeDeref(sshKeywords.SshIdentitiesOnly) && agentPath != "" {
conn, err := dialIdentityAgent(agentPath)
if err != nil {
blocklogger.Infof(connCtx, "[conndebug] failed to open identity agent socket %q: %v\n", agentPath, err)
log.Printf("Failed to open Identity Agent Socket %q: %v", agentPath, err)
} else {
agentClient = agent.NewClient(conn)
authSockSigners, _ = agentClient.Signers()
agentSigners, err := agentClient.Signers()
if err != nil {
blocklogger.Infof(connCtx, "[conndebug] failed to list identity agent keys: %v\n", err)
log.Printf("Failed to list identity agent keys: %v", err)
}
blocklogger.Infof(connCtx, "[conndebug] identity agent provided %d identities\n", len(agentSigners))
for _, agentSigner := range agentSigners {
authSockSigners = append(authSockSigners, failoverSigner{signer: agentSigner, connCtx: connCtx})
}
}
}

Expand Down
51 changes: 51 additions & 0 deletions pkg/remote/sshsigners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

package remote

import (
"context"
"io"

"github.com/wavetermdev/waveterm/pkg/blocklogger"
"golang.org/x/crypto/ssh"
)

type failoverSigner struct {
signer ssh.Signer
connCtx context.Context
}

func (f failoverSigner) PublicKey() ssh.PublicKey {
return f.signer.PublicKey()
}

func (f failoverSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
sig, err := f.signer.Sign(rand, data)
if err == nil {
return sig, nil
}
blocklogger.Infof(f.connCtx, "[conndebug] agent signing failed for key %s %s (%v); continuing with next identity\n",
f.signer.PublicKey().Type(), ssh.FingerprintSHA256(f.signer.PublicKey()), err)
return f.invalidSignature(), nil
}

func (f failoverSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*ssh.Signature, error) {
if as, ok := f.signer.(ssh.AlgorithmSigner); ok {
sig, err := as.SignWithAlgorithm(rand, data, algorithm)
if err == nil {
return sig, nil
}
blocklogger.Infof(f.connCtx, "[conndebug] agent signing failed for key %s %s (%v); continuing with next identity\n",
f.signer.PublicKey().Type(), ssh.FingerprintSHA256(f.signer.PublicKey()), err)
return f.invalidSignature(), nil
}
return f.Sign(rand, data)
}

func (f failoverSigner) invalidSignature() *ssh.Signature {
return &ssh.Signature{
Format: f.signer.PublicKey().Type(),
Blob: []byte("invalid-signature-identity-skipped"),
Comment on lines +39 to +49

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file outline ==\n'
ast-grep outline pkg/remote/sshsigners.go --view expanded || true

printf '\n== relevant file lines ==\n'
cat -n pkg/remote/sshsigners.go | sed -n '1,220p'

printf '\n== search for invalidSignature and SignWithAlgorithm usages ==\n'
rg -n "invalidSignature|SignWithAlgorithm|Sign\(" pkg/remote -S || true

Repository: wavetermdev/waveterm

Length of output: 3219


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== go env GOPATH GOMODCACHE ==\n'
go env GOPATH GOMODCACHE 2>/dev/null || true

printf '\n== locate ssh package source in module cache ==\n'
GOMODCACHE="$(go env GOMODCACHE 2>/dev/null || true)"
if [ -n "${GOMODCACHE:-}" ] && [ -d "$GOMODCACHE" ]; then
  fd -a "signature.go" "$GOMODCACHE" | rg "golang.org/x/crypto/ssh|crypto/ssh" || true
  fd -a "signer.go" "$GOMODCACHE" | rg "golang.org/x/crypto/ssh|crypto/ssh" || true
fi

printf '\n== grep for Signature.Format docs and SignWithAlgorithm implementations ==\n'
if [ -n "${GOMODCACHE:-}" ] && [ -d "$GOMODCACHE" ]; then
  rg -n "type Signature|Format.*algorithm|SignWithAlgorithm|AlgorithmSigner" "$GOMODCACHE"/**/golang.org/x/crypto@*/ssh 2>/dev/null || true
fi

Repository: wavetermdev/waveterm

Length of output: 359


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== search for failoverSigner usage ==\n'
rg -n "failoverSigner|invalidSignature\(" -S .

printf '\n== search for ssh signer/auth flow nearby ==\n'
rg -n "AlgorithmSigner|PublicKeys|PublicKeyCallback|ssh.NewSigner|ssh.Signer" pkg -S

Repository: wavetermdev/waveterm

Length of output: 2338


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== relevant sshclient lines around public key callback ==\n'
cat -n pkg/remote/sshclient.go | sed -n '276,360p'

printf '\n== relevant sshclient lines around authSockSigner wiring ==\n'
cat -n pkg/remote/sshclient.go | sed -n '760,820p'

Repository: wavetermdev/waveterm

Length of output: 7633


🌐 Web query:

golang.org/x/crypto/ssh Signature.Format SignWithAlgorithm algorithm string RSA rsa-sha2-256 documentation

💡 Result:

In the Go golang.org/x/crypto/ssh package, the Signature struct holds a Format field of type string, which identifies the signature algorithm used (e.g., "rsa-sha2-256" or "rsa-sha2-512") [1][2]. For custom signing implementations, the AlgorithmSigner interface allows you to specify a signing algorithm through the SignWithAlgorithm method [3][4]: type AlgorithmSigner interface { Signer // SignWithAlgorithm is like Signer.Sign, but allows specifying a desired // signing algorithm. SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) } When implementing SignWithAlgorithm for RSA keys, you can pass "rsa-sha2-256" or "rsa-sha2-512" as the algorithm argument [3][5]. The implementation is expected to perform the following steps [3]: 1. Validate that the requested algorithm is supported for the key type (e.g., using ssh.KeyAlgoRSASHA256 or ssh.KeyAlgoRSASHA512) [3][2]. 2. Use the corresponding hash function to hash the input data. 3. Perform the cryptographic signature operation using the underlying RSA key. 4. Construct and return a Signature struct where the Format field is set to the algorithm string provided, and the Blob contains the signature data [3]. Note that rsa-sha2-256 and rsa-sha2-512 are recognized as signature algorithm identifiers in this package [2]. While the underlying public key format for both is still ssh-rsa, these identifiers allow the SSH protocol to negotiate stronger hashing algorithms than the legacy ssh-rsa (which uses SHA-1) [2]. When verifying or signing, the ssh package uses these strings to select the appropriate hash function for the operation [3][2].

Citations:


Preserve the requested signing algorithm in the failover signature. SignWithAlgorithm should return a Signature.Format matching algorithm (for example, rsa-sha2-256), not the key type, so the fallback path doesn’t advertise the wrong signature algorithm.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/remote/sshsigners.go` around lines 39 - 49, The failover path in
failoverSigner.SignWithAlgorithm is returning a signature whose Format is based
on the key type instead of the requested signing algorithm. Update
invalidSignature (and its call site in SignWithAlgorithm) so the fallback
signature preserves the algorithm passed into SignWithAlgorithm, using that
value for Signature.Format rather than signer.PublicKey().Type().

}
}