Skip to content
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

Extend and improve unit testing. #2

Merged
merged 7 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.vscode
cmd/veil
internal/cover.html
internal/cover.out
20 changes: 14 additions & 6 deletions cmd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ import (
"github.com/stretchr/testify/require"
)

func withFlags(flag ...string) []string {
var f []string
if !enclave.IsEnclave() {
f = append(f, "-insecure")
}
return append(f, flag...)
}

func waitForSvc(t *testing.T, url string) error {
var (
start = time.Now()
Expand Down Expand Up @@ -128,7 +136,7 @@ func TestHelp(t *testing.T) {
}

func TestPages(t *testing.T) {
defer stopSvc(startSvc(t, []string{"-insecure"}))
defer stopSvc(startSvc(t, withFlags()))

cases := []struct {
name string
Expand Down Expand Up @@ -164,7 +172,7 @@ func TestPages(t *testing.T) {
}

func TestReadyHandler(t *testing.T) {
defer stopSvc(startSvc(t, []string{"-insecure", "-wait-for-app"}))
defer stopSvc(startSvc(t, withFlags("-wait-for-app")))

cases := []struct {
name string
Expand Down Expand Up @@ -211,7 +219,7 @@ func TestReadyHandler(t *testing.T) {
}

func TestAttestation(t *testing.T) {
defer stopSvc(startSvc(t, []string{"-insecure"}))
defer stopSvc(startSvc(t, withFlags()))

cases := []struct {
name string
Expand Down Expand Up @@ -253,7 +261,7 @@ func TestAttestation(t *testing.T) {
require.NoError(t, json.Unmarshal(body, &a))

// "Verify" the attestation document using our noop attester.
aux, err := enclave.NewNoopAttester().Verify(a.Doc, c.nonce)
aux, err := enclave.NewNoopAttester().Verify(&a, c.nonce)
require.NoError(t, err, errFromBody(t, resp))

// Ensure that the recovered nonce matches what we sent.
Expand All @@ -265,7 +273,7 @@ func TestAttestation(t *testing.T) {
}

func TestHashes(t *testing.T) {
defer stopSvc(startSvc(t, []string{"-insecure"}))
defer stopSvc(startSvc(t, withFlags()))

var (
hashes = new(attestation.Hashes)
Expand Down Expand Up @@ -347,7 +355,7 @@ func TestReverseProxy(t *testing.T) {
},
))
defer srv.Close()
defer stopSvc(startSvc(t, []string{"-insecure", "-app-web-srv", srv.URL}))
defer stopSvc(startSvc(t, withFlags("-app-web-srv", srv.URL)))

cases := []struct {
name string
Expand Down
25 changes: 10 additions & 15 deletions internal/enclave/attester.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@ import (
"github.com/Amnesic-Systems/veil/internal/nonce"
)

// See page 65 of the AWS Nitro Enclaves user guide for reference:
// https://docs.aws.amazon.com/pdfs/enclaves/latest/user/enclaves-user.pdf
const userDataLen = 1024
const (
// See page 65 of the AWS Nitro Enclaves user guide for reference:
// https://docs.aws.amazon.com/pdfs/enclaves/latest/user/enclaves-user.pdf
userDataLen = 1024
typeNoop = "noop"
typeNitro = "nitro"
)

var (
errPCRMismatch = errors.New("platform configuration registers differ")
errNonceMismatch = errors.New("nonce does not match")

// Check at compile-time if types implement the attester interface.
_ Attester = (*NitroAttester)(nil)
_ Attester = (*NoopAttester)(nil)
)

type Attestation []byte

// AttestationDoc holds the enclave's attestation document.
type AttestationDoc struct {
Type string `json:"type"`
Doc Attestation `json:"attestation_document"`
Type string `json:"type"`
Doc []byte `json:"attestation_document"`
}

type AuxFunc func(*nonce.Nonce) *AuxInfo
Expand All @@ -35,17 +36,11 @@ type AuxInfo struct {
Nonce [userDataLen]byte `json:"public_key"`
}

func ToAuxField(s []byte) [userDataLen]byte {
var a [userDataLen]byte
copy(a[:], s)
return a
}

// Attester defines functions for the creation and verification of attestation
// documents. Making this an interface helps with testing: It allows us to
// implement a dummy attester that works without the AWS Nitro hypervisor.
type Attester interface {
Type() string
Attest(*AuxInfo) (*AttestationDoc, error)
Verify(Attestation, *nonce.Nonce) (*AuxInfo, error)
Verify(*AttestationDoc, *nonce.Nonce) (*AuxInfo, error)
}
58 changes: 28 additions & 30 deletions internal/enclave/attester_nitro.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/hf/nitrite"
"github.com/hf/nsm"
"github.com/hf/nsm/request"
"github.com/hf/nsm/response"
)

// NitroAttester implements the attester interface by drawing on the AWS Nitro
Expand All @@ -32,66 +31,65 @@ func NewNitroAttester() (attester Attester, err error) {
}

func (*NitroAttester) Type() string {
return "nitro"
return typeNitro
}

func (a *NitroAttester) Attest(aux *AuxInfo) (*AttestationDoc, error) {
var (
err error
resp response.Response
)
func (a *NitroAttester) Attest(aux *AuxInfo) (_ *AttestationDoc, err error) {
defer errs.Wrap(&err, "failed to create attestation document")

if aux == nil {
return nil, errors.New("aux info is nil")
}

req := &request.Attestation{
Nonce: aux.Nonce[:],
UserData: aux.UserData[:],
PublicKey: aux.PublicKey[:],
}

if resp, err = a.session.Send(req); err != nil {
resp, err := a.session.Send(req)
if err != nil {
return nil, err
}
if resp.Attestation == nil || resp.Attestation.Document == nil {
return nil, errors.New("not good")
return nil, errors.New("required fields missing in attestation response")
}

return &AttestationDoc{
Type: "nitro",
Type: typeNitro,
Doc: resp.Attestation.Document,
}, nil
}

func (*NitroAttester) Verify(a Attestation, ourNonce *nonce.Nonce) (_ *AuxInfo, err error) {
func (a *NitroAttester) Verify(doc *AttestationDoc, ourNonce *nonce.Nonce) (_ *AuxInfo, err error) {
defer errs.Wrap(&err, "failed to verify attestation document")

// First, verify the remote enclave's attestation document.
opts := nitrite.VerifyOptions{CurrentTime: time.Now().UTC()}
their, err := nitrite.Verify(a, opts)
if err != nil {
return nil, err
if doc == nil {
return nil, errors.New("attestation document is nil")
}
if doc.Type != a.Type() {
return nil, errors.New("attestation document type mismatch")
}

// Verify that the remote enclave's PCR values (e.g., the image ID) are
// identical to ours.
ourPCRs, err := getPCRs()
// First, verify the attestation document.
opts := nitrite.VerifyOptions{CurrentTime: time.Now().UTC()}
res, err := nitrite.Verify(doc.Doc, opts)
if err != nil {
return nil, err
}
if !ourPCRs.Equal(their.Document.PCRs) {
return nil, errPCRMismatch
}

// Verify that the remote enclave's attestation document contains the nonce
// that we asked it to embed.
theirNonce, err := nonce.FromSlice(their.Document.Nonce)
// Verify that the attestation document contains the nonce that we may have
// asked it to embed.
docNonce, err := nonce.FromSlice(res.Document.Nonce)
if err != nil {
return nil, err
}
if *ourNonce != *theirNonce {
if ourNonce != nil && *ourNonce != *docNonce {
return nil, errNonceMismatch
}

return &AuxInfo{
Nonce: [1024]byte(their.Document.Nonce),
UserData: [1024]byte(their.Document.UserData),
PublicKey: [1024]byte(their.Document.PublicKey),
Nonce: [userDataLen]byte(res.Document.Nonce),
UserData: [userDataLen]byte(res.Document.UserData),
PublicKey: [userDataLen]byte(res.Document.PublicKey),
}, nil
}
121 changes: 121 additions & 0 deletions internal/enclave/attester_nitro_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package enclave

import (
"testing"

"github.com/Amnesic-Systems/veil/internal/nonce"
"github.com/Amnesic-Systems/veil/internal/util"
"github.com/stretchr/testify/require"
)

func getNonce(t *testing.T) [userDataLen]byte {
n, err := nonce.New()
require.NoError(t, err)
return ToAuxField(n.ToSlice())
}

func TestNitroAttest(t *testing.T) {
if !IsEnclave() {
t.Skip("skipping test; not running in an enclave")
}
attester, err := NewNitroAttester()
require.NoError(t, err)

cases := []struct {
name string
aux *AuxInfo
wantErr bool
}{
{
name: "nil aux info",
wantErr: true,
},
{
name: "empty aux info",
aux: &AuxInfo{},
},
{
name: "aux info with nonce",
aux: &AuxInfo{
Nonce: getNonce(t),
},
},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
doc, err := attester.Attest(c.aux)
if c.wantErr {
require.NotNil(t, err)
return
}
require.Equal(t, doc.Type, typeNitro)
})
}
}

func TestNitroVerify(t *testing.T) {
if !IsEnclave() {
t.Skip("skipping test; not running in an enclave")
}

attester, err := NewNitroAttester()
require.NoError(t, err)

getDoc := func(t *testing.T, n *nonce.Nonce) *AttestationDoc {
doc, err := attester.Attest(&AuxInfo{Nonce: ToAuxField(n.ToSlice())})
require.NoError(t, err)
return doc
}
testNonce := util.Must(nonce.New())

cases := []struct {
name string
doc *AttestationDoc
nonce *nonce.Nonce
wantErr bool
}{
{
name: "nil document and nonce",
wantErr: true,
},
{
name: "document type mismatch",
doc: &AttestationDoc{Type: "foo"},
wantErr: true,
},
{
name: "invalid document",
doc: &AttestationDoc{
Type: typeNitro,
Doc: []byte("foobar"),
},
wantErr: true,
},
{
name: "nonce mismatch",
doc: getDoc(t, util.Must(nonce.New())),
nonce: util.Must(nonce.New()),
wantErr: true,
},
{
name: "no nonce",
doc: getDoc(t, util.Must(nonce.New())),
},
{
name: "valid document and nonce",
doc: getDoc(t, testNonce),
nonce: testNonce,
},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
_, err := attester.Verify(c.doc, c.nonce)
if c.wantErr {
require.Error(t, err)
return
}
})
}
}
8 changes: 4 additions & 4 deletions internal/enclave/attester_noop.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func NewNoopAttester() Attester {
}

func (*NoopAttester) Type() string {
return "noop"
return typeNoop
}

func (*NoopAttester) Attest(aux *AuxInfo) (*AttestationDoc, error) {
Expand All @@ -23,14 +23,14 @@ func (*NoopAttester) Attest(aux *AuxInfo) (*AttestationDoc, error) {
return nil, err
}
return &AttestationDoc{
Type: "noop",
Type: typeNoop,
Doc: a,
}, nil
}

func (*NoopAttester) Verify(a Attestation, n *nonce.Nonce) (*AuxInfo, error) {
func (*NoopAttester) Verify(a *AttestationDoc, n *nonce.Nonce) (*AuxInfo, error) {
var aux = new(AuxInfo)
if err := json.Unmarshal(a, &aux); err != nil {
if err := json.Unmarshal(a.Doc, &aux); err != nil {
return nil, err
}
return aux, nil
Expand Down
Loading