Skip to content

Add teeattestation package for TEE attestation validation#1899

Open
nadahalli wants to merge 7 commits intomainfrom
tejaswi/tee-attestation
Open

Add teeattestation package for TEE attestation validation#1899
nadahalli wants to merge 7 commits intomainfrom
tejaswi/tee-attestation

Conversation

@nadahalli
Copy link
Contributor

@nadahalli nadahalli commented Mar 16, 2026

Summary

  • Adds pkg/teeattestation/ with platform-agnostic domain-separated hashing (DomainHash)
  • Adds pkg/teeattestation/nitro/ with AWS Nitro attestation validation (ValidateAttestation, PCR types, default CA roots)
  • Adds pkg/teeattestation/nitro/fake/ with FakeAttestor for testing without Nitro hardware
  • New dependency: github.com/hf/nitrite (Nitro attestation verifier)

Nitro-specific code is isolated in nitro/ so GCP Confidential Computing etc. can be added as sibling packages later.

Used by both confidential-compute (enclave-side) and chainlink (relay DON handler) for attestation validation and domain-separated hashing. It is an almost direct copy from https://github.com/smartcontractkit/confidential-compute/tree/main/enclave-client/attestation-validator

@nadahalli nadahalli requested a review from a team as a code owner March 16, 2026 17:24
Copilot AI review requested due to automatic review settings March 16, 2026 17:24
@github-actions
Copy link

👋 nadahalli, thanks for creating this pull request!

To help reviewers, please consider creating future PRs as drafts first. This allows you to self-review and make any final changes before notifying the team.

Once you're ready, you can mark it as "Ready for review" to request feedback. Thanks!

@nadahalli nadahalli force-pushed the tejaswi/tee-attestation branch from 0ea8118 to 2edce5f Compare March 16, 2026 17:26
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new teeattestation package providing platform-agnostic domain-separated hashing and AWS Nitro Enclave attestation validation, along with a fake attestor for testing.

Changes:

  • Introduces pkg/teeattestation/hash.go with DomainHash for domain-separated SHA-256 hashing
  • Adds pkg/teeattestation/nitro/validate.go with ValidateAttestation for verifying AWS Nitro attestation documents against expected PCRs and user data
  • Adds pkg/teeattestation/nitro/fake/ with a FakeAttestor that produces COSE Sign1 documents verifiable without real Nitro hardware

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/teeattestation/hash.go Domain-separated SHA-256 hashing primitive
pkg/teeattestation/hash_test.go Tests for DomainHash
pkg/teeattestation/nitro/validate.go AWS Nitro attestation validation with PCR and user data checks
pkg/teeattestation/nitro/fake/fake.go Fake attestor producing valid COSE Sign1 docs for testing
pkg/teeattestation/nitro/fake/fake_test.go Tests for FakeAttestor
go.mod Adds github.com/hf/nitrite dependency
Comments suppressed due to low confidence (1)

pkg/teeattestation/nitro/validate.go:1

  • The cosePayload struct has an Unprotected field of type cbor.RawMessage, but it is not set when constructing outer in fake.go (line 147). CBOR nil/zero-value for RawMessage may serialize differently than the empty CBOR map ({}) that COSE Sign1 expects for the unprotected header. If nitrite.Verify is strict about this, it could fail. This comment applies to fake.go line 147, referencing the struct in the same file.
// Package nitro provides AWS Nitro Enclave attestation validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// ValidateAttestation verifies an AWS Nitro attestation document against
// expected user data and trusted PCR measurements.
func ValidateAttestation(attestation, expectedUserData, trustedMeasurements []byte, caRootsPEM string) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Intentional. Empty string means 'use production default'. All existing callers pass empty string for production and a non-empty PEM for tests. Changing this would break the ergonomics for no practical gain.

"pcr1": hex.EncodeToString(f.pcrs[1]),
"pcr2": hex.EncodeToString(f.pcrs[2]),
}
b, _ := json.Marshal(m)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a comment. Changing the signature would break callers for an error that can't happen (json.Marshal on map[string]string).

@nadahalli nadahalli force-pushed the tejaswi/tee-attestation branch 2 times, most recently from f9dede0 to 2c727f6 Compare March 16, 2026 17:29
@nadahalli nadahalli requested a review from a team March 16, 2026 17:32

// ValidateAttestation verifies an AWS Nitro attestation document against
// expected user data and trusted PCR measurements.
func ValidateAttestation(attestation, expectedUserData, trustedMeasurements []byte, caRootsPEM string) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: rename to ValidateNitroAttestation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's inside the nitro folder. That should be enough, no?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, yeah. Forgot about that.

