Blog

How to Verify Your Application with MR-CONFIG-ID

Jun 04, 20265 min read
How to Verify Your Application with MR-CONFIG-ID
Level: Intermediate  |  Prerequisites: Familiarity with TEE attestation, TDX quotes, and SHA256 hashing  |  Code: Python

Introduction

When you deploy an application inside a dstack Confidential VM (CVM), you verify that it runs your intended configuration by comparing the hardware-signed TDX quote to a known reference value. If you have been using RTMR3 event log replay for this, there is a simpler approach: MR-CONFIG-ID, a 48-byte field set directly in the TDX quote, designed specifically for application identity verification.


What is MR-CONFIG-ID?

MR-CONFIG-ID is the mrconfigid field in the Intel TDX quote — a 48-byte value set by QEMU before the CVM boots. The guest cannot modify it.

It comes in two formats:

V1 — Compose hash only

Used when there is no KMS binding (key_provider_id is empty):

Byte 0    1                                33              48
     ┌────┬────────────────────────────────┬──────────────────┐
0x01│   compose_hash (SHA-256, 32B)  │  padding (00×15) │
     └────┴────────────────────────────────┴──────────────────┘

V2 — Full configuration binding

Used when a KMS is involved:

Byte 0    1                                33              48
     ┌────┬────────────────────────────────┬──────────────────┐
0x02│  Keccak256(compose_hash ||      │  padding (00×15) │
     │    │    app_id || kp_type || kp_id)  │                  │
     └────┴────────────────────────────────┴──────────────────┘

Where:

  • compose_hash = SHA-256 of app-compose.json
  • app_id = 20-byte application identifier
  • kp_type = key provider type: 0 = none, 1 = local SGX, 2 = KMS, 3 = TPM
  • kp_id = key provider identity (KMS root CA public key, SGX MR enclave measurement, or empty)

Why not just use RTMR3?

RTMR3 contains the full boot transcript: ten events extended by the guest agent during startup. Here is what is in there:

#EventPayloadStable as a reference value?
1system-preparingEmpty marker
2app-id20-byte app identifier✅ (if config unchanged)
3compose-hashSHA-256 of app-compose.json✅ (if config unchanged)
4instance-idSHA256(seed || app_id), seed generated on first boot and persisted✅ (if config unchanged)
5boot-mr-doneEmpty marker
6mr-kmsKMS measurement returned at runtime❌ (varies per boot: KMS probe)
7os-image-hashOS image hash from KMS✅ (if config unchanged)
8key-providerJSON: KMS type + identity✅ (if config unchanged)
9storage-fs"zfs" or "ext4"✅ (if config unchanged)
10system-readyEmpty marker

Event #6, mr-kms, makes RTMR3 unsuitable as a stable reference value. The CVM probes multiple KMS instances at startup and records the measurement of whichever one it connects to. This value changes across boots even when your application configuration is identical. Because of this, you cannot pre-compute a reference value for RTMR3 from your application manifest. Your verifier must receive the full event log, replay all ten events to confirm integrity, then extract and check each field individually.


Measurement vs Reference Value

A quick note on terminology used throughout this tutorial:

TermMeaning
MeasurementThe value returned by the hardware — e.g. RTMR3 as quoted in the TDX quote, or mrconfigid from the quote
Reference valueThe known good value you compute offline — what the measurement should be if the CVM is running your expected configuration

Verification means comparing the measurement to your reference value. MR-CONFIG-ID lets you do this in one step.


Verification flow

With RTMR3 event log replay

1. Receive the TDX quote + runtime event log from the CVM
2. Verify the quote signature (dcap-qvl, Intel root CA chain)
3. Extract the quoted RTMR3 from the TDREPORT (measurement)
4. Replay all 10 events:
     rt = 0
     for each event:
         digest = SHA384(0x08000001 || ":" || event_name || ":" || payload)
         rt = SHA384(rt || digest)
5. Compare replayed rt to quoted RTMR3 — if match, event log is authentic
6. Extract compose-hash, key-provider from event log; verify each against reference values

With MR-CONFIG-ID

1. Receive the TDX quote from the CVM
2. Verify the quote signature
3. Extract mrconfigid from the quote (measurement), compare to your pre-computed reference value

Implementation

Core verification function

import hashlib


