create-evda 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,12 +23,19 @@
23
23
  // 8. the nonce is one this phone issued, unused, and young enough;
24
24
  // 9. REPORTDATA equals reportDataFor(nonce, tlsPublicKey), which binds the quote to the TLS key the
25
25
  // phone then pins.
26
+ // 10. when the VM names a key-wrapping key (lib/attested-wrap.mjs), REPORTDATA also binds it, so the
27
+ // key a client wraps circle keys to is one the attested VM made, not one the host operator chose.
26
28
  //
27
29
  // The binding, exactly: REPORTDATA (64 bytes) = SHA-512( "EVDA-TDX-REPORTDATA-v1" || 0x00 ||
28
30
  // nonce (32 bytes) || SPKI DER of the TLS public key ). The nonce has a fixed length and SPKI DER
29
31
  // carries its own length, so the concatenation is unambiguous. The VM computes the same value with
30
32
  // the same function when it asks the TDX module for a quote.
31
33
  //
34
+ // With a key-wrapping key, REPORTDATA = SHA-512( "EVDA-TDX-REPORTDATA-v2" || 0x00 || nonce (32 bytes) ||
35
+ // SPKI DER of the TLS public key || the X25519 wrap public key (32 raw bytes) ). The wrap key has a fixed
36
+ // length and comes last, after a self-delimiting SPKI, and the tag differs, so neither form can be read
37
+ // as the other.
38
+ //
32
39
  // Not checked here yet: certificate revocation (Intel's PCK and root CRLs), quote v5 (refused as
33
40
  // unknown rather than half-checked), the TD relaunch advice v5 quotes carry, and whether the expected
34
41
  // measurements are themselves in Sigstore's log (lib/measurement-log.mjs checks a digest; tying the
@@ -71,11 +78,20 @@ const spkiDer = (key) => {
71
78
  return createPublicKey(key).export({ type: "spki", format: "der" });
72
79
  };
73
80
 
74
- /** The 64 bytes a TD must put in REPORTDATA to bind `nonce` (32 bytes) and `tlsPublicKey` to its quote. */
75
- export function reportDataFor(nonce, tlsPublicKey) {
81
+ /** The domain tag when REPORTDATA also binds the VM's key-wrapping key. */
82
+ export const REPORTDATA_WRAP_TAG = "EVDA-TDX-REPORTDATA-v2";
83
+
84
+ /**
85
+ * The 64 bytes a TD must put in REPORTDATA to bind `nonce` (32 bytes) and `tlsPublicKey` to its quote,
86
+ * and, when given, `wrapPublicKey` (the raw 32-byte X25519 key the VM made inside itself).
87
+ */
88
+ export function reportDataFor(nonce, tlsPublicKey, wrapPublicKey) {
76
89
  const n = Buffer.from(nonce);
77
90
  if (n.length !== 32) throw new Error("the nonce must be 32 bytes");
78
- return createHash("sha512").update(Buffer.concat([Buffer.from(REPORTDATA_TAG, "ascii"), Buffer.from([0]), n, spkiDer(tlsPublicKey)])).digest();
91
+ if (wrapPublicKey == null) return createHash("sha512").update(Buffer.concat([Buffer.from(REPORTDATA_TAG, "ascii"), Buffer.from([0]), n, spkiDer(tlsPublicKey)])).digest();
92
+ const w = Buffer.from(wrapPublicKey);
93
+ if (w.length !== 32) throw new Error("the wrap key must be 32 bytes");
94
+ return createHash("sha512").update(Buffer.concat([Buffer.from(REPORTDATA_WRAP_TAG, "ascii"), Buffer.from([0]), n, spkiDer(tlsPublicKey), w])).digest();
79
95
  }
80
96
 
81
97
  /**
@@ -381,8 +397,10 @@ function checkQuote({ quote, collateral, minTcb, now, trust }) {
381
397
  * nonce: the 32 bytes the phone sent; nonces: the createNonceBook that issued it (spent here, pass or fail).
382
398
  * tlsPublicKey: the TLS server key of this connection (KeyObject, PEM, or SPKI DER).
383
399
  * expected: { mrtd, rtmr0, rtmr1, rtmr2, rtmr3 } as hex. Every one must be named.
400
+ * wrapPublicKey: the VM's key-wrapping key as the host named it (32 raw bytes), or absent. When given,
401
+ * the quote must bind it too, and the result names it.
384
402
  */
385
- export function verifyTdxAttestation({ quote, nonce, nonces, tlsPublicKey, expected, minTcb, collateral, now, trust = TDX_TRUST } = {}) {
403
+ export function verifyTdxAttestation({ quote, nonce, nonces, tlsPublicKey, wrapPublicKey, expected, minTcb, collateral, now, trust = TDX_TRUST } = {}) {
386
404
  try {
387
405
  if (!nonces?.take) return fail("no nonce book was given, so freshness cannot be checked");
388
406
  const at = now === undefined ? undefined : new Date(now).getTime();
@@ -400,12 +418,14 @@ export function verifyTdxAttestation({ quote, nonce, nonces, tlsPublicKey, expec
400
418
  // 7. The code is the code expected.
401
419
  for (const n of MEASUREMENT_NAMES) if (r.body[n] !== expected[n]) return fail(`${n.toUpperCase()} is ${r.body[n].slice(0, 16)}..., not the expected ${expected[n].slice(0, 16)}...`);
402
420
 
403
- // 9. The quote is for this nonce and this TLS key.
404
- const want = reportDataFor(nonce, tlsPublicKey);
405
- if (!timingSafeEqual(want, r.body.reportData)) return fail("REPORTDATA does not bind this nonce and this TLS key");
421
+ // 9 and 10. The quote is for this nonce, this TLS key and, when named, this wrap key.
422
+ const want = reportDataFor(nonce, tlsPublicKey, wrapPublicKey);
423
+ if (!timingSafeEqual(want, r.body.reportData)) return fail(wrapPublicKey == null ? "REPORTDATA does not bind this nonce and this TLS key" : "REPORTDATA does not bind this nonce, this TLS key and this wrap key");
406
424
 
407
425
  const measurements = Object.fromEntries(MEASUREMENT_NAMES.map((n) => [n, r.body[n]]));
408
- return { ok: true, measurements, tcb: r.tcb, tlsPublicKeySha256: sha256(spkiDer(tlsPublicKey)).toString("hex") };
426
+ const out = { ok: true, measurements, tcb: r.tcb, tlsPublicKeySha256: sha256(spkiDer(tlsPublicKey)).toString("hex") };
427
+ if (wrapPublicKey != null) out.wrapPublicKey = Buffer.from(wrapPublicKey);
428
+ return out;
409
429
  } catch (err) {
410
430
  return fail(`the attestation could not be checked: ${err.message}`);
411
431
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-evda",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "type": "module",
5
5
  "description": "The person layer over the substrate: scaffold and operate an EVDA, a directory that knows what it is. Plain files are the truth, the tooling is optional, and there are no runtime dependencies.",
6
6
  "bin": {