Skip to content

Commit

Permalink
agent: learn to cache userauths for TPM keys
Browse files Browse the repository at this point in the history
Signed-off-by: Morten Linderud <[email protected]>
  • Loading branch information
Foxboron committed Aug 17, 2024
1 parent e2c978d commit b8254a2
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 9 deletions.
11 changes: 7 additions & 4 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

keyfile "github.com/foxboron/go-tpm-keyfiles"
"github.com/foxboron/ssh-tpm-agent/key"
"github.com/foxboron/ssh-tpm-agent/signer"
"github.com/google/go-tpm/tpm2/transport"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
Expand Down Expand Up @@ -97,10 +98,12 @@ func (a *Agent) signers() ([]ssh.Signer, error) {
}

for _, k := range a.keys {
s, err := ssh.NewSignerFromSigner(keyfile.NewTPMKeySigner(k.TPMKey, a.op, a.tpm, func(k *keyfile.TPMKey) ([]byte, error) {
// Shimming the function to get the correct type
return a.pin(&key.SSHTPMKey{TPMKey: k})
}))
s, err := ssh.NewSignerFromSigner(
signer.NewSSHKeySigner(k, a.op, a.tpm,
func(_ *keyfile.TPMKey) ([]byte, error) {
// Shimming the function to get the correct type
return a.pin(k)
}))
if err != nil {
return nil, fmt.Errorf("failed to prepare signer: %w", err)
}
Expand Down
19 changes: 17 additions & 2 deletions cmd/ssh-tpm-agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Options:
-o, --owner-password Ask for the owner password.
--no-cache The agent will not cache key passwords.
-d Enable debug logging.
--install-user-units Installs systemd system units and sshd configs for using
Expand Down Expand Up @@ -104,6 +106,7 @@ func main() {
swtpmFlag, printSocketFlag bool
installUserUnits, system, noLoad bool
askOwnerPassword, debugMode bool
noCache bool
)

envSocketPath := func() string {
Expand Down Expand Up @@ -131,6 +134,7 @@ func main() {
flag.BoolVar(&askOwnerPassword, "o", false, "ask for the owner password")
flag.BoolVar(&askOwnerPassword, "owner-password", false, "ask for the owner password")
flag.BoolVar(&debugMode, "d", false, "debug mode")
flag.BoolVar(&noCache, "no-cache", false, "do not cache key passwords")
flag.Parse()

opts := &slog.HandlerOptions{
Expand Down Expand Up @@ -216,10 +220,21 @@ func main() {
}
},

// PIN Callback
// PIN Callback with caching
// SSHKeySigner in signer/signer.go resets this value if
// we get a TPMRCAuthFail
func(key *key.SSHTPMKey) ([]byte, error) {
if len(key.Userauth) != 0 {
slog.Debug("providing cached userauth for key", slog.String("desc", key.Description))
return key.Userauth, nil
}
keyInfo := fmt.Sprintf("Enter passphrase for (%s): ", key.Description)
return askpass.ReadPassphrase(keyInfo, askpass.RP_USE_ASKPASS)
userauth, err := askpass.ReadPassphrase(keyInfo, askpass.RP_USE_ASKPASS)
if !noCache && err == nil {
slog.Debug("caching userauth for key", slog.String("desc", key.Description))
key.Userauth = userauth
}
return userauth, err
},
)

Expand Down
7 changes: 4 additions & 3 deletions key/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var (
// SSHTPMKey is a wrapper for TPMKey implementing the ssh.PublicKey specific parts
type SSHTPMKey struct {
*keyfile.TPMKey
Userauth []byte
Certificate *ssh.Certificate
}

Expand All @@ -28,7 +29,7 @@ func NewSSHTPMKey(tpm transport.TPMCloser, alg tpm2.TPMAlgID, bits int, owneraut
if err != nil {
return nil, err
}
return &SSHTPMKey{k, nil}, nil
return &SSHTPMKey{k, nil, nil}, nil
}

// This assumes we are just getting a local PK.
Expand All @@ -50,7 +51,7 @@ func NewImportedSSHTPMKey(tpm transport.TPMCloser, pk any, ownerauth []byte, fn
if err != nil {
return nil, fmt.Errorf("failed turning imported key to loadable key: %v", err)
}
return &SSHTPMKey{k, nil}, nil
return &SSHTPMKey{k, nil, nil}, nil
}

func (k *SSHTPMKey) SSHPublicKey() (ssh.PublicKey, error) {
Expand Down Expand Up @@ -85,5 +86,5 @@ func Decode(b []byte) (*SSHTPMKey, error) {
if err != nil {
return nil, err
}
return &SSHTPMKey{k, nil}, nil
return &SSHTPMKey{k, nil, nil}, nil
}
41 changes: 41 additions & 0 deletions signer/signer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package signer

import (
"crypto"
"errors"
"io"
"log/slog"

"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpm2/transport"

keyfile "github.com/foxboron/go-tpm-keyfiles"
"github.com/foxboron/ssh-tpm-agent/key"
)

// Shim for keyfile.TPMKeySigner
// We need access to the SSHTPMKey to change the userauth for caching
type SSHKeySigner struct {
*keyfile.TPMKeySigner
key *key.SSHTPMKey
}

// func (t *SSHKeySigner) Public() crypto.PublicKey {
// return t.TPMKeySigner.Public()
// }

func (t *SSHKeySigner) Sign(r io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
b, err := t.TPMKeySigner.Sign(r, digest, opts)
if errors.Is(err, tpm2.TPMRCAuthFail) {
slog.Debug("removed cached userauth for key", slog.Any("err", err), slog.String("desc", t.key.Description))
t.key.Userauth = []byte(nil)
}
return b, err
}

func NewSSHKeySigner(k *key.SSHTPMKey, ownerAuth func() ([]byte, error), tpm func() transport.TPMCloser, auth func(*keyfile.TPMKey) ([]byte, error)) *SSHKeySigner {
return &SSHKeySigner{
TPMKeySigner: keyfile.NewTPMKeySigner(k.TPMKey, ownerAuth, tpm, auth),
key: k,
}
}

0 comments on commit b8254a2

Please sign in to comment.