From e3226d346b4acdfb986958935d9baeafaf613b13 Mon Sep 17 00:00:00 2001 From: Hendrik Date: Sun, 28 Jan 2024 19:31:24 +0100 Subject: [PATCH] add support for owner password * Allow specifying an owner password when creating the SRK --- README.md | 2 + agent/agent.go | 6 +- agent/agent_test.go | 12 ++-- cmd/ssh-tpm-agent/main.go | 25 +++++-- cmd/ssh-tpm-agent/main_test.go | 6 +- cmd/ssh-tpm-keygen/main.go | 31 +++++++-- go.mod | 2 +- go.sum | 2 + internal/keytest/keytest.go | 4 +- key/key.go | 34 ++++----- key/key_test.go | 83 +++++++++++++++++++--- pinentry/pinentry.go | 26 +++++-- signer/signer.go | 23 +++--- signer/signer_test.go | 123 ++++++++++++++++++++++++++++++++- utils/utils.go | 3 +- 15 files changed, 317 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index bee8521..f1ee325 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ $ export SSH_AUTH_SOCK="$(ssh-tpm-agent --print-socket)" $ ssh git@github.com ``` +**Note:** For `ssh-tpm-agent` you can specify the TPM owner password using the command line flags `-o` or `--owner-password`, which are preferred. Alternatively, you can use the environment variable `SSH_TPM_AGENT_OWNER_PASSWORD`. + ### Import existing key Useful if you want to back up the key to a remote secure storage while using the key day-to-day from the TPM. diff --git a/agent/agent.go b/agent/agent.go index f079caf..46a6da3 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -29,6 +29,7 @@ var SSH_TPM_AGENT_ADD = "tpm-add-key" type Agent struct { mu sync.Mutex tpm func() transport.TPMCloser + op func() ([]byte, error) pin func(*key.Key) ([]byte, error) listener *net.UnixListener quit chan interface{} @@ -83,7 +84,7 @@ func (a *Agent) signers() ([]ssh.Signer, error) { } for _, k := range a.keys { - s, err := ssh.NewSignerFromSigner(signer.NewTPMSigner(k, a.tpm, a.pin)) + s, err := ssh.NewSignerFromSigner(signer.NewTPMSigner(k, a.op, a.tpm, a.pin)) if err != nil { return nil, fmt.Errorf("failed to prepare signer: %w", err) } @@ -343,10 +344,11 @@ func LoadKeys(keyDir string) (map[string]*key.Key, error) { return keys, err } -func NewAgent(listener *net.UnixListener, agents []agent.ExtendedAgent, tpmFetch func() transport.TPMCloser, pin func(*key.Key) ([]byte, error)) *Agent { +func NewAgent(listener *net.UnixListener, agents []agent.ExtendedAgent, tpmFetch func() transport.TPMCloser, ownerPassword func() ([]byte, error), pin func(*key.Key) ([]byte, error)) *Agent { a := &Agent{ agents: agents, tpm: tpmFetch, + op: ownerPassword, listener: listener, pin: pin, quit: make(chan interface{}), diff --git a/agent/agent_test.go b/agent/agent_test.go index 7bd13a2..2048e1f 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -31,13 +31,11 @@ func TestAddKey(t *testing.T) { ag := NewAgent(unixList, []agent.ExtendedAgent{}, // TPM Callback - func() transport.TPMCloser { - return tpm - }, + func() transport.TPMCloser { return tpm }, + // Owner password + func() ([]byte, error) { return []byte(""), nil }, // PIN Callback - func(_ *key.Key) ([]byte, error) { - return []byte(""), nil - }, + func(_ *key.Key) ([]byte, error) { return []byte(""), nil }, ) defer ag.Stop() @@ -49,7 +47,7 @@ func TestAddKey(t *testing.T) { client := agent.NewClient(conn) - k, err := key.CreateKey(tpm, tpm2.TPMAlgECC, 256, []byte(""), "") + k, err := key.CreateKey(tpm, tpm2.TPMAlgECC, 256, []byte(""), []byte(""), "") if err != nil { t.Fatal(err) } diff --git a/cmd/ssh-tpm-agent/main.go b/cmd/ssh-tpm-agent/main.go index 1366e4f..0a9c998 100644 --- a/cmd/ssh-tpm-agent/main.go +++ b/cmd/ssh-tpm-agent/main.go @@ -44,6 +44,8 @@ Options: --no-load Do not load TPM sealed keys by default. + -o, --owner-password Ask for the owner password. + -d Enable debug logging. --install-user-units Installs systemd system units and sshd configs for using @@ -98,10 +100,10 @@ func main() { } var ( - socketPath, keyDir string - swtpmFlag, printSocketFlag bool - installUserUnits bool - system, noLoad, debugMode bool + socketPath, keyDir string + swtpmFlag, printSocketFlag bool + installUserUnits, system, noLoad bool + askOwnerPassword, debugMode bool ) envSocketPath := func() string { @@ -126,6 +128,8 @@ func main() { flag.BoolVar(&installUserUnits, "install-user-units", false, "install systemd user units") flag.BoolVar(&system, "install-system", false, "install systemd user units") flag.BoolVar(&noLoad, "no-load", false, "don't load TPM sealed keys") + 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.Parse() @@ -201,12 +205,23 @@ func main() { return tpm }, + // Owner password + func() ([]byte, error) { + if askOwnerPassword { + return pinentry.GetOwnerPassword() + } else { + ownerPassword := os.Getenv("SSH_TPM_AGENT_OWNER_PASSWORD") + + return []byte(ownerPassword), nil + } + }, + // PIN Callback func(key *key.Key) ([]byte, error) { pbytes := tpm2.New2B(key.Pubkey) keyHash := sha256.Sum256(pbytes.Bytes()) keyInfo := fmt.Sprintf("ssh-tpm-agent/%x", keyHash) - return pinentry.GetPinentry(keyInfo) + return pinentry.GetPin(keyInfo) }, ) diff --git a/cmd/ssh-tpm-agent/main_test.go b/cmd/ssh-tpm-agent/main_test.go index 51e8f08..ade769d 100644 --- a/cmd/ssh-tpm-agent/main_test.go +++ b/cmd/ssh-tpm-agent/main_test.go @@ -140,9 +140,9 @@ func runSSHAuth(t *testing.T, keytype tpm2.TPMAlgID, bits int, pin []byte, keyfn ag := agent.NewAgent(unixList, []sshagent.ExtendedAgent{}, // TPM Callback - func() transport.TPMCloser { - return tpm - }, + func() transport.TPMCloser { return tpm }, + // Owner password + func() ([]byte, error) { return []byte(""), nil }, // PIN Callback func(_ *key.Key) ([]byte, error) { return pin, nil diff --git a/cmd/ssh-tpm-keygen/main.go b/cmd/ssh-tpm-keygen/main.go index 2f53d20..e37c799 100644 --- a/cmd/ssh-tpm-keygen/main.go +++ b/cmd/ssh-tpm-keygen/main.go @@ -31,6 +31,7 @@ const usage = `Usage: ssh-tpm-keygen Options: + -o, --owner-password Ask for the owner password. -C Provide a comment with the key. -f Output keyfile. -N PIN for the key. @@ -94,12 +95,24 @@ func getPin() []byte { } } +func getOwnerPassword() []byte { + fmt.Printf("Enter owner password: ") + password, err := term.ReadPassword(int(syscall.Stdin)) + fmt.Println("") + if err != nil { + log.Fatal(err) + } + + return password +} + func main() { flag.Usage = func() { fmt.Println(usage) } var ( + askOwnerPassword bool comment, outputFile, keyPin string keyType, importKey string bits int @@ -123,6 +136,8 @@ func main() { return user.Username + "@" + host }() + flag.BoolVar(&askOwnerPassword, "o", false, "ask for the owner password") + flag.BoolVar(&askOwnerPassword, "owner-password", false, "ask for the owner password") flag.StringVar(&comment, "C", defaultComment, "provide a comment, default to user@host") flag.StringVar(&outputFile, "f", "", "output keyfile") flag.StringVar(&keyPin, "N", "", "new pin for the key") @@ -164,6 +179,14 @@ func main() { os.Exit(0) } + // Ask for owner password + var ownerPassword []byte + if askOwnerPassword { + ownerPassword = getOwnerPassword() + } else { + ownerPassword = []byte("") + } + // Generate host keys if hostKeys { // Mimics the `ssh-keygen -A -f ./something` behaviour @@ -190,7 +213,7 @@ func main() { slog.Info("Generating new host key", slog.String("algorithm", strings.ToUpper(n))) - k, err := key.CreateKey(tpm, t.alg, t.bits, []byte(""), defaultComment) + k, err := key.CreateKey(tpm, t.alg, t.bits, ownerPassword, []byte(""), defaultComment) if err != nil { log.Fatal(err) } @@ -275,7 +298,7 @@ func main() { } fmt.Println() - newkey, err := key.ChangeAuth(tpm, k, oldPin, newPin) + newkey, err := key.ChangeAuth(tpm, ownerPassword, k, oldPin, newPin) if err != nil { log.Fatal("Failed changing pin on the key.") } @@ -409,12 +432,12 @@ func main() { if importKey != "" { // TODO: Read public key for comment - k, err = key.ImportKey(tpm, toImportKey, pin, comment) + k, err = key.ImportKey(tpm, ownerPassword, toImportKey, pin, comment) if err != nil { log.Fatal(err) } } else { - k, err = key.CreateKey(tpm, tpmkeyType, bits, pin, comment) + k, err = key.CreateKey(tpm, tpmkeyType, bits, ownerPassword, pin, comment) if err != nil { log.Fatal(err) } diff --git a/go.mod b/go.mod index 6eaa72a..c611a9f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.0 require ( github.com/foxboron/go-tpm-keyfiles v0.0.0-20240225134915-950e719db3d9 github.com/foxboron/swtpm_test v0.0.0-20230726224112-46aaafdf7006 - github.com/google/go-tpm v0.9.1-0.20240411180339-1fb84445f623 + github.com/google/go-tpm v0.9.1-0.20240514145214-58e3e47cd434 github.com/twpayne/go-pinentry v0.3.0 golang.org/x/crypto v0.21.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 diff --git a/go.sum b/go.sum index ad7ff7f..34df035 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/google/go-sev-guest v0.6.1 h1:NajHkAaLqN9/aW7bCFSUplUMtDgk2+HcN7jC2bt github.com/google/go-sev-guest v0.6.1/go.mod h1:UEi9uwoPbLdKGl1QHaq1G8pfCbQ4QP0swWX4J0k6r+Q= github.com/google/go-tpm v0.9.1-0.20240411180339-1fb84445f623 h1:LGYp08nFCGgxM/pRoE4etWElLB2WsrhJiBG4jK04MPE= github.com/google/go-tpm v0.9.1-0.20240411180339-1fb84445f623/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/go-tpm v0.9.1-0.20240514145214-58e3e47cd434 h1:uPadaCeI0VnloLvthGLalr0Io0IDoI1VEQ95APzVAiw= +github.com/google/go-tpm v0.9.1-0.20240514145214-58e3e47cd434/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba h1:qJEJcuLzH5KDR0gKc0zcktin6KSAwL7+jWKBYceddTc= github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:EFYHy8/1y2KfgTAsx7Luu7NGhoxtuVHnNo8jE7FikKc= github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ= diff --git a/internal/keytest/keytest.go b/internal/keytest/keytest.go index 3dec9f3..a14673c 100644 --- a/internal/keytest/keytest.go +++ b/internal/keytest/keytest.go @@ -36,7 +36,7 @@ func MkECDSA(t *testing.T, a elliptic.Curve) ecdsa.PrivateKey { // Test helper for CreateKey func MkKey(t *testing.T, tpm transport.TPMCloser, keytype tpm2.TPMAlgID, bits int, pin []byte, comment string) (*key.Key, error) { t.Helper() - return key.CreateKey(tpm, keytype, bits, pin, comment) + return key.CreateKey(tpm, keytype, bits, []byte(""), pin, comment) } // Helper to make an importable key @@ -56,7 +56,7 @@ func MkImportableKey(t *testing.T, tpm transport.TPMCloser, keytype tpm2.TPMAlgI case tpm2.TPMAlgRSA: pk = MkRSA(t, bits) } - return key.ImportKey(tpm, pk, pin, comment) + return key.ImportKey(tpm, []byte(""), pk, pin, comment) } // Give us some random bytes diff --git a/key/key.go b/key/key.go index f7d8276..bfb35f3 100644 --- a/key/key.go +++ b/key/key.go @@ -153,13 +153,16 @@ func DecodeKey(pemBytes []byte) (*Key, error) { } // Creates a Storage Key, or return the loaded storage key -func CreateSRK(tpm transport.TPMCloser) (*tpm2.AuthHandle, *tpm2.TPMTPublic, error) { +func CreateSRK(tpm transport.TPMCloser, ownerPassword []byte) (*tpm2.AuthHandle, *tpm2.TPMTPublic, error) { srk := tpm2.CreatePrimary{ - PrimaryHandle: tpm2.TPMRHOwner, + PrimaryHandle: tpm2.AuthHandle{ + Handle: tpm2.TPMRHOwner, + Auth: tpm2.PasswordAuth(ownerPassword), + }, InSensitive: tpm2.TPM2BSensitiveCreate{ Sensitive: &tpm2.TPMSSensitiveCreate{ UserAuth: tpm2.TPM2BAuth{ - Buffer: []byte(nil), + Buffer: []byte(""), }, }, }, @@ -230,7 +233,7 @@ func createRSAKey(bits tpm2.TPMKeyBits, sha tpm2.TPMAlgID) tpm2.TPM2B[tpm2.TPMTP }) } -func CreateKey(tpm transport.TPMCloser, keytype tpm2.TPMAlgID, bits int, pin []byte, comment string) (*Key, error) { +func CreateKey(tpm transport.TPMCloser, keytype tpm2.TPMAlgID, bits int, ownerPassword []byte, pin []byte, comment string) (*Key, error) { rsaBits := []int{2048} ecdsaBits := []int{256, 384, 521} @@ -258,7 +261,7 @@ func CreateKey(tpm transport.TPMCloser, keytype tpm2.TPMAlgID, bits int, pin []b return nil, fmt.Errorf("unsupported key type") } - srkHandle, srkPublic, err := CreateSRK(tpm) + srkHandle, srkPublic, err := CreateSRK(tpm, ownerPassword) if err != nil { return nil, fmt.Errorf("failed creating SRK: %v", err) } @@ -296,8 +299,7 @@ func CreateKey(tpm transport.TPMCloser, keytype tpm2.TPMAlgID, bits int, pin []b emptyAuth = false } - var createRsp *tpm2.CreateResponse - createRsp, err = createKey.Execute(tpm, + createRsp, err := createKey.Execute(tpm, tpm2.HMAC(tpm2.TPMAlgSHA256, 16, tpm2.AESEncryption(128, tpm2.EncryptIn), tpm2.Salted(srkHandle.Handle, *srkPublic))) @@ -315,7 +317,7 @@ func CreateKey(tpm transport.TPMCloser, keytype tpm2.TPMAlgID, bits int, pin []b return &Key{tpmkey}, nil } -func ImportKey(tpm transport.TPMCloser, pk any, pin []byte, comment string) (*Key, error) { +func ImportKey(tpm transport.TPMCloser, ownerPassword []byte, pk any, pin []byte, comment string) (*Key, error) { var public tpm2.TPMTPublic var sensitive tpm2.TPMTSensitive var unique tpm2.TPMUPublicID @@ -419,7 +421,7 @@ func ImportKey(tpm transport.TPMCloser, pk any, pin []byte, comment string) (*Ke return nil, fmt.Errorf("unsupported key type") } - srkHandle, srkPublic, err := CreateSRK(tpm) + srkHandle, srkPublic, err := CreateSRK(tpm, ownerPassword) if err != nil { return nil, fmt.Errorf("failed creating SRK: %v", err) } @@ -434,7 +436,7 @@ func ImportKey(tpm transport.TPMCloser, pk any, pin []byte, comment string) (*Ke emptyAuth = false } - // We need the size calcualted in the buffer, so we do this serialization dance + // We need the size calculated in the buffer, so we do this serialization dance l := tpm2.Marshal(tpm2.TPM2BPrivate{Buffer: tpm2.Marshal(sensitive)}) pubbytes := tpm2.New2B(public) @@ -483,8 +485,8 @@ func LoadKeyWithParent(tpm transport.TPMCloser, parent tpm2.AuthHandle, key *Key }, nil } -func LoadKey(tpm transport.TPMCloser, key *Key) (*tpm2.AuthHandle, error) { - srkHandle, _, err := CreateSRK(tpm) +func LoadKey(tpm transport.TPMCloser, ownerPassword []byte, key *Key) (*tpm2.AuthHandle, error) { + srkHandle, _, err := CreateSRK(tpm, ownerPassword) if err != nil { return nil, err } @@ -571,7 +573,7 @@ func newRSASigScheme(digest tpm2.TPMAlgID) tpm2.TPMTSigScheme { } } -func Sign(tpm transport.TPMCloser, key *Key, digest []byte, auth []byte, digestalg tpm2.TPMAlgID) ([]byte, error) { +func Sign(tpm transport.TPMCloser, ownerPassword []byte, key *Key, digest []byte, auth []byte, digestalg tpm2.TPMAlgID) ([]byte, error) { var digestlength int switch digestalg { @@ -587,7 +589,7 @@ func Sign(tpm transport.TPMCloser, key *Key, digest []byte, auth []byte, digesta return nil, fmt.Errorf("incorrect checksum length. expected %v got %v", digestlength, len(digest)) } - srkHandle, srkPublic, err := CreateSRK(tpm) + srkHandle, srkPublic, err := CreateSRK(tpm, ownerPassword) if err != nil { return nil, fmt.Errorf("failed creating SRK: %v", err) } @@ -649,10 +651,10 @@ func Sign(tpm transport.TPMCloser, key *Key, digest []byte, auth []byte, digesta // ChangeAuth changes the object authn header to something else // notice this changes the private blob inside the key in-place. -func ChangeAuth(tpm transport.TPMCloser, key *Key, oldpin, newpin []byte) (*Key, error) { +func ChangeAuth(tpm transport.TPMCloser, ownerPassword []byte, key *Key, oldpin, newpin []byte) (*Key, error) { var err error - srkHandle, _, err := CreateSRK(tpm) + srkHandle, _, err := CreateSRK(tpm, ownerPassword) if err != nil { return nil, fmt.Errorf("failed creating SRK: %v", err) } diff --git a/key/key_test.go b/key/key_test.go index e753788..6b1efdb 100644 --- a/key/key_test.go +++ b/key/key_test.go @@ -52,14 +52,14 @@ func TestCreateKey(t *testing.T) { for _, c := range cases { t.Run(c.text, func(t *testing.T) { - k, err := CreateKey(tpm, c.alg, c.bits, []byte(""), "") + k, err := CreateKey(tpm, c.alg, c.bits, []byte(""), []byte(""), "") if err != nil { t.Fatalf("failed key import: %v", err) } // Test if we can load the key // signer/signer_test.go tests the signing of the key - handle, err := LoadKey(tpm, k) + handle, err := LoadKey(tpm, []byte(""), k) if err != nil { t.Fatalf("failed loading key: %v", err) } @@ -68,6 +68,71 @@ func TestCreateKey(t *testing.T) { } } +func TestCreateKeyWithOwnerPassword(t *testing.T) { + cases := []struct { + text string + alg tpm2.TPMAlgID + bits int + }{ + { + text: "p256", + alg: tpm2.TPMAlgECC, + bits: 256, + }, + { + text: "p384", + alg: tpm2.TPMAlgECC, + bits: 384, + }, + { + text: "p521", + alg: tpm2.TPMAlgECC, + bits: 521, + }, + { + text: "rsa", + alg: tpm2.TPMAlgRSA, + bits: 2048, + }, + } + + tpm, err := simulator.OpenSimulator() + if err != nil { + t.Fatal(err) + } + defer tpm.Close() + + ownerPassword := []byte("testPassword") + + hca := tpm2.HierarchyChangeAuth{ + AuthHandle: tpm2.TPMRHOwner, + NewAuth: tpm2.TPM2BAuth{ + Buffer: ownerPassword, + }, + } + _, err = hca.Execute(tpm) + if err != nil { + t.Errorf("failed HierarchyChangeAuth: %v", err) + } + + for _, c := range cases { + t.Run(c.text, func(t *testing.T) { + k, err := CreateKey(tpm, c.alg, c.bits, ownerPassword, []byte(""), "") + if err != nil { + t.Errorf("failed key import: %v", err) + } + + // Test if we can load the key + // signer/signer_test.go tests the signing of the key + handle, err := LoadKey(tpm, ownerPassword, k) + if err != nil { + t.Errorf("failed loading key: %v", err) + } + utils.FlushHandle(tpm, handle) + }) + } +} + func TestImport(t *testing.T) { tpm, err := simulator.OpenSimulator() if err != nil { @@ -100,7 +165,7 @@ func TestImport(t *testing.T) { }, } { t.Run(c.text, func(t *testing.T) { - k, err := ImportKey(tpm, c.pk, []byte(""), "") + k, err := ImportKey(tpm, []byte(""), c.pk, []byte(""), "") if err != nil && c.fail { return } @@ -110,7 +175,7 @@ func TestImport(t *testing.T) { // Test if we can load the key // signer/signer_test.go tests the signing of the key - handle, err := LoadKey(tpm, k) + handle, err := LoadKey(tpm, []byte(""), k) if err != nil { t.Fatalf("failed loading key: %v", err) } @@ -156,7 +221,7 @@ func TestKeyPublickey(t *testing.T) { }, } { t.Run(c.text, func(t *testing.T) { - k, err := ImportKey(tpm, c.pk, []byte(""), "") + k, err := ImportKey(tpm, []byte(""), c.pk, []byte(""), "") if err != nil && c.fail { return } @@ -271,22 +336,22 @@ func TestChangeAuth(t *testing.T) { h.Write([]byte(c.text)) b := h.Sum(nil) - _, err = Sign(tpm, k, b, c.oldPin, tpm2.TPMAlgSHA256) + _, err = Sign(tpm, []byte(""), k, b, c.oldPin, tpm2.TPMAlgSHA256) if err != nil { t.Fatalf("signing with correct pin should not fail: %v", err) } - key, err := ChangeAuth(tpm, k, c.oldPin, c.newPin) + key, err := ChangeAuth(tpm, []byte(""), k, c.oldPin, c.newPin) if err != nil { t.Fatalf("ChangeAuth shouldn't fail: %v", err) } - _, err = Sign(tpm, key, b, c.oldPin, tpm2.TPMAlgSHA256) + _, err = Sign(tpm, []byte(""), key, b, c.oldPin, tpm2.TPMAlgSHA256) if errors.Is(err, tpm2.TPMRCBadAuth) { t.Fatalf("old pin works on updated key") } - _, err = Sign(tpm, key, b, c.newPin, tpm2.TPMAlgSHA256) + _, err = Sign(tpm, []byte(""), key, b, c.newPin, tpm2.TPMAlgSHA256) if errors.Is(err, tpm2.TPMRCBadAuth) { t.Fatalf("new pin doesn't work") } diff --git a/pinentry/pinentry.go b/pinentry/pinentry.go index 50fa805..1c275b5 100644 --- a/pinentry/pinentry.go +++ b/pinentry/pinentry.go @@ -10,16 +10,16 @@ var ( ErrPinentryCancelled = errors.New("cancelled pinentry") ) -func GetPinentry(keyInfo string) ([]byte, error) { +func GetPinentry(keyInfo string, description string, prompt string, title string) ([]byte, error) { // TODO: Include some additional key metadata client, err := pinentry.NewClient( pinentry.WithCommand("OPTION allow-external-password-cache"), pinentry.WithCommandf("SETKEYINFO %v", keyInfo), pinentry.WithBinaryNameFromGnuPGAgentConf(), - pinentry.WithDesc("Enter PIN for TPM key"), + pinentry.WithDesc(description), pinentry.WithGPGTTY(), - pinentry.WithPrompt("PIN:"), - pinentry.WithTitle("ssh-tpm-agent PIN entry"), + pinentry.WithPrompt(prompt), + pinentry.WithTitle(title), ) if err != nil { return nil, err @@ -37,3 +37,21 @@ func GetPinentry(keyInfo string) ([]byte, error) { return []byte(pin), nil } } + +func GetPin(keyInfo string) ([]byte, error) { + return GetPinentry( + keyInfo, + "Enter PIN for TPM key", + "PIN:", + "ssh-tpm-agent PIN entry", + ) +} + +func GetOwnerPassword() ([]byte, error) { + return GetPinentry( + "ssh-tpm-agent/owner-password", + "Enter owner password for TPM", + "Owner password:", + "ssh-tpm-agent owner password entry", + ) +} diff --git a/signer/signer.go b/signer/signer.go index 702b3ac..2336010 100644 --- a/signer/signer.go +++ b/signer/signer.go @@ -11,18 +11,20 @@ import ( ) type TPMSigner struct { - key *key.Key - tpm func() transport.TPMCloser - pin func(*key.Key) ([]byte, error) + key *key.Key + ownerPassword func() ([]byte, error) + tpm func() transport.TPMCloser + pin func(*key.Key) ([]byte, error) } var _ crypto.Signer = &TPMSigner{} -func NewTPMSigner(k *key.Key, tpm func() transport.TPMCloser, pin func(*key.Key) ([]byte, error)) *TPMSigner { +func NewTPMSigner(k *key.Key, ownerPassword func() ([]byte, error), tpm func() transport.TPMCloser, pin func(*key.Key) ([]byte, error)) *TPMSigner { return &TPMSigner{ - key: k, - tpm: tpm, - pin: pin, + key: k, + ownerPassword: ownerPassword, + tpm: tpm, + pin: pin, } } @@ -58,5 +60,10 @@ func (t *TPMSigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([] return nil, fmt.Errorf("%s is not a supported hashing algorithm", opts.HashFunc()) } - return key.Sign(t.tpm(), t.key, digest, auth, digestalg) + ownerPassword, err := t.ownerPassword() + if err != nil { + return nil, err + } + + return key.Sign(t.tpm(), ownerPassword, t.key, digest, auth, digestalg) } diff --git a/signer/signer_test.go b/signer/signer_test.go index be767f5..5b3da81 100644 --- a/signer/signer_test.go +++ b/signer/signer_test.go @@ -112,12 +112,13 @@ func TestSigning(t *testing.T) { h.Write([]byte("heyho")) b := h.Sum(nil) - k, err := key.CreateKey(tpm, c.keytype, c.bits, c.pin, "") + k, err := key.CreateKey(tpm, c.keytype, c.bits, []byte(""), c.pin, "") if err != nil { t.Fatalf("%v", err) } signer := NewTPMSigner(k, + func() ([]byte, error) { return []byte(""), nil }, func() transport.TPMCloser { return tpm }, func(_ *key.Key) ([]byte, error) { return c.signpin, nil }, ) @@ -166,6 +167,123 @@ func TestSigning(t *testing.T) { } } +func TestSigningWithOwnerPassword(t *testing.T) { + ownerPassword := []byte("testPassword") + + cases := []struct { + msg string + keytype tpm2.TPMAlgID + bits int + digest crypto.Hash + filekey []byte + pin []byte + signpin []byte + ownerpassword []byte + shouldfail bool + }{ + { + msg: "ecdsa - test encryption/decrypt - no pin", + filekey: []byte("this is a test filekey"), + keytype: tpm2.TPMAlgECC, + digest: crypto.SHA256, + bits: 256, + ownerpassword: ownerPassword, + }, + { + msg: "ecdsa - test encryption/decrypt - pin", + filekey: []byte("this is a test filekey"), + pin: []byte("123"), + signpin: []byte("123"), + keytype: tpm2.TPMAlgECC, + digest: crypto.SHA256, + bits: 256, + ownerpassword: ownerPassword, + }, + { + msg: "ecdsa - test encryption/decrypt - no pin - invalid owner password", + filekey: []byte("this is a test filekey"), + keytype: tpm2.TPMAlgECC, + digest: crypto.SHA256, + bits: 256, + shouldfail: true, + ownerpassword: []byte("invalidPassword"), + }, + } + + for n, c := range cases { + t.Run(fmt.Sprintf("case %d, %s", n, c.msg), func(t *testing.T) { + // Always re-init simulator as the Signer is going to close it, + // and we can't retain state. + tpm, err := simulator.OpenSimulator() + if err != nil { + t.Fatal(err) + } + defer tpm.Close() + + hca := tpm2.HierarchyChangeAuth{ + AuthHandle: tpm2.TPMRHOwner, + NewAuth: tpm2.TPM2BAuth{ + Buffer: ownerPassword, + }, + } + _, err = hca.Execute(tpm) + if err != nil { + t.Errorf("failed HierarchyChangeAuth: %v", err) + } + + h := c.digest.New() + h.Write([]byte("heyho")) + b := h.Sum(nil) + + k, err := key.CreateKey(tpm, c.keytype, c.bits, c.ownerpassword, c.pin, "") + if err != nil { + if c.shouldfail { + return + } + t.Fatalf("%v", err) + } + + signer := NewTPMSigner(k, + func() ([]byte, error) { return c.ownerpassword, nil }, + func() transport.TPMCloser { return tpm }, + func(_ *key.Key) ([]byte, error) { return c.signpin, nil }, + ) + + // Empty reader, we don't use this + var r io.Reader + + sig, err := signer.Sign(r, b[:], c.digest) + if err != nil { + t.Fatalf("%v", err) + } + + pubkey, err := k.PublicKey() + if err != nil { + t.Fatalf("failed getting pubkey: %v", err) + } + + if err != nil { + t.Fatalf("failed test: %v", err) + } + + if c.shouldfail { + t.Fatalf("test should be failing") + } + + switch pk := pubkey.(type) { + case *ecdsa.PublicKey: + if !ecdsa.VerifyASN1(pk, b[:], sig) { + t.Fatalf("invalid signature") + } + case *rsa.PublicKey: + if err := rsa.VerifyPKCS1v15(pk, c.digest, b[:], sig); err != nil { + t.Errorf("Signature verification failed: %v", err) + } + } + }) + } +} + func TestSigningWithImportedKey(t *testing.T) { cases := []struct { msg string @@ -256,12 +374,13 @@ func TestSigningWithImportedKey(t *testing.T) { pk = *p } - k, err := key.ImportKey(tpm, pk, c.pin, "") + k, err := key.ImportKey(tpm, []byte(""), pk, c.pin, "") if err != nil { t.Fatalf("failed key import: %v", err) } signer := NewTPMSigner(k, + func() ([]byte, error) { return []byte(""), nil }, func() transport.TPMCloser { return tpm }, func(_ *key.Key) ([]byte, error) { return c.signpin, nil }, ) diff --git a/utils/utils.go b/utils/utils.go index eddb105..755c41e 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -3,12 +3,11 @@ package utils import ( "errors" "fmt" + "github.com/foxboron/ssh-tpm-agent/contrib" "html/template" "io/fs" "os" "path" - - "github.com/foxboron/ssh-tpm-agent/contrib" ) func SSHDir() string {