Skip to content

Commit

Permalink
Merge branch 'morten/add-agent'
Browse files Browse the repository at this point in the history
* morten/add-agent:
  ssh-tpm-agent: Add ssh-agent proxy functionality with -A
  ssh-tpm-agent: unroll two functions used once
  • Loading branch information
Foxboron committed Aug 5, 2023
2 parents 6092d3f + ead045b commit 81f5cef
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 55 deletions.
83 changes: 41 additions & 42 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,17 @@ import (
"log"
"net"
"os"
"os/signal"
"path"
"path/filepath"
"strings"
"sync"
"syscall"
"time"

"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"
"golang.org/x/term"
)

var ErrOperationUnsupported = errors.New("operation unsupported")
Expand All @@ -36,6 +33,7 @@ type Agent struct {
quit chan interface{}
wg sync.WaitGroup
keys map[string]*key.Key
agents []agent.ExtendedAgent
}

var _ agent.ExtendedAgent = &Agent{}
Expand Down Expand Up @@ -67,6 +65,16 @@ func (a *Agent) Close() error {

func (a *Agent) signers() ([]ssh.Signer, error) {
var signers []ssh.Signer

for _, agent := range a.agents {
l, err := agent.Signers()
if err != nil {
log.Printf("failed getting Signers from agent: %f", err)
continue
}
signers = append(signers, l...)
}

for _, k := range a.keys {
s, err := ssh.NewSignerFromSigner(signer.NewTPMSigner(k, a.tpm, a.pin))
if err != nil {
Expand All @@ -89,6 +97,15 @@ func (a *Agent) List() ([]*agent.Key, error) {
a.mu.Lock()
defer a.mu.Unlock()

for _, agent := range a.agents {
l, err := agent.List()
if err != nil {
log.Printf("failed getting list from agent: %v", err)
continue
}
agentKeys = append(agentKeys, l...)
}

for _, k := range a.keys {
pk, err := k.SSHPublicKey()
if err != nil {
Expand Down Expand Up @@ -118,6 +135,21 @@ func (a *Agent) SignWithFlags(key ssh.PublicKey, data []byte, flags agent.Signat
return s.(ssh.AlgorithmSigner).SignWithAlgorithm(rand.Reader, data, key.Type())
}

log.Printf("trying to sign as proxy...")
for _, agent := range a.agents {
signers, err := agent.Signers()
if err != nil {
log.Printf("failed getting signers from agent: %v", err)
continue
}
for _, s := range signers {
if !bytes.Equal(s.PublicKey().Marshal(), key.Marshal()) {
continue
}
return s.(ssh.AlgorithmSigner).SignWithAlgorithm(rand.Reader, data, key.Type())
}
}

return nil, fmt.Errorf("no private keys match the requested public key")
}

Expand Down Expand Up @@ -233,12 +265,13 @@ func LoadKeys() (map[string]*key.Key, error) {
return keys, nil
}

func NewAgent(socketPath string, tpmFetch func() transport.TPMCloser, pin func(*key.Key) ([]byte, error)) *Agent {
func NewAgent(socketPath string, agents []agent.ExtendedAgent, tpmFetch func() transport.TPMCloser, pin func(*key.Key) ([]byte, error)) *Agent {
a := &Agent{
tpm: tpmFetch,
pin: pin,
quit: make(chan interface{}),
keys: make(map[string]*key.Key),
agents: agents,
tpm: tpmFetch,
pin: pin,
quit: make(chan interface{}),
keys: make(map[string]*key.Key),
}
l, err := net.Listen("unix", socketPath)
if err != nil {
Expand All @@ -250,37 +283,3 @@ func NewAgent(socketPath string, tpmFetch func() transport.TPMCloser, pin func(*
go a.serve()
return a
}

func execAgent(socketPath string, tpmFetch func() transport.TPMCloser, pin func(*key.Key) ([]byte, error)) *Agent {
os.Remove(socketPath)
if err := os.MkdirAll(filepath.Dir(socketPath), 0777); err != nil {
log.Fatalln("Failed to create UNIX socket folder:", err)
}
log.Printf("Listening on %v\n", socketPath);
a := NewAgent(socketPath, tpmFetch, pin)

c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
go func() {
for range c {
a.Stop()
}
}()

return a
}

func RunAgent(socketPath string, tpmFetch func() transport.TPMCloser, pin func(*key.Key) ([]byte, error)) {
if term.IsTerminal(int(os.Stdin.Fd())) {
log.Println("Warning: ssh-tpm-agent is meant to run as a background daemon.")
log.Println("Running multiple instances is likely to lead to conflicts.")
log.Println("Consider using a systemd service.")
}

a := execAgent(socketPath, tpmFetch, pin)

//TODO: Maybe we should allow people to not auto-load keys
a.LoadKeys()

a.Wait()
}
104 changes: 91 additions & 13 deletions cmd/ssh-tpm-agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,21 @@ import (
"flag"
"fmt"
"log"
"net"
"os"
"os/signal"
"path"
"path/filepath"
"syscall"

"github.com/foxboron/ssh-tpm-agent/agent"
"github.com/foxboron/ssh-tpm-agent/key"
"github.com/foxboron/ssh-tpm-agent/pinentry"
"github.com/foxboron/ssh-tpm-agent/utils"
"github.com/google/go-tpm/tpm2/transport"
sshagent "golang.org/x/crypto/ssh/agent"
"golang.org/x/exp/slices"
"golang.org/x/term"
)

var Version string
Expand All @@ -21,10 +28,12 @@ const usage = `Usage:
ssh-tpm-agent -l [PATH]
Options:
-l path of the UNIX socket to listen on, defaults to
$XDG_RUNTIME_DIR/ssh-tpm-agent.sock
-l PATH Path of the UNIX socket to open, defaults to
$XDG_RUNTIME_DIR/ssh-tpm-agent.sock.
--print-socket prints the socket to STDIN
-A PATH Fallback ssh-agent sockets for additional key lookup.
--print-socket Prints the socket to STDIN.
ssh-tpm-agent is a program that loads TPM sealed keys for public key
authentication. It is an ssh-agent(1) compatible program and can be used for
Expand All @@ -43,6 +52,31 @@ Example:
$ export SSH_AUTH_SOCK=$(ssh-tpm-agent --print-socket)
$ ssh git@github.com`

type SocketSet struct {
Value []string
}

func (s SocketSet) String() string {
return "set"
}

func (s *SocketSet) Set(p string) error {
if !slices.Contains(s.Value, p) {
s.Value = append(s.Value, p)
}
return nil
}

func (s SocketSet) Type() string {
return "[PATH]"
}

func NewSocketSet(allowed []string, d string) *SocketSet {
return &SocketSet{
Value: []string{},
}
}

func main() {
flag.Usage = func() {
fmt.Println(usage)
Expand All @@ -62,7 +96,10 @@ func main() {
return path.Join(dir, "ssh-tpm-agent.sock")
}()

var sockets SocketSet

flag.StringVar(&socketPath, "l", defaultSocketPath, "path of the UNIX socket to listen on")
flag.Var(&sockets, "A", "fallback ssh-agent sockets")
flag.BoolVar(&swtpmFlag, "swtpm", false, "use swtpm instead of actual tpm")
flag.BoolVar(&printSocketFlag, "print-socket", false, "print path of UNIX socket to stdout")
flag.Parse()
Expand All @@ -77,18 +114,59 @@ func main() {
os.Exit(0)
}

tpmFetch := func() (tpm transport.TPMCloser) {
// the agent will close the TPM after this is called
tpm, err := utils.GetTPM(swtpmFlag)
if term.IsTerminal(int(os.Stdin.Fd())) {
log.Println("Warning: ssh-tpm-agent is meant to run as a background daemon.")
log.Println("Running multiple instances is likely to lead to conflicts.")
log.Println("Consider using a systemd service.")
}

os.Remove(socketPath)
if err := os.MkdirAll(filepath.Dir(socketPath), 0777); err != nil {
log.Fatalln("Failed to create UNIX socket folder:", err)
}
log.Printf("Listening on %v\n", socketPath)

var agents []sshagent.ExtendedAgent

for _, s := range sockets.Value {
conn, err := net.Dial("unix", s)
if err != nil {
log.Fatal(err)
}
return tpm
}
pin := func(key *key.Key) ([]byte, error) {
keyHash := sha256.Sum256(key.Public.Bytes())
keyInfo := fmt.Sprintf("ssh-tpm-agent/%x", keyHash)
return pinentry.GetPinentry(keyInfo)
agents = append(agents, sshagent.NewClient(conn))
}
agent.RunAgent(socketPath, tpmFetch, pin)

a := agent.NewAgent(socketPath,
agents,
// TPM Callback
func() (tpm transport.TPMCloser) {
// the agent will close the TPM after this is called
tpm, err := utils.GetTPM(swtpmFlag)
if err != nil {
log.Fatal(err)
}
return tpm
},

// PIN Callback
func(key *key.Key) ([]byte, error) {
keyHash := sha256.Sum256(key.Public.Bytes())
keyInfo := fmt.Sprintf("ssh-tpm-agent/%x", keyHash)
return pinentry.GetPinentry(keyInfo)
},
)

// Signal handling
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
go func() {
for range c {
a.Stop()
}
}()

//TODO: Maybe we should allow people to not auto-load keys
a.LoadKeys()

a.Wait()
}
2 changes: 2 additions & 0 deletions cmd/ssh-tpm-agent/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/google/go-tpm/tpm2/transport"
"github.com/google/go-tpm/tpm2/transport/simulator"
"golang.org/x/crypto/ssh"
sshagent "golang.org/x/crypto/ssh/agent"
)

func newSSHKey() ssh.Signer {
Expand Down Expand Up @@ -127,6 +128,7 @@ func TestSSHAuth(t *testing.T) {
socket := path.Join(t.TempDir(), "socket")

ag := agent.NewAgent(socket,
[]sshagent.ExtendedAgent{},
// TPM Callback
func() transport.TPMCloser {
return tpm
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ require (
github.com/rs/zerolog v1.26.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect
golang.org/x/sys v0.10.0 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
Expand Down

0 comments on commit 81f5cef

Please sign in to comment.