Skip to content

Commit

Permalink
Merge pull request #2 from Amnesic-Systems/test-nitro-attester
Browse files Browse the repository at this point in the history
Extend and improve unit testing.
  • Loading branch information
NullHypothesis authored Oct 19, 2024
2 parents 387575e + 8009a94 commit d6b92c3
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 70 deletions.
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

0 comments on commit d6b92c3

Please sign in to comment.