-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for tapping into TPM communication #515
Open
hslatman
wants to merge
10
commits into
master
Choose a base branch
from
herman/tpm-tap
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+651
−3
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
b57bb2a
Add support for tapping into TPM communication
hslatman 74d5c26
Support tapping into `go-tpm` calls
hslatman af71644
Partially working with actual TPM
hslatman 3de9b39
Iterate some more
hslatman ea03186
Cleanup some TPM tapping code
hslatman 502a8aa
Make TPM tapping work with simulator on socket
hslatman f6f961f
Add rudimentary PCAP and binary TPM taps
hslatman bd806d8
Fix linting issues
hslatman 6217d97
Use PcapNG format and add tests
hslatman 62969fa
Add some TPM simulator tests for the TPM tap
hslatman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package debug | ||
|
||
import ( | ||
"io" | ||
"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 | ||
} | ||
|
||
func (t *binTap) Tx() io.Writer { | ||
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() | ||
|
||
return t.w.Write(data) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package debug | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net" | ||
"runtime" | ||
"sync" | ||
"time" | ||
|
||
"github.com/gopacket/gopacket" | ||
"github.com/gopacket/gopacket/layers" | ||
"github.com/gopacket/gopacket/pcapgo" | ||
) | ||
|
||
var ngSectionInfo = pcapgo.NgSectionInfo{ | ||
Application: "com.smallstep.crypto.tpmtap", | ||
Hardware: runtime.GOARCH, | ||
OS: runtime.GOOS, | ||
} | ||
|
||
var ngInterface = pcapgo.NgInterface{ | ||
Name: "tpm", | ||
Description: "TPM Command Channel", | ||
OS: runtime.GOOS, | ||
SnapLength: 0, //unlimited | ||
TimestampResolution: 9, | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
// 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, | ||
writer: pw, | ||
}, | ||
out: &pcapWriter{ | ||
writer: pw, | ||
}, | ||
}, 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.NgWriter | ||
} | ||
|
||
var ( | ||
outSeq uint32 | ||
inSeq uint32 | ||
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() | ||
|
||
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, | ||
} | ||
|
||
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) | ||
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 | ||
} | ||
|
||
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, | ||
ipLayer, | ||
tcpLayer, | ||
gopacket.Payload(data), | ||
); err != nil { | ||
return fmt.Errorf("failed serializing layers: %w", err) | ||
} | ||
|
||
p := gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeEthernet, decodeOptions) | ||
|
||
// 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 | ||
ci.Timestamp = time.Now() | ||
|
||
if err := w.WritePacket(ci, p.Data()); err != nil { | ||
return fmt.Errorf("failed writing packet data: %w", err) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package debug | ||
|
||
import ( | ||
"bytes" | ||
"encoding/hex" | ||
"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_pcapNGTapWrites(t *testing.T) { | ||
var buf bytes.Buffer | ||
tr, flush, err := NewPcapngTap(&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) | ||
|
||
// 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.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 | ||
} | ||
|
||
require.Equal(t, 4, count) // 4 packets expected | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package debug | ||
|
||
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 | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't look too much into this PR, but we probably want to move these ports customizable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, but that wouldn't change much functionally. The PCAP is used just as a container in this case; we're not actually capturing traffic from/to those ports. The bytes read from and written to the TPM device are "massaged into network packets", so to say.