1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
package ssh
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"net"
"os"
"syscall"
"time"
"github.com/mimecast/dtail/internal/io/dlog"
gossh "golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/term"
)
// GeneratePrivateRSAKey is used by the server to generate its key.
func GeneratePrivateRSAKey(size int) (*rsa.PrivateKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, size)
if err != nil {
return nil, fmt.Errorf("failed to generate RSA key: %w", err)
}
if err = privateKey.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate generated RSA key: %w", err)
}
return privateKey, nil
}
// EncodePrivateKeyToPEM is a helper function for converting a key to PEM format.
func EncodePrivateKeyToPEM(privateKey *rsa.PrivateKey) []byte {
derFormat := x509.MarshalPKCS1PrivateKey(privateKey)
block := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: derFormat,
}
return pem.EncodeToMemory(&block)
}
// Agent used for SSH auth.
func Agent() (gossh.AuthMethod, error) {
return AgentWithKeyIndex(-1)
}
// AgentWithKeyIndex used for SSH auth with a specific key index from the agent.
// If keyIndex is -1, all keys are used. Otherwise, only the specified key is used.
func AgentWithKeyIndex(keyIndex int) (gossh.AuthMethod, error) {
// Use context-aware dialing for SSH agent connection (local Unix socket).
// 2-second timeout is reasonable for local socket connections.
dialer := &net.Dialer{
Timeout: 2 * time.Second,
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
sshAgent, err := dialer.DialContext(ctx, "unix", os.Getenv("SSH_AUTH_SOCK"))
if err != nil {
return nil, fmt.Errorf("failed to connect to SSH agent: %w", err)
}
agentClient := agent.NewClient(sshAgent)
keys, err := agentClient.List()
if err != nil {
return nil, fmt.Errorf("failed to list SSH agent keys: %w", err)
}
for i, key := range keys {
dlog.Common.Debug("Public key", i, key)
}
// If no specific key index requested, use all keys (backwards compatible default)
if keyIndex < 0 {
return gossh.PublicKeysCallback(agentClient.Signers), nil
}
// Use only the specified key index (0-based)
if keyIndex >= len(keys) {
return nil, fmt.Errorf("key index %d out of range (agent has %d keys)", keyIndex, len(keys))
}
dlog.Common.Debug("Using SSH agent key at index", keyIndex)
return gossh.PublicKeysCallback(func() ([]gossh.Signer, error) {
signers, err := agentClient.Signers()
if err != nil {
return nil, err
}
if keyIndex >= len(signers) {
return nil, fmt.Errorf("key index %d out of range (agent has %d signers)", keyIndex, len(signers))
}
// Return only the specified signer
return []gossh.Signer{signers[keyIndex]}, nil
}), nil
}
// EnterKeyPhrase is required to read phrase protected private keys.
func EnterKeyPhrase(keyFile string) []byte {
fmt.Printf("Enter phrase for key %s: ", keyFile)
phrase, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
panic(err)
}
fmt.Printf("%s\n", string(phrase))
return phrase
}
// KeyFile returns the key as a SSH auth method.
func KeyFile(keyFile string) (gossh.AuthMethod, error) {
buffer, err := os.ReadFile(keyFile)
if err != nil {
return nil, err
}
key, err := gossh.ParsePrivateKey(buffer)
if err != nil {
return nil, err
}
// Key phrase support disabled as password will be printed to stdout!
/*
if err == nil {
return gossh.PublicKeys(key), nil
}
keyPhrase := EnterKeyPhrase(keyFile)
key, err = gossh.ParsePrivateKeyWithPassphrase(buffer, keyPhrase)
if err != nil {
return nil, err
}
*/
return gossh.PublicKeys(key), nil
}
// PrivateKey returns the private key as a SSH auth method.
func PrivateKey(keyFile string) (gossh.AuthMethod, error) {
signer, err := KeyFile(keyFile)
if err != nil {
dlog.Common.Debug(keyFile, err)
return nil, err
}
return gossh.AuthMethod(signer), nil
}
|