sandbox-tee: check a TDX quote's PCK chain to a pinned Intel root #415

Merged
dcharlot merged 1 commit from feat/tdx-pck-chain into main 2026-10-22 06:51:25 -04:00
7 changed files with 351 additions and 9 deletions

View file

@ -78,7 +78,11 @@ jobs:
env:
WORKSPACES: |
- { name: jcp-rs, dir: jcp/jcp-rs, runner: linux, conformance: jcp-conformance, feature_tests: "-p jcp-energy --features nvml" }
- { name: sandbox-rs, dir: sandbox/sandbox-rs, runner: linux, conformance: sandbox-conformance }
# sandbox-tee's certificate-chain verifiers sit behind features (their
# X.509 and RSA dependencies are heavy), so a plain `cargo test` never
# runs their tests: `amd-kds` (SEV-SNP's VCEK to AMD's root) and
# `intel-pcs` (TDX's PCK to Intel's root).
- { name: sandbox-rs, dir: sandbox/sandbox-rs, runner: linux, conformance: sandbox-conformance, feature_tests: "-p sandbox-tee --features amd-kds,intel-pcs" }
- { name: smart-byte-rs, dir: smart-byte/smart-byte-rs, runner: linux }
- { name: pool-rs, dir: pool/pool-rs, runner: linux }
- { name: openpay, dir: openpay, runner: linux-arm64 }

View file

@ -298,7 +298,7 @@ At S6 and above, `tee` **MUST** carry a summary of the hardware attestation repo
- for SEV-SNP, the chip's **VCEK**, from AMD's KDS;
- for TDX, the platform's **PCK** key, from Intel's PCS, with its certificate chain checked.
A TDX quote carries its PCK certificate in its certification data, and that key **MUST** be the pinned one. Anyone can make a key, endorse a quoting-enclave report with it and assemble a quote that is consistent with itself. The host seals with a provider and an anchor of the same kind, and a third party verifies with its own anchor; evidence of another kind than the verifier's anchor is refused.
A TDX quote carries its PCK certificate chain in its certification data, and the PCK key **MUST** be the pinned one. A verifier may come to pin it by checking that chain to Intel's root, which it pins in turn: the reference does so in `sandbox-tee::intel_pcs` (signatures, issuer and subject names, validity at a given time; revocation lists are the deployment's to consult), as `sandbox-tee::amd_kds` checks a VCEK to AMD's root. Anyone can make a key, endorse a quoting-enclave report with it and assemble a quote that is consistent with itself. The host seals with a provider and an anchor of the same kind, and a third party verifies with its own anchor; evidence of another kind than the verifier's anchor is refused.
**The binding.** `report_data` is one preimage over everything a report vouches for:
@ -397,7 +397,7 @@ Each part is classified by how much of it is proven in the reference implementat
| Deterministic replay journal (§8) | **Engineering** | journal + root specified; reference records and verifies |
| Anti-evasion (§7.5) | **Engineering** | normalization + probing record specified |
| TEE report verification — SEV-SNP (§7.6) | **Solid** | `sandbox-tee` parses the SEV-SNP report, checks version/measurement/TCB/debug/report-data, and verifies the real ECDSA-P384/SHA-384 signature; vectored |
| TEE report verification — Intel TDX (§7.6) | **Solid** | `sandbox-tee::tdx` parses a DCAP v4 quote + TD10 report body and verifies the ECDSA-P256 quote signature, the QE-report endorsement of the attestation key, and the signature over the QE report by the **pinned** PCK key, which the quote must carry, then policy (MR_TD/debug/report-data). The run path seals and verifies TDX as it does SEV-SNP, through `TeeAnchor`; `configfs-tsm` reports on a TDX guest, and a software-grounded `LocalTdxProvider` covers wiring. Self-tested; fetching the PCK certificate from Intel's PCS and checking its chain is the deployment's step, as the VCEK from AMD's KDS is |
| TEE report verification — Intel TDX (§7.6) | **Solid** | `sandbox-tee::tdx` parses a DCAP v4 quote + TD10 report body and verifies the ECDSA-P256 quote signature, the QE-report endorsement of the attestation key, and the signature over the QE report by the **pinned** PCK key, which the quote must carry, then policy (MR_TD/debug/report-data). The run path seals and verifies TDX as it does SEV-SNP, through `TeeAnchor`; `configfs-tsm` reports on a TDX guest, and a software-grounded `LocalTdxProvider` covers wiring. The PCK chain is checked to a pinned Intel root by `sandbox-tee::intel_pcs` (feature `intel-pcs`). Self-tested against a chain of Intel's structure; pinning Intel's real root and consulting revocation lists are the deployment's steps |
| Accelerator attestation — NVIDIA GPU EAT (§7.6) | **Solid** | `sandbox-tee::gpu` verifies an NRAS GPU EAT (JWT/ES384) — the real ECDSA-P384/SHA-384 signature, `alg` pinning (never `none`), and the `eat_nonce` binding — and surfaces the device claims; self-tested |
| TEE/quote/JWKS chaining to vendor roots (§7.6) | **Engineering** | the reference verifies signatures against the supplied key; fetching and pinning the endorsing key to the vendor root (AMD KDS VCEK, Intel PCS PCK, NVIDIA NRAS JWKS) is the production wiring, documented and not faked |
| TEE report generation (§2.2 S6, §7.6) | **Engineering** | the `configfs-tsm` provider requests a report on Linux confidential hardware and returns `Unavailable` elsewhere; a software-grounded provider (tagged, never claiming hardware) covers wiring |

