diff --git a/agent/agent.go b/agent/agent.go index 873af92..1047bb5 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) } @@ -316,10 +317,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..e525854 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), 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(nil), []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..3188e71 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,21 @@ func main() { return tpm }, + // Owner password + func() ([]byte, error) { + if askOwnerPassword { + return pinentry.GetOwnerPassword() + } else { + return []byte(nil), 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 437b43e..fa77b63 100644 --- a/cmd/ssh-tpm-agent/main_test.go +++ b/cmd/ssh-tpm-agent/main_test.go @@ -111,7 +111,7 @@ func runSSHAuth(t *testing.T, keytype tpm2.TPMAlgID, bits int) { } defer tpm.Close() - k, err := key.CreateKey(tpm, keytype, bits, []byte(""), "") + k, err := key.CreateKey(tpm, keytype, bits, []byte(nil), []byte(""), "") if err != nil { t.Fatalf("failed creating key: %v", err) } @@ -139,13 +139,11 @@ func runSSHAuth(t *testing.T, keytype tpm2.TPMAlgID, bits int) { 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), nil }, // PIN Callback - func(_ *key.Key) ([]byte, error) { - return []byte(""), nil - }, + func(_ *key.Key) ([]byte, error) { return []byte(""), nil }, ) defer ag.Stop() diff --git a/cmd/ssh-tpm-keygen/main.go b/cmd/ssh-tpm-keygen/main.go index 62c6ab3..0b0ba9a 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,26 @@ func getPin() []byte { } } +func getOwnerPassword() []byte { + for { + 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 +138,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") @@ -163,6 +180,14 @@ func main() { os.Exit(0) } + // Ask for owner password + var ownerPassword []byte + if askOwnerPassword { + ownerPassword = getOwnerPassword() + } else { + ownerPassword = []byte(nil) + } + // Generate host keys if hostKeys { // Mimics the `ssh-keygen -A -f ./something` behaviour @@ -189,7 +214,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) } @@ -351,12 +376,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/key/key.go b/key/key.go index 3424543..e561288 100644 --- a/key/key.go +++ b/key/key.go @@ -151,9 +151,12 @@ 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{ @@ -228,7 +231,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} @@ -256,7 +259,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) } @@ -294,8 +297,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))) @@ -313,7 +315,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 @@ -417,7 +419,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) } @@ -432,7 +434,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) @@ -481,8 +483,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 } diff --git a/key/key_test.go b/key/key_test.go index 9287c29..e9c1312 100644 --- a/key/key_test.go +++ b/key/key_test.go @@ -94,14 +94,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(nil), []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(nil), k) if err != nil { t.Fatalf("failed loading key: %v", err) } 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 04df950..3dd8f26 100644 --- a/signer/signer.go +++ b/signer/signer.go @@ -15,18 +15,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, } } @@ -119,7 +121,12 @@ func (t *TPMSigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([] tpm := t.getTPM() defer tpm.Close() - srkHandle, srkPublic, err := key.CreateSRK(tpm) + ownerPassword, err := t.ownerPassword() + if err != nil { + return nil, err + } + + srkHandle, srkPublic, err := key.CreateSRK(tpm, ownerPassword) if err != nil { return nil, fmt.Errorf("failed creating SRK: %v", err) } diff --git a/signer/signer_test.go b/signer/signer_test.go index be767f5..1617181 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(nil), c.pin, "") if err != nil { t.Fatalf("%v", err) } signer := NewTPMSigner(k, + func() ([]byte, error) { return []byte(nil), nil }, func() transport.TPMCloser { return tpm }, func(_ *key.Key) ([]byte, error) { return c.signpin, nil }, ) @@ -256,12 +257,13 @@ func TestSigningWithImportedKey(t *testing.T) { pk = *p } - k, err := key.ImportKey(tpm, pk, c.pin, "") + k, err := key.ImportKey(tpm, []byte(nil), pk, c.pin, "") if err != nil { t.Fatalf("failed key import: %v", err) } signer := NewTPMSigner(k, + func() ([]byte, error) { return []byte(nil), 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 {