TEE trust
A hosted relay, a TEE replica or an agent gate runs inside an Intel TDX trust domain (TD). Before
the app sends one anything private, or signs a login that names its key, the app checks the TD’s
quote itself, on the phone. A cloud API or TLS certificate does not stand in for that check.
This page covers the trust pieces in com.calimero.mero.tee and the relay’s node key. They are a
port of mero-js src/sealed/ and src/relay-attestation/, and they use the same fixtures.
Every check here throws TeeTrustException with the reason. A refusal is final, so retrying cannot
turn it into a yes.
What a quote proves
Section titled “What a quote proves”A TDX quote proves what runs in a TD only after four things check out:
- Its signature chain. The quote chains to Intel’s SGX root CA, with collateral (TCB info, QE identity and CRLs) that is valid now.
- The platform’s TCB status. It must be
UpToDateunless you allow more.Revokedis never accepted. - The image’s measurements. All five registers (MRTD and RTMR0–3) must be those of an image you trust. On GCP the MRTD only measures the shared TD firmware. The image itself is in RTMR1–3.
- Its report data. It must hold the nonce you sent, followed by the binding you computed: a transport key, a node key, or an agent’s keys.
The SDK checks all four with nothing but the node. The node sends the Intel-signed collateral along
with its quote (includeCollateral). A node cannot forge collateral. It can only choose which valid
collateral to serve.
DCAP on the device: Dcap
Section titled “DCAP on the device: Dcap”Dcap verifies quotes with no third-party library. It is a port of verify from
@phala/dcap-qvl 0.5.3, the library mero-js is handed. It makes the same checks in the same order,
with the same messages:
val report: DcapVerifiedReport = Dcap.verify(quoteBytes, collateral, nowSecs)report.status // "UpToDate", …report.report.td10 // mrTd, rtMr0…rtMr3, reportData, tdAttributes- The CRLs are authenticated before any revocation answer they give is used.
- Every certificate in every chain is checked against Intel’s root and those CRLs.
- The TCB info and the QE identity must be signed and current.
- The QE report must be signed by the PCK key and bound to the attestation key.
- The quote must be signed by the attestation key.
- The platform’s TCB level is matched and merged with the QE’s.
- Debug enclaves and debug TDs are refused, and so are TDs with reserved attribute bits set.
DcapCollateral.fromJson reads the collateral in dcap-qvl’s form, the form
/admin-api/tee/attest returns. PccsCollateral.fetch(quote) gets collateral from a PCCS for a
quote whose sender includes none, such as an agent gate.
DcapVerify is a fun interface. Pass your own implementation wherever a verifier takes
dcapVerify.
Which images: signed releases
Section titled “Which images: signed releases”Each mero-tee node release publishes published-mrtds.json (the measurements of each image
profile) with a cosign keyless bundle, signed by the release workflow on GitHub Actions. The SDK
verifies that signature against the Sigstore trust root embedded in it (FULCIO_AUTHORITIES,
REKOR_LOGS). So the file can come from anywhere, and the server it came from is never trusted:
val version = fetchNodeReleaseVersion(relayUrl) // from GET /admin-api/tee/info (a claim)val release = fetchNodeRelease(cloudNodeReleaseUrl(DEFAULT_RELEASE_MIRROR, version))val trusted = trustSignedRelease(release, PRODUCTION_PROFILE, minReleaseVersion = "2.3.99")val verifier = trusted.verifier() // a QuoteVerifier over exactly that imageThe verification passes only if all of these hold (verifySignedNodeRelease):
- The signature covers the exact bytes.
- The Fulcio certificate chains to the trust root and was valid when Rekor logged it.
- It names
NODE_RELEASE_SIGNER. - Rekor’s signed entry timestamp verifies, for an entry that is this signature over this file.
- The file is a node release that lists measured profiles.
Nothing reads the clock, so a release that verifies once always does. minReleaseVersion is
required. Every release ever signed stays validly signed, so without a floor a relay could present
an old release with a flaw since fixed.
PRODUCTION_PROFILE is locked-read-only, the only image with no shell. The debug profiles need
allowDebugProfile = true, and are for testing only.
createSignedReleaseVerifier(SignedReleaseVerifierOptions(release = { … }, minReleaseVersion))
fetches the release on each attestation. A node that restarted may run a newer one.
Quote verifiers
Section titled “Quote verifiers”A VerifyTransportQuote answers whether a quote is genuine, is one you trust, and carries
nonce ‖ reportDataSuffix. createQuoteVerifier builds the on-device one:
val verifier = createQuoteVerifier( QuoteVerifierOptions( allowedMeasurements = trustedMeasurementsFromReleases(listOf(published), PRODUCTION_PROFILE).allowedMeasurements, allowedTcbStatuses = listOf("UpToDate"), ), )allowedMeasurements names whole images, so the registers of two images never combine.
allowedMrtd is accepted only together with allowedRtmr1–allowedRtmr3, because an MRTD alone
does not name an image. A QuoteVerifier asks the node for collateral (includeCollateral). A
verifier that returns false or throws refuses the quote.
The relay’s node key: DcapRelayKeyVerifier
Section titled “The relay’s node key: DcapRelayKeyVerifier”A login statement names the relay node’s key. The key never comes from the relay’s own answer,
because a quote cannot carry a key the TD did not bind. DcapRelayKeyVerifier is the default
RelayKeyVerifier of CloudAccount and RelayLogin. It is the check mero-js’s delegated session
runs:
- the quote is verified with
Dcap, against the collateral the relay sends with it; - its image must be the
locked-read-onlyprofile of the signed release the relay names, atMIN_RELAY_RELEASE(2.3.99) or newer; - its report data must be our nonce, followed by
SHA-256("calimero.tee-attest.key-binding.v1" ‖ 0^32 ‖ nodeKey)(attestKeyBinding), read out of the raw quote bytes (reportDataOf).
val account = CloudAccount(store) // DcapRelayKeyVerifier() by defaultwhen (val attempt = account.attemptRelayNodeKey(relayUrl)) { is RelayNodeKeyAttempt.Learned -> attempt.nodeKey is RelayNodeKeyAttempt.Unavailable -> retryLater() // e.g. the release mirror did not answer is RelayNodeKeyAttempt.Refused -> showProblem() // the quote did not hold}A step that only moves bytes can fail: reading /tee/info or fetching the release. Such a failure
is a RelayKeyTransportException, so the attempt is Unavailable and worth repeating. Any other
failure is the verification saying no.
A mock quote (MOCK_TDX_QUOTE_V1) is accepted only from a loopback relay (a dev rig: localhost,
127.0.0.1, ::1 or the emulator’s 10.0.2.2), or with DcapRelayKeyVerifier(allowMock = true).
attestRelayNodeKey(relayUrl, verify = …) runs the same checks without a CloudAccount.
Once the relay’s key is trusted, the requests themselves can be sealed to the TD: Sealed transport. For agents in a private TD, see Agent gate.