Skip to content

Commit 4aba3de

Browse files
committed
Merge branch 'morten/keyring'
* morten/keyring: agent: add SIGINT to the signal handler testdata: Include a test for askpass during signing scripts_test: set agent simulator to an underscore env agent: Integrate ThreadKeyring into the ssh agent keyring/threadkeyring: Implement a keyring pinned to the os thread keyring: add a keyctl implementation for keyrings keyring/key: implement a boxed byte slice for memory sensitive things agent: pass staticcheck Signed-off-by: Morten Linderud <[email protected]>
2 parents d579cf8 + 7275e88 commit 4aba3de

File tree

18 files changed

+488
-75
lines changed

18 files changed

+488
-75
lines changed

Diff for: agent/agent.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"log/slog"
1919

2020
keyfile "github.com/foxboron/go-tpm-keyfiles"
21+
"github.com/foxboron/ssh-tpm-agent/internal/keyring"
2122
"github.com/foxboron/ssh-tpm-agent/key"
2223
"github.com/foxboron/ssh-tpm-agent/signer"
2324
"github.com/google/go-tpm/tpm2/transport"
@@ -40,6 +41,7 @@ type Agent struct {
4041
listener *net.UnixListener
4142
quit chan interface{}
4243
wg sync.WaitGroup
44+
keyring func() *keyring.ThreadKeyring
4345
keys []*key.SSHTPMKey
4446
agents []agent.ExtendedAgent
4547
}
@@ -106,7 +108,7 @@ func (a *Agent) signers() ([]ssh.Signer, error) {
106108

107109
for _, k := range a.keys {
108110
s, err := ssh.NewSignerFromSigner(
109-
signer.NewSSHKeySigner(k, a.op, a.tpm,
111+
signer.NewSSHKeySigner(k, a.keyring(), a.op, a.tpm,
110112
func(_ *keyfile.TPMKey) ([]byte, error) {
111113
// Shimming the function to get the correct type
112114
return a.pin(k)
@@ -432,7 +434,7 @@ func LoadKeys(keyDir string) ([]*key.SSHTPMKey, error) {
432434
return keys, err
433435
}
434436

435-
func NewAgent(listener *net.UnixListener, agents []agent.ExtendedAgent, tpmFetch func() transport.TPMCloser, ownerPassword func() ([]byte, error), pin func(*key.SSHTPMKey) ([]byte, error)) *Agent {
437+
func NewAgent(listener *net.UnixListener, agents []agent.ExtendedAgent, keyring func() *keyring.ThreadKeyring, tpmFetch func() transport.TPMCloser, ownerPassword func() ([]byte, error), pin func(*key.SSHTPMKey) ([]byte, error)) *Agent {
436438
a := &Agent{
437439
agents: agents,
438440
tpm: tpmFetch,
@@ -441,6 +443,7 @@ func NewAgent(listener *net.UnixListener, agents []agent.ExtendedAgent, tpmFetch
441443
pin: pin,
442444
quit: make(chan interface{}),
443445
keys: []*key.SSHTPMKey{},
446+
keyring: keyring,
444447
}
445448

446449
a.wg.Add(1)

Diff for: agent/agent_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"testing"
1212

1313
"github.com/foxboron/ssh-tpm-agent/agent"
14+
"github.com/foxboron/ssh-tpm-agent/internal/keyring"
1415
"github.com/foxboron/ssh-tpm-agent/internal/keytest"
1516
"github.com/foxboron/ssh-tpm-agent/key"
1617
"github.com/google/go-tpm/tpm2"
@@ -37,6 +38,8 @@ func TestAddKey(t *testing.T) {
3738

3839
ag := agent.NewAgent(unixList,
3940
[]sshagent.ExtendedAgent{},
41+
// Keyring callback
42+
func() *keyring.ThreadKeyring { return &keyring.ThreadKeyring{} },
4043
// TPM Callback
4144
func() transport.TPMCloser { return tpm },
4245
// Owner password

Diff for: agent/gocrypto.go

+41-51
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
package agent
22

3-
import (
4-
"encoding/binary"
5-
"fmt"
6-
7-
"golang.org/x/crypto/ssh"
8-
9-
sshagent "golang.org/x/crypto/ssh/agent"
10-
)
11-
123
// Code taken from crypto/x/ssh/agent
134

145
const (
@@ -18,59 +9,58 @@ const (
189
// Constraint extension identifier up to version 2 of the protocol. A
1910
// backward incompatible change will be required if we want to add support
2011
// for SSH_AGENT_CONSTRAIN_MAXSIGN which uses the same ID.
21-
agentConstrainExtensionV00 = 3
12+
// agentConstrainExtensionV00 = 3
2213
// Constraint extension identifier in version 3 and later of the protocol.
23-
agentConstrainExtension = 255
14+
// agentConstrainExtension = 255
2415
)
2516

26-
type constrainExtensionAgentMsg struct {
27-
ExtensionName string `sshtype:"255|3"`
28-
ExtensionDetails []byte
17+
// type constrainExtensionAgentMsg struct {
18+
// ExtensionName string `sshtype:"255|3"`
19+
// ExtensionDetails []byte
2920

30-
// Rest is a field used for parsing, not part of message
31-
Rest []byte `ssh:"rest"`
32-
}
21+
// // Rest is a field used for parsing, not part of message
22+
// Rest []byte `ssh:"rest"`
23+
// }
3324

3425
// 3.7 Key constraint identifiers
3526
type constrainLifetimeAgentMsg struct {
3627
LifetimeSecs uint32 `sshtype:"1"`
3728
}
3829

39-
func parseConstraints(constraints []byte) (lifetimeSecs uint32, confirmBeforeUse bool, extensions []sshagent.ConstraintExtension, err error) {
40-
for len(constraints) != 0 {
41-
switch constraints[0] {
42-
case agentConstrainLifetime:
43-
lifetimeSecs = binary.BigEndian.Uint32(constraints[1:5])
44-
constraints = constraints[5:]
45-
case agentConstrainConfirm:
46-
confirmBeforeUse = true
47-
constraints = constraints[1:]
48-
case agentConstrainExtension, agentConstrainExtensionV00:
49-
var msg constrainExtensionAgentMsg
50-
if err = ssh.Unmarshal(constraints, &msg); err != nil {
51-
return 0, false, nil, err
52-
}
53-
extensions = append(extensions, sshagent.ConstraintExtension{
54-
ExtensionName: msg.ExtensionName,
55-
ExtensionDetails: msg.ExtensionDetails,
56-
})
57-
constraints = msg.Rest
58-
default:
59-
return 0, false, nil, fmt.Errorf("unknown constraint type: %d", constraints[0])
60-
}
61-
}
62-
return
63-
}
30+
// func parseConstraints(constraints []byte) (lifetimeSecs uint32, confirmBeforeUse bool, extensions []sshagent.ConstraintExtension, err error) {
31+
// for len(constraints) != 0 {
32+
// switch constraints[0] {
33+
// case agentConstrainLifetime:
34+
// lifetimeSecs = binary.BigEndian.Uint32(constraints[1:5])
35+
// constraints = constraints[5:]
36+
// case agentConstrainConfirm:
37+
// confirmBeforeUse = true
38+
// constraints = constraints[1:]
39+
// // case agentConstrainExtension, agentConstrainExtensionV00:
40+
// // var msg constrainExtensionAgentMsg
41+
// // if err = ssh.Unmarshal(constraints, &msg); err != nil {
42+
// // return 0, false, nil, err
43+
// // }
44+
// // extensions = append(extensions, sshagent.ConstraintExtension{
45+
// // ExtensionName: msg.ExtensionName,
46+
// // ExtensionDetails: msg.ExtensionDetails,
47+
// // })
48+
// // constraints = msg.Rest
49+
// default:
50+
// return 0, false, nil, fmt.Errorf("unknown constraint type: %d", constraints[0])
51+
// }
52+
// }
53+
// return
54+
// }
6455

65-
// TODO: Add constraints to our keys
6656
// func setConstraints(key *key.SSHTPMKey, constraintBytes []byte) error {
67-
// lifetimeSecs, confirmBeforeUse, constraintExtensions, err := parseConstraints(constraintBytes)
68-
// if err != nil {
69-
// return err
70-
// }
57+
// lifetimeSecs, confirmBeforeUse, constraintExtensions, err := parseConstraints(constraintBytes)
58+
// if err != nil {
59+
// return err
60+
// }
7161

72-
// key.LifetimeSecs = lifetimeSecs
73-
// key.ConfirmBeforeUse = confirmBeforeUse
74-
// key.ConstraintExtensions = constraintExtensions
75-
// return nil
62+
// key.LifetimeSecs = lifetimeSecs
63+
// key.ConfirmBeforeUse = confirmBeforeUse
64+
// key.ConstraintExtensions = constraintExtensions
65+
// return nil
7666
// }

Diff for: cmd/scripts_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func ScriptsWithPath(t *testing.T, path string) {
2929
Deadline: time.Now().Add(5 * time.Second),
3030
Setup: func(e *testscript.Env) error {
3131
e.Setenv("PATH", tmp+string(filepath.ListSeparator)+e.Getenv("PATH"))
32-
e.Vars = append(e.Vars, "SSH_TPM_AGENT_SIMULATOR=1")
32+
e.Vars = append(e.Vars, "_SSH_TPM_AGENT_SIMULATOR=1")
3333
e.Vars = append(e.Vars, fmt.Sprintf("SSH_AUTH_SOCK=%s/agent.sock", e.WorkDir))
3434
e.Vars = append(e.Vars, fmt.Sprintf("SSH_TPM_AUTH_SOCK=%s/agent.sock", e.WorkDir))
3535
e.Vars = append(e.Vars, fmt.Sprintf("HOME=%s", e.WorkDir))

Diff for: cmd/ssh-tpm-agent/main.go

+35-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package main
22

33
import (
4+
"context"
5+
"errors"
46
"flag"
57
"fmt"
68
"log"
@@ -17,6 +19,7 @@ import (
1719

1820
"github.com/foxboron/ssh-tpm-agent/agent"
1921
"github.com/foxboron/ssh-tpm-agent/askpass"
22+
"github.com/foxboron/ssh-tpm-agent/internal/keyring"
2023
"github.com/foxboron/ssh-tpm-agent/key"
2124
"github.com/foxboron/ssh-tpm-agent/utils"
2225
"github.com/google/go-tpm/tpm2/transport"
@@ -198,8 +201,22 @@ func main() {
198201
os.Exit(1)
199202
}
200203

204+
// TODO: Ensure the agent also uses thix context
205+
ctx, cancel := context.WithCancel(context.Background())
206+
defer cancel()
207+
208+
agentkeyring, err := keyring.NewThreadKeyring(ctx, keyring.SessionKeyring)
209+
if err != nil {
210+
log.Fatal(err)
211+
}
212+
201213
agent := agent.NewAgent(listener, agents,
202214

215+
// Keyring Callback
216+
func() *keyring.ThreadKeyring {
217+
return agentkeyring
218+
},
219+
203220
// TPM Callback
204221
func() (tpm transport.TPMCloser) {
205222
// the agent will close the TPM after this is called
@@ -216,7 +233,6 @@ func main() {
216233
return askpass.ReadPassphrase("Enter owner password for TPM", askpass.RP_USE_ASKPASS)
217234
} else {
218235
ownerPassword := os.Getenv("SSH_TPM_AGENT_OWNER_PASSWORD")
219-
220236
return []byte(ownerPassword), nil
221237
}
222238
},
@@ -225,23 +241,31 @@ func main() {
225241
// SSHKeySigner in signer/signer.go resets this value if
226242
// we get a TPMRCAuthFail
227243
func(key *key.SSHTPMKey) ([]byte, error) {
228-
if len(key.Userauth) != 0 {
229-
slog.Debug("providing cached userauth for key", slog.String("desc", key.Description))
230-
return key.Userauth, nil
231-
}
232-
keyInfo := fmt.Sprintf("Enter passphrase for (%s): ", key.Description)
233-
userauth, err := askpass.ReadPassphrase(keyInfo, askpass.RP_USE_ASKPASS)
234-
if !noCache && err == nil {
235-
slog.Debug("caching userauth for key", slog.String("desc", key.Description))
236-
key.Userauth = userauth
244+
auth, err := agentkeyring.ReadKey(key.Fingerprint())
245+
if err == nil {
246+
slog.Debug("providing cached userauth for key", slog.String("fp", key.Fingerprint()))
247+
// TODO: This is not great, but easier for now
248+
return auth.Read(), nil
249+
} else if errors.Is(err, syscall.ENOKEY) || errors.Is(err, syscall.EACCES) {
250+
keyInfo := fmt.Sprintf("Enter passphrase for (%s): ", key.Description)
251+
// TODOt kjk: askpass should box the byte slice
252+
userauth, err := askpass.ReadPassphrase(keyInfo, askpass.RP_USE_ASKPASS)
253+
if !noCache && err == nil {
254+
slog.Debug("caching userauth for key in keyring", slog.String("fp", key.Fingerprint()))
255+
if err := agentkeyring.AddKey(key.Fingerprint(), userauth); err != nil {
256+
return nil, err
257+
}
258+
}
259+
return userauth, err
237260
}
238-
return userauth, err
261+
return nil, fmt.Errorf("failed getting pin for key: %w", err)
239262
},
240263
)
241264

242265
// Signal handling
243266
c := make(chan os.Signal, 1)
244267
signal.Notify(c, syscall.SIGHUP)
268+
signal.Notify(c, syscall.SIGINT)
245269
go func() {
246270
for range c {
247271
agent.Stop()

Diff for: cmd/ssh-tpm-agent/main_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
"github.com/foxboron/ssh-tpm-agent/agent"
16+
"github.com/foxboron/ssh-tpm-agent/internal/keyring"
1617
"github.com/foxboron/ssh-tpm-agent/internal/keytest"
1718
"github.com/foxboron/ssh-tpm-agent/key"
1819
"github.com/google/go-tpm/tpm2"
@@ -135,6 +136,8 @@ func runSSHAuth(t *testing.T, keytype tpm2.TPMAlgID, bits int, pin []byte, keyfn
135136

136137
ag := agent.NewAgent(unixList,
137138
[]sshagent.ExtendedAgent{},
139+
// Keyring Callback
140+
func() *keyring.ThreadKeyring { return &keyring.ThreadKeyring{} },
138141
// TPM Callback
139142
func() transport.TPMCloser { return tpm },
140143
// Owner password

Diff for: cmd/ssh-tpm-agent/testdata/script/agent_password.txt

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Create an askpass binary
2+
exec go build -o askpass-test askpass.go
3+
exec ./askpass-test passphrase
4+
5+
# Env
6+
env SSH_ASKPASS=./askpass-test
7+
env SSH_ASKPASS_REQUIRE=force
8+
9+
# ssh sign file with password
10+
env _ASKPASS_PASSWORD=12345
11+
exec ssh-tpm-agent -d --no-load &agent&
12+
exec ssh-tpm-keygen -N $_ASKPASS_PASSWORD
13+
exec ssh-tpm-add
14+
stdout id_ecdsa.tpm
15+
exec ssh-add -l
16+
stdout ECDSA
17+
exec ssh-keygen -Y sign -n file -f .ssh/id_ecdsa.pub file_to_sign.txt
18+
stdin file_to_sign.txt
19+
exec ssh-keygen -Y check-novalidate -n file -f .ssh/id_ecdsa.pub -s file_to_sign.txt.sig
20+
exists file_to_sign.txt.sig
21+
exec ssh-add -D
22+
rm file_to_sign.txt.sig
23+
rm .ssh/id_ecdsa.tpm .ssh/id_ecdsa.pub
24+
25+
-- file_to_sign.txt --
26+
Hello World
27+
28+
-- go.mod --
29+
module example.com/askpass
30+
31+
-- askpass.go --
32+
package main
33+
34+
import (
35+
"fmt"
36+
"os"
37+
"strings"
38+
)
39+
40+
func main() {
41+
if strings.Contains(os.Args[1], "passphrase") {
42+
fmt.Println(os.Getenv("_ASKPASS_PASSWORD"))
43+
}
44+
}

Diff for: go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ go 1.22.4
55
toolchain go1.22.5
66

77
require (
8+
github.com/awnumar/memcall v0.4.0
89
github.com/foxboron/go-tpm-keyfiles v0.0.0-20240805214234-f870d6f1ff68
910
github.com/foxboron/ssh-tpm-ca-authority v0.0.0-20240806093457-88eeced81948
1011
github.com/google/go-tpm v0.9.2-0.20240625170440-991b038b62b6
1112
github.com/google/go-tpm-tools v0.4.4
1213
github.com/rogpeppe/go-internal v1.13.1
1314
golang.org/x/crypto v0.25.0
14-
golang.org/x/sys v0.27.0
15+
golang.org/x/sys v0.28.0
1516
golang.org/x/term v0.26.0
1617
)
1718

Diff for: go.sum

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/awnumar/memcall v0.4.0 h1:B7hgZYdfH6Ot1Goaz8jGne/7i8xD4taZie/PNSFZ29g=
2+
github.com/awnumar/memcall v0.4.0/go.mod h1:8xOx1YbfyuCg3Fy6TO8DK0kZUua3V42/goA5Ru47E8w=
13
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
24
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
35
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -93,8 +95,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
9395
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9496
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9597
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
96-
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
97-
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
98+
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
99+
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
98100
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
99101
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
100102
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=

0 commit comments

Comments
 (0)