From b57bb2a0464d297dbdee6303c8104c07681ce81a Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 30 May 2024 16:08:26 +0200 Subject: [PATCH 01/10] Add support for tapping into TPM communication --- tpm/debug/tap.go | 28 ++++++++++++ tpm/internal/close/close_others.go | 9 +++- tpm/internal/interceptor/interceptor.go | 61 +++++++++++++++++++++++++ tpm/tpm.go | 37 +++++++++++++++ 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 tpm/debug/tap.go create mode 100644 tpm/internal/interceptor/interceptor.go diff --git a/tpm/debug/tap.go b/tpm/debug/tap.go new file mode 100644 index 00000000..e45d0b92 --- /dev/null +++ b/tpm/debug/tap.go @@ -0,0 +1,28 @@ +package debug + +import "io" + +type Tap interface { + In() io.Writer + Out() io.Writer +} + +type tap struct { + in io.Writer + out io.Writer +} + +func (t tap) In() io.Writer { + return t.in +} + +func (t tap) Out() io.Writer { + return t.out +} + +func NewTap(reads io.Writer, writes io.Writer) Tap { + return &tap{ + in: reads, + out: writes, + } +} diff --git a/tpm/internal/close/close_others.go b/tpm/internal/close/close_others.go index 395c23be..284d5859 100644 --- a/tpm/internal/close/close_others.go +++ b/tpm/internal/close/close_others.go @@ -9,10 +9,12 @@ import ( "github.com/google/go-tpm/tpmutil" "github.com/smallstep/go-attestation/attest" + "go.step.sm/crypto/tpm/internal/interceptor" "go.step.sm/crypto/tpm/internal/socket" ) func closeRWC(rwc io.ReadWriteCloser) error { + // TODO(hs): support an intercepted emulator connection if _, ok := rwc.(*tpmutil.EmulatorReadWriteCloser); ok { return nil // EmulatorReadWriteCloser automatically closes on every write/read cycle } @@ -20,8 +22,13 @@ func closeRWC(rwc io.ReadWriteCloser) error { } func attestTPM(t *attest.TPM, c *attest.OpenConfig) error { - if _, ok := c.CommandChannel.(*socket.CommandChannelWithoutMeasurementLog); ok { + cc := c.CommandChannel + if ic, ok := cc.(*interceptor.CommandChannel); ok { + cc = ic.Unwrap() + } + if _, ok := cc.(*socket.CommandChannelWithoutMeasurementLog); ok { return nil // backed by tpmutil.EmulatorReadWriteCloser; already closed } + return t.Close() } diff --git a/tpm/internal/interceptor/interceptor.go b/tpm/internal/interceptor/interceptor.go new file mode 100644 index 00000000..be0fb41a --- /dev/null +++ b/tpm/internal/interceptor/interceptor.go @@ -0,0 +1,61 @@ +package interceptor + +import ( + "io" + + "github.com/smallstep/go-attestation/attest" + "go.step.sm/crypto/tpm/debug" +) + +type CommandChannel struct { + in io.Writer + out io.Writer + wrapped attest.CommandChannelTPM20 +} + +func FromTap(tap debug.Tap) *CommandChannel { + return &CommandChannel{ + in: tap.In(), + out: tap.Out(), + } +} + +func (c *CommandChannel) Wrap(cc attest.CommandChannelTPM20) *CommandChannel { + c.wrapped = cc + return c +} + +func (c *CommandChannel) Unwrap() attest.CommandChannelTPM20 { + return c.wrapped +} + +func (c *CommandChannel) Close() error { + return c.wrapped.Close() +} + +func (c *CommandChannel) MeasurementLog() ([]byte, error) { + return c.wrapped.MeasurementLog() +} + +func (c *CommandChannel) Read(data []byte) (int, error) { + n, err := c.wrapped.Read(data) + if err != nil { + return n, err + } + + _, _ = c.in.Write(data[:n]) + + return n, nil +} + +func (c *CommandChannel) Write(data []byte) (int, error) { + n, err := c.wrapped.Write(data) + if err != nil { + return n, err + } + + _, _ = c.out.Write(data[:n]) + return n, nil +} + +var _ attest.CommandChannelTPM20 = (*CommandChannel)(nil) diff --git a/tpm/tpm.go b/tpm/tpm.go index 2f998635..f085a32e 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -6,11 +6,14 @@ import ( "fmt" "io" "net/http" + "os" "sync" "github.com/smallstep/go-attestation/attest" + "go.step.sm/crypto/tpm/debug" closer "go.step.sm/crypto/tpm/internal/close" + "go.step.sm/crypto/tpm/internal/interceptor" "go.step.sm/crypto/tpm/internal/open" "go.step.sm/crypto/tpm/internal/socket" "go.step.sm/crypto/tpm/simulator" @@ -31,6 +34,7 @@ type TPM struct { store storage.TPMStore simulator simulator.Simulator commandChannel CommandChannel + tap debug.Tap downloader *downloader options *options initCommandChannelOnce sync.Once @@ -86,6 +90,8 @@ func WithSimulator(sim simulator.Simulator) NewTPMOption { type CommandChannel attest.CommandChannelTPM20 +// WithCommandChannel is used to configure a preconfigured and +// preinitialized TPM command channel. func WithCommandChannel(commandChannel CommandChannel) NewTPMOption { return func(o *options) error { o.commandChannel = commandChannel @@ -93,6 +99,16 @@ func WithCommandChannel(commandChannel CommandChannel) NewTPMOption { } } +// WithTap is used to configure a tap. The tap taps into all of +// the communication to and from the TPM. This can be used to +// inspect and debug the commands and responses. +func WithTap(tap debug.Tap) NewTPMOption { + return func(o *options) error { + o.tap = tap + return nil + } +} + type options struct { deviceName string attestConfig *attest.OpenConfig @@ -100,6 +116,7 @@ type options struct { commandChannel CommandChannel store storage.TPMStore downloader *downloader + tap debug.Tap } func (o *options) validate() error { @@ -133,6 +150,7 @@ func New(opts ...NewTPMOption) (*TPM, error) { downloader: tpmOptions.downloader, simulator: tpmOptions.simulator, commandChannel: tpmOptions.commandChannel, + tap: tpmOptions.tap, options: &tpmOptions, }, nil } @@ -246,6 +264,12 @@ func (t *TPM) initializeCommandChannel() error { } } + t.tap = debug.NewTap(&wrapper{os.Stderr, "in"}, &wrapper{os.Stderr, "out"}) // TODO(hs): remove + + if t.tap != nil { + t.commandChannel = interceptor.FromTap(t.tap).Wrap(t.commandChannel) + } + // update `attestConfig` with the command channel, so that it is used whenever // attestation operations are being performed. Note that the command channel can // still be nil. It simply won't be used (wrapped) by `go-attestation` in that case. @@ -257,6 +281,19 @@ func (t *TPM) initializeCommandChannel() error { return nil } +type wrapper struct { + w io.Writer + direction string +} + +func (w *wrapper) Write(data []byte) (int, error) { + if w.direction == "in" { + return w.w.Write([]byte(fmt.Sprintf("<- %v\n", data))) + } else { + return w.w.Write([]byte(fmt.Sprintf("-> %v\n", data))) + } +} + // trySocketCommandChannel tries func trySocketCommandChannel(path string) (*socket.CommandChannelWithoutMeasurementLog, error) { rwc, err := socket.New(path) From 74d5c2652fe8ba7720396d04f59037a7998cf4b6 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 30 May 2024 16:35:33 +0200 Subject: [PATCH 02/10] Support tapping into `go-tpm` calls --- tpm/internal/close/close_others.go | 4 +- tpm/internal/interceptor/interceptor.go | 51 ++++++++++++++++++++++++- tpm/tpm.go | 34 ++++++++--------- 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/tpm/internal/close/close_others.go b/tpm/internal/close/close_others.go index 284d5859..c837d3ff 100644 --- a/tpm/internal/close/close_others.go +++ b/tpm/internal/close/close_others.go @@ -14,7 +14,9 @@ import ( ) func closeRWC(rwc io.ReadWriteCloser) error { - // TODO(hs): support an intercepted emulator connection + if ic, ok := rwc.(*interceptor.RWC); ok { + rwc = ic.Unwrap() + } if _, ok := rwc.(*tpmutil.EmulatorReadWriteCloser); ok { return nil // EmulatorReadWriteCloser automatically closes on every write/read cycle } diff --git a/tpm/internal/interceptor/interceptor.go b/tpm/internal/interceptor/interceptor.go index be0fb41a..2ae31d11 100644 --- a/tpm/internal/interceptor/interceptor.go +++ b/tpm/internal/interceptor/interceptor.go @@ -13,7 +13,7 @@ type CommandChannel struct { wrapped attest.CommandChannelTPM20 } -func FromTap(tap debug.Tap) *CommandChannel { +func CommandChannelFromTap(tap debug.Tap) *CommandChannel { return &CommandChannel{ in: tap.In(), out: tap.Out(), @@ -59,3 +59,52 @@ func (c *CommandChannel) Write(data []byte) (int, error) { } var _ attest.CommandChannelTPM20 = (*CommandChannel)(nil) + +type RWC struct { + in io.Writer + out io.Writer + wrapped io.ReadWriteCloser +} + +func RWCFromTap(tap debug.Tap) *RWC { + return &RWC{ + in: tap.In(), + out: tap.Out(), + } +} + +func (c *RWC) Wrap(rwc io.ReadWriteCloser) *RWC { + c.wrapped = rwc + return c +} + +func (c *RWC) Unwrap() io.ReadWriteCloser { + return c.wrapped +} + +func (c *RWC) Close() error { + return c.wrapped.Close() +} + +func (c *RWC) Read(data []byte) (int, error) { + n, err := c.wrapped.Read(data) + if err != nil { + return n, err + } + + _, _ = c.in.Write(data[:n]) + + return n, nil +} + +func (c *RWC) Write(data []byte) (int, error) { + n, err := c.wrapped.Write(data) + if err != nil { + return n, err + } + + _, _ = c.out.Write(data[:n]) + return n, nil +} + +var _ io.ReadWriteCloser = (*RWC)(nil) diff --git a/tpm/tpm.go b/tpm/tpm.go index f085a32e..2fa22928 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -6,7 +6,7 @@ import ( "fmt" "io" "net/http" - "os" + "runtime" "sync" "github.com/smallstep/go-attestation/attest" @@ -101,7 +101,9 @@ func WithCommandChannel(commandChannel CommandChannel) NewTPMOption { // WithTap is used to configure a tap. The tap taps into all of // the communication to and from the TPM. This can be used to -// inspect and debug the commands and responses. +// inspect and debug the commands and responses. Due to how TPM +// operations (that go through go-attestation) work, the tap option +// is ignored on Windows. func WithTap(tap debug.Tap) NewTPMOption { return func(o *options) error { o.tap = tap @@ -196,6 +198,9 @@ func (t *TPM) open(ctx context.Context) (err error) { t.attestTPM = at } t.rwc = t.simulator + if t.tap != nil { + t.rwc = interceptor.RWCFromTap(t.tap).Wrap(t.rwc) + } } else { // TODO(hs): when an internal call to open is performed, but when // switching the "TPM implementation" to use between the two types, @@ -208,6 +213,9 @@ func (t *TPM) open(ctx context.Context) (err error) { return fmt.Errorf("failed opening TPM: %w", err) } t.rwc = rwc + if t.tap != nil { + t.rwc = interceptor.RWCFromTap(t.tap).Wrap(t.rwc) + } } else { // TODO(hs): attest.OpenTPM doesn't currently take into account the // device name provided. This doesn't seem to be an available option @@ -264,10 +272,11 @@ func (t *TPM) initializeCommandChannel() error { } } - t.tap = debug.NewTap(&wrapper{os.Stderr, "in"}, &wrapper{os.Stderr, "out"}) // TODO(hs): remove - - if t.tap != nil { - t.commandChannel = interceptor.FromTap(t.tap).Wrap(t.commandChannel) + // enable tapping into TPM communication. This does not work on Windows, because go-attestation + // relies on calling into the Windows Platform Crypto Provider libraries instead of interacting + // with a TPM through binary commands directly. + if t.tap != nil && runtime.GOOS != "windows" { + t.commandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.commandChannel) } // update `attestConfig` with the command channel, so that it is used whenever @@ -281,19 +290,6 @@ func (t *TPM) initializeCommandChannel() error { return nil } -type wrapper struct { - w io.Writer - direction string -} - -func (w *wrapper) Write(data []byte) (int, error) { - if w.direction == "in" { - return w.w.Write([]byte(fmt.Sprintf("<- %v\n", data))) - } else { - return w.w.Write([]byte(fmt.Sprintf("-> %v\n", data))) - } -} - // trySocketCommandChannel tries func trySocketCommandChannel(path string) (*socket.CommandChannelWithoutMeasurementLog, error) { rwc, err := socket.New(path) From af71644ab50871dd725585ab8ec91c13083e040a Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Tue, 4 Jun 2024 00:54:32 +0200 Subject: [PATCH 03/10] Partially working with actual TPM --- tpm/debug/tap.go | 39 +++++++++++++++---- tpm/internal/close/close_others.go | 3 +- tpm/internal/inject/inject_linux.go | 14 +++++++ tpm/internal/inject/inject_others.go | 14 +++++++ tpm/internal/interceptor/interceptor.go | 28 ++++++++++++-- tpm/tpm.go | 51 ++++++++++++++++++------- 6 files changed, 122 insertions(+), 27 deletions(-) create mode 100644 tpm/internal/inject/inject_linux.go create mode 100644 tpm/internal/inject/inject_others.go diff --git a/tpm/debug/tap.go b/tpm/debug/tap.go index e45d0b92..51a12248 100644 --- a/tpm/debug/tap.go +++ b/tpm/debug/tap.go @@ -1,10 +1,13 @@ package debug -import "io" +import ( + "fmt" + "io" +) type Tap interface { - In() io.Writer - Out() io.Writer + Tx() io.Writer + Rx() io.Writer } type tap struct { @@ -12,17 +15,37 @@ type tap struct { out io.Writer } -func (t tap) In() io.Writer { +func (t tap) Rx() io.Writer { return t.in } -func (t tap) Out() io.Writer { +func (t tap) Tx() io.Writer { return t.out } -func NewTap(reads io.Writer, writes io.Writer) Tap { +func NewTap(rx io.Writer, tx io.Writer) Tap { return &tap{ - in: reads, - out: writes, + in: rx, + out: tx, + } +} + +func NewTextTap(reads io.Writer, writes io.Writer) Tap { + return &tap{ + in: &wrapper{reads, true}, + out: &wrapper{writes, false}, + } +} + +type wrapper struct { + w io.Writer + in bool +} + +func (w *wrapper) Write(data []byte) (int, error) { + if w.in { + return w.w.Write([]byte(fmt.Sprintf("<- %x\n", data))) + } else { + return w.w.Write([]byte(fmt.Sprintf("-> %x\n", data))) } } diff --git a/tpm/internal/close/close_others.go b/tpm/internal/close/close_others.go index c837d3ff..e30c1bda 100644 --- a/tpm/internal/close/close_others.go +++ b/tpm/internal/close/close_others.go @@ -26,7 +26,8 @@ func closeRWC(rwc io.ReadWriteCloser) error { func attestTPM(t *attest.TPM, c *attest.OpenConfig) error { cc := c.CommandChannel if ic, ok := cc.(*interceptor.CommandChannel); ok { - cc = ic.Unwrap() + //cc = ic.Unwrap() + _ = ic } if _, ok := cc.(*socket.CommandChannelWithoutMeasurementLog); ok { return nil // backed by tpmutil.EmulatorReadWriteCloser; already closed diff --git a/tpm/internal/inject/inject_linux.go b/tpm/internal/inject/inject_linux.go new file mode 100644 index 00000000..3fb2ceec --- /dev/null +++ b/tpm/internal/inject/inject_linux.go @@ -0,0 +1,14 @@ +//go:build linux +// +build linux + +package inject + +import ( + "io" + + "github.com/smallstep/go-attestation/attest" +) + +func Inject(rwc io.ReadWriteCloser) *attest.TPM { + return attest.InjectSimulatedTPMForTest(rwc) +} diff --git a/tpm/internal/inject/inject_others.go b/tpm/internal/inject/inject_others.go new file mode 100644 index 00000000..7e1949bf --- /dev/null +++ b/tpm/internal/inject/inject_others.go @@ -0,0 +1,14 @@ +//go:build !linux +// +build !linux + +package inject + +import ( + "io" + + "github.com/smallstep/go-attestation/attest" +) + +func Inject(rwc io.ReadWriteCloser) *attest.TPM { + return nil +} diff --git a/tpm/internal/interceptor/interceptor.go b/tpm/internal/interceptor/interceptor.go index 2ae31d11..5e572bb6 100644 --- a/tpm/internal/interceptor/interceptor.go +++ b/tpm/internal/interceptor/interceptor.go @@ -1,6 +1,7 @@ package interceptor import ( + "fmt" "io" "github.com/smallstep/go-attestation/attest" @@ -15,8 +16,8 @@ type CommandChannel struct { func CommandChannelFromTap(tap debug.Tap) *CommandChannel { return &CommandChannel{ - in: tap.In(), - out: tap.Out(), + in: tap.Rx(), + out: tap.Tx(), } } @@ -30,6 +31,13 @@ func (c *CommandChannel) Unwrap() attest.CommandChannelTPM20 { } func (c *CommandChannel) Close() error { + fmt.Println("calling cc close") + fmt.Println(fmt.Sprintf("%#+v", c.wrapped)) + if c.wrapped == nil { + fmt.Println("wrapped cc nil") + return nil + } + return c.wrapped.Close() } @@ -38,6 +46,7 @@ func (c *CommandChannel) MeasurementLog() ([]byte, error) { } func (c *CommandChannel) Read(data []byte) (int, error) { + fmt.Println("read called") n, err := c.wrapped.Read(data) if err != nil { return n, err @@ -49,11 +58,15 @@ func (c *CommandChannel) Read(data []byte) (int, error) { } func (c *CommandChannel) Write(data []byte) (int, error) { + fmt.Println("write called") n, err := c.wrapped.Write(data) if err != nil { + fmt.Println("error from wrapped write", err) return n, err } + fmt.Println("wrote", data[:n]) + _, _ = c.out.Write(data[:n]) return n, nil } @@ -68,8 +81,8 @@ type RWC struct { func RWCFromTap(tap debug.Tap) *RWC { return &RWC{ - in: tap.In(), - out: tap.Out(), + in: tap.Rx(), + out: tap.Tx(), } } @@ -83,6 +96,13 @@ func (c *RWC) Unwrap() io.ReadWriteCloser { } func (c *RWC) Close() error { + fmt.Println("calling rwc close") + fmt.Println(fmt.Sprintf("%#+v", c.wrapped)) + if c.wrapped == nil { + fmt.Println("wrapped rwc nil") + return nil + } + return c.wrapped.Close() } diff --git a/tpm/tpm.go b/tpm/tpm.go index 2fa22928..bec037c3 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "os" "runtime" "sync" @@ -13,6 +14,7 @@ import ( "go.step.sm/crypto/tpm/debug" closer "go.step.sm/crypto/tpm/internal/close" + "go.step.sm/crypto/tpm/internal/inject" "go.step.sm/crypto/tpm/internal/interceptor" "go.step.sm/crypto/tpm/internal/open" "go.step.sm/crypto/tpm/internal/socket" @@ -145,6 +147,8 @@ func New(opts ...NewTPMOption) (*TPM, error) { return nil, fmt.Errorf("invalid TPM options provided: %w", err) } + tpmOptions.tap = debug.NewTextTap(os.Stderr, os.Stderr) + return &TPM{ deviceName: tpmOptions.deviceName, attestConfig: tpmOptions.attestConfig, @@ -161,9 +165,13 @@ func New(opts ...NewTPMOption) (*TPM, error) { // in use. This makes using the instance safe for // concurrent use. func (t *TPM) open(ctx context.Context) (err error) { + + fmt.Println("open called") + // prevent opening the TPM multiple times if Open is called // within the package multiple times. if isInternalCall(ctx) { + fmt.Println("internal call") return } @@ -198,7 +206,7 @@ func (t *TPM) open(ctx context.Context) (err error) { t.attestTPM = at } t.rwc = t.simulator - if t.tap != nil { + if t.tap != nil && runtime.GOOS != "windows" { t.rwc = interceptor.RWCFromTap(t.tap).Wrap(t.rwc) } } else { @@ -213,17 +221,36 @@ func (t *TPM) open(ctx context.Context) (err error) { return fmt.Errorf("failed opening TPM: %w", err) } t.rwc = rwc - if t.tap != nil { + if t.tap != nil && runtime.GOOS != "windows" { t.rwc = interceptor.RWCFromTap(t.tap).Wrap(t.rwc) } } else { // TODO(hs): attest.OpenTPM doesn't currently take into account the // device name provided. This doesn't seem to be an available option // to filter on currently? - at, err := attest.OpenTPM(t.attestConfig) - if err != nil { - return fmt.Errorf("failed opening TPM: %w", err) + // enable tapping into TPM communication. This does not work on Windows, because go-attestation + // relies on calling into the Windows Platform Crypto Provider libraries instead of interacting + // with a TPM through binary commands directly. + var at *attest.TPM + if t.tap != nil && runtime.GOOS != "windows" { + rwc, err := open.TPM(t.deviceName) + if err != nil { + return fmt.Errorf("failed opening TPM: %w", err) + } + fmt.Println("tapping command channel") + //t.rwc = + at = inject.Inject(interceptor.RWCFromTap(t.tap).Wrap(rwc)) + + //t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.commandChannel) } + if at == nil { + fmt.Println("at is nil") + at, err = attest.OpenTPM(t.attestConfig) + if err != nil { + return fmt.Errorf("failed opening TPM: %w", err) + } + } + t.attestTPM = at } } @@ -272,13 +299,6 @@ func (t *TPM) initializeCommandChannel() error { } } - // enable tapping into TPM communication. This does not work on Windows, because go-attestation - // relies on calling into the Windows Platform Crypto Provider libraries instead of interacting - // with a TPM through binary commands directly. - if t.tap != nil && runtime.GOOS != "windows" { - t.commandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.commandChannel) - } - // update `attestConfig` with the command channel, so that it is used whenever // attestation operations are being performed. Note that the command channel can // still be nil. It simply won't be used (wrapped) by `go-attestation` in that case. @@ -302,6 +322,9 @@ func trySocketCommandChannel(path string) (*socket.CommandChannelWithoutMeasurem // Close closes the TPM instance, cleaning up resources and // marking it ready to be use again. func (t *TPM) close(ctx context.Context) error { + + fmt.Println("tpm close called") + // prevent closing the TPM multiple times if Open is called // within the package multiple times. if isInternalCall(ctx) { @@ -323,7 +346,7 @@ func (t *TPM) close(ctx context.Context) error { // clean up the attest.TPM if t.attestTPM != nil { - defer func() { t.attestTPM = nil }() + defer func() { t.attestTPM = nil; fmt.Println("attest TPM set to nil") }() if err := closer.AttestTPM(t.attestTPM, t.attestConfig); err != nil { return fmt.Errorf("failed closing attest.TPM: %w", err) } @@ -331,7 +354,7 @@ func (t *TPM) close(ctx context.Context) error { // clean up the go-tpm rwc if t.rwc != nil { - defer func() { t.rwc = nil }() + defer func() { t.rwc = nil; fmt.Println("rwc set to nil") }() if err := closer.RWC(t.rwc); err != nil { return fmt.Errorf("failed closing rwc: %w", err) } From 3de9b3933cabab16ba49a3c7f5fef83201369629 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Tue, 4 Jun 2024 01:32:48 +0200 Subject: [PATCH 04/10] Iterate some more --- tpm/tpm.go | 32 ++++++++++++++++++++++++++++---- tpm/tpm_simulator_test.go | 4 ++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/tpm/tpm.go b/tpm/tpm.go index bec037c3..c323d03f 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -232,14 +232,25 @@ func (t *TPM) open(ctx context.Context) (err error) { // relies on calling into the Windows Platform Crypto Provider libraries instead of interacting // with a TPM through binary commands directly. var at *attest.TPM + fmt.Println("command channel", t.attestConfig.CommandChannel) if t.tap != nil && runtime.GOOS != "windows" { - rwc, err := open.TPM(t.deviceName) - if err != nil { - return fmt.Errorf("failed opening TPM: %w", err) + if t.attestConfig.CommandChannel == nil { + rwc, err := open.TPM(t.deviceName) + if err != nil { + return fmt.Errorf("failed opening TPM: %w", err) + } + // cc := interceptor.RWCFromTap(t.tap).Wrap(rwc) + at = inject.Inject(interceptor.RWCFromTap(t.tap).Wrap(rwc)) // TODO: can we circumvent inject? + // t.attestConfig.CommandChannel = cc + // cc := &linuxCmdChannel{rwc} + // t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(cc) + } else { + t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.commandChannel) } + fmt.Println("tapping command channel") //t.rwc = - at = inject.Inject(interceptor.RWCFromTap(t.tap).Wrap(rwc)) + //at = inject.Inject(interceptor.RWCFromTap(t.tap).Wrap(rwc)) //t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.commandChannel) } @@ -258,6 +269,15 @@ func (t *TPM) open(ctx context.Context) (err error) { return nil } +type linuxCmdChannel struct { + io.ReadWriteCloser +} + +// MeasurementLog implements CommandChannelTPM20. +func (cc *linuxCmdChannel) MeasurementLog() ([]byte, error) { + return os.ReadFile("/sys/kernel/security/tpm0/binary_bios_measurements") +} + // initializeCommandChannel initializes the TPM's command channel based on // configuration provided when creating the TPM instance. The method is // primarily used to be able to use a TPM simulator in lieu of a real TPM @@ -299,6 +319,10 @@ func (t *TPM) initializeCommandChannel() error { } } + if t.tap != nil && runtime.GOOS != "windows" { + + } + // update `attestConfig` with the command channel, so that it is used whenever // attestation operations are being performed. Note that the command channel can // still be nil. It simply won't be used (wrapped) by `go-attestation` in that case. diff --git a/tpm/tpm_simulator_test.go b/tpm/tpm_simulator_test.go index c277df8f..f764c06b 100644 --- a/tpm/tpm_simulator_test.go +++ b/tpm/tpm_simulator_test.go @@ -75,6 +75,8 @@ func TestTPM_Info(t *testing.T) { } require.Equal(t, expected, info) + + t.Fail() } func TestTPM_GenerateRandom(t *testing.T) { @@ -86,6 +88,8 @@ func TestTPM_GenerateRandom(t *testing.T) { b, err = tpm.GenerateRandom(context.Background(), 10) require.NoError(t, err) require.Len(t, b, 10) + + t.Fail() } func newErrorTPM(t *testing.T) *TPM { From ea031869eb43c9582026077c80bc1b8aa042e221 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Tue, 4 Jun 2024 02:13:19 +0200 Subject: [PATCH 05/10] Cleanup some TPM tapping code --- tpm/internal/close/close_others.go | 3 +- tpm/internal/inject/inject_linux.go | 14 ---- tpm/internal/inject/inject_others.go | 14 ---- tpm/internal/interceptor/interceptor.go | 20 ----- tpm/tpm.go | 98 ++++++++++++++----------- tpm/tpm_simulator_test.go | 4 - 6 files changed, 55 insertions(+), 98 deletions(-) delete mode 100644 tpm/internal/inject/inject_linux.go delete mode 100644 tpm/internal/inject/inject_others.go diff --git a/tpm/internal/close/close_others.go b/tpm/internal/close/close_others.go index e30c1bda..c837d3ff 100644 --- a/tpm/internal/close/close_others.go +++ b/tpm/internal/close/close_others.go @@ -26,8 +26,7 @@ func closeRWC(rwc io.ReadWriteCloser) error { func attestTPM(t *attest.TPM, c *attest.OpenConfig) error { cc := c.CommandChannel if ic, ok := cc.(*interceptor.CommandChannel); ok { - //cc = ic.Unwrap() - _ = ic + cc = ic.Unwrap() } if _, ok := cc.(*socket.CommandChannelWithoutMeasurementLog); ok { return nil // backed by tpmutil.EmulatorReadWriteCloser; already closed diff --git a/tpm/internal/inject/inject_linux.go b/tpm/internal/inject/inject_linux.go deleted file mode 100644 index 3fb2ceec..00000000 --- a/tpm/internal/inject/inject_linux.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build linux -// +build linux - -package inject - -import ( - "io" - - "github.com/smallstep/go-attestation/attest" -) - -func Inject(rwc io.ReadWriteCloser) *attest.TPM { - return attest.InjectSimulatedTPMForTest(rwc) -} diff --git a/tpm/internal/inject/inject_others.go b/tpm/internal/inject/inject_others.go deleted file mode 100644 index 7e1949bf..00000000 --- a/tpm/internal/inject/inject_others.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build !linux -// +build !linux - -package inject - -import ( - "io" - - "github.com/smallstep/go-attestation/attest" -) - -func Inject(rwc io.ReadWriteCloser) *attest.TPM { - return nil -} diff --git a/tpm/internal/interceptor/interceptor.go b/tpm/internal/interceptor/interceptor.go index 5e572bb6..43d4c750 100644 --- a/tpm/internal/interceptor/interceptor.go +++ b/tpm/internal/interceptor/interceptor.go @@ -1,7 +1,6 @@ package interceptor import ( - "fmt" "io" "github.com/smallstep/go-attestation/attest" @@ -31,13 +30,6 @@ func (c *CommandChannel) Unwrap() attest.CommandChannelTPM20 { } func (c *CommandChannel) Close() error { - fmt.Println("calling cc close") - fmt.Println(fmt.Sprintf("%#+v", c.wrapped)) - if c.wrapped == nil { - fmt.Println("wrapped cc nil") - return nil - } - return c.wrapped.Close() } @@ -46,7 +38,6 @@ func (c *CommandChannel) MeasurementLog() ([]byte, error) { } func (c *CommandChannel) Read(data []byte) (int, error) { - fmt.Println("read called") n, err := c.wrapped.Read(data) if err != nil { return n, err @@ -58,15 +49,11 @@ func (c *CommandChannel) Read(data []byte) (int, error) { } func (c *CommandChannel) Write(data []byte) (int, error) { - fmt.Println("write called") n, err := c.wrapped.Write(data) if err != nil { - fmt.Println("error from wrapped write", err) return n, err } - fmt.Println("wrote", data[:n]) - _, _ = c.out.Write(data[:n]) return n, nil } @@ -96,13 +83,6 @@ func (c *RWC) Unwrap() io.ReadWriteCloser { } func (c *RWC) Close() error { - fmt.Println("calling rwc close") - fmt.Println(fmt.Sprintf("%#+v", c.wrapped)) - if c.wrapped == nil { - fmt.Println("wrapped rwc nil") - return nil - } - return c.wrapped.Close() } diff --git a/tpm/tpm.go b/tpm/tpm.go index c323d03f..94f424a4 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -14,7 +14,6 @@ import ( "go.step.sm/crypto/tpm/debug" closer "go.step.sm/crypto/tpm/internal/close" - "go.step.sm/crypto/tpm/internal/inject" "go.step.sm/crypto/tpm/internal/interceptor" "go.step.sm/crypto/tpm/internal/open" "go.step.sm/crypto/tpm/internal/socket" @@ -37,6 +36,7 @@ type TPM struct { simulator simulator.Simulator commandChannel CommandChannel tap debug.Tap + shouldRetap bool downloader *downloader options *options initCommandChannelOnce sync.Once @@ -157,6 +157,7 @@ func New(opts ...NewTPMOption) (*TPM, error) { simulator: tpmOptions.simulator, commandChannel: tpmOptions.commandChannel, tap: tpmOptions.tap, + shouldRetap: false, options: &tpmOptions, }, nil } @@ -165,13 +166,9 @@ func New(opts ...NewTPMOption) (*TPM, error) { // in use. This makes using the instance safe for // concurrent use. func (t *TPM) open(ctx context.Context) (err error) { - - fmt.Println("open called") - // prevent opening the TPM multiple times if Open is called // within the package multiple times. if isInternalCall(ctx) { - fmt.Println("internal call") return } @@ -199,6 +196,12 @@ func (t *TPM) open(ctx context.Context) (err error) { // The simulator is currently only used for testing. if t.simulator != nil { if t.attestTPM == nil { + // enable tapping into TPM communication. This does not work on Windows, because go-attestation + // relies on calling into the Windows Platform Crypto Provider libraries instead of interacting + // with a TPM through binary commands directly. + if err := t.tapCommandChannel(); err != nil { + return fmt.Errorf("failed tapping TPM command channel: %w", err) + } at, err := attest.OpenTPM(t.attestConfig) if err != nil { return fmt.Errorf("failed opening attest.TPM: %w", err) @@ -225,41 +228,19 @@ func (t *TPM) open(ctx context.Context) (err error) { t.rwc = interceptor.RWCFromTap(t.tap).Wrap(t.rwc) } } else { - // TODO(hs): attest.OpenTPM doesn't currently take into account the - // device name provided. This doesn't seem to be an available option - // to filter on currently? // enable tapping into TPM communication. This does not work on Windows, because go-attestation // relies on calling into the Windows Platform Crypto Provider libraries instead of interacting // with a TPM through binary commands directly. - var at *attest.TPM - fmt.Println("command channel", t.attestConfig.CommandChannel) - if t.tap != nil && runtime.GOOS != "windows" { - if t.attestConfig.CommandChannel == nil { - rwc, err := open.TPM(t.deviceName) - if err != nil { - return fmt.Errorf("failed opening TPM: %w", err) - } - // cc := interceptor.RWCFromTap(t.tap).Wrap(rwc) - at = inject.Inject(interceptor.RWCFromTap(t.tap).Wrap(rwc)) // TODO: can we circumvent inject? - // t.attestConfig.CommandChannel = cc - // cc := &linuxCmdChannel{rwc} - // t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(cc) - } else { - t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.commandChannel) - } - - fmt.Println("tapping command channel") - //t.rwc = - //at = inject.Inject(interceptor.RWCFromTap(t.tap).Wrap(rwc)) - - //t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.commandChannel) + if err := t.tapCommandChannel(); err != nil { + return fmt.Errorf("failed tapping TPM command channel: %w", err) } - if at == nil { - fmt.Println("at is nil") - at, err = attest.OpenTPM(t.attestConfig) - if err != nil { - return fmt.Errorf("failed opening TPM: %w", err) - } + + // TODO(hs): attest.OpenTPM doesn't currently take into account the + // device name provided. This doesn't seem to be an available option + // to filter on currently? + at, err := attest.OpenTPM(t.attestConfig) + if err != nil { + return fmt.Errorf("failed opening TPM: %w", err) } t.attestTPM = at @@ -269,6 +250,26 @@ func (t *TPM) open(ctx context.Context) (err error) { return nil } +func (t *TPM) tapCommandChannel() error { + if t.tap == nil || runtime.GOOS != "linux" { + return nil + } + + if t.attestConfig.CommandChannel == nil || t.shouldRetap { + rwc, err := open.TPM(t.deviceName) + if err != nil { + return fmt.Errorf("failed opening TPM: %w", err) + } + cc := &linuxCmdChannel{rwc} + t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(cc) + t.shouldRetap = true // retap required; the wrapped command channel is closed and thus needs to be tapped again + } else { + t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.attestConfig.CommandChannel) + } + + return nil +} + type linuxCmdChannel struct { io.ReadWriteCloser } @@ -319,9 +320,21 @@ func (t *TPM) initializeCommandChannel() error { } } - if t.tap != nil && runtime.GOOS != "windows" { - - } + // if t.tap != nil && runtime.GOOS != "windows" { + // if t.commandChannel == nil { + // rwc, err := open.TPM(t.deviceName) + // if err != nil { + // return fmt.Errorf("failed opening TPM: %w", err) + // } + // //cc := interceptor.RWCFromTap(t.tap).Wrap(rwc) + // //at = inject.Inject(interceptor.RWCFromTap(t.tap).Wrap(rwc)) // TODO: can we circumvent inject? + // //t.attestConfig.CommandChannel = cc + // cc := &linuxCmdChannel{rwc} + // t.tappedChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(cc) + // } else { + // t.tappedChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.commandChannel) + // } + // } // update `attestConfig` with the command channel, so that it is used whenever // attestation operations are being performed. Note that the command channel can @@ -346,9 +359,6 @@ func trySocketCommandChannel(path string) (*socket.CommandChannelWithoutMeasurem // Close closes the TPM instance, cleaning up resources and // marking it ready to be use again. func (t *TPM) close(ctx context.Context) error { - - fmt.Println("tpm close called") - // prevent closing the TPM multiple times if Open is called // within the package multiple times. if isInternalCall(ctx) { @@ -370,7 +380,7 @@ func (t *TPM) close(ctx context.Context) error { // clean up the attest.TPM if t.attestTPM != nil { - defer func() { t.attestTPM = nil; fmt.Println("attest TPM set to nil") }() + defer func() { t.attestTPM = nil }() if err := closer.AttestTPM(t.attestTPM, t.attestConfig); err != nil { return fmt.Errorf("failed closing attest.TPM: %w", err) } @@ -378,7 +388,7 @@ func (t *TPM) close(ctx context.Context) error { // clean up the go-tpm rwc if t.rwc != nil { - defer func() { t.rwc = nil; fmt.Println("rwc set to nil") }() + defer func() { t.rwc = nil }() if err := closer.RWC(t.rwc); err != nil { return fmt.Errorf("failed closing rwc: %w", err) } diff --git a/tpm/tpm_simulator_test.go b/tpm/tpm_simulator_test.go index f764c06b..c277df8f 100644 --- a/tpm/tpm_simulator_test.go +++ b/tpm/tpm_simulator_test.go @@ -75,8 +75,6 @@ func TestTPM_Info(t *testing.T) { } require.Equal(t, expected, info) - - t.Fail() } func TestTPM_GenerateRandom(t *testing.T) { @@ -88,8 +86,6 @@ func TestTPM_GenerateRandom(t *testing.T) { b, err = tpm.GenerateRandom(context.Background(), 10) require.NoError(t, err) require.Len(t, b, 10) - - t.Fail() } func newErrorTPM(t *testing.T) *TPM { From 502a8aa62f780bc93f99b0e2ed1fa255ea12f4d2 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Tue, 4 Jun 2024 09:19:11 +0200 Subject: [PATCH 06/10] Make TPM tapping work with simulator on socket --- tpm/tpm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tpm/tpm.go b/tpm/tpm.go index 94f424a4..80b00000 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -251,7 +251,7 @@ func (t *TPM) open(ctx context.Context) (err error) { } func (t *TPM) tapCommandChannel() error { - if t.tap == nil || runtime.GOOS != "linux" { + if t.tap == nil || runtime.GOOS == "windows" { return nil } @@ -264,7 +264,7 @@ func (t *TPM) tapCommandChannel() error { t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(cc) t.shouldRetap = true // retap required; the wrapped command channel is closed and thus needs to be tapped again } else { - t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.attestConfig.CommandChannel) + t.attestConfig.CommandChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.commandChannel) } return nil From f6f961fb76c73b7bd84589e14d9b8177923f3638 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Tue, 4 Jun 2024 13:20:57 +0200 Subject: [PATCH 07/10] Add rudimentary PCAP and binary TPM taps --- go.mod | 1 + go.sum | 4 ++ tpm/debug/bin.go | 30 +++++++++ tpm/debug/pcap.go | 146 +++++++++++++++++++++++++++++++++++++++++ tpm/debug/pcap_test.go | 55 ++++++++++++++++ tpm/debug/tap.go | 21 ------ tpm/debug/text.go | 26 ++++++++ tpm/tpm.go | 2 - 8 files changed, 262 insertions(+), 23 deletions(-) create mode 100644 tpm/debug/bin.go create mode 100644 tpm/debug/pcap.go create mode 100644 tpm/debug/pcap_test.go create mode 100644 tpm/debug/text.go diff --git a/go.mod b/go.mod index 274a5677..b134b9b8 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/google/go-tpm v0.9.0 github.com/google/go-tpm-tools v0.4.4 github.com/googleapis/gax-go/v2 v2.12.4 + github.com/gopacket/gopacket v1.2.0 github.com/peterbourgon/diskv/v3 v3.0.1 github.com/pkg/errors v0.9.1 github.com/schollz/jsonstore v1.1.0 diff --git a/go.sum b/go.sum index dba0cf2a..30db09d8 100644 --- a/go.sum +++ b/go.sum @@ -442,6 +442,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/gopacket/gopacket v1.2.0 h1:eXbzFad7f73P1n2EJHQlsKuvIMJjVXK5tXoSca78I3A= +github.com/gopacket/gopacket v1.2.0/go.mod h1:BrAKEy5EOGQ76LSqh7DMAr7z0NNPdczWm2GxCG7+I8M= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/goreleaser/goreleaser v0.134.0/go.mod h1:ZT6Y2rSYa6NxQzIsdfWWNWAlYGXGbreo66NmE+3X3WQ= @@ -798,6 +800,8 @@ github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= diff --git a/tpm/debug/bin.go b/tpm/debug/bin.go new file mode 100644 index 00000000..2ef3a20a --- /dev/null +++ b/tpm/debug/bin.go @@ -0,0 +1,30 @@ +package debug + +import ( + "io" + "sync" +) + +type binTap struct { + sync.Mutex + w io.Writer +} + +func (t *binTap) Rx() io.Writer { + return t.w +} + +func (t *binTap) Tx() io.Writer { + return t.w +} + +func NewBinTap(w io.Writer) Tap { + return &binTap{w: w} +} + +func (w *binTap) Write(data []byte) (int, error) { + w.Lock() + defer w.Unlock() + + return w.w.Write(data) +} diff --git a/tpm/debug/pcap.go b/tpm/debug/pcap.go new file mode 100644 index 00000000..82728901 --- /dev/null +++ b/tpm/debug/pcap.go @@ -0,0 +1,146 @@ +package debug + +import ( + "fmt" + "io" + "net" + "sync" + "time" + + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" + "github.com/gopacket/gopacket/pcapgo" +) + +type pcapTap struct { + in *pcapWriter + out *pcapWriter +} + +func (t *pcapTap) Rx() io.Writer { + return t.in +} + +func (t *pcapTap) Tx() io.Writer { + return t.out +} + +func NewPcapTap(w io.Writer) (Tap, error) { + pw := pcapgo.NewWriter(w) + return &pcapTap{ + in: &pcapWriter{ + in: true, + writer: pw, + }, + out: &pcapWriter{ + writer: pw, + }, + }, nil +} + +type pcapWriter struct { + in bool + writer *pcapgo.Writer +} + +var ( + outSeq uint32 + inSeq uint32 + mu sync.Mutex +) + +func (w *pcapWriter) Write(data []byte) (int, error) { + mu.Lock() + defer mu.Unlock() + + err := write(w.writer, data, w.in, inSeq, outSeq) + + if w.in { + inSeq += uint32(len(data)) + } else { + outSeq += uint32(len(data)) + } + + return len(data), err +} + +var ethernetLayer = &layers.Ethernet{ + SrcMAC: net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + DstMAC: net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + EthernetType: layers.EthernetTypeIPv4, +} + +var ipLayer = &layers.IPv4{ + Version: 4, + TTL: 64, + Flags: layers.IPv4DontFragment, + Protocol: layers.IPProtocolTCP, + SrcIP: net.IP{127, 0, 0, 1}, + DstIP: net.IP{127, 0, 0, 1}, +} + +var serializeOptions = gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, +} + +var decodeOptions = gopacket.DecodeOptions{ + Lazy: true, + NoCopy: true, +} + +const snapLen = uint32(65536) + +var once sync.Once +var headerWriteError error + +func write(w *pcapgo.Writer, data []byte, in bool, inSeq uint32, outSeq uint32) error { + var tcpLayer = &layers.TCP{Window: 16} + if in { + tcpLayer.SrcPort = layers.TCPPort(2321) + tcpLayer.DstPort = layers.TCPPort(50001) + tcpLayer.ACK = true + tcpLayer.Seq = inSeq + tcpLayer.Ack = outSeq + tcpLayer.PSH = true + } else { + tcpLayer.SrcPort = layers.TCPPort(50001) + tcpLayer.DstPort = layers.TCPPort(2321) + tcpLayer.ACK = false + tcpLayer.Seq = outSeq + } + + tcpLayer.SetNetworkLayerForChecksum(ipLayer) + buffer := gopacket.NewSerializeBuffer() + if err := gopacket.SerializeLayers(buffer, serializeOptions, + ethernetLayer, + ipLayer, + tcpLayer, + gopacket.Payload(data), + ); err != nil { + return fmt.Errorf("failed serializing layers: %w", err) + } + + p := gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeEthernet, decodeOptions) + + once.Do(func() { + // TODO: do once on the very first write to the output; if it's a file, check there's no pcap header yet + if err := w.WriteFileHeader(snapLen, layers.LinkTypeEthernet); err != nil { + headerWriteError = err + } + }) + if headerWriteError != nil { + return fmt.Errorf("failed writing pcap header: %w", headerWriteError) + } + + ci := p.Metadata().CaptureInfo + ci.CaptureLength = len(p.Data()) + ci.Length = ci.CaptureLength + ci.Timestamp = time.Now() + + if err := w.WritePacket(ci, p.Data()); err != nil { + return fmt.Errorf("failed writing packet data: %w", err) + } + + return nil +} diff --git a/tpm/debug/pcap_test.go b/tpm/debug/pcap_test.go new file mode 100644 index 00000000..510f74a4 --- /dev/null +++ b/tpm/debug/pcap_test.go @@ -0,0 +1,55 @@ +package debug + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/pcapgo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_PcapTapWrites(t *testing.T) { + var buf bytes.Buffer + tr, err := NewPcapTap(&buf) + require.NoError(t, err) + + b, err := hex.DecodeString("8001000000160000017a000000060000010600000001") + require.NoError(t, err) + n, err := tr.Tx().Write(b) + require.NoError(t, err) + + assert.Equal(t, 22, n) + + b, err = hex.DecodeString("80010000001b000000000100000006000000010000010678434720") + require.NoError(t, err) + n, err = tr.Rx().Write(b) + require.NoError(t, err) + assert.Equal(t, 27, n) + + b, err = hex.DecodeString("8001000000160000017a000000060000010700000001") + require.NoError(t, err) + n, err = tr.Tx().Write(b) + require.NoError(t, err) + assert.Equal(t, 22, n) + + b, err = hex.DecodeString("80010000001b00000000010000000600000001000001076654504d") + require.NoError(t, err) + n, err = tr.Rx().Write(b) + require.NoError(t, err) + assert.Equal(t, 27, n) + + r, err := pcapgo.NewReader(&buf) + require.NoError(t, err) + + count := 0 + packetSource := gopacket.NewPacketSource(r, r.LinkType()) + for packet := range packetSource.Packets() { + t.Log(packet.Dump()) + count += 1 + } + + require.Equal(t, 4, count) // 4 packets expected +} diff --git a/tpm/debug/tap.go b/tpm/debug/tap.go index 51a12248..021a604d 100644 --- a/tpm/debug/tap.go +++ b/tpm/debug/tap.go @@ -1,7 +1,6 @@ package debug import ( - "fmt" "io" ) @@ -29,23 +28,3 @@ func NewTap(rx io.Writer, tx io.Writer) Tap { out: tx, } } - -func NewTextTap(reads io.Writer, writes io.Writer) Tap { - return &tap{ - in: &wrapper{reads, true}, - out: &wrapper{writes, false}, - } -} - -type wrapper struct { - w io.Writer - in bool -} - -func (w *wrapper) Write(data []byte) (int, error) { - if w.in { - return w.w.Write([]byte(fmt.Sprintf("<- %x\n", data))) - } else { - return w.w.Write([]byte(fmt.Sprintf("-> %x\n", data))) - } -} diff --git a/tpm/debug/text.go b/tpm/debug/text.go new file mode 100644 index 00000000..f3e34f67 --- /dev/null +++ b/tpm/debug/text.go @@ -0,0 +1,26 @@ +package debug + +import ( + "fmt" + "io" +) + +func NewTextTap(reads io.Writer, writes io.Writer) Tap { + return &tap{ + in: &wrapper{reads, true}, + out: &wrapper{writes, false}, + } +} + +type wrapper struct { + w io.Writer + in bool +} + +func (w *wrapper) Write(data []byte) (int, error) { + if w.in { + return w.w.Write([]byte(fmt.Sprintf("<- %x\n", data))) + } else { + return w.w.Write([]byte(fmt.Sprintf("-> %x\n", data))) + } +} diff --git a/tpm/tpm.go b/tpm/tpm.go index 80b00000..b2598d4e 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -147,8 +147,6 @@ func New(opts ...NewTPMOption) (*TPM, error) { return nil, fmt.Errorf("invalid TPM options provided: %w", err) } - tpmOptions.tap = debug.NewTextTap(os.Stderr, os.Stderr) - return &TPM{ deviceName: tpmOptions.deviceName, attestConfig: tpmOptions.attestConfig, From bd806d8fc1011084d585702a4e887360f90189f3 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Tue, 4 Jun 2024 13:35:13 +0200 Subject: [PATCH 08/10] Fix linting issues --- tpm/debug/bin.go | 8 ++++---- tpm/debug/pcap.go | 10 +++++----- tpm/debug/tap.go | 6 +++--- tpm/debug/text.go | 8 ++++---- tpm/tpm.go | 16 ---------------- 5 files changed, 16 insertions(+), 32 deletions(-) diff --git a/tpm/debug/bin.go b/tpm/debug/bin.go index 2ef3a20a..88f64314 100644 --- a/tpm/debug/bin.go +++ b/tpm/debug/bin.go @@ -22,9 +22,9 @@ func NewBinTap(w io.Writer) Tap { return &binTap{w: w} } -func (w *binTap) Write(data []byte) (int, error) { - w.Lock() - defer w.Unlock() +func (t *binTap) Write(data []byte) (int, error) { + t.Lock() + defer t.Unlock() - return w.w.Write(data) + return t.w.Write(data) } diff --git a/tpm/debug/pcap.go b/tpm/debug/pcap.go index 82728901..30c4241c 100644 --- a/tpm/debug/pcap.go +++ b/tpm/debug/pcap.go @@ -92,9 +92,9 @@ var decodeOptions = gopacket.DecodeOptions{ const snapLen = uint32(65536) var once sync.Once -var headerWriteError error +var errHeaderWrite error -func write(w *pcapgo.Writer, data []byte, in bool, inSeq uint32, outSeq uint32) error { +func write(w *pcapgo.Writer, data []byte, in bool, inSeq, outSeq uint32) error { var tcpLayer = &layers.TCP{Window: 16} if in { tcpLayer.SrcPort = layers.TCPPort(2321) @@ -126,11 +126,11 @@ func write(w *pcapgo.Writer, data []byte, in bool, inSeq uint32, outSeq uint32) once.Do(func() { // TODO: do once on the very first write to the output; if it's a file, check there's no pcap header yet if err := w.WriteFileHeader(snapLen, layers.LinkTypeEthernet); err != nil { - headerWriteError = err + errHeaderWrite = err } }) - if headerWriteError != nil { - return fmt.Errorf("failed writing pcap header: %w", headerWriteError) + if errHeaderWrite != nil { + return fmt.Errorf("failed writing pcap header: %w", errHeaderWrite) } ci := p.Metadata().CaptureInfo diff --git a/tpm/debug/tap.go b/tpm/debug/tap.go index 021a604d..276af06b 100644 --- a/tpm/debug/tap.go +++ b/tpm/debug/tap.go @@ -14,15 +14,15 @@ type tap struct { out io.Writer } -func (t tap) Rx() io.Writer { +func (t *tap) Rx() io.Writer { return t.in } -func (t tap) Tx() io.Writer { +func (t *tap) Tx() io.Writer { return t.out } -func NewTap(rx io.Writer, tx io.Writer) Tap { +func NewTap(rx, tx io.Writer) Tap { return &tap{ in: rx, out: tx, diff --git a/tpm/debug/text.go b/tpm/debug/text.go index f3e34f67..bbbecdaa 100644 --- a/tpm/debug/text.go +++ b/tpm/debug/text.go @@ -5,7 +5,7 @@ import ( "io" ) -func NewTextTap(reads io.Writer, writes io.Writer) Tap { +func NewTextTap(reads, writes io.Writer) Tap { return &tap{ in: &wrapper{reads, true}, out: &wrapper{writes, false}, @@ -19,8 +19,8 @@ type wrapper struct { func (w *wrapper) Write(data []byte) (int, error) { if w.in { - return w.w.Write([]byte(fmt.Sprintf("<- %x\n", data))) - } else { - return w.w.Write([]byte(fmt.Sprintf("-> %x\n", data))) + return fmt.Fprintf(w.w, "<- %x\n", data) } + + return fmt.Fprintf(w.w, "-> %x\n", data) } diff --git a/tpm/tpm.go b/tpm/tpm.go index b2598d4e..aecd95ca 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -318,22 +318,6 @@ func (t *TPM) initializeCommandChannel() error { } } - // if t.tap != nil && runtime.GOOS != "windows" { - // if t.commandChannel == nil { - // rwc, err := open.TPM(t.deviceName) - // if err != nil { - // return fmt.Errorf("failed opening TPM: %w", err) - // } - // //cc := interceptor.RWCFromTap(t.tap).Wrap(rwc) - // //at = inject.Inject(interceptor.RWCFromTap(t.tap).Wrap(rwc)) // TODO: can we circumvent inject? - // //t.attestConfig.CommandChannel = cc - // cc := &linuxCmdChannel{rwc} - // t.tappedChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(cc) - // } else { - // t.tappedChannel = interceptor.CommandChannelFromTap(t.tap).Wrap(t.commandChannel) - // } - // } - // update `attestConfig` with the command channel, so that it is used whenever // attestation operations are being performed. Note that the command channel can // still be nil. It simply won't be used (wrapped) by `go-attestation` in that case. From 6217d9771a5ff7b9511980a837a1eedd8e061f55 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 5 Jun 2024 20:11:53 +0200 Subject: [PATCH 09/10] Use PcapNG format and add tests --- tpm/debug/bin.go | 18 ++-- tpm/debug/bin_test.go | 20 +++++ tpm/debug/{pcap.go => pcapng.go} | 100 +++++++++++++++------ tpm/debug/{pcap_test.go => pcapng_test.go} | 33 +++++-- tpm/debug/tap.go | 24 +---- tpm/debug/text.go | 27 +++++- tpm/debug/text_test.go | 23 +++++ 7 files changed, 180 insertions(+), 65 deletions(-) create mode 100644 tpm/debug/bin_test.go rename tpm/debug/{pcap.go => pcapng.go} (51%) rename tpm/debug/{pcap_test.go => pcapng_test.go} (55%) create mode 100644 tpm/debug/text_test.go diff --git a/tpm/debug/bin.go b/tpm/debug/bin.go index 88f64314..2106a017 100644 --- a/tpm/debug/bin.go +++ b/tpm/debug/bin.go @@ -5,23 +5,29 @@ import ( "sync" ) +// NewBinTap creates a new TPM tap that writes the +// the outgoing and incoming TPM communication verbatim +// to the (single) underlying [io.Writer]. +func NewBinTap(w io.Writer) Tap { + return &binTap{w: w} +} + type binTap struct { sync.Mutex w io.Writer } func (t *binTap) Rx() io.Writer { - return t.w + return t } func (t *binTap) Tx() io.Writer { - return t.w -} - -func NewBinTap(w io.Writer) Tap { - return &binTap{w: w} + return t } +// Write implements [io.Writer] and writes the provided data +// to the underlying [io.Writer] verbatim for both TPM commands +// and responses. func (t *binTap) Write(data []byte) (int, error) { t.Lock() defer t.Unlock() diff --git a/tpm/debug/bin_test.go b/tpm/debug/bin_test.go new file mode 100644 index 00000000..27e5ae68 --- /dev/null +++ b/tpm/debug/bin_test.go @@ -0,0 +1,20 @@ +package debug + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_binTapWrites(t *testing.T) { + var buf bytes.Buffer + bt := NewBinTap(&buf) + n, err := bt.Tx().Write([]byte{1, 2, 3, 4}) + require.NoError(t, err) + require.Equal(t, 4, n) + n, err = bt.Rx().Write([]byte{5, 6, 7, 8}) + require.NoError(t, err) + require.Equal(t, 4, n) + require.Equal(t, []byte{1, 2, 3, 4, 5, 6, 7, 8}, buf.Bytes()) +} diff --git a/tpm/debug/pcap.go b/tpm/debug/pcapng.go similarity index 51% rename from tpm/debug/pcap.go rename to tpm/debug/pcapng.go index 30c4241c..cbba6bbd 100644 --- a/tpm/debug/pcap.go +++ b/tpm/debug/pcapng.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "net" + "runtime" "sync" "time" @@ -12,21 +13,59 @@ import ( "github.com/gopacket/gopacket/pcapgo" ) -type pcapTap struct { - in *pcapWriter - out *pcapWriter +var ngSectionInfo = pcapgo.NgSectionInfo{ + Application: "com.smallstep.crypto.tpmtap", + Hardware: runtime.GOARCH, + OS: runtime.GOOS, } -func (t *pcapTap) Rx() io.Writer { - return t.in +var ngInterface = pcapgo.NgInterface{ + Name: "tpm", + Description: "TPM Command Channel", + OS: runtime.GOOS, + SnapLength: 0, //unlimited + TimestampResolution: 9, } -func (t *pcapTap) Tx() io.Writer { - return t.out -} +// FlushFunc is the type of function returned when creating +// a new PcapNG tap. The underlying PcapNG writer must be flushed +// before the [io.Writer] it's writing to is closed. +type FlushFunc func() error + +// NewPcapngTap creates a new TPM tap that writes the +// the outgoing and incoming TPM communication to the +// provided [io.Writer] in PcapNG format. +func NewPcapngTap(w io.Writer) (Tap, FlushFunc, error) { + // record current defaults, so that they can be restored after + // creating a PcapNG writer. + currentSectionInfo := pcapgo.DefaultNgWriterOptions.SectionInfo + defer func() { + pcapgo.DefaultNgWriterOptions.SectionInfo = currentSectionInfo + }() + currentInterface := pcapgo.DefaultNgInterface + defer func() { + pcapgo.DefaultNgInterface = currentInterface + }() + + // override some default properties of the PcapNG writer + pcapgo.DefaultNgWriterOptions.SectionInfo = ngSectionInfo + pcapgo.DefaultNgInterface = ngInterface + + // create a new PcapNG writer + pw, err := pcapgo.NewNgWriter(w, layers.LinkTypeEthernet) + if err != nil { + return nil, nil, fmt.Errorf("failed creating PcapNG writer: %w", err) + } + finish := func() error { + if err := pw.Flush(); err != nil { + return fmt.Errorf("failed to flush PcapNG writer: %w", err) + } + return nil + } -func NewPcapTap(w io.Writer) (Tap, error) { - pw := pcapgo.NewWriter(w) + // the single PcapNG writer is used for both TPM commands and responses. + // It's wrapped, so that the direction of the data is know at the time of + // writing a new packet to the PcapNG writer. return &pcapTap{ in: &pcapWriter{ in: true, @@ -35,12 +74,25 @@ func NewPcapTap(w io.Writer) (Tap, error) { out: &pcapWriter{ writer: pw, }, - }, nil + }, finish, nil +} + +type pcapTap struct { + in *pcapWriter + out *pcapWriter +} + +func (t *pcapTap) Rx() io.Writer { + return t.in +} + +func (t *pcapTap) Tx() io.Writer { + return t.out } type pcapWriter struct { in bool - writer *pcapgo.Writer + writer *pcapgo.NgWriter } var ( @@ -49,6 +101,8 @@ var ( mu sync.Mutex ) +// Write implements [io.Writer] and writes the provided data +// to the underlying [io.Writer] in PcapNG format. func (w *pcapWriter) Write(data []byte) (int, error) { mu.Lock() defer mu.Unlock() @@ -89,12 +143,7 @@ var decodeOptions = gopacket.DecodeOptions{ NoCopy: true, } -const snapLen = uint32(65536) - -var once sync.Once -var errHeaderWrite error - -func write(w *pcapgo.Writer, data []byte, in bool, inSeq, outSeq uint32) error { +func write(w *pcapgo.NgWriter, data []byte, in bool, inSeq, outSeq uint32) error { var tcpLayer = &layers.TCP{Window: 16} if in { tcpLayer.SrcPort = layers.TCPPort(2321) @@ -110,7 +159,9 @@ func write(w *pcapgo.Writer, data []byte, in bool, inSeq, outSeq uint32) error { tcpLayer.Seq = outSeq } - tcpLayer.SetNetworkLayerForChecksum(ipLayer) + if err := tcpLayer.SetNetworkLayerForChecksum(ipLayer); err != nil { + return fmt.Errorf("failed setting network layer: %w", err) + } buffer := gopacket.NewSerializeBuffer() if err := gopacket.SerializeLayers(buffer, serializeOptions, ethernetLayer, @@ -123,16 +174,7 @@ func write(w *pcapgo.Writer, data []byte, in bool, inSeq, outSeq uint32) error { p := gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeEthernet, decodeOptions) - once.Do(func() { - // TODO: do once on the very first write to the output; if it's a file, check there's no pcap header yet - if err := w.WriteFileHeader(snapLen, layers.LinkTypeEthernet); err != nil { - errHeaderWrite = err - } - }) - if errHeaderWrite != nil { - return fmt.Errorf("failed writing pcap header: %w", errHeaderWrite) - } - + // construct the capture info based on what's in the new packet ci := p.Metadata().CaptureInfo ci.CaptureLength = len(p.Data()) ci.Length = ci.CaptureLength diff --git a/tpm/debug/pcap_test.go b/tpm/debug/pcapng_test.go similarity index 55% rename from tpm/debug/pcap_test.go rename to tpm/debug/pcapng_test.go index 510f74a4..ae13c582 100644 --- a/tpm/debug/pcap_test.go +++ b/tpm/debug/pcapng_test.go @@ -6,14 +6,15 @@ import ( "testing" "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" "github.com/gopacket/gopacket/pcapgo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func Test_PcapTapWrites(t *testing.T) { +func Test_pcapNGTapWrites(t *testing.T) { var buf bytes.Buffer - tr, err := NewPcapTap(&buf) + tr, flush, err := NewPcapngTap(&buf) require.NoError(t, err) b, err := hex.DecodeString("8001000000160000017a000000060000010600000001") @@ -41,13 +42,33 @@ func Test_PcapTapWrites(t *testing.T) { require.NoError(t, err) assert.Equal(t, 27, n) - r, err := pcapgo.NewReader(&buf) + // finish writing to the PcapNG tap + flush() + + r, err := pcapgo.NewNgReader(&buf, pcapgo.DefaultNgReaderOptions) + require.NoError(t, err) + + info := r.SectionInfo() + assert.Equal(t, "com.smallstep.crypto.tpmtap", info.Application) + + iface, err := r.Interface(0) require.NoError(t, err) + assert.Equal(t, "tpm", iface.Name) + assert.Equal(t, "TPM Command Channel", iface.Description) count := 0 - packetSource := gopacket.NewPacketSource(r, r.LinkType()) - for packet := range packetSource.Packets() { - t.Log(packet.Dump()) + packetSource := gopacket.NewZeroCopyPacketSource(r, r.LinkType()) + for p := range packetSource.Packets() { + t.Log(p.Dump()) + + if assert.IsType(t, &layers.TCP{}, p.TransportLayer().(*layers.TCP)) { + tcp := p.TransportLayer().(*layers.TCP) + tcp.SetNetworkLayerForChecksum(p.NetworkLayer()) + + err, errs := p.VerifyChecksums() + assert.NoError(t, err) + assert.Empty(t, errs) + } count += 1 } diff --git a/tpm/debug/tap.go b/tpm/debug/tap.go index 276af06b..9979db1b 100644 --- a/tpm/debug/tap.go +++ b/tpm/debug/tap.go @@ -4,27 +4,11 @@ import ( "io" ) +// Tap is an interface providing TPM communication tapping +// capabilities. [Tx] and [Rx] provide access to [io.Writer]s +// that correspond to all (serialized) transmitted and received +// TPM commands and responses, respectively. type Tap interface { Tx() io.Writer Rx() io.Writer } - -type tap struct { - in io.Writer - out io.Writer -} - -func (t *tap) Rx() io.Writer { - return t.in -} - -func (t *tap) Tx() io.Writer { - return t.out -} - -func NewTap(rx, tx io.Writer) Tap { - return &tap{ - in: rx, - out: tx, - } -} diff --git a/tpm/debug/text.go b/tpm/debug/text.go index bbbecdaa..a8b86350 100644 --- a/tpm/debug/text.go +++ b/tpm/debug/text.go @@ -5,19 +5,38 @@ import ( "io" ) +// NewTextTap creates a new TPM tap that writes the +// the outgoing and incoming TPM communication in hex +// format. func NewTextTap(reads, writes io.Writer) Tap { return &tap{ - in: &wrapper{reads, true}, - out: &wrapper{writes, false}, + in: &directionalWrapper{reads, true}, + out: &directionalWrapper{writes, false}, } } -type wrapper struct { +type tap struct { + in io.Writer + out io.Writer +} + +func (t *tap) Rx() io.Writer { + return t.in +} + +func (t *tap) Tx() io.Writer { + return t.out +} + +type directionalWrapper struct { w io.Writer in bool } -func (w *wrapper) Write(data []byte) (int, error) { +// Write implements [io.Writer] and writes the provided data +// to the underlying [io.Writer], formatted as hex, and prefixed +// with the direction of the data. +func (w directionalWrapper) Write(data []byte) (int, error) { if w.in { return fmt.Fprintf(w.w, "<- %x\n", data) } diff --git a/tpm/debug/text_test.go b/tpm/debug/text_test.go new file mode 100644 index 00000000..3b183555 --- /dev/null +++ b/tpm/debug/text_test.go @@ -0,0 +1,23 @@ +package debug + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_directionalWrapper_Write(t *testing.T) { + var reads bytes.Buffer + var writes bytes.Buffer + tt := NewTextTap(&reads, &writes) + n, err := tt.Tx().Write([]byte{1, 2, 3, 4}) + require.NoError(t, err) + require.Equal(t, 12, n) + require.Equal(t, "-> 01020304\n", writes.String()) + + n, err = tt.Rx().Write([]byte{5, 6, 7, 8}) + require.NoError(t, err) + require.Equal(t, 12, n) + require.Equal(t, "<- 05060708\n", reads.String()) +} From 62969fa3652da2245e4d5b3ad340a121e6fa5ecd Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 5 Jun 2024 20:49:10 +0200 Subject: [PATCH 10/10] Add some TPM simulator tests for the TPM tap --- tpm/internal/interceptor/interceptor.go | 1 + tpm/tpm.go | 16 +++++--- tpm/tpm_simulator_test.go | 49 ++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/tpm/internal/interceptor/interceptor.go b/tpm/internal/interceptor/interceptor.go index 43d4c750..503b8c0f 100644 --- a/tpm/internal/interceptor/interceptor.go +++ b/tpm/internal/interceptor/interceptor.go @@ -4,6 +4,7 @@ import ( "io" "github.com/smallstep/go-attestation/attest" + "go.step.sm/crypto/tpm/debug" ) diff --git a/tpm/tpm.go b/tpm/tpm.go index aecd95ca..1edc635f 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -207,9 +207,7 @@ func (t *TPM) open(ctx context.Context) (err error) { t.attestTPM = at } t.rwc = t.simulator - if t.tap != nil && runtime.GOOS != "windows" { - t.rwc = interceptor.RWCFromTap(t.tap).Wrap(t.rwc) - } + t.tapRWC() } else { // TODO(hs): when an internal call to open is performed, but when // switching the "TPM implementation" to use between the two types, @@ -222,9 +220,7 @@ func (t *TPM) open(ctx context.Context) (err error) { return fmt.Errorf("failed opening TPM: %w", err) } t.rwc = rwc - if t.tap != nil && runtime.GOOS != "windows" { - t.rwc = interceptor.RWCFromTap(t.tap).Wrap(t.rwc) - } + t.tapRWC() } else { // enable tapping into TPM communication. This does not work on Windows, because go-attestation // relies on calling into the Windows Platform Crypto Provider libraries instead of interacting @@ -248,6 +244,14 @@ func (t *TPM) open(ctx context.Context) (err error) { return nil } +func (t *TPM) tapRWC() { + if t.tap == nil || runtime.GOOS == "windows" { + return + } + + t.rwc = interceptor.RWCFromTap(t.tap).Wrap(t.rwc) +} + func (t *TPM) tapCommandChannel() error { if t.tap == nil || runtime.GOOS == "windows" { return nil diff --git a/tpm/tpm_simulator_test.go b/tpm/tpm_simulator_test.go index c277df8f..7df36e6b 100644 --- a/tpm/tpm_simulator_test.go +++ b/tpm/tpm_simulator_test.go @@ -4,6 +4,7 @@ package tpm import ( + "bytes" "context" "crypto" "crypto/ecdsa" @@ -26,16 +27,19 @@ import ( "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" + "go.step.sm/crypto/tpm/debug" "go.step.sm/crypto/tpm/simulator" "go.step.sm/crypto/tpm/storage" "go.step.sm/crypto/tpm/tss2" "go.step.sm/crypto/x509util" ) -func newSimulatedTPM(t *testing.T) *TPM { +func newSimulatedTPM(t *testing.T, opts ...NewTPMOption) *TPM { t.Helper() tmpDir := t.TempDir() - tpm, err := New(withSimulator(t), WithStore(storage.NewDirstore(tmpDir))) // TODO: provide in-memory storage implementation instead + allOpts := append([]NewTPMOption{}, withSimulator(t), WithStore(storage.NewDirstore(tmpDir))) // TODO: provide in-memory storage implementation instead + allOpts = append(allOpts, opts...) + tpm, err := New(allOpts...) require.NoError(t, err) return tpm } @@ -77,6 +81,31 @@ func TestTPM_Info(t *testing.T) { require.Equal(t, expected, info) } +func TestTPM_InfoWithTap(t *testing.T) { + var reads bytes.Buffer + var writes bytes.Buffer + tpm := newSimulatedTPM(t, WithTap(debug.NewTextTap(&reads, &writes))) + info, err := tpm.Info(context.Background()) + require.NoError(t, err) + + // expected TPM info for the Microsoft TPM simulator + expected := &Info{ + Version: Version(2), + Interface: Interface(3), + Manufacturer: GetManufacturerByID(1297303124), + VendorInfo: "xCG fTPM", + FirmwareVersion: FirmwareVersion{ + Major: 8215, + Minor: 1561, + }, + } + + require.Equal(t, expected, info) + + assert.Equal(t, "<- 80010000001b000000000100000006000000010000010678434720\n<- 80010000001b00000000010000000600000001000001076654504d\n<- 80010000001b000000000100000006000000010000010800000000\n<- 80010000001b000000000100000006000000010000010900000000\n<- 80010000001b00000000010000000600000001000001054d534654\n<- 80010000001b000000000100000006000000010000010b20170619\n", reads.String()) + assert.Equal(t, "-> 8001000000160000017a000000060000010600000001\n-> 8001000000160000017a000000060000010700000001\n-> 8001000000160000017a000000060000010800000001\n-> 8001000000160000017a000000060000010900000001\n-> 8001000000160000017a000000060000010500000001\n-> 8001000000160000017a000000060000010b00000001\n", writes.String()) +} + func TestTPM_GenerateRandom(t *testing.T) { tpm := newSimulatedTPM(t) b, err := tpm.GenerateRandom(context.Background(), 16) @@ -88,6 +117,22 @@ func TestTPM_GenerateRandom(t *testing.T) { require.Len(t, b, 10) } +func TestTPM_GenerateRandomWithTap(t *testing.T) { + var reads bytes.Buffer + var writes bytes.Buffer + tpm := newSimulatedTPM(t, WithTap(debug.NewTextTap(&reads, &writes))) + b, err := tpm.GenerateRandom(context.Background(), 16) + require.NoError(t, err) + require.Len(t, b, 16) + + b, err = tpm.GenerateRandom(context.Background(), 10) + require.NoError(t, err) + require.Len(t, b, 10) + + assert.Len(t, reads.Bytes(), 108) + assert.Equal(t, "-> 80010000000c0000017b0010\n-> 80010000000c0000017b000a\n", writes.String()) +} + func newErrorTPM(t *testing.T) *TPM { t.Helper() tmpDir := t.TempDir()