feat: 增强 ssh-agent 认证与转发可靠性
- 拆分 ssh-agent 认证、连接与 endpoint 解析逻辑 - 新增 IdentityAgent、SSHAgentTimeout、SSHAgentForwardTimeout 和调试事件 - 为 agent list/sign 操作增加独立 deadline,避免硬件 agent 卡死登录 - 支持 agent signer 失败后跳过坏 key 并重试后续 key - 优先处理 RSA-SHA2 签名,兼容现代 OpenSSH 认证要求 - 增强 agent forwarding 的探测、通道空闲超时和关闭清理 - 补充 Windows OpenSSH pipe 与 GPG S.gpg-agent.ssh socket 文件支持 - 增加相关回归测试和 Windows 编译验证覆盖
This commit is contained in:
@@ -0,0 +1,668 @@
|
||||
package starssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
sshagent "golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
var errSSHAgentUnavailable = errors.New("ssh-agent unavailable")
|
||||
var errRetrySSHAgentAuth = errors.New("retry ssh-agent auth")
|
||||
var buildSSHAgentAuthMethodFunc = buildSSHAgentAuthMethod
|
||||
|
||||
type sshAgentTimeouts struct {
|
||||
Dial time.Duration
|
||||
Operation time.Duration
|
||||
Forward time.Duration
|
||||
Endpoint string
|
||||
Resolved resolvedSSHAgentEndpoint
|
||||
Debug SSHAgentDebugFunc
|
||||
SkipFingerprints map[string]struct{}
|
||||
SignFailure func(ssh.PublicKey, error)
|
||||
}
|
||||
|
||||
type sshAgentAuthAttempt struct {
|
||||
mu sync.Mutex
|
||||
skipFingerprints map[string]struct{}
|
||||
retryRequested bool
|
||||
}
|
||||
|
||||
var defaultAuthOrder = []AuthMethodKind{
|
||||
AuthMethodSSHAgent,
|
||||
AuthMethodPrivateKey,
|
||||
AuthMethodPassword,
|
||||
AuthMethodKeyboardInteractive,
|
||||
}
|
||||
|
||||
func effectiveSSHAgentTimeout(info LoginInput) time.Duration {
|
||||
switch {
|
||||
case info.SSHAgentTimeout < 0:
|
||||
return 0
|
||||
case info.SSHAgentTimeout > 0:
|
||||
return info.SSHAgentTimeout
|
||||
default:
|
||||
return defaultSSHAgentTimeout
|
||||
}
|
||||
}
|
||||
|
||||
func effectiveSSHAgentTimeouts(info LoginInput) sshAgentTimeouts {
|
||||
return sshAgentTimeouts{
|
||||
Dial: effectiveDialTimeout(info),
|
||||
Operation: effectiveSSHAgentTimeout(info),
|
||||
Forward: effectiveSSHAgentForwardTimeout(info),
|
||||
Endpoint: info.IdentityAgent,
|
||||
Debug: info.SSHAgentDebug,
|
||||
}
|
||||
}
|
||||
|
||||
func effectiveSSHAgentForwardTimeout(info LoginInput) time.Duration {
|
||||
if info.SSHAgentForwardTimeout > 0 {
|
||||
return info.SSHAgentForwardTimeout
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func buildAuthMethods(info LoginInput) ([]ssh.AuthMethod, func(), error) {
|
||||
return buildAuthMethodsWithAgentAttempt(info, nil)
|
||||
}
|
||||
|
||||
func buildAuthMethodsWithAgentAttempt(info LoginInput, agentAttempt *sshAgentAuthAttempt) ([]ssh.AuthMethod, func(), error) {
|
||||
order, err := normalizeAuthOrder(info.AuthOrder)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
auth := make([]ssh.AuthMethod, 0, len(order))
|
||||
var agentErr error
|
||||
var cleanupFuncs []func()
|
||||
|
||||
for _, methodKind := range order {
|
||||
switch methodKind {
|
||||
case AuthMethodPrivateKey:
|
||||
method, err := buildPrivateKeyAuthMethod(info, agentAttempt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if method != nil {
|
||||
auth = append(auth, method)
|
||||
}
|
||||
case AuthMethodPassword:
|
||||
method := buildPasswordAuthMethod(info.Password, info.PasswordCallback, agentAttempt)
|
||||
if method != nil {
|
||||
auth = append(auth, method)
|
||||
}
|
||||
case AuthMethodKeyboardInteractive:
|
||||
method := buildKeyboardInteractiveAuthMethod(info.Password, info.PasswordCallback, info.KeyboardInteractiveCallback, agentAttempt)
|
||||
if method != nil {
|
||||
auth = append(auth, method)
|
||||
}
|
||||
case AuthMethodSSHAgent:
|
||||
if info.DisableSSHAgent {
|
||||
continue
|
||||
}
|
||||
timeouts := effectiveSSHAgentTimeouts(info)
|
||||
if agentAttempt != nil {
|
||||
timeouts.SkipFingerprints = agentAttempt.skipSnapshot()
|
||||
timeouts.SignFailure = agentAttempt.recordSignFailure
|
||||
}
|
||||
agentMethod, cleanup, err := buildSSHAgentAuthMethodFunc(timeouts)
|
||||
if err != nil {
|
||||
agentErr = err
|
||||
continue
|
||||
}
|
||||
if agentMethod != nil {
|
||||
auth = append(auth, agentMethod)
|
||||
}
|
||||
if cleanup != nil {
|
||||
cleanupFuncs = append(cleanupFuncs, cleanup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(auth) == 0 {
|
||||
if agentErr != nil {
|
||||
return nil, nil, fmt.Errorf("no authentication method provided; ssh-agent unavailable: %w", agentErr)
|
||||
}
|
||||
return nil, nil, errors.New("no authentication method provided: password, private key, or ssh-agent is required")
|
||||
}
|
||||
|
||||
return auth, composeCleanup(cleanupFuncs...), nil
|
||||
}
|
||||
|
||||
func normalizeAuthOrder(order []AuthMethodKind) ([]AuthMethodKind, error) {
|
||||
if len(order) == 0 {
|
||||
return append([]AuthMethodKind(nil), defaultAuthOrder...), nil
|
||||
}
|
||||
|
||||
normalized := make([]AuthMethodKind, 0, len(order))
|
||||
seen := make(map[AuthMethodKind]struct{}, len(order))
|
||||
for _, raw := range order {
|
||||
kind := AuthMethodKind(strings.ToLower(strings.TrimSpace(string(raw))))
|
||||
if kind == "" {
|
||||
return nil, errors.New("auth order contains an empty auth method")
|
||||
}
|
||||
if !isSupportedAuthMethodKind(kind) {
|
||||
return nil, fmt.Errorf("unsupported auth method %q", raw)
|
||||
}
|
||||
if _, exists := seen[kind]; exists {
|
||||
continue
|
||||
}
|
||||
seen[kind] = struct{}{}
|
||||
normalized = append(normalized, kind)
|
||||
}
|
||||
|
||||
if len(normalized) == 0 {
|
||||
return nil, errors.New("auth order is empty")
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
func isSupportedAuthMethodKind(kind AuthMethodKind) bool {
|
||||
switch kind {
|
||||
case AuthMethodPrivateKey, AuthMethodPassword, AuthMethodKeyboardInteractive, AuthMethodSSHAgent:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func shouldRetrySSHAgentAuth(info LoginInput, order []AuthMethodKind) bool {
|
||||
if info.DisableSSHAgent {
|
||||
return false
|
||||
}
|
||||
for _, methodKind := range order {
|
||||
if methodKind == AuthMethodSSHAgent {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func buildPrivateKeyAuthMethod(info LoginInput, agentAttempt *sshAgentAuthAttempt) (ssh.AuthMethod, error) {
|
||||
if strings.TrimSpace(info.Prikey) == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
pemBytes := []byte(info.Prikey)
|
||||
if info.PrikeyPwd == "" {
|
||||
signer, err := ssh.ParsePrivateKey(pemBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ssh.PublicKeysCallback(privateKeySignersCallback(signer, agentAttempt)), nil
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(info.PrikeyPwd))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ssh.PublicKeysCallback(privateKeySignersCallback(signer, agentAttempt)), nil
|
||||
}
|
||||
|
||||
func privateKeySignersCallback(signer ssh.Signer, agentAttempt *sshAgentAuthAttempt) func() ([]ssh.Signer, error) {
|
||||
return func() ([]ssh.Signer, error) {
|
||||
if err := checkSSHAgentRetryPending(agentAttempt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []ssh.Signer{signer}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func buildPasswordAuthMethod(password string, callback func() (string, error), agentAttempt *sshAgentAuthAttempt) ssh.AuthMethod {
|
||||
if password == "" && callback == nil {
|
||||
return nil
|
||||
}
|
||||
return ssh.PasswordCallback(func() (string, error) {
|
||||
if err := checkSSHAgentRetryPending(agentAttempt); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if password != "" {
|
||||
return password, nil
|
||||
}
|
||||
return callback()
|
||||
})
|
||||
}
|
||||
|
||||
func buildKeyboardInteractiveAuthMethod(
|
||||
password string,
|
||||
passwordCallback func() (string, error),
|
||||
challenge ssh.KeyboardInteractiveChallenge,
|
||||
agentAttempt *sshAgentAuthAttempt,
|
||||
) ssh.AuthMethod {
|
||||
if challenge != nil {
|
||||
return ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||
if err := checkSSHAgentRetryPending(agentAttempt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return challenge(user, instruction, questions, echos)
|
||||
})
|
||||
}
|
||||
if password == "" && passwordCallback == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
keyboardInteractiveChallenge := func(user, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||
if err := checkSSHAgentRetryPending(agentAttempt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(questions) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
answer := password
|
||||
if answer == "" {
|
||||
var err error
|
||||
answer, err = passwordCallback()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
answers := make([]string, len(questions))
|
||||
for i := range questions {
|
||||
answers[i] = answer
|
||||
}
|
||||
return answers, nil
|
||||
}
|
||||
return ssh.KeyboardInteractive(keyboardInteractiveChallenge)
|
||||
}
|
||||
|
||||
func buildSSHAgentAuthMethod(timeouts sshAgentTimeouts) (ssh.AuthMethod, func(), error) {
|
||||
conn, resolved, err := dialSSHAgentWithDebug("auth", timeouts)
|
||||
if err != nil {
|
||||
if errors.Is(err, errSSHAgentUnavailable) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
if conn == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
conn = wrapSSHAgentConnWithDeadline(conn, timeouts.Operation)
|
||||
|
||||
started := time.Now()
|
||||
signers, err := sshagent.NewClient(conn).Signers()
|
||||
err = normalizeSSHAgentError(err)
|
||||
logSSHAgentDebug(timeouts.Debug, SSHAgentDebugEvent{
|
||||
Step: "auth",
|
||||
Source: resolved.Source,
|
||||
Endpoint: resolved.Endpoint,
|
||||
Network: resolved.Network,
|
||||
Phase: "list",
|
||||
Status: debugStatus(err),
|
||||
Duration: time.Since(started),
|
||||
KeyCount: len(signers),
|
||||
Err: err,
|
||||
})
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(signers) == 0 {
|
||||
_ = conn.Close()
|
||||
return nil, nil, errors.New("ssh-agent has no loaded keys")
|
||||
}
|
||||
|
||||
timeouts.Resolved = resolved
|
||||
orderedSigners := orderSSHAgentSigners(signers)
|
||||
filteredSigners := filterSSHAgentSignersForRetry(orderedSigners, timeouts)
|
||||
if len(filteredSigners) == 0 {
|
||||
_ = conn.Close()
|
||||
return nil, nil, errors.New("ssh-agent has no usable keys")
|
||||
}
|
||||
|
||||
return ssh.PublicKeys(filteredSigners...), func() {
|
||||
_ = conn.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
func orderSSHAgentSigners(signers []ssh.Signer) []ssh.Signer {
|
||||
type orderedSigner struct {
|
||||
signer ssh.Signer
|
||||
index int
|
||||
score int
|
||||
comment string
|
||||
}
|
||||
|
||||
ordered := make([]orderedSigner, 0, len(signers))
|
||||
for index, signer := range signers {
|
||||
if signer == nil || signer.PublicKey() == nil {
|
||||
continue
|
||||
}
|
||||
ordered = append(ordered, orderedSigner{
|
||||
signer: signer,
|
||||
index: index,
|
||||
score: sshAgentSignerPriority(signer),
|
||||
comment: sshAgentSignerComment(signer),
|
||||
})
|
||||
}
|
||||
|
||||
sort.SliceStable(ordered, func(i, j int) bool {
|
||||
if ordered[i].score != ordered[j].score {
|
||||
return ordered[i].score > ordered[j].score
|
||||
}
|
||||
return ordered[i].index < ordered[j].index
|
||||
})
|
||||
|
||||
result := make([]ssh.Signer, 0, len(ordered))
|
||||
for _, item := range ordered {
|
||||
result = append(result, item.signer)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func sshAgentSignerComment(signer ssh.Signer) string {
|
||||
if signer == nil {
|
||||
return ""
|
||||
}
|
||||
if key, ok := signer.PublicKey().(*sshagent.Key); ok {
|
||||
return key.Comment
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func sshAgentSignerPriority(signer ssh.Signer) int {
|
||||
comment := strings.TrimSpace(sshAgentSignerComment(signer))
|
||||
if comment == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
score := 0
|
||||
if priority, ok := parseSSHAgentSignerPriority(comment); ok {
|
||||
score += 100000 + priority*1000
|
||||
}
|
||||
|
||||
lower := strings.ToLower(comment)
|
||||
if strings.Contains(lower, "current") {
|
||||
score += 400
|
||||
}
|
||||
if strings.Contains(lower, "cardno:") {
|
||||
score += 300
|
||||
}
|
||||
if strings.Contains(lower, "card ") || strings.Contains(lower, " card") || strings.Contains(lower, "card:") {
|
||||
score += 100
|
||||
}
|
||||
if strings.Contains(lower, "openpgp") || strings.Contains(lower, "gpg") {
|
||||
score += 50
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
func parseSSHAgentSignerPriority(comment string) (int, bool) {
|
||||
lower := strings.ToLower(comment)
|
||||
index := strings.Index(lower, "priority=")
|
||||
if index < 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
value := strings.TrimSpace(comment[index+len("priority="):])
|
||||
if value == "" {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
end := 0
|
||||
for end < len(value) {
|
||||
ch := value[end]
|
||||
if ch == '+' || ch == '-' || (ch >= '0' && ch <= '9') {
|
||||
end++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if end == 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
priority, err := strconv.Atoi(value[:end])
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return priority, true
|
||||
}
|
||||
|
||||
func filterSSHAgentSignersForRetry(signers []ssh.Signer, timeouts sshAgentTimeouts) []ssh.Signer {
|
||||
filteredSigners := make([]ssh.Signer, 0, len(signers))
|
||||
for _, signer := range signers {
|
||||
if signer == nil {
|
||||
continue
|
||||
}
|
||||
publicKey := signer.PublicKey()
|
||||
if publicKey == nil {
|
||||
continue
|
||||
}
|
||||
if _, skip := timeouts.SkipFingerprints[ssh.FingerprintSHA256(publicKey)]; skip {
|
||||
continue
|
||||
}
|
||||
if timeouts.SignFailure == nil && timeouts.Debug == nil {
|
||||
filteredSigners = append(filteredSigners, signer)
|
||||
continue
|
||||
}
|
||||
filteredSigners = append(filteredSigners, wrapSSHAgentSigner(signer, sshAgentSignerOptions{
|
||||
Resolved: timeouts.Resolved,
|
||||
Debug: timeouts.Debug,
|
||||
SignFailure: timeouts.SignFailure,
|
||||
}))
|
||||
}
|
||||
return filteredSigners
|
||||
}
|
||||
|
||||
func newSSHAgentAuthAttempt() *sshAgentAuthAttempt {
|
||||
return &sshAgentAuthAttempt{
|
||||
skipFingerprints: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *sshAgentAuthAttempt) begin() {
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.retryRequested = false
|
||||
}
|
||||
|
||||
func (a *sshAgentAuthAttempt) skipSnapshot() map[string]struct{} {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if len(a.skipFingerprints) == 0 {
|
||||
return nil
|
||||
}
|
||||
snapshot := make(map[string]struct{}, len(a.skipFingerprints))
|
||||
for fingerprint := range a.skipFingerprints {
|
||||
snapshot[fingerprint] = struct{}{}
|
||||
}
|
||||
return snapshot
|
||||
}
|
||||
|
||||
func (a *sshAgentAuthAttempt) recordSignFailure(publicKey ssh.PublicKey, err error) {
|
||||
_ = err
|
||||
if a == nil || publicKey == nil {
|
||||
return
|
||||
}
|
||||
a.skipFingerprint(ssh.FingerprintSHA256(publicKey))
|
||||
}
|
||||
|
||||
func (a *sshAgentAuthAttempt) skipFingerprint(fingerprint string) {
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.retryRequested = true
|
||||
if fingerprint != "" {
|
||||
a.skipFingerprints[fingerprint] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *sshAgentAuthAttempt) shouldRetry() bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.retryRequested
|
||||
}
|
||||
|
||||
func checkSSHAgentRetryPending(agentAttempt *sshAgentAuthAttempt) error {
|
||||
if agentAttempt != nil && agentAttempt.shouldRetry() {
|
||||
return errRetrySSHAgentAuth
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sshAgentRetrySigner struct {
|
||||
signer ssh.Signer
|
||||
publicKey ssh.PublicKey
|
||||
options sshAgentSignerOptions
|
||||
}
|
||||
|
||||
type sshAgentRetryAlgorithmSigner struct {
|
||||
sshAgentRetrySigner
|
||||
algorithmSigner ssh.AlgorithmSigner
|
||||
}
|
||||
|
||||
type sshAgentRetryMultiAlgorithmSigner struct {
|
||||
sshAgentRetryAlgorithmSigner
|
||||
multiAlgorithmSigner ssh.MultiAlgorithmSigner
|
||||
}
|
||||
|
||||
type sshAgentSignerOptions struct {
|
||||
Resolved resolvedSSHAgentEndpoint
|
||||
Debug SSHAgentDebugFunc
|
||||
SignFailure func(ssh.PublicKey, error)
|
||||
}
|
||||
|
||||
func wrapSSHAgentSignerForRetry(signer ssh.Signer, onFailure func(ssh.PublicKey, error)) ssh.Signer {
|
||||
return wrapSSHAgentSigner(signer, sshAgentSignerOptions{SignFailure: onFailure})
|
||||
}
|
||||
|
||||
func wrapSSHAgentSigner(signer ssh.Signer, options sshAgentSignerOptions) ssh.Signer {
|
||||
publicKey := signer.PublicKey()
|
||||
base := sshAgentRetrySigner{
|
||||
signer: signer,
|
||||
publicKey: publicKey,
|
||||
options: options,
|
||||
}
|
||||
if multiAlgorithmSigner, ok := signer.(ssh.MultiAlgorithmSigner); ok {
|
||||
return &sshAgentRetryMultiAlgorithmSigner{
|
||||
sshAgentRetryAlgorithmSigner: sshAgentRetryAlgorithmSigner{
|
||||
sshAgentRetrySigner: base,
|
||||
algorithmSigner: multiAlgorithmSigner,
|
||||
},
|
||||
multiAlgorithmSigner: multiAlgorithmSigner,
|
||||
}
|
||||
}
|
||||
if algorithmSigner, ok := signer.(ssh.AlgorithmSigner); ok {
|
||||
return &sshAgentRetryAlgorithmSigner{
|
||||
sshAgentRetrySigner: base,
|
||||
algorithmSigner: algorithmSigner,
|
||||
}
|
||||
}
|
||||
return &base
|
||||
}
|
||||
|
||||
func (s *sshAgentRetrySigner) PublicKey() ssh.PublicKey {
|
||||
return s.publicKey
|
||||
}
|
||||
|
||||
func (s *sshAgentRetrySigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
|
||||
started := time.Now()
|
||||
signature, err := s.signer.Sign(rand, data)
|
||||
return signature, s.finishSign(started, err)
|
||||
}
|
||||
|
||||
func (s *sshAgentRetrySigner) finishSign(started time.Time, err error) error {
|
||||
err = normalizeSSHAgentError(err)
|
||||
s.logSignDebug(started, err)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if s.options.SignFailure != nil {
|
||||
s.options.SignFailure(s.publicKey, err)
|
||||
return wrapSSHAgentSignError(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *sshAgentRetrySigner) logSignDebug(started time.Time, err error) {
|
||||
if s == nil || s.options.Debug == nil {
|
||||
return
|
||||
}
|
||||
logSSHAgentDebug(s.options.Debug, SSHAgentDebugEvent{
|
||||
Step: "auth",
|
||||
Source: s.options.Resolved.Source,
|
||||
Endpoint: s.options.Resolved.Endpoint,
|
||||
Network: s.options.Resolved.Network,
|
||||
Phase: "sign",
|
||||
Status: debugStatus(err),
|
||||
Duration: time.Since(started),
|
||||
Err: err,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sshAgentRetryAlgorithmSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*ssh.Signature, error) {
|
||||
algorithm = preferredSSHAgentSignAlgorithm(s.publicKey, algorithm, nil)
|
||||
started := time.Now()
|
||||
signature, err := s.algorithmSigner.SignWithAlgorithm(rand, data, algorithm)
|
||||
return signature, s.finishSign(started, err)
|
||||
}
|
||||
|
||||
func (s *sshAgentRetryMultiAlgorithmSigner) Algorithms() []string {
|
||||
return s.multiAlgorithmSigner.Algorithms()
|
||||
}
|
||||
|
||||
func (s *sshAgentRetryMultiAlgorithmSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*ssh.Signature, error) {
|
||||
algorithm = preferredSSHAgentSignAlgorithm(s.publicKey, algorithm, s.multiAlgorithmSigner.Algorithms())
|
||||
started := time.Now()
|
||||
signature, err := s.multiAlgorithmSigner.SignWithAlgorithm(rand, data, algorithm)
|
||||
return signature, s.finishSign(started, err)
|
||||
}
|
||||
|
||||
func preferredSSHAgentSignAlgorithm(publicKey ssh.PublicKey, requested string, algorithms []string) string {
|
||||
if publicKey == nil || publicKey.Type() != ssh.KeyAlgoRSA || requested != ssh.KeyAlgoRSA {
|
||||
return requested
|
||||
}
|
||||
if len(algorithms) == 0 {
|
||||
return ssh.KeyAlgoRSASHA256
|
||||
}
|
||||
for _, algorithm := range algorithms {
|
||||
if algorithm == ssh.KeyAlgoRSA {
|
||||
break
|
||||
}
|
||||
if algorithm == ssh.KeyAlgoRSASHA256 || algorithm == ssh.KeyAlgoRSASHA512 {
|
||||
return algorithm
|
||||
}
|
||||
}
|
||||
return requested
|
||||
}
|
||||
|
||||
func wrapSSHAgentSignError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%w: %v", errRetrySSHAgentAuth, normalizeSSHAgentError(err))
|
||||
}
|
||||
|
||||
func composeCleanup(funcs ...func()) func() {
|
||||
if len(funcs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return func() {
|
||||
for i := len(funcs) - 1; i >= 0; i-- {
|
||||
if funcs[i] != nil {
|
||||
funcs[i]()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user