Fixup tests, add testing pipelines and small refactors

Signed-off-by: Ettore Di Giacinto <edigiacinto@suse.com>
This commit is contained in:
Ettore Di Giacinto
2022-02-17 10:10:04 +01:00
parent 1ab3e10e4d
commit e5997ada4c
12 changed files with 200 additions and 60 deletions

37
.github/workflows/static_analisys.yaml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Static Analysis
on: [push, pull_request]
concurrency:
group: static-analysis-${{ github.head_ref || github.ref }}-${{ github.repository }}
cancel-in-progress: true
jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: Get dependencies
run: |
# Needed for github.com/google/go-tspi/tspi
sudo apt-get install libtspi-dev
go install golang.org/x/tools/cmd/goimports@latest
go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
go install golang.org/x/lint/golint@latest
go install honnef.co/go/tools/cmd/staticcheck@v0.2.0
- name: Vet
run: go vet -tags ci ./...
- name: Goimports
run: test -z $(goimports -e -d . | tee /dev/stderr)
- name: Gocyclo
run: gocyclo -over 30 .
- name: Golint
run: golint -set_exit_status $(go list -tags ci ./...)
- name: Staticcheck
run: staticcheck -go 1.12 ./...

27
.github/workflows/tests.yaml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Tests
on: [push, pull_request]
concurrency:
group: tests-${{ github.head_ref || github.ref }}-${{ github.repository }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: Get dependencies
run: |
# Needed for github.com/google/go-tspi/tspi
# in opensuse this is trousers-devel
sudo add-apt-repository ppa:smoser/swtpm
sudo apt-get update
sudo apt-get install libtspi-dev swtpm
swtpm socket --server type=unixio,path=/tmp/tpm-server --ctrl type=unixio,path=/tmp/tpm-ctrl --tpm2 -d
go get github.com/onsi/ginkgo/v2/ginkgo
go get github.com/onsi/gomega/...
- name: Run tests
run: |
TPM_SOCKET=/tmp/tpm-ctrl ginkgo -r ./...

View File

@@ -4,6 +4,7 @@ import (
"io"
)
// FakeTPM is a wrapper for fake TPM devices
type FakeTPM struct {
io.ReadWriteCloser
}
@@ -14,4 +15,10 @@ var fixedLog = []byte{0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x20, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x2, 0x0, 0x2, 0x1, 0x0, 0x0, 0x0, 0xb, 0x0, 0x20, 0x0, 0x0}
// MeasurementLog returns static log data to comply to TPM interface
func (*FakeTPM) MeasurementLog() ([]byte, error) { return fixedLog, nil }
// Fake returns a fake TPM-satisfying interface from a ReadWriteCloser
func Fake(rw io.ReadWriteCloser) *FakeTPM {
return &FakeTPM{ReadWriteCloser: rw}
}

View File

@@ -2,6 +2,7 @@ package backend
import "net"
// Socket returns a fake TPM interface from a unix socket
func Socket(f string) (*FakeTPM, error) {
conn, err := net.Dial("unix", f)
if err != nil {

View File

@@ -9,7 +9,7 @@ import (
)
var _ = Describe("SWTPM", func() {
socket := os.Getenv("SWTPM_SOCKET")
socket := os.Getenv("TPM_SOCKET")
Context("opening socket connection", func() {
It("fails on invalid files", func() {

View File

@@ -1,35 +1,64 @@
package tpm
import "github.com/google/go-attestation/attest"
import (
"net/http"
type Config struct {
"github.com/google/go-attestation/attest"
)
type config struct {
emulated bool
commandChannel attest.CommandChannelTPM20
seed int64
cacerts []byte
header http.Header
}
type Option func(c *Config) error
// Option is a generic option for TPM configuration
type Option func(c *config) error
var Emulated Option = func(c *Config) error {
// Emulated sets an emulated device in place of a real native TPM device.
// Note, the emulated device is embedded and it is unsafe.
// Should just be used for testing.
var Emulated Option = func(c *config) error {
c.emulated = true
return nil
}
// WithCAs sets the root CAs for the request
func WithCAs(ca []byte) Option {
return func(c *config) error {
c.cacerts = ca
return nil
}
}
// WithHeader sets a specific header for the request
func WithHeader(header http.Header) Option {
return func(c *config) error {
c.header = header
return nil
}
}
// WithSeed sets a permanent seed. Used with TPM emulated device.
func WithSeed(s int64) Option {
return func(c *Config) error {
return func(c *config) error {
c.seed = s
return nil
}
}
// WithCommandChannel overrides the TPM command channel
func WithCommandChannel(cc attest.CommandChannelTPM20) Option {
return func(c *Config) error {
return func(c *config) error {
c.commandChannel = cc
return nil
}
}
func (c *Config) Apply(opts ...Option) error {
func (c *config) apply(opts ...Option) error {
for _, o := range opts {
if err := o(c); err != nil {
return err

22
get.go
View File

@@ -16,11 +16,21 @@ import (
"github.com/sirupsen/logrus"
)
func Get(cacerts []byte, url string, header http.Header, opts ...Option) ([]byte, error) {
// Get retrieves a message from a remote ws server after
// a successfully process of the TPM challenge
func Get(url string, opts ...Option) ([]byte, error) {
c := &config{}
c.apply(opts...)
header := c.header
if c.header == nil {
header = http.Header{}
}
dialer := websocket.DefaultDialer
if len(cacerts) > 0 {
if len(c.cacerts) > 0 {
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(cacerts)
pool.AppendCertsFromPEM(c.cacerts)
dialer = &websocket.Dialer{
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 45 * time.Second,
@@ -30,9 +40,6 @@ func Get(cacerts []byte, url string, header http.Header, opts ...Option) ([]byte
}
}
c := &Config{}
c.Apply(opts...)
attestationData, aikBytes, err := getAttestationData(c)
if err != nil {
return nil, err
@@ -48,9 +55,6 @@ func Get(cacerts []byte, url string, header http.Header, opts ...Option) ([]byte
return nil, err
}
if header == nil {
header = http.Header{}
}
header.Add("Authorization", token)
wsURL := strings.Replace(url, "http", "ws", 1)
logrus.Infof("Using TPMHash %s to dial %s", hash, wsURL)

2
go.mod
View File

@@ -5,6 +5,7 @@ go 1.17
require (
github.com/google/certificate-transparency-go v1.1.2
github.com/google/go-attestation v0.4.3
github.com/google/go-tpm-tools v0.3.2
github.com/gorilla/websocket v1.5.0
github.com/onsi/ginkgo/v2 v2.1.3
github.com/onsi/gomega v1.17.0
@@ -14,7 +15,6 @@ require (
require (
github.com/google/go-tpm v0.3.3 // indirect
github.com/google/go-tpm-tools v0.3.2 // indirect
github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad // indirect
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b // indirect
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 // indirect

1
go.sum
View File

@@ -331,7 +331,6 @@ github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51B
github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0=
github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4=
github.com/google/go-tpm-tools v0.2.1/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4=
github.com/google/go-tpm-tools v0.3.1 h1:AFlmenDrIe0WU5AvpbfGFOLprTJTg/fCwmTyFdDEjbM=
github.com/google/go-tpm-tools v0.3.1/go.mod h1:PSg+r5hSZI5tP3X7LBQx2sW1VSZUqZHBSrKyDqrB21U=
github.com/google/go-tpm-tools v0.3.2 h1:2KbPTrwqLTJUZIZNoMSd1UlzoUvmSNbtm14WuDovZjw=
github.com/google/go-tpm-tools v0.3.2/go.mod h1:FYUkglac8nSi15sPnNrP9fha3A6PRb6qzEVA+Zkh8SA=

25
tpm.go
View File

@@ -11,6 +11,9 @@ import (
"github.com/rancher-sandbox/go-tpm/backend"
)
// ResolveToken is just syntax sugar around GetPubHash.
// If the token provided is in EK's form it just returns it, otherwise
// retrieves the pubhash
func ResolveToken(token string, opts ...Option) (bool, string, error) {
if !strings.HasPrefix(token, "tpm://") {
return false, token, nil
@@ -20,10 +23,10 @@ func ResolveToken(token string, opts ...Option) (bool, string, error) {
return true, hash, err
}
// GetPubHash returns the EK's pub hash
func GetPubHash(opts ...Option) (string, error) {
c := &Config{}
c.Apply(opts...)
c := &config{}
c.apply(opts...)
ek, err := getEK(c)
if err != nil {
@@ -38,7 +41,7 @@ func GetPubHash(opts ...Option) (string, error) {
return hash, nil
}
func getTPM(c *Config) (*attest.TPM, error) {
func getTPM(c *config) (*attest.TPM, error) {
cfg := &attest.OpenConfig{
TPMVersion: attest.TPMVersion20,
@@ -52,20 +55,20 @@ func getTPM(c *Config) (*attest.TPM, error) {
if err != nil {
return nil, err
}
cfg.CommandChannel = &backend.FakeTPM{ReadWriteCloser: sim}
cfg.CommandChannel = backend.Fake(sim)
} else {
sim, err := simulator.GetWithFixedSeedInsecure(c.seed)
if err != nil {
return nil, err
}
cfg.CommandChannel = &backend.FakeTPM{ReadWriteCloser: sim}
cfg.CommandChannel = backend.Fake(sim)
}
return attest.OpenTPM(cfg)
}
func getEK(c *Config) (*attest.EK, error) {
func getEK(c *config) (*attest.EK, error) {
var err error
tpm, err := getTPM(c)
@@ -86,7 +89,7 @@ func getEK(c *Config) (*attest.EK, error) {
return &eks[0], nil
}
func getToken(data *AttestationData) (string, error) {
func getToken(data *attestationData) (string, error) {
bytes, err := json.Marshal(data)
if err != nil {
return "", fmt.Errorf("marshalling attestation data: %w", err)
@@ -95,7 +98,7 @@ func getToken(data *AttestationData) (string, error) {
return "Bearer TPM" + base64.StdEncoding.EncodeToString(bytes), nil
}
func getAttestationData(c *Config) (*AttestationData, []byte, error) {
func getAttestationData(c *config) (*attestationData, []byte, error) {
var err error
tpm, err := getTPM(c)
@@ -121,7 +124,7 @@ func getAttestationData(c *Config) (*AttestationData, []byte, error) {
}
ek := &eks[0]
ekBytes, err := EncodeEK(ek)
ekBytes, err := encodeEK(ek)
if err != nil {
return nil, nil, err
}
@@ -131,7 +134,7 @@ func getAttestationData(c *Config) (*AttestationData, []byte, error) {
return nil, nil, fmt.Errorf("marshaling AK: %w", err)
}
return &AttestationData{
return &attestationData{
EK: ekBytes,
AK: &params,
}, aikBytes, nil

View File

@@ -23,21 +23,22 @@ import (
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-attestation/attest"
"github.com/pkg/errors"
)
type AttestationData struct {
type attestationData struct {
EK []byte
AK *attest.AttestationParameters
}
// Challenge represent the struct returned from the ws server,
// used to resolve the TPM challenge.
type Challenge struct {
EC *attest.EncryptedCredential
}
type KeyData struct {
Keys []string `json:"keys"`
}
// ChallengeResponse represent the struct returned to the ws server
// as a challenge response.
type ChallengeResponse struct {
Secret []byte
}
@@ -52,7 +53,7 @@ func getPubHash(ek *attest.EK) (string, error) {
return hashEncoded, nil
}
func EncodeEK(ek *attest.EK) ([]byte, error) {
func encodeEK(ek *attest.EK) ([]byte, error) {
if ek.Certificate != nil {
return pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
@@ -78,3 +79,36 @@ func pubBytes(ek *attest.EK) ([]byte, error) {
}
return data, nil
}
// DecodeEK decodes EK pem bytes to attest.EK
func DecodeEK(pemBytes []byte) (*attest.EK, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("invalid pemBytes")
}
switch block.Type {
case "CERTIFICATE":
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("error parsing certificate: %v", err)
}
return &attest.EK{
Certificate: cert,
Public: cert.PublicKey,
}, nil
case "PUBLIC KEY":
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("error parsing ecdsa public key: %v", err)
}
return &attest.EK{
Public: pub,
}, nil
}
return nil, fmt.Errorf("invalid pem type: %s", block.Type)
}

View File

@@ -9,30 +9,6 @@ import (
. "github.com/rancher-sandbox/go-tpm/backend"
)
// In order to run this suite a swtpm socket is required. e.g.:
// swtpm socket --server type=unixio,path=/tmp/tpm-server --ctrl type=unixio,path=/tmp/tpm-ctrl --tpm2
// SWTPM_SOCKET=/tmp/tpm-ctrl ginkgo -r ./
var _ = Describe("TPM with SWTPM", func() {
socket := os.Getenv("SWTPM_SOCKET")
Context("opening socket connection", func() {
// Note, this doesn't work
PIt("dials in just fine", func() {
if socket == "" {
Skip("No socket file specified")
}
b, err := Socket(socket)
Expect(err).ToNot(HaveOccurred())
str, err := GetPubHash(WithCommandChannel(b))
Expect(err).ToNot(HaveOccurred())
Expect(str).ToNot(BeEmpty())
})
})
})
var _ = Describe("Simulated TPM", func() {
Context("opening socket connection", func() {
It("dials in just fine", func() {
@@ -48,7 +24,30 @@ var _ = Describe("Simulated TPM", func() {
Expect(err).ToNot(HaveOccurred())
str2, err := GetPubHash(Emulated, WithSeed(1))
Expect(err).ToNot(HaveOccurred())
str3, err := GetPubHash(Emulated, WithSeed(2))
Expect(err).ToNot(HaveOccurred())
Expect(str).To(Equal(str2))
Expect(str).ToNot(Equal(str3))
})
})
Context("from a socket", func() {
// In order to run this test a swtpm socket is required. e.g.:
// swtpm socket --server type=unixio,path=/tmp/tpm-server --ctrl type=unixio,path=/tmp/tpm-ctrl --tpm2
// TPM_SOCKET=/tmp/tpm-ctrl ginkgo -r ./
socket := os.Getenv("TPM_SOCKET")
It("gets pubhash", func() {
if socket == "" {
Skip("No socket file specified")
}
b, err := Socket(socket)
Expect(err).ToNot(HaveOccurred())
str, err := GetPubHash(WithCommandChannel(b))
Expect(err).ToNot(HaveOccurred())
Expect(str).ToNot(BeEmpty())
})
})
})