Skip to content
Customize

Acceptance policies, TPM classes & assurance tiers

You decide which kinds of device you trust and how strongly the user must be authenticated. That decision is a named acceptance policy: pick a built-in, or author your own. The default is conservative: hardware-rooted TPMs only.

TPM classes: what kind of device is this?

Every enrolled device is classified into one root-of-trust class, derived from its hardware key's certificate (the EK cert issuer + the TCG manufacturer identifier) or, for mobile, from the platform attestation. Classes roll up into groups you author policy against:

GroupClassesWhat the cert proves
hardwarehardware-discrete-infineon · -st-micro · -nuvoton · -otherDiscrete silicon, signed by the TPM vendor's PKI. Highest.
firmware-tpmfirmware-tpm-intel-ptt · -amd-ftpm · -plutonFirmware TPM with a manufacturer chain (Intel PTT, AMD fTPM, Microsoft Pluton). High.
cloud-vtpmcloud-vtpm-aws-nitro · -azure · -gcp · -hyper-vA virtual TPM in a VM under that cloud account, not a physical chip. Medium; rejected by default.
emulatedemulated-swtpmA software TPM emulator. No external trust anchor. Rejected by default.
mobile-hardwaremobile-android-strongbox · -tee · mobile-apple-secure-enclave · mobile-apple-app-attestHardware-rooted mobile attestation (StrongBox / TEE / Secure Enclave / App Attest). High.
mobile-softwaremobile-android-softwareSoftware-level attestation only. No hardware backing. Rejected by default.

Tokens carry the device's class as tpm_class (kebab-cased, e.g. hardware-discrete-infineon), so you can branch on it in your own code even when the policy accepted it.

Note
Why a cloud vTPM isn't "hardware".AWS NitroTPM, Azure vTPM and GCP Shielded VM vTPM are real TPM 2.0 implementations doing real crypto. But the certificate proves "a virtual TPM in an instance under this cloud account": an identity an attacker mints with one API call, for pennies, at scale. If your problem is "one real machine = one identity" (Sybil, smurfing, free-tier farming), you want these rejected. If you're attesting your own cloud workloads, accept them, but require cross-validation (below).

Built-in policies: the easy path

You don't have to author anything. Select a built-in by name from the dashboard, pin it to an API key, or name it per-call as the policy argument to rh.verify(). The appraisal is run against it server→server, so the verdict you get back already reflects it.

NameAcceptsNotes
rootherald:builtin:strict-hardwarehardware, firmware-tpm, mobile-hardwareThe default. Requires device:high and full boot-chain verification. Rejects all virtual/emulated TPMs.
rootherald:builtin:strict-hardware-permissive-mobilesame, incl. Android TEE & macOS Secure EnclaveNo device:high floor. For consumer surfaces where StrongBox isn't universal.
rootherald:builtin:cloud-permissive+ cloud-vtpm (cross-validation required)For B2B customers serving endpoints and cloud workloads.
rootherald:builtin:enterprise-managed-onlyhardware, firmware-tpm only; device:highPair with EK pre-registration: only IT-provisioned devices enrol. Strongest stance against relayed attestations.
rootherald:builtin:deveverything, incl. emulated-swtpmDevelopment only. Always carries at least a warn verdict.

Custom policies: the coarse form

