Skip to content

Commit

Permalink
Merge branch 'morten/cert-signing'
Browse files Browse the repository at this point in the history
* morten/cert-signing:
  agent: ensure certs are not embedded into the key
  agent: implement AgentKey to serialize tpmkey
  key: ensure we embed ssh.PublicKey inside the SSHTPMKey struct
  agent: ensure we can remove certificates from the agent
  agent: introduce AddProxyAgent
  agent: create setup for certificate testing
  • Loading branch information
Foxboron committed Dec 21, 2024
2 parents 117a1d9 + 663ea55 commit 3aba1f9
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 69 deletions.
67 changes: 38 additions & 29 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import (
"golang.org/x/crypto/ssh/agent"
)

var ErrOperationUnsupported = errors.New("operation unsupported")
var (
ErrOperationUnsupported = errors.New("operation unsupported")
ErrNoMatchPrivateKeys = errors.New("no private keys match the requested public key")
)

var SSH_TPM_AGENT_ADD = "tpm-add-key"

Expand Down Expand Up @@ -79,6 +82,15 @@ func (a *Agent) AddTPMKey(addedkey []byte) ([]byte, error) {
return []byte(""), nil
}

func (a *Agent) AddProxyAgent(es agent.ExtendedAgent) error {
// TODO: Write this up as an extension
slog.Debug("called addproxyagent")
a.mu.Lock()
defer a.mu.Unlock()
a.agents = append(a.agents, es)
return nil
}