View file

@ -32,6 +32,10 @@ signature = { version = "2", optional = true }
# AMD KDS cert-chain verification (SEV-SNP): turns a genuine AMD ARK->ASK->VCEK
# chain into a hardware-grounded verifying key for the `chain`/grounding machinery.
amd-kds = ["dep:rsa", "dep:x509-cert", "dep:spki", "dep:der", "dep:signature"]
# Intel PCK cert-chain verification (TDX): checks the PCK chain a quote carries
# to a pinned Intel root, so the TDX anchor is derived rather than pinned by hand.
# ECDSA-P256 throughout, so it needs the X.509 parsing and not the RSA stack.
intel-pcs = ["dep:x509-cert", "dep:spki", "dep:der"]
# Dev-only, for the `attested_efficiency` example: composes this crate's energy
# attestation with the efficiency/1 receipt (jcp-rs), over a figure this host's

View file

@ -0,0 +1,316 @@
//! Intel PCK cert-chain verification for TDX (the `intel-pcs` feature).
//!
//! A TDX quote's QE report is signed by the platform's **PCK** key, and the
//! quote carries the PCK's certificate chain in its certification data: the
//! PCK leaf, then the Intel CA that issued it (the platform or processor CA),
//! then the root. [`crate::tdx::verify_tdx`] trusts the PCK the verifier pins,
//! never the one a quote presents. This module is how a verifier comes to pin
//! it: by checking the presented chain to Intel's root, which the relying
//! party pins, as SEV-SNP's VCEK is checked to AMD's ARK
//! ([`crate::amd_kds`]).
//!
//! Every link is ECDSA-P256 with SHA-256. The checks are signatures, issuer
//! and subject names linking up the chain, and each certificate valid at the
//! time given.
//!
//! **Not checked here:** revocation. Intel publishes CRLs for the PCK CAs, and
//! consulting them is the deployment's step.
//!
//! **Tested against:** a self-generated chain of Intel's structure, which
//! exercises the cryptographic constructions. Pinning Intel's real root is
//! the relying party's step.
use crate::TeeError;
use der::asn1::UintRef;
use der::{Decode, Encode, Reader, SliceReader};
use p256::ecdsa::signature::Verifier;
use p256::ecdsa::{Signature, VerifyingKey};
use x509_cert::Certificate;
/// The OID of `ecdsa-with-SHA256`.
const ECDSA_WITH_SHA256: &str = "1.2.840.10045.4.3.2";
/// The certificates of a PEM bundle, as DER, in order.
fn pem_certs(bytes: &[u8]) -> Result<Vec<Vec<u8>>, TeeError> {
use base64::Engine;
let text = core::str::from_utf8(bytes).map_err(|_| TeeError::Malformed("the PCK chain is not PEM".into()))?;
let (begin, end) = ("-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
let mut out = Vec::new();
let mut rest = text;
while let Some(b) = rest.find(begin) {
let after = &rest[b + begin.len()..];
let e = after.find(end).ok_or_else(|| TeeError::Malformed("an unterminated certificate".into()))?;
let b64: String = after[..e].chars().filter(|c| !c.is_whitespace()).collect();
out.push(
base64::engine::general_purpose::STANDARD
.decode(b64)
.map_err(|e| TeeError::Malformed(format!("certificate base64: {e}")))?,
);
rest = &after[e + end.len()..];
}
Ok(out)
}
/// The P-256 key in a certificate's SubjectPublicKeyInfo.
fn subject_key(cert: &Certificate) -> Result<VerifyingKey, TeeError> {
let point = cert
.tbs_certificate
.subject_public_key_info
.subject_public_key
.as_bytes()
.ok_or_else(|| TeeError::Malformed("an unaligned public key".into()))?;
VerifyingKey::from_sec1_bytes(point).map_err(|e| TeeError::Key(format!("P-256 key: {e}")))
}
/// An X.509 ECDSA signature: DER `SEQUENCE { r INTEGER, s INTEGER }`.
fn ecdsa_signature(der: &[u8]) -> Result<Signature, TeeError> {
let bad = |e: der::Error| TeeError::Signature(format!("ECDSA signature: {e}"));
let mut reader = SliceReader::new(der).map_err(bad)?;
let (r, s) = reader
.sequence(|seq| Ok((UintRef::decode(seq)?, UintRef::decode(seq)?)))
.map_err(bad)?;
let field = |n: UintRef<'_>| -> Result<[u8; 32], TeeError> {
let b = n.as_bytes();
if b.len() > 32 {
return Err(TeeError::Signature("an ECDSA scalar wider than P-256".into()));
}
let mut out = [0u8; 32];
out[32 - b.len()..].copy_from_slice(b);
Ok(out)
};
Signature::from_scalars(field(r)?, field(s)?).map_err(|e| TeeError::Signature(format!("ECDSA signature: {e}")))
}
/// Whether `cert` is signed by `issuer` (ECDSA-P256-SHA256 over its TBS), names
/// `issuer` as its issuer, and is valid at `now_unix`.
fn check_link(cert: &Certificate, issuer: &Certificate, now_unix: u64, what: &str) -> Result<(), TeeError> {
let fail = |why: String| TeeError::Policy(format!("{what}: {why}"));
if cert.signature_algorithm.oid.to_string() != ECDSA_WITH_SHA256 {
return Err(fail(format!("signed with {}, not ecdsa-with-SHA256", cert.signature_algorithm.oid)));
}
if cert.tbs_certificate.issuer != issuer.tbs_certificate.subject {
return Err(fail("its issuer is not the next certificate's subject".into()));
}
let validity = &cert.tbs_certificate.validity;
let (from, until) = (validity.not_before.to_unix_duration().as_secs(), validity.not_after.to_unix_duration().as_secs());
if now_unix < from || now_unix > until {
return Err(fail(format!("not valid at {now_unix} (valid {from}..{until})")));
}
let tbs = cert.tbs_certificate.to_der().map_err(|e| TeeError::Malformed(format!("{what} TBS: {e}")))?;
let sig = ecdsa_signature(
cert.signature.as_bytes().ok_or_else(|| TeeError::Malformed(format!("{what}: an unaligned signature")))?,
)?;
subject_key(issuer)?.verify(&tbs, &sig).map_err(|_| fail("the signature does not verify".into()))
}
/// Verify a PCK certificate chain to the pinned Intel root, and return the PCK
/// key: the anchor [`crate::tdx::verify_tdx`] checks a quote's QE report by.
///
/// - `trusted_root_der`: Intel's published root CA certificate, which the
/// relying party pins.
/// - `chain_pem`: the chain as a quote carries it, the PCK leaf first, then
/// its issuing CA, optionally ending in the root. A root the chain presents
/// must be the pinned one, byte for byte. The pinned root is used either way.
/// - `now_unix`: when the chain must be valid.
pub fn verify_pck_chain(trusted_root_der: &[u8], chain_pem: &[u8], now_unix: u64) -> Result<VerifyingKey, TeeError> {
let root = Certificate::from_der(trusted_root_der).map_err(|e| TeeError::Malformed(format!("root: {e}")))?;
let certs = pem_certs(chain_pem)?;
let (leaf_der, rest) = certs.split_first().ok_or_else(|| TeeError::Malformed("an empty PCK chain".into()))?;
let (ca_der, rest) = rest.split_first().ok_or_else(|| TeeError::Malformed("a PCK chain without its CA".into()))?;
match rest {
[] => {}
[presented] if presented.as_slice() == trusted_root_der => {}
[_] => return Err(TeeError::Policy("the chain ends in another root than the pinned one".into())),
_ => return Err(TeeError::Malformed("a PCK chain longer than leaf, CA and root".into())),
}
let leaf = Certificate::from_der(leaf_der).map_err(|e| TeeError::Malformed(format!("PCK: {e}")))?;
let ca = Certificate::from_der(ca_der).map_err(|e| TeeError::Malformed(format!("PCK CA: {e}")))?;
check_link(&ca, &root, now_unix, "the PCK CA")?;
check_link(&leaf, &ca, now_unix, "the PCK certificate")?;
subject_key(&leaf)
}
/// The anchor for a TDX quote, derived from the chain the quote itself carries
/// and checked to the pinned Intel root ([`verify_pck_chain`]). The trust
/// comes from the root, never from the quote.
pub fn pck_from_quote(quote: &[u8], trusted_root_der: &[u8], now_unix: u64) -> Result<VerifyingKey, TeeError> {
let q = crate::tdx::parse(quote)?;
verify_pck_chain(trusted_root_der, q.pck_chain(), now_unix)
}
#[cfg(test)]
mod tests {
use super::*;
use der::asn1::{BitString, ObjectIdentifier, UtcTime};
use der::Any;
use p256::ecdsa::signature::Signer;
use p256::ecdsa::SigningKey;
use spki::{AlgorithmIdentifierOwned, SubjectPublicKeyInfoOwned};
use std::str::FromStr;
use std::time::Duration;
use x509_cert::certificate::{TbsCertificate, Version};
use x509_cert::name::Name;
use x509_cert::serial_number::SerialNumber;
use x509_cert::time::{Time, Validity};
const NOW: u64 = 1_800_000_000;
const YEAR: u64 = 365 * 24 * 3600;
/// An ECDSA signature as X.509 carries it: DER `SEQUENCE { r, s }`.
fn der_signature(sig: &Signature) -> Vec<u8> {
let int = |b: &[u8]| {
let mut v: Vec<u8> = b.iter().copied().skip_while(|&x| x == 0).collect();
if v.first().is_none_or(|&x| x & 0x80 != 0) {
v.insert(0, 0);
}
[vec![0x02, v.len() as u8], v].concat()
};
let (r, s) = (sig.r().to_bytes(), sig.s().to_bytes());
let body = [int(&r), int(&s)].concat();
[vec![0x30, body.len() as u8], body].concat()
}
/// A certificate for `key`, named `subject`, issued by `issuer` and signed
/// by `signer`, valid from `from` until `until`.
fn cert(subject: &str, issuer: &str, key: &VerifyingKey, signer: &SigningKey, from: u64, until: u64) -> Vec<u8> {
let sig_alg = AlgorithmIdentifierOwned { oid: ObjectIdentifier::new_unwrap(ECDSA_WITH_SHA256), parameters: None };
let spki = SubjectPublicKeyInfoOwned {
algorithm: AlgorithmIdentifierOwned {
oid: ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"),
parameters: Some(Any::encode_from(&ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7")).unwrap()),
},
subject_public_key: BitString::from_bytes(key.to_encoded_point(false).as_bytes()).unwrap(),
};
let at = |s: u64| Time::UtcTime(UtcTime::from_unix_duration(Duration::from_secs(s)).unwrap());
let tbs = TbsCertificate {
version: Version::V3,
serial_number: SerialNumber::new(&[1]).unwrap(),
signature: sig_alg.clone(),
issuer: Name::from_str(&format!("CN={issuer}")).unwrap(),
validity: Validity { not_before: at(from), not_after: at(until) },
subject: Name::from_str(&format!("CN={subject}")).unwrap(),
subject_public_key_info: spki,
issuer_unique_id: None,
subject_unique_id: None,
extensions: None,
};
let sig: Signature = signer.sign(&tbs.to_der().unwrap());
let signature = BitString::from_bytes(&der_signature(&sig)).unwrap();
Certificate { tbs_certificate: tbs, signature_algorithm: sig_alg, signature }.to_der().unwrap()
}
fn pem(der: &[u8]) -> String {
use base64::Engine;
let b64 = base64::engine::general_purpose::STANDARD.encode(der);
format!("-----BEGIN CERTIFICATE-----\n{b64}\n-----END CERTIFICATE-----\n")
}
struct Chain {
root: Vec<u8>,
ca: Vec<u8>,
leaf: Vec<u8>,
pck: SigningKey,
}
/// A chain of Intel's structure: a self-signed root, a platform CA it
/// signs, and a PCK leaf the CA signs.
fn chain() -> Chain {
let root_key = SigningKey::from_slice(&[0x31; 32]).unwrap();
let ca_key = SigningKey::from_slice(&[0x32; 32]).unwrap();
let pck = SigningKey::from_slice(&[0x33; 32]).unwrap();
let (from, until) = (NOW - YEAR, NOW + YEAR);
Chain {
root: cert("Root CA", "Root CA", root_key.verifying_key(), &root_key, from, until),
ca: cert("Platform CA", "Root CA", ca_key.verifying_key(), &root_key, from, until),
leaf: cert("PCK Certificate", "Platform CA", pck.verifying_key(), &ca_key, from, until),
pck,
}
}
/// A genuine chain yields the PCK key, whether or not it ends in the root.
#[test]
fn a_chain_to_the_pinned_root_yields_the_pck() {
let c = chain();
let leaf_ca = pem(&c.leaf) + &pem(&c.ca);
assert_eq!(verify_pck_chain(&c.root, leaf_ca.as_bytes(), NOW).unwrap(), *c.pck.verifying_key());
let with_root = leaf_ca + &pem(&c.root);
assert_eq!(verify_pck_chain(&c.root, with_root.as_bytes(), NOW).unwrap(), *c.pck.verifying_key());
}
/// A chain to another root is refused, whether the verifier pins another
/// root or the chain presents one.
#[test]
fn a_chain_to_another_root_is_refused() {
let c = chain();
let other = chain_from(0x41);
let leaf_ca = pem(&c.leaf) + &pem(&c.ca);
assert!(verify_pck_chain(&other.root, leaf_ca.as_bytes(), NOW).is_err(), "another root pinned");
let presenting_other = leaf_ca + &pem(&other.root);
let err = verify_pck_chain(&c.root, presenting_other.as_bytes(), NOW).unwrap_err();
assert!(format!("{err}").contains("pinned"), "{err}");
}
/// A link that does not hold is refused: a leaf its CA did not sign, a
/// CA another key signed, a leaf naming another issuer.
#[test]
fn every_link_must_hold() {
let c = chain();
let stranger = SigningKey::from_slice(&[0x51; 32]).unwrap();
let forged_leaf = cert("PCK Certificate", "Platform CA", c.pck.verifying_key(), &stranger, NOW - YEAR, NOW + YEAR);
assert!(verify_pck_chain(&c.root, (pem(&forged_leaf) + &pem(&c.ca)).as_bytes(), NOW).is_err());
let ca_key = SigningKey::from_slice(&[0x32; 32]).unwrap();
let forged_ca = cert("Platform CA", "Root CA", ca_key.verifying_key(), &stranger, NOW - YEAR, NOW + YEAR);
assert!(verify_pck_chain(&c.root, (pem(&c.leaf) + &pem(&forged_ca)).as_bytes(), NOW).is_err());
let misnamed = cert("PCK Certificate", "Processor CA", c.pck.verifying_key(), &ca_key, NOW - YEAR, NOW + YEAR);
let err = verify_pck_chain(&c.root, (pem(&misnamed) + &pem(&c.ca)).as_bytes(), NOW).unwrap_err();
assert!(format!("{err}").contains("issuer"), "{err}");
assert!(verify_pck_chain(&c.root, pem(&c.leaf).as_bytes(), NOW).is_err(), "a chain without its CA");
let long = pem(&c.leaf) + &pem(&c.ca) + &pem(&c.root) + &pem(&c.root);
assert!(verify_pck_chain(&c.root, long.as_bytes(), NOW).is_err(), "a chain longer than leaf, CA and root");
// A certificate declaring another signature algorithm is refused, even
// though its signature still verifies as ECDSA-P256-SHA256.
let mut relabeled = Certificate::from_der(&c.leaf).unwrap();
relabeled.signature_algorithm.oid = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.3");
let relabeled = relabeled.to_der().unwrap();
let err = verify_pck_chain(&c.root, (pem(&relabeled) + &pem(&c.ca)).as_bytes(), NOW).unwrap_err();
assert!(format!("{err}").contains("signed with"), "{err}");
}
/// Every certificate must be valid at the time given.
#[test]
fn a_chain_outside_its_validity_is_refused() {
let c = chain();
let leaf_ca = pem(&c.leaf) + &pem(&c.ca);
assert!(verify_pck_chain(&c.root, leaf_ca.as_bytes(), NOW + 2 * YEAR).is_err(), "expired");
assert!(verify_pck_chain(&c.root, leaf_ca.as_bytes(), NOW - 2 * YEAR).is_err(), "not yet valid");
}
/// The anchor derived from a quote's own chain verifies that quote, and
/// a quote whose chain does not reach the pinned root yields none.
#[test]
fn the_anchor_comes_from_the_quotes_chain_checked_to_the_root() {
let c = chain();
let ak = SigningKey::from_slice(&[3u8; 32]).unwrap();
let chain_pem = pem(&c.leaf) + &pem(&c.ca) + &pem(&c.root);
let quote = crate::tdx::local_quote(&ak, &c.pck, [0xAA; 48], [0x11; 64], false, Some(chain_pem.as_bytes()));
let pck = pck_from_quote(&quote, &c.root, NOW).unwrap();
let policy = crate::policy::TeePolicy::tdx_baseline().with_report_data([0x11; 64]);
crate::tdx::verify_tdx(&quote, &policy, &pck).unwrap();
assert!(pck_from_quote(&quote, &chain_from(0x41).root, NOW).is_err());
}
/// Another chain of the same shape, from other keys.
fn chain_from(seed: u8) -> Chain {
let root_key = SigningKey::from_slice(&[seed; 32]).unwrap();
let ca_key = SigningKey::from_slice(&[seed + 1; 32]).unwrap();
let pck = SigningKey::from_slice(&[seed + 2; 32]).unwrap();
let (from, until) = (NOW - YEAR, NOW + YEAR);
Chain {
root: cert("Root CA", "Root CA", root_key.verifying_key(), &root_key, from, until),
ca: cert("Platform CA", "Root CA", ca_key.verifying_key(), &root_key, from, until),
leaf: cert("PCK Certificate", "Platform CA", pck.verifying_key(), &ca_key, from, until),
pck,
}
}
}

View file

@ -17,6 +17,8 @@
#[cfg(feature = "amd-kds")]
pub mod amd_kds;
#[cfg(feature = "intel-pcs")]
pub mod intel_pcs;
pub mod chain;
pub mod energy;
pub mod gpu;

View file

@ -209,7 +209,7 @@ impl AttestationProvider for LocalTdxProvider {
}
fn report(&self, report_data: &[u8; 64]) -> Result<Vec<u8>, TeeError> {
Ok(crate::tdx::local_quote(&self.attestation_key, &self.pck, self.mr_td, *report_data, false))
Ok(crate::tdx::local_quote(&self.attestation_key, &self.pck, self.mr_td, *report_data, false, None))
}
}

View file

@ -88,6 +88,14 @@ pub struct TdxQuote {
pck_chain: Vec<u8>,
}
impl TdxQuote {
/// The certification data the quote carries for its PCK: a PEM chain, the
/// PCK leaf first, for [`crate::intel_pcs::verify_pck_chain`].
pub fn pck_chain(&self) -> &[u8] {
&self.pck_chain
}
}
fn arr<const N: usize>(b: &[u8], off: usize) -> Result<[u8; N], TeeError> {
b.get(off..off + N)
.and_then(|s| s.try_into().ok())
@ -290,6 +298,7 @@ pub(crate) fn local_quote(
mr_td: [u8; 48],
report_data: [u8; 64],
debug: bool,
certification: Option<&[u8]>,
) -> Vec<u8> {
let ak_xy = ak.verifying_key().to_encoded_point(false).as_bytes()[1..].to_vec();
let pck_xy = pck.verifying_key().to_encoded_point(false).as_bytes()[1..].to_vec();
@ -322,9 +331,16 @@ pub(crate) fn local_quote(
let qe_sig: Signature = pck.sign(&qe);
let qe_sig_b = qe_sig.to_bytes();
// PCK certification data: a minimal DER-ish blob carrying the point.
let mut pck_blob = vec![0x30u8, 0x00, 0x03, 0x42, 0x00, 0x04];
pck_blob.extend_from_slice(&pck_xy);
// PCK certification data: the chain given, or a minimal DER-ish blob
// carrying the point.
let pck_blob = match certification {
Some(chain) => chain.to_vec(),
None => {
let mut blob = vec![0x30u8, 0x00, 0x03, 0x42, 0x00, 0x04];
blob.extend_from_slice(&pck_xy);
blob
}
};
// Assemble signature section.
let mut sigsec = Vec::new();
@ -356,7 +372,7 @@ mod tests {
fn synthetic_quote(mr_td: [u8; 48], report_data: [u8; 64], debug: bool) -> Vec<u8> {
let (ak, pck) = dev_keys();
local_quote(&ak, &pck, mr_td, report_data, debug)
local_quote(&ak, &pck, mr_td, report_data, debug, None)
}
fn dev_pck() -> VerifyingKey {
@ -417,7 +433,7 @@ mod tests {
fn a_quote_endorsed_by_its_own_pck_is_refused() {
let (ak, _) = dev_keys();
let forger = P256SigningKey::from_slice(&[9u8; 32]).unwrap();
let forged = local_quote(&ak, &forger, [0xAA; 48], [0x11; 64], false);
let forged = local_quote(&ak, &forger, [0xAA; 48], [0x11; 64], false, None);
let own = *forger.verifying_key();
verify_tdx(&forged, &baseline(), &own).expect("consistent with itself");
let err = verify_tdx(&forged, &baseline(), &dev_pck()).unwrap_err();