diff --git a/lib/tpm/testdata/TestPrintQuery/ekcert.golden b/lib/tpm/testdata/TestPrintQuery/ekcert.golden new file mode 100644 index 0000000000000..99fa7d0c361c7 --- /dev/null +++ b/lib/tpm/testdata/TestPrintQuery/ekcert.golden @@ -0,0 +1,4 @@ +TPM Information +EKPub Hash: aabbaabbcc +EKCert Detected: true +EKCert Serial: aa:bb:cc diff --git a/lib/tpm/testdata/TestPrintQuery/ekcert_debug.golden b/lib/tpm/testdata/TestPrintQuery/ekcert_debug.golden new file mode 100644 index 0000000000000..fed739fac3690 --- /dev/null +++ b/lib/tpm/testdata/TestPrintQuery/ekcert_debug.golden @@ -0,0 +1,12 @@ +TPM Information +EKPub Hash: aabbaabbcc +EKCert Detected: true +EKCert Serial: aa:bb:cc +EKPub: +-----BEGIN PUBLIC KEY----- +ZWtwdWI= +-----END PUBLIC KEY----- +EKCert: +-----BEGIN CERTIFICATE----- +ZWtjZXJ0 +-----END CERTIFICATE----- diff --git a/lib/tpm/testdata/TestPrintQuery/ekpub.golden b/lib/tpm/testdata/TestPrintQuery/ekpub.golden new file mode 100644 index 0000000000000..a9bb8c429df9f --- /dev/null +++ b/lib/tpm/testdata/TestPrintQuery/ekpub.golden @@ -0,0 +1,3 @@ +TPM Information +EKPub Hash: aabbaabbcc +EKCert Detected: false diff --git a/lib/tpm/testdata/TestPrintQuery/ekpub_debug.golden b/lib/tpm/testdata/TestPrintQuery/ekpub_debug.golden new file mode 100644 index 0000000000000..1151728fc3f28 --- /dev/null +++ b/lib/tpm/testdata/TestPrintQuery/ekpub_debug.golden @@ -0,0 +1,7 @@ +TPM Information +EKPub Hash: aabbaabbcc +EKCert Detected: false +EKPub: +-----BEGIN PUBLIC KEY----- +ZWtwdWI= +-----END PUBLIC KEY----- diff --git a/lib/tpm/tpm.go b/lib/tpm/tpm.go index e83000535bf53..4ab87298226be 100644 --- a/lib/tpm/tpm.go +++ b/lib/tpm/tpm.go @@ -22,7 +22,9 @@ import ( "context" "crypto/sha256" "crypto/x509" + "encoding/pem" "fmt" + "io" "log/slog" "math/big" "strings" @@ -229,3 +231,26 @@ func AttestWithTPM(ctx context.Context, log *slog.Logger, tpm *attest.TPM) ( }, }, nil } + +// PrintQuery prints a human-readable summary of the TPM information to the +// specified io.Writer. +func PrintQuery(data *QueryRes, debug bool, w io.Writer) { + _, _ = fmt.Fprintf(w, "TPM Information\n") + _, _ = fmt.Fprintf(w, "EKPub Hash: %s\n", data.EKPubHash) + _, _ = fmt.Fprintf(w, "EKCert Detected: %t\n", data.EKCert != nil) + if data.EKCert != nil { + _, _ = fmt.Fprintf(w, "EKCert Serial: %s\n", data.EKCert.SerialNumber) + } + if debug { + _, _ = fmt.Fprintf(w, "EKPub:\n%s", pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: data.EKPub, + })) + if data.EKCert != nil { + _, _ = fmt.Fprintf(w, "EKCert:\n%s", pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: data.EKCert.Raw, + })) + } + } +} diff --git a/lib/tpm/tpm_test.go b/lib/tpm/tpm_test.go new file mode 100644 index 0000000000000..e95ea18612518 --- /dev/null +++ b/lib/tpm/tpm_test.go @@ -0,0 +1,86 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package tpm_test + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/gravitational/teleport/lib/tpm" + "github.com/gravitational/teleport/lib/utils/golden" +) + +func TestPrintQuery(t *testing.T) { + tests := []struct { + name string + query *tpm.QueryRes + debug bool + }{ + { + name: "ekpub", + query: &tpm.QueryRes{ + EKPub: []byte("ekpub"), + EKPubHash: "aabbaabbcc", + }, + }, + { + name: "ekpub debug", + query: &tpm.QueryRes{ + EKPub: []byte("ekpub"), + EKPubHash: "aabbaabbcc", + }, + debug: true, + }, + { + name: "ekcert", + query: &tpm.QueryRes{ + EKPub: []byte("ekpub"), + EKPubHash: "aabbaabbcc", + EKCert: &tpm.QueryEKCert{ + Raw: []byte("ekcert"), + SerialNumber: "aa:bb:cc", + }, + }, + }, + { + name: "ekcert debug", + query: &tpm.QueryRes{ + EKPub: []byte("ekpub"), + EKPubHash: "aabbaabbcc", + EKCert: &tpm.QueryEKCert{ + Raw: []byte("ekcert"), + SerialNumber: "aa:bb:cc", + }, + }, + debug: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + buf := bytes.NewBuffer(nil) + tpm.PrintQuery(tt.query, tt.debug, buf) + if golden.ShouldSet() { + golden.Set(t, buf.Bytes()) + } + assert.Equal(t, string(golden.Get(t)), buf.String()) + }) + } +} diff --git a/tool/tbot/main.go b/tool/tbot/main.go index 5b018a68a48cb..1b031c271bf3a 100644 --- a/tool/tbot/main.go +++ b/tool/tbot/main.go @@ -39,6 +39,7 @@ import ( "github.com/gravitational/teleport/lib/observability/tracing" "github.com/gravitational/teleport/lib/tbot" "github.com/gravitational/teleport/lib/tbot/config" + "github.com/gravitational/teleport/lib/tpm" "github.com/gravitational/teleport/lib/utils" ) @@ -166,6 +167,9 @@ func Run(args []string, stdout io.Writer) error { spiffeInspectCmd := app.Command("spiffe-inspect", "Inspects a SPIFFE Workload API endpoint to ensure it is working correctly.") spiffeInspectCmd.Flag("path", "The path to the SPIFFE Workload API endpoint to test.").Required().StringVar(&spiffeInspectPath) + tpmCommand := app.Command("tpm", "Commands related to managing TPM joining functionality.") + tpmIdentifyCommand := tpmCommand.Command("identify", "Output identifying information related to the TPM detected on the system.") + utils.UpdateAppUsageTemplate(app, args) command, err := app.Parse(args) if err != nil { @@ -239,6 +243,12 @@ func Run(args []string, stdout io.Writer) error { err = onKubeCredentialsCommand(botConfig) case spiffeInspectCmd.FullCommand(): err = onSPIFFEInspect(spiffeInspectPath) + case tpmIdentifyCommand.FullCommand(): + query, err := tpm.Query(context.Background(), slog.Default()) + if err != nil { + return trace.Wrap(err, "querying TPM") + } + tpm.PrintQuery(query, cf.Debug, os.Stdout) default: // This should only happen when there's a missing switch case above. err = trace.BadParameter("command %q not configured", command) diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 1e4239c8df615..cef352a5087ec 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -61,6 +61,7 @@ import ( "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/srv" "github.com/gravitational/teleport/lib/sshutils/scp" + "github.com/gravitational/teleport/lib/tpm" "github.com/gravitational/teleport/lib/utils" ) @@ -520,6 +521,9 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con integrationSAMLIdPGCPWorkforce.Flag("pool-provider-name", "Name for the new workforce identity pool provider.").Required().StringVar(&ccf.IntegrationConfSAMLIdPGCPWorkforceArguments.PoolProviderName) integrationSAMLIdPGCPWorkforce.Flag("idp-metadata-url", "Teleport SAML IdP metadata endpoint.").Required().StringVar(&ccf.IntegrationConfSAMLIdPGCPWorkforceArguments.SAMLIdPMetadataURL) + tpmCmd := app.Command("tpm", "Commands related to managing TPM joining functionality.") + tpmIdentifyCmd := tpmCmd.Command("identify", "Output identifying information related to the TPM detected on the system.") + // parse CLI commands+flags: utils.UpdateAppUsageTemplate(app, options.Args) command, err := app.Parse(options.Args) @@ -623,6 +627,13 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con err = onIntegrationConfAccessGraphAWSSync(ccf.IntegrationConfAccessGraphAWSSyncArguments) case integrationSAMLIdPGCPWorkforce.FullCommand(): err = onIntegrationConfSAMLIdPGCPWorkforce(ccf.IntegrationConfSAMLIdPGCPWorkforceArguments) + case tpmIdentifyCmd.FullCommand(): + var query *tpm.QueryRes + query, err = tpm.Query(context.Background(), slog.Default()) + if err != nil { + break + } + tpm.PrintQuery(query, ccf.Debug, os.Stdout) } if err != nil { utils.FatalError(err)