@vreff
Copy link
Contributor

vreff commented Mar 16, 2026

Would be nice to put this behind a /privacy/ or /confidential-compute/ root package and mark that thing as being Privacy codeowners. That way we can maintain better autonomy for approvals.

@nadahalli
Copy link
Contributor Author

Would be nice to put this behind a /privacy/ or /confidential-compute/ root package and mark that thing as being Privacy codeowners. That way we can maintain better autonomy for approvals.

Let's add privacy as the owner of this folder. I don't want to add a privacy specific top-level package.

@vreff
Copy link
Contributor

vreff commented Mar 16, 2026

Let's add privacy as the owner of this folder. I don't want to add a privacy specific top-level package.

sure

vreff
vreff previously approved these changes Mar 16, 2026
@vreff
Copy link
Contributor

vreff commented Mar 26, 2026

LGTM, will ship when the linting is fixed.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: af5248c Previous: c7b8c13 Ratio
BenchmarkKeystore_Sign/nop/in-process 758 ns/op 356.1 ns/op 2.13

This comment was automatically generated by workflow using github-action-benchmark.

@nadahalli nadahalli force-pushed the tejaswi/tee-attestation branch from eef5bc4 to af5248c Compare March 26, 2026 14:43
ValidateAttestation now always uses the hardcoded AWS Nitro root cert.
ValidateAttestationWithRoots is available for testing with fake enclaves
that use self-signed CA roots.
if !bytes.Equal(result.Document.PCRs[1], trustedPCRs.PCR1) {
return fmt.Errorf("PCR1 mismatch: expected %x", trustedPCRs.PCR1)
}
if !bytes.Equal(result.Document.PCRs[2], trustedPCRs.PCR2) {
Copy link
Contributor

Choose a reason for hiding this comment

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

should we check somewhere that length of result.Document.PCRs is 3 (or at least 3) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yikes. Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

And sorry, how do you know that trustedPCRs will have 3 (or at least 3)?

Copy link
Contributor

Choose a reason for hiding this comment

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

sorry, can't you just write:

maps.EqualFunc(result.Document.PCRs, map[uint]HexBytes{0: trustedPCRs.PCR0, 1: trustedPCRs.PCR1, 2: trustedPCRs.PCR2},
		func(v1 []byte, v2 HexBytes) bool {
			return bytes.Equal(v1, v2)
		},
	)

to compare these maps?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could. But I want to keep the error of which PCR mismatched, if at all. It's quite simple, the way it is right now, and I prefer that.

@nadahalli
Copy link
Contributor Author

Yikes. Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need to be a separate module?

Copy link
Contributor

Choose a reason for hiding this comment

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

(a separate module would also require a general github boilerplate like https://github.com/smartcontractkit/chainlink-common/blob/main/.github/workflows/keystore.yml)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not a strong reason. It has two TEE specific dependencies (nitrite and cbor/v2) - didn't want to bloat the common dependency tree.

But if you think common is already bloated enough and 2 more won't do much damage, I am happy to dump the separate module.

And I didn't know about the github boilerplate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved it to the root module now.

// COSE Sign1 attestation documents. These documents pass nitrite.Verify's
// full validation chain (CBOR parsing, cert chain, ECDSA signature, UserData,
// PCRs) without requiring real Nitro hardware.
package fake
Copy link
Contributor

Choose a reason for hiding this comment

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

How about a more descriptive name?

Suggested change
package fake
package nitrofake

As in https://pkg.go.dev/net/http/httptest

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@nadahalli
Copy link
Contributor Author

nadahalli commented Mar 26, 2026 via email

@nadahalli
Copy link
Contributor Author

Done.

@nadahalli
Copy link
Contributor Author

@pavel-raykov re: how do you know trustedPCRs will have 3

PCRs is a struct with three fixed fields (PCR0, PCR1, PCR2), not a slice. If any field is missing from the JSON, it unmarshals as nil and the comparison fails with a mismatch error. The bounds check is for result.Document.PCRs (from the attestation document), which is a map and could have fewer than 3 entries.

@github-actions
Copy link

✅ API Diff Results - github.com/smartcontractkit/chainlink-common

✅ Compatible Changes (3)

package github (3)
  • com/smartcontractkit/chainlink-common/pkg/teeattestation — ➕ Added

  • com/smartcontractkit/chainlink-common/pkg/teeattestation/nitro — ➕ Added

  • com/smartcontractkit/chainlink-common/pkg/teeattestation/nitro/fake — ➕ Added


📄 View full apidiff report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants