Skip to content
Closed
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
57 changes: 57 additions & 0 deletions pkg/teeattestation/nitro/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/json"
"errors"
"fmt"
"sort"
"time"
)

Expand Down Expand Up @@ -76,6 +77,62 @@ type Document struct {
ModuleID string
}

// VerifyPCR checks that the PCR at index equals expected. Use it for
// per-instance PCRs such as PCR4 (the SHA384 of the parent instance ID), which
// differ per enclave and therefore cannot be part of the shared trusted
// measurements checked by ValidateAndParse.
//
// It returns an error if expected is empty, the PCR is absent or
// length-mismatched, the PCR is all zero (debug-mode enclaves emit all-zero
// PCRs, which must never be accepted as a real measurement), or the values
// differ.
func (d *Document) VerifyPCR(index uint, expected []byte) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't we just returning a generic []bytes ID field from the verifyMeasurements function and just pass the PCR4 through there? We are really gating ourselves into Nitro here. Although that probably doesn't matter, it's still quite specific.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already inside teeattestation/nitro with cose parsing, etc. I think it should be ok at this level. If we ever move to proper TEEAL, we will need to rework a lot of other code. I'd rather do that then.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about when we move this code back into confidential-compute once it is open sourced?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was previously very simple. It would be nice to revert to that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the original PR for reference: https://github.com/smartcontractkit/confidential-compute/pull/293/changes#diff-822b5dfcbad5be1fed590586f99dd1ba4d0cba29c1b45761d03f659c2007cc94R13.

By simply extending the interface to include a []bytes field our consumers didn't have to dig into Nitro details.

if len(expected) == 0 {
return fmt.Errorf("expected PCR%d value is empty", index)
}
actual, ok := d.PCRs[index]
if !ok {
return fmt.Errorf("attestation has no PCR%d", index)
}
if len(actual) != len(expected) {
return fmt.Errorf("PCR%d length mismatch: expected %d bytes, got %d", index, len(expected), len(actual))
}
if allZero(actual) {
return fmt.Errorf("PCR%d is all zero (debug-mode enclave), refusing", index)
}
if !bytes.Equal(actual, expected) {
return fmt.Errorf("PCR%d mismatch: expected %x, got %x", index, expected, actual)
}
Comment thread
Copilot marked this conversation as resolved.
return nil
}

// VerifyExpectedPCRs checks each index/value in expected against the document
// via VerifyPCR. It is a convenience for callers asserting several per-instance
// PCRs at once and returns the first failure. Indices are checked in ascending
// order so the reported failure is deterministic.
func (d *Document) VerifyExpectedPCRs(expected map[uint][]byte) error {
indices := make([]uint, 0, len(expected))
for index := range expected {
indices = append(indices, index)
}
sort.Slice(indices, func(i, j int) bool { return indices[i] < indices[j] })
for _, index := range indices {
if err := d.VerifyPCR(index, expected[index]); err != nil {
return err
}
}
return nil
}

func allZero(b []byte) bool {
for _, x := range b {
if x != 0 {
return false
}
}
return len(b) != 0
}

// ValidateAttestation verifies an AWS Nitro attestation document against
// expected user data and trusted PCR measurements. Always validates against
// the AWS Nitro Enclaves root certificate.
Expand Down
66 changes: 66 additions & 0 deletions pkg/teeattestation/nitro/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,69 @@ func TestValidateAndParse_FailsOnWrongUserData(t *testing.T) {
require.Nil(t, parsed)
require.Contains(t, err.Error(), "expected user data")
}

func TestDocument_VerifyPCR(t *testing.T) {
pcr4 := make([]byte, 48)
for i := range pcr4 {
pcr4[i] = byte(i + 1)
}
doc := &Document{PCRs: map[uint][]byte{4: pcr4}}

t.Run("match", func(t *testing.T) {
require.NoError(t, doc.VerifyPCR(4, pcr4))
})

t.Run("mismatch", func(t *testing.T) {
other := make([]byte, 48)
copy(other, pcr4)
other[0] ^= 0xff
err := doc.VerifyPCR(4, other)
require.Error(t, err)
require.Contains(t, err.Error(), "PCR4 mismatch")
})

t.Run("absent index", func(t *testing.T) {
err := doc.VerifyPCR(3, pcr4)
require.Error(t, err)
require.Contains(t, err.Error(), "no PCR3")
})

t.Run("length mismatch", func(t *testing.T) {
err := doc.VerifyPCR(4, pcr4[:32])
require.Error(t, err)
require.Contains(t, err.Error(), "length mismatch")
})

t.Run("empty expected", func(t *testing.T) {
err := doc.VerifyPCR(4, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "empty")
})

t.Run("all-zero PCR is rejected (debug mode)", func(t *testing.T) {
zero := make([]byte, 48)
debugDoc := &Document{PCRs: map[uint][]byte{4: zero}}
err := debugDoc.VerifyPCR(4, zero)
require.Error(t, err)
require.Contains(t, err.Error(), "all zero")
})
}

func TestDocument_VerifyExpectedPCRs(t *testing.T) {
pcr4 := make([]byte, 48)
pcr8 := make([]byte, 48)
for i := range pcr4 {
pcr4[i] = byte(i + 1)
pcr8[i] = byte(i + 100)
}
doc := &Document{PCRs: map[uint][]byte{4: pcr4, 8: pcr8}}

require.NoError(t, doc.VerifyExpectedPCRs(map[uint][]byte{4: pcr4, 8: pcr8}))

wrong := make([]byte, 48)
copy(wrong, pcr8)
wrong[0] ^= 0xff
err := doc.VerifyExpectedPCRs(map[uint][]byte{4: pcr4, 8: wrong})
require.Error(t, err)
require.Contains(t, err.Error(), "PCR8 mismatch")
}
Loading