Need something between the built-ins? Register a named policy (you get back a UUID you reference afterwards; the policy lives server-side, you don't ship a rule blob in every request). The coarse form is a few fields:

POST /api/v1/admin/policiests
// Authorization: Bearer rh_sk_live_…   (Owner role)
{
  "name": "My app — accept hardware + Nitro",
  "description": "Hardware and firmware TPMs everywhere; AWS NitroTPM for our CI runners.",
  "acceptedClassGroups": ["hardware", "firmware-tpm", "mobile-hardware"],
  "acceptedSpecificClasses": ["cloud-vtpm-aws-nitro"],
  "unacceptedBehavior": "reject",          // "reject" | "warn"
  "requireCloudCrossValidation": true,
  "minAcr": "urn:rootherald:device:high",
  "minimumEarStatus": "affirming",
  "mode": "strict"
}
// → 201 Created (PolicyDetail):
// {
//   "id": "8f3a…",
//   "name": "My app — accept hardware + Nitro",
//   "isBuiltIn": false,
//   "version": 1,
//   "acceptedClassGroups": ["hardware", "firmware-tpm", "mobile-hardware"],
//   "acceptedSpecificClasses": ["cloud-vtpm-aws-nitro"],
//   "unacceptedBehavior": "reject",
//   "requireCloudCrossValidation": true,
//   "minAcr": "urn:rootherald:device:high",
//   "mode": "strict"
// }

acceptedClassGroups takes group names (hardware, cloud-vtpm, …); acceptedSpecificClasses pins individual vendor classes (e.g. cloud-vtpm-aws-nitro). Anything outside the accepted set is governed by unacceptedBehavior: "warn" lets out-of-set devices through with a warn verdict and a degraded trustworthiness vector (useful for shadow-banning or review queues), while "reject" blocks them outright.

Note
Advanced (Rego). A fine-grained tier is on the roadmap: arbitrary predicates over the full evidence (PCR patterns, event-log shape, EK issuer DN) expressed in OPA/Rego. The coarse fields above are authoritative today; Rego evaluation is deferred and not accepted on the create call yet.

Assurance tiers (ACR): how strongly is the user authenticated?

A separate axis from TPM class. The acr value says what the authenticationachieved. Root Herald treats a requested tier as a hard requirement: if it can't be satisfied, the attestation does not issue a token at that tier, never a silently-weaker result. A policy may also impose an ACR floor (minAcr).

URNMeansNIST AAL
urn:rootherald:device:anyAttested device, no user identity
urn:rootherald:device:highAttested device + verified boot chain + event-log replay
urn:rootherald:user:1faDevice + passwordAAL1
urn:rootherald:user:2faDevice + user + any second factorAAL2
urn:rootherald:user:phrDevice + user + phishing-resistant MFA (passkey)AAL2+
urn:rootherald:user:phrhDevice + user + hardware-bound phishing-resistant MFAAAL3
urn:rootherald:user:phrh:freshphrh + auth within the last 60 sAAL3 + freshness

Higher tiers subsume lower ones. The acr claim in the issued token is the single tier that was actually satisfied, never the preference list, never a partial match.

Cloud vTPMs & cross-validation

If your policy accepts a cloud-vtpm class and requireCloudCrossValidationis on (the default), the device must additionally submit cloud-provider instance-identity evidence so the verifier can bind "this virtual TPM" to "this specific instance, in this account": the defence against relaying a vTPM attestation from a different instance.

The cloud evidence is bundled into the opaque blob the dumb client collects. Your server submits it with rh.verify() (naming a cloud-cross-validating policy) and reads the resulting device verdict returned directly.

AWS cross-validation evidence (attestation request)ts
cloudEvidence: {
  provider: "aws",
  nitroAttestationDocument: body.nitroDoc,        // CBOR/COSE, from the Nitro hypervisor
  ec2InstanceIdentityDocument: body.iid,          // from IMDSv2
  ec2InstanceIdentitySignature: body.iidSig,
  getInstanceTpmEkPubResult: body.ekPub,          // from the EC2 GetInstanceTpmEkPub API
}

Azure (provider: "azure", IMDS attested document) and GCP (provider: "gcp", GCE instance identity token) follow the same pattern. The verifier checks the cloud doc chains to that cloud's root, the EK matches what was enrolled, and the instance/account/region are what you expect.

Heads up
Honest status. The wire shape is stable, but the server-side validators for the cloud documents are still landing. Until they do, presenting cloud evidence sets the class to a cloud-vtpm class and (under cloud-permissive / dev) the attestation proceeds with a warn verdict noting cross-validation was not performed. Under any strict policy, cloud classes are rejected regardless.

For server/workload attestation generally, also consider cloud-native primitives (AWS Nitro Enclaves attestation documents, SPIFFE/SPIRE, cloud workload identity). A hardware TPM is the wrong tool for "is this container the workload I expect?". Root Herald is strongest for endpoint devices.