mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
Real parsers for the three production TEE quote formats, decoding the documented binary layouts and verifying the full vendor trust chain (no longer the canonical-model stand-in): - SGX (DCAP v3): header+report body offsets (MRENCLAVE@112, report_data@368); PCK chain→pinned Intel root, PCK signs the QE report, the QE report binds the attestation key (sha256(attestPub‖authData)), the attestation key signs the quote — full DCAP flow. - TDX (DCAP v4): version 4 / tee_type 0x81, MRTD@184, report_data@568; same sigData chain. - SEV-SNP: 1184-byte report, measurement@0x90, report_data@0x50, P-384 VCEK signature@0x2A0 (LE scalars), VCEK→pinned AMD root. All verify the operator-key binding in report_data. 9/9 incl. tampered-measurement (sig fails), unpinned-root (chain fails), unbound-key for each format.
197 lines
7.3 KiB
Go
197 lines
7.3 KiB
Go
package attestation
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"math/big"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// genCA makes a self-signed ECDSA-P256 root CA.
|
|
func genCA(t *testing.T, cn string) (*x509.Certificate, *ecdsa.PrivateKey) {
|
|
t.Helper()
|
|
key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
tmpl := &x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{CommonName: cn},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
IsCA: true,
|
|
KeyUsage: x509.KeyUsageCertSign,
|
|
BasicConstraintsValid: true,
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cert, _ := x509.ParseCertificate(der)
|
|
return cert, key
|
|
}
|
|
|
|
// genLeaf makes an attestation-key leaf cert signed by the root.
|
|
func genLeaf(t *testing.T, root *x509.Certificate, rootKey *ecdsa.PrivateKey) (*ecdsa.PrivateKey, []byte) {
|
|
t.Helper()
|
|
key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
tmpl := &x509.Certificate{
|
|
SerialNumber: big.NewInt(2),
|
|
Subject: pkix.Name{CommonName: "attestation-key"},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, tmpl, root, &key.PublicKey, rootKey)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return key, der
|
|
}
|
|
|
|
// genCAWithKey makes a self-signed CA using a caller-supplied key (any curve — P-384 for AMD).
|
|
func genCAWithKey(t *testing.T, cn string, key *ecdsa.PrivateKey) (*x509.Certificate, []byte) {
|
|
t.Helper()
|
|
tmpl := &x509.Certificate{
|
|
SerialNumber: big.NewInt(10), Subject: pkix.Name{CommonName: cn},
|
|
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour),
|
|
IsCA: true, KeyUsage: x509.KeyUsageCertSign, BasicConstraintsValid: true,
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cert, _ := x509.ParseCertificate(der)
|
|
return cert, der
|
|
}
|
|
|
|
// genLeafWithKey makes a leaf cert for leafKey signed by root/rootKey (any curve).
|
|
func genLeafWithKey(t *testing.T, root *x509.Certificate, rootKey, leafKey *ecdsa.PrivateKey) []byte {
|
|
t.Helper()
|
|
tmpl := &x509.Certificate{
|
|
SerialNumber: big.NewInt(11), Subject: pkix.Name{CommonName: "leaf"},
|
|
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour),
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, tmpl, root, &leafKey.PublicKey, rootKey)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return der
|
|
}
|
|
|
|
func makeQuote(attestKey *ecdsa.PrivateKey, leafDER []byte, m [32]byte, operatorPub []byte) *Quote {
|
|
rd := BindKey(operatorPub)
|
|
digest := sha256.Sum256(signedBody(m, rd))
|
|
sig, _ := ecdsa.SignASN1(rand.Reader, attestKey, digest[:])
|
|
return &Quote{Measurement: m, ReportData: rd, LeafDER: leafDER, Signature: sig}
|
|
}
|
|
|
|
// the happy path: a genuine quote from an enclave running allow-listed code, bound to the operator's
|
|
// confidentiality key, verifies.
|
|
func TestVerify_GenuineQuote(t *testing.T) {
|
|
root, rootKey := genCA(t, "vendor-root")
|
|
attestKey, leafDER := genLeaf(t, root, rootKey)
|
|
measurement := sha256.Sum256([]byte("the expected enclave image"))
|
|
operatorPub := []byte("operator-confidentiality-pubkey")
|
|
|
|
roots := x509.NewCertPool()
|
|
roots.AddCert(root)
|
|
policy := &Policy{Roots: roots, AllowedMeasure: map[[32]byte]bool{measurement: true}}
|
|
|
|
q := makeQuote(attestKey, leafDER, measurement, operatorPub)
|
|
if err := Verify(q, policy, operatorPub); err != nil {
|
|
t.Fatalf("a genuine quote must verify, got %v", err)
|
|
}
|
|
}
|
|
|
|
// ADVERSARIAL: a forged signature is rejected (the prior stub didn't check this at all).
|
|
func TestVerify_BadSignatureRejected(t *testing.T) {
|
|
root, rootKey := genCA(t, "vendor-root")
|
|
attestKey, leafDER := genLeaf(t, root, rootKey)
|
|
m := sha256.Sum256([]byte("img"))
|
|
op := []byte("opkey")
|
|
roots := x509.NewCertPool()
|
|
roots.AddCert(root)
|
|
policy := &Policy{Roots: roots, AllowedMeasure: map[[32]byte]bool{m: true}}
|
|
|
|
q := makeQuote(attestKey, leafDER, m, op)
|
|
q.Signature[len(q.Signature)-1] ^= 0x01 // flip a signature byte
|
|
if err := Verify(q, policy, op); err != ErrSignature {
|
|
t.Fatalf("a tampered signature must be ErrSignature, got %v", err)
|
|
}
|
|
}
|
|
|
|
// ADVERSARIAL — THE KEY ONE: an attacker mints a perfectly-formed quote (own root, valid sig,
|
|
// correct measurement, correct binding), but its root is NOT the pinned vendor root → rejected.
|
|
func TestVerify_UnpinnedRootRejected(t *testing.T) {
|
|
pinnedRoot, _ := genCA(t, "vendor-root")
|
|
attackerRoot, attackerKey := genCA(t, "attacker-root")
|
|
attestKey, leafDER := genLeaf(t, attackerRoot, attackerKey) // chains to the ATTACKER root
|
|
m := sha256.Sum256([]byte("img"))
|
|
op := []byte("opkey")
|
|
|
|
roots := x509.NewCertPool()
|
|
roots.AddCert(pinnedRoot) // only the genuine vendor root is pinned
|
|
policy := &Policy{Roots: roots, AllowedMeasure: map[[32]byte]bool{m: true}}
|
|
|
|
q := makeQuote(attestKey, leafDER, m, op)
|
|
if err := Verify(q, policy, op); err != ErrChain {
|
|
t.Fatalf("a quote not chaining to a pinned root must be ErrChain, got %v", err)
|
|
}
|
|
}
|
|
|
|
// ADVERSARIAL: a genuine quote for a DIFFERENT (not allow-listed) enclave image is rejected — the
|
|
// operator cannot swap in unattested code.
|
|
func TestVerify_WrongMeasurementRejected(t *testing.T) {
|
|
root, rootKey := genCA(t, "vendor-root")
|
|
attestKey, leafDER := genLeaf(t, root, rootKey)
|
|
expected := sha256.Sum256([]byte("expected"))
|
|
actual := sha256.Sum256([]byte("some other code")) // validly signed, but not allow-listed
|
|
op := []byte("opkey")
|
|
roots := x509.NewCertPool()
|
|
roots.AddCert(root)
|
|
policy := &Policy{Roots: roots, AllowedMeasure: map[[32]byte]bool{expected: true}}
|
|
|
|
q := makeQuote(attestKey, leafDER, actual, op)
|
|
if err := Verify(q, policy, op); err != ErrMeasurement {
|
|
t.Fatalf("a non-allow-listed measurement must be ErrMeasurement, got %v", err)
|
|
}
|
|
}
|
|
|
|
// ADVERSARIAL: a genuine quote that binds operator A's key cannot vouch for operator B — so a sealed
|
|
// prompt for A cannot be opened by an enclave attested for B.
|
|
func TestVerify_UnboundKeyRejected(t *testing.T) {
|
|
root, rootKey := genCA(t, "vendor-root")
|
|
attestKey, leafDER := genLeaf(t, root, rootKey)
|
|
m := sha256.Sum256([]byte("img"))
|
|
roots := x509.NewCertPool()
|
|
roots.AddCert(root)
|
|
policy := &Policy{Roots: roots, AllowedMeasure: map[[32]byte]bool{m: true}}
|
|
|
|
q := makeQuote(attestKey, leafDER, m, []byte("operator-A-key"))
|
|
if err := Verify(q, policy, []byte("operator-B-key")); err != ErrBinding {
|
|
t.Fatalf("a quote bound to a different key must be ErrBinding, got %v", err)
|
|
}
|
|
}
|
|
|
|
// ADVERSARIAL: tampering the measurement after signing breaks the signature (you cannot keep a valid
|
|
// sig while changing what was attested).
|
|
func TestVerify_TamperedMeasurementBreaksSignature(t *testing.T) {
|
|
root, rootKey := genCA(t, "vendor-root")
|
|
attestKey, leafDER := genLeaf(t, root, rootKey)
|
|
m := sha256.Sum256([]byte("img"))
|
|
op := []byte("opkey")
|
|
roots := x509.NewCertPool()
|
|
roots.AddCert(root)
|
|
policy := &Policy{Roots: roots, AllowedMeasure: map[[32]byte]bool{m: true, {}: true}}
|
|
|
|
q := makeQuote(attestKey, leafDER, m, op)
|
|
q.Measurement = [32]byte{} // change the attested code after signing
|
|
if err := Verify(q, policy, op); err != ErrSignature {
|
|
t.Fatalf("changing the measurement after signing must be ErrSignature, got %v", err)
|
|
}
|
|
}
|