This repository has no description
13 kB
320 lines
1diff --git a/agent/agent.go b/agent/agent.go
2index ac39d24..b1e399b 100644
3--- a/agent/agent.go
4+++ b/agent/agent.go
5@@ -26,6 +26,7 @@ import (
6 "github.com/google/go-tpm/tpm2/transport"
7 "golang.org/x/crypto/ssh"
8 "golang.org/x/crypto/ssh/agent"
9+ "golang.org/x/sys/unix"
10 "golang.org/x/text/cases"
11 "golang.org/x/text/language"
12 )
13@@ -39,17 +40,18 @@ var (
14 var SSH_TPM_AGENT_ADD = "tpm-add-key"
15
16 type Agent struct {
17- mu sync.Mutex
18- tpm func() transport.TPMCloser
19- op func() ([]byte, error)
20- pin func(key.SSHTPMKeys) ([]byte, error)
21- listener *net.UnixListener
22- quit chan any
23- wg sync.WaitGroup
24- keyring func() *keyring.ThreadKeyring
25- keys []key.SSHTPMKeys
26- hierkeys []*key.HierSSHTPMKey
27- agents []agent.ExtendedAgent
28+ mu sync.Mutex
29+ tpm func() transport.TPMCloser
30+ op func() ([]byte, error)
31+ pin func(key.SSHTPMKeys, int) ([]byte, error)
32+ listener *net.UnixListener
33+ quit chan any
34+ wg sync.WaitGroup
35+ keyring func() *keyring.ThreadKeyring
36+ keys []key.SSHTPMKeys
37+ hierkeys []*key.HierSSHTPMKey
38+ agents []agent.ExtendedAgent
39+ clientPID int
40 }
41
42 var _ agent.ExtendedAgent = &Agent{}
43@@ -120,8 +122,8 @@ func (a *Agent) signers() ([]ssh.Signer, error) {
44 s, err := ssh.NewSignerFromSigner(k.Signer(
45 a.keyring(), a.op, a.tpm,
46 func(_ *keyfile.TPMKey) ([]byte, error) {
47- // Shimming the function to get the correct type
48- return a.pin(k)
49+ // Shimming the function to get the correct type, pass client PID
50+ return a.pin(k, a.clientPID)
51 }),
52 )
53 if err != nil {
54@@ -260,7 +262,7 @@ func (a *Agent) confirmKeyUse(wantKey []byte, peer string) error {
55 if peer != "" {
56 prompt += "\nRequested by " + peer
57 }
58- ok, err := askpass.AskPermission(prompt)
59+ ok, err := askpass.AskPermission(prompt, a.clientPID)
60 if err != nil {
61 slog.Info("askpass confirmation failed", slog.String("error", err.Error()))
62 return ErrUserDeniedConfirm
63@@ -289,6 +291,18 @@ func (c *connAgent) SignWithFlags(key ssh.PublicKey, data []byte, flags agent.Si
64 }
65
66 func (a *Agent) serveConn(c *net.UnixConn) {
67+ // Extract peer credentials to get the client PID
68+ if rc, err := c.SyscallConn(); err == nil {
69+ rc.Control(func(fd uintptr) {
70+ if cred, err := unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED); err == nil {
71+ a.mu.Lock()
72+ a.clientPID = int(cred.Pid)
73+ a.mu.Unlock()
74+ slog.Debug("client connected", slog.Int("pid", int(cred.Pid)))
75+ }
76+ })
77+ }
78+
79 ca := &connAgent{Agent: a, peer: peerChain(c)}
80 if err := agent.ServeAgent(ca, c); err != io.EOF {
81 slog.Info("Agent client connection ended unsuccessfully", slog.String("error", err.Error()))
82@@ -524,7 +538,7 @@ func LoadKeys(keyDir string) ([]key.SSHTPMKeys, error) {
83 return keys, err
84 }
85
86-func NewAgent(listener *net.UnixListener, agents []agent.ExtendedAgent, keyring func() *keyring.ThreadKeyring, tpmFetch func() transport.TPMCloser, ownerPassword func() ([]byte, error), pin func(key.SSHTPMKeys) ([]byte, error)) *Agent {
87+func NewAgent(listener *net.UnixListener, agents []agent.ExtendedAgent, keyring func() *keyring.ThreadKeyring, tpmFetch func() transport.TPMCloser, ownerPassword func() ([]byte, error), pin func(key.SSHTPMKeys, int) ([]byte, error)) *Agent {
88 a := &Agent{
89 agents: agents,
90 tpm: tpmFetch,
91diff --git a/agent/agent_test.go b/agent/agent_test.go
92index acaec3d..189b607 100644
93--- a/agent/agent_test.go
94+++ b/agent/agent_test.go
95@@ -45,7 +45,7 @@ func TestAddKey(t *testing.T) {
96 // Owner password
97 func() ([]byte, error) { return []byte(""), nil },
98 // PIN Callback
99- func(_ key.SSHTPMKeys) ([]byte, error) { return []byte(""), nil },
100+ func(_ key.SSHTPMKeys, _ int) ([]byte, error) { return []byte(""), nil },
101 )
102 defer ag.Stop()
103
104diff --git a/askpass/askpass.go b/askpass/askpass.go
105index 9830586..aefe469 100644
106--- a/askpass/askpass.go
107+++ b/askpass/askpass.go
108@@ -8,6 +8,7 @@ import (
109 "log/slog"
110 "os"
111 "os/exec"
112+ "strconv"
113 "strings"
114 "syscall"
115
116@@ -66,7 +67,7 @@ func isTerminal(fd uintptr) bool {
117 return err == nil
118 }
119
120-func ReadPassphrase(prompt string, flags ReadPassFlags) ([]byte, error) {
121+func ReadPassphrase(prompt string, flags ReadPassFlags, clientPID int) ([]byte, error) {
122 var allow_askpass bool
123 var use_askpass bool
124 var askpass_hint string
125@@ -104,7 +105,7 @@ func ReadPassphrase(prompt string, flags ReadPassFlags) ([]byte, error) {
126 if (flags & RP_ASK_PERMISSION) != 0 {
127 askpass_hint = "confirm"
128 }
129- return SshAskPass(prompt, askpass_hint)
130+ return SshAskPass(prompt, askpass_hint, clientPID)
131 }
132
133 // If we want to echo stuff, we read directly from stdin
134@@ -130,7 +131,7 @@ func ReadPassphrase(prompt string, flags ReadPassFlags) ([]byte, error) {
135 return pin, nil
136 }
137
138-func SshAskPass(prompt, hint string) ([]byte, error) {
139+func SshAskPass(prompt, hint string, clientPID int) ([]byte, error) {
140 var askpass string
141 var err error
142 if s, ok := os.LookupEnv("SSH_ASKPASS"); ok {
143@@ -144,7 +145,15 @@ func SshAskPass(prompt, hint string) ([]byte, error) {
144 }
145 }
146
147+ if clientPID > 0 && os.Getenv("SSH_CLIENT_PID") == "" {
148+ // Pass the requesting client's PID to pinentry so it can cache per-client.
149+ os.Setenv("SSH_CLIENT_PID", strconv.Itoa(clientPID))
150+ }
151+
152 cmd := exec.Command(askpass, prompt)
153+ if clientPID > 0 {
154+ cmd.Env = append(os.Environ(), "SSH_CLIENT_PID="+strconv.Itoa(clientPID))
155+ }
156 if hint != "" {
157 // Set SSH_ASKPASS_PROMPT only on the subprocess. os.Setenv() would
158 // leak into subsequent passphrase prompts from the same agent
159@@ -173,8 +182,8 @@ func SshAskPass(prompt, hint string) ([]byte, error) {
160
161 // AskPermission runs SSH_ASKPASS with SSH_ASKPASS_PROMPT=confirm set as env.
162 // It will expect exit code 0 or !0 and return true and false respectively.
163-func AskPermission(prompt string) (bool, error) {
164- a, err := ReadPassphrase(prompt, RP_USE_ASKPASS|RP_ASK_PERMISSION)
165+func AskPermission(prompt string, clientPID int) (bool, error) {
166+ a, err := ReadPassphrase(prompt, RP_USE_ASKPASS|RP_ASK_PERMISSION, clientPID)
167 if err != nil {
168 return false, err
169 }
170diff --git a/cmd/ssh-tpm-agent/main.go b/cmd/ssh-tpm-agent/main.go
171index 7c6f397..8e19473 100644
172--- a/cmd/ssh-tpm-agent/main.go
173+++ b/cmd/ssh-tpm-agent/main.go
174@@ -243,7 +243,7 @@ func main() {
175 // Owner password
176 func() ([]byte, error) {
177 if askOwnerPassword {
178- return askpass.ReadPassphrase("Enter owner password for TPM", askpass.RP_USE_ASKPASS)
179+ return askpass.ReadPassphrase("Enter owner password for TPM", askpass.RP_USE_ASKPASS, 0)
180 } else {
181 ownerPassword := os.Getenv("SSH_TPM_AGENT_OWNER_PASSWORD")
182 return []byte(ownerPassword), nil
183@@ -253,7 +253,7 @@ func main() {
184 // PIN Callback with caching
185 // SSHKeySigner in signer/signer.go resets this value if
186 // we get a TPMRCAuthFail
187- func(key key.SSHTPMKeys) ([]byte, error) {
188+ func(key key.SSHTPMKeys, clientPID int) ([]byte, error) {
189 auth, err := agentkeyring.ReadKey(key.Fingerprint())
190 switch {
191 case errors.Is(err, syscall.ENOENT):
192@@ -262,7 +262,7 @@ func main() {
193 case errors.Is(err, syscall.ENOKEY) || errors.Is(err, syscall.EACCES):
194 keyInfo := fmt.Sprintf("Enter passphrase for (%s): ", key.GetDescription())
195 // TODO: askpass should box the byte slice
196- userauth, err := askpass.ReadPassphrase(keyInfo, askpass.RP_USE_ASKPASS)
197+ userauth, err := askpass.ReadPassphrase(keyInfo, askpass.RP_USE_ASKPASS, clientPID)
198 fmt.Println(err)
199 if !noCache && err == nil {
200 slog.Debug("caching userauth for key in keyring", slog.String("fp", key.Fingerprint()))
201diff --git a/cmd/ssh-tpm-agent/main_test.go b/cmd/ssh-tpm-agent/main_test.go
202index 8335058..89697f2 100644
203--- a/cmd/ssh-tpm-agent/main_test.go
204+++ b/cmd/ssh-tpm-agent/main_test.go
205@@ -144,7 +144,7 @@ func runSSHAuth(t *testing.T, keytype tpm2.TPMAlgID, bits int, pin []byte, keyfn
206 // Owner password
207 func() ([]byte, error) { return []byte(""), nil },
208 // PIN Callback
209- func(_ key.SSHTPMKeys) ([]byte, error) {
210+ func(_ key.SSHTPMKeys, _ int) ([]byte, error) {
211 return pin, nil
212 },
213 )
214diff --git a/cmd/ssh-tpm-keygen/main.go b/cmd/ssh-tpm-keygen/main.go
215index 4002026..eae8d39 100644
216--- a/cmd/ssh-tpm-keygen/main.go
217+++ b/cmd/ssh-tpm-keygen/main.go
218@@ -89,11 +89,11 @@ Example:
219
220 func getPin() ([]byte, error) {
221 for {
222- pin1, err := askpass.ReadPassphrase("Enter passphrase (empty for no passphrase): ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE)
223+ pin1, err := askpass.ReadPassphrase("Enter passphrase (empty for no passphrase): ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE, 0)
224 if err != nil {
225 return nil, err
226 }
227- pin2, err := askpass.ReadPassphrase("Enter same passphrase again: ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE)
228+ pin2, err := askpass.ReadPassphrase("Enter same passphrase again: ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE, 0)
229 if err != nil {
230 return nil, err
231 }
232@@ -106,7 +106,7 @@ func getPin() ([]byte, error) {
233 }
234
235 func getOwnerPassword() ([]byte, error) {
236- return askpass.ReadPassphrase("Enter owner password: ", askpass.RP_ALLOW_STDIN)
237+ return askpass.ReadPassphrase("Enter owner password: ", askpass.RP_ALLOW_STDIN, 0)
238 }
239
240 func doHostKeys(tpm transport.TPMCloser, outputFile string, ownerPassword []byte, hierarchy string) {
241@@ -191,7 +191,7 @@ func doChangePin(tpm transport.TPMCloser, passphrase, keyPin, ownerPassword []by
242 fmt.Printf("Key has comment '%s'\n", k.Description)
243 }
244 if outputFile == "" {
245- f, err := askpass.ReadPassphrase(fmt.Sprintf("Enter file in which the key is (%s): ", filename), askpass.RP_ALLOW_STDIN|askpass.RPP_ECHO_ON)
246+ f, err := askpass.ReadPassphrase(fmt.Sprintf("Enter file in which the key is (%s): ", filename), askpass.RP_ALLOW_STDIN|askpass.RPP_ECHO_ON, 0)
247 if err != nil {
248 return err
249 }
250@@ -202,18 +202,18 @@ func doChangePin(tpm transport.TPMCloser, passphrase, keyPin, ownerPassword []by
251 }
252
253 if len(passphrase) == 0 {
254- passphrase, err = askpass.ReadPassphrase("Enter old passphrase: ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE)
255+ passphrase, err = askpass.ReadPassphrase("Enter old passphrase: ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE, 0)
256 if err != nil {
257 return err
258 }
259 }
260
261 if len(keyPin) == 0 {
262- keyPin, err = askpass.ReadPassphrase("Enter new passphrase (empty for no passphrase): ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE)
263+ keyPin, err = askpass.ReadPassphrase("Enter new passphrase (empty for no passphrase): ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE, 0)
264 if err != nil {
265 return err
266 }
267- newPin, err := askpass.ReadPassphrase("Enter same passphrase: ", askpass.RP_ALLOW_STDIN)
268+ newPin, err := askpass.ReadPassphrase("Enter same passphrase: ", askpass.RP_ALLOW_STDIN, 0)
269 if err != nil {
270 return err
271 }
272@@ -260,7 +260,7 @@ func doWrapWith(supportedECCBitsizes []int, wrap, wrapWith string, keyParentHand
273 rawKey, err = ssh.ParseRawPrivateKey(pem)
274 if errors.As(err, &kerr) {
275 for {
276- pin, err := askpass.ReadPassphrase("Enter existing passphrase (empty for no passphrase): ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE)
277+ pin, err := askpass.ReadPassphrase("Enter existing passphrase (empty for no passphrase): ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE, 0)
278 if err != nil {
279 log.Fatal(err)
280 }
281@@ -358,7 +358,7 @@ func supportedECCBitsizes(tpm transport.TPMCloser) []int {
282 func checkFile(f string) error {
283 if utils.FileExists(f) {
284 fmt.Printf("%s already exists.\n", f)
285- s, err := askpass.ReadPassphrase("Overwrite (y/n)? ", askpass.RP_ALLOW_STDIN|askpass.RPP_ECHO_ON)
286+ s, err := askpass.ReadPassphrase("Overwrite (y/n)? ", askpass.RP_ALLOW_STDIN|askpass.RPP_ECHO_ON, 0)
287 if err != nil {
288 return err
289 }
290@@ -378,7 +378,7 @@ func doImportKey(tpm transport.TPMCloser, keyParentHandle tpm2.TPMHandle, ownerP
291 rawKey, err = ssh.ParseRawPrivateKey(pem)
292 if errors.As(err, &kerr) {
293 for {
294- pin, err := askpass.ReadPassphrase("Enter existing passphrase (empty for no passphrase): ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE)
295+ pin, err := askpass.ReadPassphrase("Enter existing passphrase (empty for no passphrase): ", askpass.RP_ALLOW_STDIN|askpass.RP_NEWLINE, 0)
296 if err != nil {
297 return nil, err
298 }
299@@ -670,7 +670,7 @@ func main() {
300 // Else create a normal key
301 fmt.Printf("Generating a sealed public/private %s key pair.\n", keyType)
302 if outputFile == "" {
303- f, err := askpass.ReadPassphrase(fmt.Sprintf("Enter file in which to save the key (%s): ", filename), askpass.RP_ALLOW_STDIN|askpass.RPP_ECHO_ON)
304+ f, err := askpass.ReadPassphrase(fmt.Sprintf("Enter file in which to save the key (%s): ", filename), askpass.RP_ALLOW_STDIN|askpass.RPP_ECHO_ON, 0)
305 if err != nil {
306 log.Fatal(err)
307 }
308diff --git a/internal/keytest/keytest.go b/internal/keytest/keytest.go
309index ade4ad8..90fcf77 100644
310--- a/internal/keytest/keytest.go
311+++ b/internal/keytest/keytest.go
312@@ -121,7 +121,7 @@ func NewTestAgent(t *testing.T, tpm transport.TPMCloser) *agent.Agent {
313 func() *keyring.ThreadKeyring { return &keyring.ThreadKeyring{} },
314 func() transport.TPMCloser { return tpm },
315 func() ([]byte, error) { return []byte(""), nil },
316- func(_ key.SSHTPMKeys) ([]byte, error) {
317+ func(_ key.SSHTPMKeys, _ int) ([]byte, error) {
318 return []byte(""), nil
319 },
320 )