def verify_mr_config_id(
    measurement: bytes,          # 48-byte mrconfigid from TDX quote
    compose_hash: bytes,         # SHA-256 of your app-compose.json
    app_id: bytes,               # 20-byte application identifier
    kp_type: int,                # 0=none, 1=local, 2=kms, 3=tpm
    kp_id: bytes,                # KMS root CA pubkey, SGX MR, or b''
) -> bool:
    """Compare the measurement (mrconfigid from quote) to the reference value."""
    if len(measurement) != 48:
        raise ValueError(f"mrconfigid must be 48 bytes, got {len(measurement)}")

    # Backward compatibility: pre-0.5.2 images set mrconfigid to all zeros
    if measurement == b'\\x00' * 48:
        return verify_via_rtmr3(quote)  # fallback to RTMR3 replay

    version = measurement[0]

    if version == 1:
        reference_value = b'\\x01' + compose_hash + b'\\x00' * 15
    elif version == 2:
        data = compose_hash + app_id + bytes([kp_type]) + kp_id
        keccak = hashlib.sha3_256(data).digest()
        reference_value = b'\\x02' + keccak + b'\\x00' * 15
    else:
        raise ValueError(f"Unknown mr_config_id version: {version}")

    return measurement == reference_value

Extracting mrconfigid from a TDX quote

from dcap_qvl.quote import Quote


def get_mr_config_id(quote_bytes: bytes) -> bytes:
    """Extract the 48-byte mrconfigid measurement from a TDX quote."""
    quote = Quote.parse(quote_bytes)
    return quote.report.as_td10().mr_config_id

Building the reference value

1. compose_hash

The SHA-256 hash of your app-compose.json. If you deploy via Phala Cloud, get it from the provision API:

curl -s -X POST "<https://cloud.phala.network/api/v1/cvms/provision>" \\
  -H "Authorization: Bearer $PHALA_API_TOKEN" \\
  -H "Content-Type: application/json" \\
  -d '{
    "compose_file": {
      "manifest_version": 2,
      "name": "my-app",
      "runner": "docker-compose",
      "docker_compose_file": "...",
      "allowed_envs": ["API_KEY"],
      "kms_enabled": true,
      "gateway_enabled": true
    }
  }' | jq -r '.compose_hash'

If you provide your own pre_launch_script, you can pre-compute it locally:

import hashlib, json
from collections import OrderedDict

def compute_compose_hash(app_compose: dict) -> str:
    d = OrderedDict(sorted(app_compose.items()))
    s = json.dumps(d, separators=(",", ":"), ensure_ascii=False)
    return hashlib.sha256(s.encode("utf-8")).hexdigest()

2. app_id

The 20-byte application identifier, returned in the provision API response as app_id.

3. KMS binding

# KMS backend:
kp_type = 2
kp_id = bytes.fromhex(kms_root_ca_pubkey_hex)

# Local key provider (no external KMS):
kp_type = 1
kp_id = bytes.fromhex(sgx_mr_enclave_hex)

# No key provider:
kp_type = 0
kp_id = b''

Complete end-to-end example

import hashlib
from dcap_qvl.quote import Quote


# ── Reference values (pre-computed, e.g. from CI or provision API) ──
COMPOSE_HASH = bytes.fromhex("a1b2c3d4...")
APP_ID       = bytes.fromhex("deadbeef...")
KP_TYPE      = 2
KP_ID        = bytes.fromhex("feedface...")


def build_reference_value(compose_hash, app_id, kp_type, kp_id):
    data = compose_hash + app_id + bytes([kp_type]) + kp_id
    keccak = hashlib.sha3_256(data).digest()
    return b'\\x02' + keccak + b'\\x00' * 15


REFERENCE = build_reference_value(COMPOSE_HASH, APP_ID, KP_TYPE, KP_ID)


def verify(quote_bytes: bytes) -> bool:
    # (1) Verify DCAP signature chain
    if not dcap_qvl_verify(quote_bytes):
        return False

    # (2) Read the measurement from the quote
    quote = Quote.parse(quote_bytes)
    measurement = quote.report.as_td10().mr_config_id

    # (3) Compare measurement to reference value
    return measurement == REFERENCE


# Usage
attestation_quote = receive_from_cvm()
if verify(attestation_quote):
    print("Application identity verified.")
else:
    print("Verification failed.")

When to use which

ScenarioRTMR3MR-CONFIG-ID
Verify application identity (compose-hash + KMS binding)
Audit the full boot transcript of a specific instance
Pre-compute reference value in CI
Verify across multiple CVM instances with the same config
Instance-level traceability (which instance produced this quote)

Summary

MR-CONFIG-ID is what it says: the configuration identifier set into the hardware measurement. It contains only the information relevant to application identity — compose_hash, app_id, and KMS binding — and nothing else. Because it excludes mr-kms (which varies per boot), the same configuration always produces the same value. You can build the reference value offline from your application manifest and KMS public key, without any interaction with a running CVM.

Recent Posts

Related Posts