func (a *Agent) Close() error {
slog.Debug("called close")
a.Stop()
Expand Down Expand Up @@ -126,6 +138,11 @@ func (a *Agent) List() ([]*agent.Key, error) {
a.mu.Lock()
defer a.mu.Unlock()

// Our keys first, then proxied agents
for _, k := range a.keys {
agentKeys = append(agentKeys, k.AgentKey())
}

for _, agent := range a.agents {
l, err := agent.List()
if err != nil {
Expand All @@ -135,27 +152,6 @@ func (a *Agent) List() ([]*agent.Key, error) {
agentKeys = append(agentKeys, l...)
}

for _, k := range a.keys {
pk, err := k.SSHPublicKey()
if err != nil {
return nil, err
}

agentKeys = append(agentKeys, &agent.Key{
Format: pk.Type(),
Blob: pk.Marshal(),
Comment: k.Description,
})

if k.Certificate != nil {
agentKeys = append(agentKeys, &agent.Key{
Format: k.Certificate.Type(),
Blob: k.Certificate.Marshal(),
Comment: k.Description,
})
}
}

return agentKeys, nil
}

Expand Down Expand Up @@ -207,7 +203,7 @@ func (a *Agent) SignWithFlags(key ssh.PublicKey, data []byte, flags agent.Signat
}
}

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

func (a *Agent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
Expand Down Expand Up @@ -296,16 +292,23 @@ func (a *Agent) Remove(sshkey ssh.PublicKey) error {
a.mu.Lock()
defer a.mu.Unlock()

fp := ssh.FingerprintSHA256(sshkey)

var found bool
a.keys = slices.DeleteFunc(a.keys, func(k *key.SSHTPMKey) bool {
if k.Fingerprint() == ssh.FingerprintSHA256(sshkey) {
slog.Debug("deleting key from ssh-tpm-agent", slog.String("fingerprint", fp))
if bytes.Equal(sshkey.Marshal(), k.AgentKey().Marshal()) {
slog.Debug("deleting key from ssh-tpm-agent",
slog.String("fingerprint", ssh.FingerprintSHA256(sshkey)),
slog.String("type", sshkey.Type()),
)
found = true
return true
}
return false
})

if found {
return nil
}

for _, agent := range a.agents {
lkeys, err := agent.List()
if err != nil {
Expand All @@ -320,11 +323,17 @@ func (a *Agent) Remove(sshkey ssh.PublicKey) error {
if err := agent.Remove(sshkey); err != nil {
slog.Debug("agent returned err on Remove()", slog.Any("err", err))
}
slog.Debug("deleting key from an proxy agent", slog.String("fingerprint", fp))
slog.Debug("deleting key from an proxy agent",
slog.String("fingerprint", ssh.FingerprintSHA256(sshkey)),
slog.String("type", sshkey.Type()),
)
return nil
}
}
slog.Debug("could not find key in any proxied agent", slog.String("fingerprint", fp))
slog.Debug("could not find key in any proxied agent",
slog.String("fingerprint", ssh.FingerprintSHA256(sshkey)),
slog.String("type", sshkey.Type()),
)
return fmt.Errorf("key not found")
}

Expand Down
177 changes: 170 additions & 7 deletions agent/agent_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
package agent
package agent_test

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"errors"
"log"
"net"
"path"
"testing"

"github.com/foxboron/ssh-tpm-agent/agent"
"github.com/foxboron/ssh-tpm-agent/internal/keytest"
"github.com/foxboron/ssh-tpm-agent/key"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpm2/transport"
"github.com/google/go-tpm/tpm2/transport/simulator"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/crypto/ssh"
sshagent "golang.org/x/crypto/ssh/agent"
)

func TestAddKey(t *testing.T) {
Expand All @@ -28,8 +35,8 @@ func TestAddKey(t *testing.T) {
}
defer unixList.Close()

ag := NewAgent(unixList,
[]agent.ExtendedAgent{},
ag := agent.NewAgent(unixList,
[]sshagent.ExtendedAgent{},
// TPM Callback
func() transport.TPMCloser { return tpm },
// Owner password
Expand All @@ -45,21 +52,177 @@ func TestAddKey(t *testing.T) {
}
defer conn.Close()

client := agent.NewClient(conn)
client := sshagent.NewClient(conn)

k, err := key.NewSSHTPMKey(tpm, tpm2.TPMAlgECC, 256, []byte(""))
if err != nil {
t.Fatal(err)
}

addedkey := agent.AddedKey{
addedkey := sshagent.AddedKey{
PrivateKey: k,
Certificate: nil,
Comment: k.Description,
}

_, err = client.Extension(SSH_TPM_AGENT_ADD, MarshalTPMKeyMsg(&addedkey))
_, err = client.Extension(agent.SSH_TPM_AGENT_ADD, agent.MarshalTPMKeyMsg(&addedkey))
if err != nil {
t.Fatal(err)
}
}

func TestSigning(t *testing.T) {
tpm, err := simulator.OpenSimulator()
if err != nil {
t.Fatal(err)
}
defer tpm.Close()

ca := keytest.MkECDSA(t, elliptic.P256())
for _, c := range []struct {
text string
alg tpm2.TPMAlgID
bits int
f keytest.KeyFunc
wanterr error
}{
{
text: "sign key",
alg: tpm2.TPMAlgECC,
bits: 256,
f: keytest.MkKey,
},
{
text: "sign key cert",
alg: tpm2.TPMAlgECC,
bits: 256,
f: keytest.MkCertificate(t, &ca),
},
} {

t.Run(c.text, func(t *testing.T) {
k, err := c.f(t, tpm, c.alg, c.bits, []byte(""), "")
if err != nil {
t.Fatalf("failed key import: %v", err)
}

ag := keytest.NewTestAgent(t, tpm)
defer ag.Stop()

if err := ag.AddKey(k); err != nil {
t.Fatalf("failed saving key: %v", err)
}

// Shim the certificate if there is one
var sshkey ssh.PublicKey
if k.Certificate != nil {
sshkey = k.Certificate
} else {
sshkey = *k.PublicKey
}

_, err = ag.Sign(sshkey, []byte("test"))
if !errors.Is(err, c.wanterr) {
t.Fatalf("failed signing: %v", err)
}
})
}
}

func TestRemoveCertFromProxy(t *testing.T) {
tpm, err := simulator.OpenSimulator()
if err != nil {
t.Fatal(err)
}
defer tpm.Close()

caEcdsa, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("failed creating CA key")
}

for _, c := range []struct {
text string
alg tpm2.TPMAlgID
bits int
f keytest.KeyFunc
wanterr error
numkeys int
}{
{
text: "sign key",
alg: tpm2.TPMAlgECC,
bits: 256,
f: keytest.MkKey,
numkeys: 0,
},
{
text: "sign key cert",
alg: tpm2.TPMAlgECC,
bits: 256,
f: keytest.MkCertificate(t, caEcdsa),
numkeys: 1,
},
} {

t.Run(c.text, func(t *testing.T) {
k, err := c.f(t, tpm, c.alg, c.bits, []byte(""), "")
if err != nil {
t.Fatalf("failed key import: %v", err)
}

proxyagent := keytest.NewTestAgent(t, tpm)
defer proxyagent.Stop()

testagent := keytest.NewTestAgent(t, tpm)
defer testagent.Stop()

if err := testagent.AddKey(k); err != nil {
t.Fatalf("failed saving key: %v", err)
}

if k.Certificate != nil {
// If we have a certificate, include
// the key without the certificate
c := *k
c.Certificate = nil
if err := testagent.AddKey(&c); err != nil {
t.Fatalf("failed saving key: %v", err)
}
}

// Add testagent to proxyagent
// We'll try to remove the key from testagent.
proxyagent.AddProxyAgent(testagent)

// Shim the certificate if there is one
var sshkey ssh.PublicKey
if k.Certificate != nil {
sshkey = k.Certificate
} else {
sshkey = *k.PublicKey
}

if err := proxyagent.Remove(sshkey); err != nil {
t.Fatalf("failed to remove key: %v", err)
}

// Check the key doesn't exist in the proxy nor the agent
proxysl, err := proxyagent.List()
if err != nil {
t.Fatalf("%v", err)
}
if len(proxysl) != c.numkeys {
t.Fatalf("still keys in the agent. Should be 0")
}

sl, err := testagent.List()
if err != nil {
t.Fatalf("%v", err)
}
if len(sl) != c.numkeys {
t.Fatalf("still keys in the agent. Should be 0")
}
})
}
}
25 changes: 16 additions & 9 deletions cmd/ssh-tpm-add/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,13 @@ func main() {

client := sshagent.NewClient(conn)

addedkey := sshagent.AddedKey{
PrivateKey: k,
Comment: k.Description,
if _, err = client.Extension(agent.SSH_TPM_AGENT_ADD, agent.MarshalTPMKeyMsg(
&sshagent.AddedKey{
PrivateKey: k,
Comment: k.Description,
},
)); err != nil {
log.Fatal(err)
}

certStr := fmt.Sprintf("%s-cert.pub", strings.TrimSuffix(path, filepath.Ext(path)))
Expand All @@ -127,15 +131,18 @@ func main() {
if !ok {
log.Fatal("failed parsing ssh certificate")
}
addedkey.Certificate = cert
if _, err = client.Extension(agent.SSH_TPM_AGENT_ADD, agent.MarshalTPMKeyMsg(
&sshagent.AddedKey{
PrivateKey: k,
Certificate: cert,
Comment: k.Description,
},
)); err != nil {
log.Fatal(err)
}
fmt.Printf("Identity added: %s\n", certStr)
}

_, err = client.Extension(agent.SSH_TPM_AGENT_ADD, agent.MarshalTPMKeyMsg(&addedkey))
if err != nil {
log.Fatal(err)
}

fmt.Printf("Identity added: %s\n", path)
}
}
Loading

0 comments on commit 3aba1f9

Please sign in to comment.