Skip to main content

AWS Nitro Attestation & Verification

VisualSign runs in AWS Nitro Enclaves to ensure parsing happens in a secure, tamper-proof environment. This guide explains how to verify the attestation.

Overview

┌──────────────┐     ┌───────────────┐     ┌──────────────┐
│ Enclave │────▶│ Attestation │────▶│ Verifier │
│ (Parser) │ │ Document │ │ (Wallet) │
└──────────────┘ └───────────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
Parse TX Signed by AWS Verify Trust

Verification Levels

Level 1: Signature Verification

Basic verification of the parser's P256 signature.

// Extract from parser response
signature := response.ParsedTransaction.Signature
publicKey := signature.PublicKey
message := signature.Message
sig := signature.Signature

// Verify P256 ECDSA signature
valid := verifyP256Signature(publicKey, message, sig)
if !valid {
return errors.New("invalid signature")
}

What this proves:

  • Data hasn't been tampered with
  • Response came from holder of private key

What this doesn't prove:

  • Code running in an enclave
  • Specific parser version

Level 2: Boot Attestation

Verify the enclave boot measurements (PCRs).

// Parse attestation document
doc, err := parseAttestationDocument(attestationBytes)
if err != nil {
return err
}

// Check PCR values against allowlist
expectedPCRs := map[int]string{
0: "abc123...", // Enclave image
1: "def456...", // Enclave boot
2: "ghi789...", // Application hash
}

for idx, expected := range expectedPCRs {
if doc.PCRs[idx] != expected {
return fmt.Errorf("PCR%d mismatch", idx)
}
}

What this proves:

  • Code is running in a Nitro Enclave
  • Specific enclave configuration
  • Boot-time measurements match

Level 3: Manifest Verification

Verify the exact application binary.

// Get manifest from attestation
manifest := doc.UserData.Manifest

// Verify manifest signature
manifestSig := manifest.Signature
valid := verifyManifestSignature(manifestSig)

// Check application hash
appHash := sha256.Sum256(applicationBinary)
if manifest.AppHash != appHash {
return errors.New("application hash mismatch")
}

What this proves:

  • Exact parser binary version
  • No code modifications
  • Complete supply chain verification

Implementation Guide

Step 1: Extract Attestation

The parser includes attestation in its responses:

type ParseResponse struct {
ParsedTransaction Transaction
Attestation []byte // CBOR-encoded attestation document
}

Step 2: Decode CBOR

AWS attestation documents use CBOR encoding:

import "github.com/fxamacker/cbor/v2"

var doc AttestationDocument
err := cbor.Unmarshal(attestationBytes, &doc)

Step 3: Verify Certificate Chain

The attestation includes an X.509 certificate chain:

// Extract certificates
certs := doc.Certificate
chain := x509.NewCertPool()

// Build chain
for _, certDER := range certs {
cert, _ := x509.ParseCertificate(certDER)
chain.AddCert(cert)
}

// Verify against AWS root CA
opts := x509.VerifyOptions{
Roots: awsNitroRootCA,
Intermediates: chain,
}

_, err := leafCert.Verify(opts)

Step 4: Verify PCR Values

Platform Configuration Registers contain measurements:

type PCRs struct {
PCR0 []byte // Enclave image file
PCR1 []byte // Linux kernel and boot ramfs
PCR2 []byte // Application binary
PCR3 []byte // Parent instance ID
PCR4 []byte // Parent instance IP
PCR8 []byte // Enclave certificate
}

Maintain an allowlist of valid PCR values:

# pcr-allowlist.yaml
production:
pcr0: "7fb5c55bc2ecbb68ed99a13d7122abfc0666b926a79d5379bc58b9445c84217f"
pcr1: "235c9e6050abf6b993c915505f3220e2a82b51a4b8b244d5e19e6c7b2d7e8b25"
pcr2: "0f0e3e8118b61c8c5b21f92e1e2e52e89d09a92c627d7f6cf42c135f5d5c7c82"

Step 5: Extract Public Key

After verification, extract the public key:

// IMPORTANT: Only after successful attestation verification!
if !verifyAttestation(doc) {
return errors.New("attestation verification failed")
}

// Now safe to use the public key
publicKey := doc.PublicKey

Complete Verification Example

package verify

import (
"crypto/ecdsa"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"fmt"

"github.com/fxamacker/cbor/v2"
)

func VerifyParserResponse(resp *ParseResponse) error {
// Step 1: Decode attestation
var doc AttestationDocument
if err := cbor.Unmarshal(resp.Attestation, &doc); err != nil {
return fmt.Errorf("decode attestation: %w", err)
}

// Step 2: Verify certificate chain
if err := verifyCertificateChain(doc.Certificate); err != nil {
return fmt.Errorf("verify cert chain: %w", err)
}

// Step 3: Check PCR values
if err := verifyPCRs(doc.PCRs); err != nil {
return fmt.Errorf("verify PCRs: %w", err)
}

// Step 4: Verify signature using attested public key
pubKey, err := parsePublicKey(doc.PublicKey)
if err != nil {
return fmt.Errorf("parse public key: %w", err)
}

// Step 5: Verify the transaction signature
hash := sha256.Sum256([]byte(resp.ParsedTransaction.Payload))
valid := ecdsa.VerifyASN1(pubKey, hash[:], resp.ParsedTransaction.Signature)
if !valid {
return fmt.Errorf("invalid transaction signature")
}

return nil
}

func verifyPCRs(pcrs map[int][]byte) error {
// Load allowlist
allowlist := loadPCRAllowlist()

// Check each PCR
for idx, expected := range allowlist {
actual := hex.EncodeToString(pcrs[idx])
if actual != expected {
return fmt.Errorf("PCR%d mismatch: got %s, want %s",
idx, actual, expected)
}
}

return nil
}

Monitoring & Maintenance

PCR Rotation

When updating the parser:

  1. Deploy new version to staging
  2. Capture new PCR values
  3. Add to allowlist
  4. Deploy to production
  5. Remove old PCRs after migration

Health Monitoring

Monitor attestation failures:

metrics.Counter("attestation.verification.success")
metrics.Counter("attestation.verification.failure")
metrics.Histogram("attestation.verification.duration")

Audit Logging

Log all verification attempts:

log.Info("attestation_verified",
"pcr0", hex.EncodeToString(doc.PCRs[0]),
"pcr2", hex.EncodeToString(doc.PCRs[2]),
"timestamp", doc.Timestamp,
"nonce", doc.Nonce,
)

Security Best Practices

  1. Always verify before trusting - Never use parsed data without verification
  2. Pin PCR values - Maintain strict allowlist
  3. Monitor changes - Alert on unexpected PCR values
  4. Rotate regularly - Update parser and PCRs periodically
  5. Fail securely - Reject on any verification failure

Troubleshooting

Common Issues

PCR Mismatch:

  • Parser was updated
  • Running wrong environment
  • Allowlist needs update

Certificate Chain Invalid:

  • Clock skew
  • Expired certificates
  • Network issues fetching CRLs

Signature Verification Failed:

  • Data corruption
  • Wrong public key
  • Implementation bug

Debug Commands

Check current PCR values:

nitro-cli describe-enclave --enclave-id $EID

Verify attestation document:

nitro-cli verify-attestation --document attestation.cbor

Resources