constellation/internal/attestation/vtpm/attestation_test.go
Daniel Weiße 3467df6b69 Move attestation, atls and oid packages to internal directory
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
2022-06-08 17:17:06 +02:00

371 lines
9.2 KiB
Go

package vtpm
import (
"crypto"
"encoding/json"
"errors"
"io"
"testing"
tpmsim "github.com/edgelesssys/constellation/internal/attestation/simulator"
tpmclient "github.com/google/go-tpm-tools/client"
"github.com/google/go-tpm-tools/proto/attest"
"github.com/google/go-tpm-tools/proto/tpm"
"github.com/google/go-tpm-tools/simulator"
"github.com/google/go-tpm/tpm2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type simTPMWithEventLog struct {
*simulator.Simulator
}
func newSimTPMWithEventLog() (io.ReadWriteCloser, error) {
tpmSim, err := simulator.Get()
if err != nil {
return nil, err
}
return &simTPMWithEventLog{tpmSim}, nil
}
// EventLog overrides the default event log getter.
func (s simTPMWithEventLog) EventLog() ([]byte, error) {
// event log header for successful parsing of event log
header := []byte{
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x53, 0x70, 0x65,
0x63, 0x20, 0x49, 0x44, 0x20, 0x45, 0x76, 0x65, 0x6E, 0x74, 0x30, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0x00, 0x0B, 0x00, 0x20, 0x00, 0x00,
}
return header, nil
}
func fakeGetInstanceInfo(tpm io.ReadWriteCloser) ([]byte, error) {
return []byte("unit-test"), nil
}
func fakeGetTrustedKey(aKPub, instanceInfo []byte) (crypto.PublicKey, error) {
pubArea, err := tpm2.DecodePublic(aKPub)
if err != nil {
return nil, err
}
return pubArea.Key()
}
func fakeValidateCVM(AttestationDocument) error { return nil }
var fakeTrustedPcrs = map[uint32][]byte{
0: {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
1: {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
}
func TestValidate(t *testing.T) {
require := require.New(t)
issuer := NewIssuer(newSimTPMWithEventLog, tpmclient.AttestationKeyRSA, fakeGetInstanceInfo)
validator := NewValidator(fakeTrustedPcrs, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15)
nonce := []byte{1, 2, 3, 4}
challenge := []byte("Constellation")
attDocRaw, err := issuer.Issue(challenge, nonce)
require.NoError(err)
var attDoc AttestationDocument
err = json.Unmarshal(attDocRaw, &attDoc)
require.NoError(err)
require.Equal(challenge, attDoc.UserData)
// valid test
out, err := validator.Validate(attDocRaw, nonce)
require.NoError(err)
require.Equal(challenge, out)
testCases := map[string]struct {
validator *Validator
attDoc []byte
nonce []byte
wantErr bool
}{
"invalid nonce": {
validator: NewValidator(fakeTrustedPcrs, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15),
attDoc: mustMarshalAttestation(attDoc, require),
nonce: []byte{4, 3, 2, 1},
wantErr: true,
},
"invalid signature": {
validator: NewValidator(fakeTrustedPcrs, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15),
attDoc: mustMarshalAttestation(AttestationDocument{
Attestation: attDoc.Attestation,
InstanceInfo: attDoc.InstanceInfo,
UserData: []byte("wrong data"),
UserDataSignature: attDoc.UserDataSignature,
}, require),
nonce: nonce,
wantErr: true,
},
"untrusted attestation public key": {
validator: NewValidator(
fakeTrustedPcrs,
func(akPub, instanceInfo []byte) (crypto.PublicKey, error) {
return nil, errors.New("untrusted")
},
fakeValidateCVM, VerifyPKCS1v15),
attDoc: mustMarshalAttestation(attDoc, require),
nonce: nonce,
wantErr: true,
},
"not a CVM": {
validator: NewValidator(
fakeTrustedPcrs,
fakeGetTrustedKey,
func(attestation AttestationDocument) error {
return errors.New("untrusted")
},
VerifyPKCS1v15),
attDoc: mustMarshalAttestation(attDoc, require),
nonce: nonce,
wantErr: true,
},
"untrusted PCRs": {
validator: NewValidator(
map[uint32][]byte{
0: {0xFF},
},
fakeGetTrustedKey,
fakeValidateCVM,
VerifyPKCS1v15),
attDoc: mustMarshalAttestation(attDoc, require),
nonce: nonce,
wantErr: true,
},
"no sha256 quote": {
validator: NewValidator(fakeTrustedPcrs, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15),
attDoc: mustMarshalAttestation(AttestationDocument{
Attestation: &attest.Attestation{
AkPub: attDoc.Attestation.AkPub,
Quotes: []*tpm.Quote{
attDoc.Attestation.Quotes[2],
},
EventLog: attDoc.Attestation.EventLog,
InstanceInfo: attDoc.Attestation.InstanceInfo,
},
InstanceInfo: attDoc.InstanceInfo,
UserData: attDoc.UserData,
UserDataSignature: attDoc.UserDataSignature,
}, require),
nonce: nonce,
wantErr: true,
},
"invalid attestation document": {
validator: NewValidator(fakeTrustedPcrs, fakeGetTrustedKey, fakeValidateCVM, VerifyPKCS1v15),
attDoc: []byte("invalid attestation"),
nonce: nonce,
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
_, err = tc.validator.Validate(tc.attDoc, tc.nonce)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
}
})
}
}
func mustMarshalAttestation(attDoc AttestationDocument, require *require.Assertions) []byte {
out, err := json.Marshal(attDoc)
require.NoError(err)
return out
}
func TestFailIssuer(t *testing.T) {
testCases := map[string]struct {
issuer *Issuer
userData []byte
nonce []byte
}{
"fail openTPM": {
issuer: NewIssuer(
func() (io.ReadWriteCloser, error) {
return nil, errors.New("failure")
},
tpmclient.AttestationKeyRSA,
fakeGetInstanceInfo,
),
userData: []byte("Constellation"),
nonce: []byte{1, 2, 3, 4},
},
"fail getAttestationKey": {
issuer: NewIssuer(
newSimTPMWithEventLog,
func(tpm io.ReadWriter) (*tpmclient.Key, error) {
return nil, errors.New("failure")
},
fakeGetInstanceInfo,
),
userData: []byte("Constellation"),
nonce: []byte{1, 2, 3, 4},
},
"fail Attest": {
issuer: NewIssuer(
newSimTPMWithEventLog,
func(tpm io.ReadWriter) (*tpmclient.Key, error) {
return &tpmclient.Key{}, nil
},
fakeGetInstanceInfo,
),
userData: []byte("Constellation"),
nonce: []byte{1, 2, 3, 4},
},
"fail getInstanceInfo": {
issuer: NewIssuer(
newSimTPMWithEventLog,
tpmclient.AttestationKeyRSA,
func(io.ReadWriteCloser) ([]byte, error) { return nil, errors.New("failure") },
),
userData: []byte("Constellation"),
nonce: []byte{1, 2, 3, 4},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
_, err := tc.issuer.Issue(tc.userData, tc.nonce)
assert.Error(err)
})
}
}
func TestGetSHA256QuoteIndex(t *testing.T) {
testCases := map[string]struct {
quotes []*tpm.Quote
wantIdx int
wantErr bool
}{
"idx 0 is valid": {
quotes: []*tpm.Quote{
{
Pcrs: &tpm.PCRs{
Hash: tpm.HashAlgo_SHA256,
},
},
},
wantIdx: 0,
wantErr: false,
},
"idx 1 is valid": {
quotes: []*tpm.Quote{
{
Pcrs: &tpm.PCRs{
Hash: tpm.HashAlgo_SHA1,
},
},
{
Pcrs: &tpm.PCRs{
Hash: tpm.HashAlgo_SHA256,
},
},
},
wantIdx: 1,
wantErr: false,
},
"no quotes": {
quotes: nil,
wantErr: true,
},
"quotes is nil": {
quotes: make([]*tpm.Quote, 2),
wantErr: true,
},
"pcrs is nil": {
quotes: []*tpm.Quote{
{
Pcrs: &tpm.PCRs{
Hash: tpm.HashAlgo_SHA1,
},
},
{
Pcrs: nil,
},
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
idx, err := GetSHA256QuoteIndex(tc.quotes)
if tc.wantErr {
assert.Error(err)
assert.Equal(0, idx)
} else {
assert.NoError(err)
assert.Equal(tc.wantIdx, idx)
}
})
}
}
func TestGetSelectedPCRs(t *testing.T) {
testCases := map[string]struct {
openFunc TPMOpenFunc
pcrSelection tpm2.PCRSelection
wantErr bool
}{
"error": {
openFunc: func() (io.ReadWriteCloser, error) { return nil, errors.New("error") },
pcrSelection: tpm2.PCRSelection{
Hash: tpm2.AlgSHA256,
PCRs: []int{0, 1, 2},
},
wantErr: true,
},
"3 PCRs": {
openFunc: tpmsim.OpenSimulatedTPM,
pcrSelection: tpm2.PCRSelection{
Hash: tpm2.AlgSHA256,
PCRs: []int{0, 1, 2},
},
},
"Azure PCRS": {
openFunc: tpmsim.OpenSimulatedTPM,
pcrSelection: AzurePCRSelection,
},
"GCP PCRs": {
openFunc: tpmsim.OpenSimulatedTPM,
pcrSelection: GCPPCRSelection,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
pcrs, err := GetSelectedPCRs(tc.openFunc, tc.pcrSelection)
if tc.wantErr {
assert.Error(err)
} else {
require.NoError(err)
assert.Equal(len(pcrs), len(tc.pcrSelection.PCRs))
for _, pcr := range pcrs {
assert.Len(pcr, 32)
}
}
})
}
}