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" "io"
) )
// FakeTPM is a wrapper for fake TPM devices
type FakeTPM struct { type FakeTPM struct {
io.ReadWriteCloser 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, 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} 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 } 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" import "net"
// Socket returns a fake TPM interface from a unix socket
func Socket(f string) (*FakeTPM, error) { func Socket(f string) (*FakeTPM, error) {
conn, err := net.Dial("unix", f) conn, err := net.Dial("unix", f)
if err != nil { if err != nil {

View File

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

View File

@@ -1,35 +1,64 @@
package tpm 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 emulated bool
commandChannel attest.CommandChannelTPM20 commandChannel attest.CommandChannelTPM20
seed int64 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 c.emulated = true
return nil 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 { func WithSeed(s int64) Option {
return func(c *Config) error { return func(c *config) error {
c.seed = s c.seed = s
return nil return nil
} }
} }
// WithCommandChannel overrides the TPM command channel
func WithCommandChannel(cc attest.CommandChannelTPM20) Option { func WithCommandChannel(cc attest.CommandChannelTPM20) Option {
return func(c *Config) error { return func(c *config) error {
c.commandChannel = cc c.commandChannel = cc
return nil return nil
} }
} }
func (c *Config) Apply(opts ...Option) error { func (c *config) apply(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
if err := o(c); err != nil { if err := o(c); err != nil {
return err return err

22
get.go
View File

@@ -16,11 +16,21 @@ import (
"github.com/sirupsen/logrus" "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 dialer := websocket.DefaultDialer
if len(cacerts) > 0 { if len(c.cacerts) > 0 {
pool := x509.NewCertPool() pool := x509.NewCertPool()
pool.AppendCertsFromPEM(cacerts) pool.AppendCertsFromPEM(c.cacerts)
dialer = &websocket.Dialer{ dialer = &websocket.Dialer{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 45 * time.Second, 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) attestationData, aikBytes, err := getAttestationData(c)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -48,9 +55,6 @@ func Get(cacerts []byte, url string, header http.Header, opts ...Option) ([]byte
return nil, err return nil, err
} }
if header == nil {
header = http.Header{}
}
header.Add("Authorization", token) header.Add("Authorization", token)
wsURL := strings.Replace(url, "http", "ws", 1) wsURL := strings.Replace(url, "http", "ws", 1)
logrus.Infof("Using TPMHash %s to dial %s", hash, wsURL) logrus.Infof("Using TPMHash %s to dial %s", hash, wsURL)

2
go.mod
View File

@@ -5,6 +5,7 @@ go 1.17
require ( require (
github.com/google/certificate-transparency-go v1.1.2 github.com/google/certificate-transparency-go v1.1.2
github.com/google/go-attestation v0.4.3 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/gorilla/websocket v1.5.0
github.com/onsi/ginkgo/v2 v2.1.3 github.com/onsi/ginkgo/v2 v2.1.3
github.com/onsi/gomega v1.17.0 github.com/onsi/gomega v1.17.0
@@ -14,7 +15,6 @@ require (
require ( require (
github.com/google/go-tpm v0.3.3 // indirect 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 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/crypto v0.0.0-20210314154223-e6e6c4f2bb5b // indirect
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 // 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.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.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.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.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 h1:2KbPTrwqLTJUZIZNoMSd1UlzoUvmSNbtm14WuDovZjw=
github.com/google/go-tpm-tools v0.3.2/go.mod h1:FYUkglac8nSi15sPnNrP9fha3A6PRb6qzEVA+Zkh8SA= 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" "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) { func ResolveToken(token string, opts ...Option) (bool, string, error) {
if !strings.HasPrefix(token, "tpm://") { if !strings.HasPrefix(token, "tpm://") {
return false, token, nil return false, token, nil
@@ -20,10 +23,10 @@ func ResolveToken(token string, opts ...Option) (bool, string, error) {
return true, hash, err return true, hash, err
} }
// GetPubHash returns the EK's pub hash
func GetPubHash(opts ...Option) (string, error) { func GetPubHash(opts ...Option) (string, error) {
c := &config{}
c := &Config{} c.apply(opts...)
c.Apply(opts...)
ek, err := getEK(c) ek, err := getEK(c)
if err != nil { if err != nil {
@@ -38,7 +41,7 @@ func GetPubHash(opts ...Option) (string, error) {
return hash, nil return hash, nil
} }
func getTPM(c *Config) (*attest.TPM, error) { func getTPM(c *config) (*attest.TPM, error) {
cfg := &attest.OpenConfig{ cfg := &attest.OpenConfig{
TPMVersion: attest.TPMVersion20, TPMVersion: attest.TPMVersion20,
@@ -52,20 +55,20 @@ func getTPM(c *Config) (*attest.TPM, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.CommandChannel = &backend.FakeTPM{ReadWriteCloser: sim} cfg.CommandChannel = backend.Fake(sim)
} else { } else {
sim, err := simulator.GetWithFixedSeedInsecure(c.seed) sim, err := simulator.GetWithFixedSeedInsecure(c.seed)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg.CommandChannel = &backend.FakeTPM{ReadWriteCloser: sim} cfg.CommandChannel = backend.Fake(sim)
} }
return attest.OpenTPM(cfg) return attest.OpenTPM(cfg)
} }
func getEK(c *Config) (*attest.EK, error) { func getEK(c *config) (*attest.EK, error) {
var err error var err error
tpm, err := getTPM(c) tpm, err := getTPM(c)
@@ -86,7 +89,7 @@ func getEK(c *Config) (*attest.EK, error) {
return &eks[0], nil return &eks[0], nil
} }
func getToken(data *AttestationData) (string, error) { func getToken(data *attestationData) (string, error) {
bytes, err := json.Marshal(data) bytes, err := json.Marshal(data)
if err != nil { if err != nil {
return "", fmt.Errorf("marshalling attestation data: %w", err) 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 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 var err error
tpm, err := getTPM(c) tpm, err := getTPM(c)
@@ -121,7 +124,7 @@ func getAttestationData(c *Config) (*AttestationData, []byte, error) {
} }
ek := &eks[0] ek := &eks[0]
ekBytes, err := EncodeEK(ek) ekBytes, err := encodeEK(ek)
if err != nil { if err != nil {
return nil, nil, err 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 nil, nil, fmt.Errorf("marshaling AK: %w", err)
} }
return &AttestationData{ return &attestationData{
EK: ekBytes, EK: ekBytes,
AK: &params, AK: &params,
}, aikBytes, nil }, aikBytes, nil

View File

@@ -23,21 +23,22 @@ import (
"github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509"
"github.com/google/go-attestation/attest" "github.com/google/go-attestation/attest"
"github.com/pkg/errors"
) )
type AttestationData struct { type attestationData struct {
EK []byte EK []byte
AK *attest.AttestationParameters AK *attest.AttestationParameters
} }
// Challenge represent the struct returned from the ws server,
// used to resolve the TPM challenge.
type Challenge struct { type Challenge struct {
EC *attest.EncryptedCredential EC *attest.EncryptedCredential
} }
type KeyData struct { // ChallengeResponse represent the struct returned to the ws server
Keys []string `json:"keys"` // as a challenge response.
}
type ChallengeResponse struct { type ChallengeResponse struct {
Secret []byte Secret []byte
} }
@@ -52,7 +53,7 @@ func getPubHash(ek *attest.EK) (string, error) {
return hashEncoded, nil return hashEncoded, nil
} }
func EncodeEK(ek *attest.EK) ([]byte, error) { func encodeEK(ek *attest.EK) ([]byte, error) {
if ek.Certificate != nil { if ek.Certificate != nil {
return pem.EncodeToMemory(&pem.Block{ return pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Type: "CERTIFICATE",
@@ -78,3 +79,36 @@ func pubBytes(ek *attest.EK) ([]byte, error) {
} }
return data, nil 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" . "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() { var _ = Describe("Simulated TPM", func() {
Context("opening socket connection", func() { Context("opening socket connection", func() {
It("dials in just fine", func() { It("dials in just fine", func() {
@@ -48,7 +24,30 @@ var _ = Describe("Simulated TPM", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
str2, err := GetPubHash(Emulated, WithSeed(1)) str2, err := GetPubHash(Emulated, WithSeed(1))
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
str3, err := GetPubHash(Emulated, WithSeed(2))
Expect(err).ToNot(HaveOccurred())
Expect(str).To(Equal(str2)) 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())
}) })
}) })
}) })