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:
- Deploy new version to staging
- Capture new PCR values
- Add to allowlist
- Deploy to production
- 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
- Always verify before trusting - Never use parsed data without verification
- Pin PCR values - Maintain strict allowlist
- Monitor changes - Alert on unexpected PCR values
- Rotate regularly - Update parser and PCRs periodically
- 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