Skip to content

Instantly share code, notes, and snippets.

@amiller
Created May 14, 2026 20:35
Show Gist options
  • Select an option

  • Save amiller/361708a41cffaf66a7dcf9216d911963 to your computer and use it in GitHub Desktop.

Select an option

Save amiller/361708a41cffaf66a7dcf9216d911963 to your computer and use it in GitHub Desktop.
RT1180 Sealing Architecture - SGX-style data sealing for embedded MCU

RT1180 Sealing Architecture — Security Evaluation

Scope: rt1180-sealing-architecture.md, src/seal.h, src/seal.c Date: 2026-05-14


1. Attack Vectors Compromising Sealed Data

Finding Severity Mitigation
SRK cached as VolatilePermanent — survives warm reset; attacker can extract key from ELE registers before cold boot HIGH Periodic SRK rotation; watchdog-driven cold-boot path
QSPI NOR flash manipulation — physical attacker reads, modifies, replays sealed blobs; no authenticated flash storage HIGH Add MAC verification; blob versioning; replay-prevention counters in internal flash
Chosen-ciphertext oracle — differentiated error codes (AAD mismatch vs. decrypt fail vs. bad blob) leak which check failed MEDIUM Return uniform error codes for all decryption failures

2. Key Derivation / Binding Weaknesses

Finding Severity Mitigation
Raw SHA-256 instead of HKDF (seal.c:114-136) — seal_key = SHA-256(HAB4 ‖ device_id), no HMAC salt, no Expand step HIGH Implement full HKDF-SHA256 with ELE_Hmac()
Zeroed nonce (seal.c:71) — nonce = {0} always; attestation response is deterministic and replayable HIGH Generate random nonce per call per architecture spec
Silicon rev parsed but excluded from IKM — different silicon revs with same HAB4+device_id derive identical keys MEDIUM Include silicon_rev and firmware_version in IKM
ELE FW version not in key derivation — ELE downgrade doesn't change seal key MEDIUM Include ELE FW version in HKDF context

3. Fault Injection Risks

Finding Severity Mitigation
Glitching HAB4 comparison (seal.c:304) — memcmp short-circuits; voltage/clock glitch can skip mismatch check HIGH Use hash-then-compare; redundant loops; timing check
Glitching ELE internal ops — fault injection on key derivation/AEAD; ELE firmware is fixed/unpatchable MEDIUM Rely on ELE built-in fault detection; software AEAD tag verification
Glitching flash write during seal — corrupted blobs create chosen-ciphertext attack material MEDIUM Write to scratch buffer first; verified readback with CRC/MAC

4. Side-Channel Concerns

Finding Severity Mitigation
Non-constant-time memcmp (seal.c:304, 334) — leaks matching bytes via timing on HAB4 comparison MEDIUM Use secure_compare() or hash-based comparison
Data key in M33 SRAM (seal.c:172) — raw 32-byte key exists in plaintext during ELE import MEDIUM Generate key inside ELE via ELE_GenerateKey(); never export raw material
Variable ELE timing — attestation/derive/encrypt have different latencies leaking which step failed LOW Add fixed delay padding to equalize paths

5. HAB4 Measurement Binding (Firmware Updates)

Finding Severity Mitigation
Any boot image change permanently locks sealed data — SPL, bootloader, app, or monitor update changes HAB4 hashes, new seal key, all data inaccessible HIGH Bind to root-of-trust subset (SPL+bootloader only); implement seal migration during OTA; use HAB4 signing-key identity instead of binary hashes
No versioning or migration — single blob version, no schema evolution path MEDIUM Implement version negotiation; seal_migrate() API

6. M7-to-M33 Attack Surface

Finding Severity Mitigation
Unauthenticated IPC — M7 (non-secure) can call seal/unseal with no request authentication, rate limiting, or validation; enables DoS, chosen-ciphertext attacks, timing side-channels HIGH Per-session MAC on IPC; rate limiting (e.g. 10 unseal/min); validate all message sizes
Plaintext returned via IPC — decrypted data traverses M7-M33 boundary unencrypted MEDIUM Accept as design constraint; encrypt IPC payloads with per-session key

7. ELE Trust Assumptions

Finding Severity Mitigation
Fixed, unpatchable firmware — any ELE vulnerability is permanent; 100-key limit is hard HIGH Monitor NXP advisories; minimize ELE attack surface; plan hardware upgrade path
Opaque key material — M33 cannot verify key quality or RNG health MEDIUM Run ELE self-tests on boot; verify attestation signatures

8. Lifecycle Management Risks

Finding Severity Mitigation
JTAG disable not enforced at runtime — if fuse not blown, JTAG can halt M33 and read all keys from SRAM HIGH Check JTAG fuse at seal_init(); abort if not fused
No graceful lifecycle transition — Field Return makes keystore inaccessible; pre-transition data is lost MEDIUM Pre-transition migration API; additional key surviving lifecycle changes
Key group exhaustion — 100-key max; seal.c never calls ELE_DeleteKey for ephemeral data keys MEDIUM Explicitly delete ephemeral keys; key count monitoring

9. Comparison to Intel SGX Sealing

Aspect Intel SGX RT1180 Gap
Key derivation MRENCLAVE+MRSIGNER via CPU key hierarchy HAB4 regs+device ID via ELE Comparable
Update compatibility Policy flags: MRSIGNER-only binding ignores code changes Full HAB4 binding; any change locks data Critical: RT1180 lacks update policy
TCB versioning TCBSEL forward/backward compatibility None; ELE FW opaque and fixed Gap: No ELE update handling
Key isolation CPU internal hierarchy ELE hardware enclave Comparable (ELE less audited)
Attestation Specified quote signing (ECDSA) Proprietary TLV (272B) SGX more transparent
Fault detection Multiple CPU hardware layers ELE claims monitoring; no details SGX better documented
SW implementation Well-audited; known side channels memcmp timing; raw hash KDF RT1180 has known SW issues

Summary

Severity Count Key Findings
HIGH 9 Raw SHA-256 KDF, zeroed nonce, SRK warm-reset cache, firmware-update data lock, QSPI manipulation, unauthenticated IPC, unpatchable ELE, fault injection on memcmp, JTAG not enforced
MEDIUM 8 Chosen-ciphertext oracle, data key in SRAM, timing side-channel, no seal migration, key group exhaustion, no lifecycle transition, ELE key opacity, FW version not in IKM
LOW 2 Variable ELE timing, HAB4 hash vs full regs in wrap

Bottom line: The cryptographic primitives (AES-256-GCM, ELE enclave) are sound, but the implementation has critical flaws in key derivation (raw hash instead of HKDF, zeroed nonce), the HAB4 binding is too rigid for production firmware updates, and the M7-to-M33 IPC lacks authentication. Address all HIGH findings before production deployment.

RT1180 Sealing — Security Evaluation Response

Date: 2026-05-14 | Scope: All 19 findings (9 HIGH, 8 MEDIUM, 2 LOW)


HIGH SEVERITY

H1: Raw SHA-256 Instead of HKDF (seal.c:114-136)

AGREE / ACCEPT / IMMEDIATE — Replace ELE_Hash() with full HKDF-SHA256 via ELE_Hmac(). Also include silicon_rev + firmware_version in IKM (covers M7).

/* HKDF-Extract: PRK = HMAC-256(salt=0, IKM) */
ele_mac_t mac;
mac.algo = kELE_HmacSha256;
mac.key = (uint32_t)zero_salt; mac.key_size = 32;
mac.message = (uint32_t)ikm; mac.message_size = ikm_len;
mac.mac_result = (uint32_t)prk; mac.mac_size = 32;
ELE_Hmac(S3MU, &mac);
/* HKDF-Expand: T = HMAC-256(PRK, info || 0x01) */
/* ... (mac_expand uses PRK as key) ... */

H2: Zeroed Nonce (seal.c:71, 357)

AGREE / ACCEPT / IMMEDIATE — Replace nonce = {0} with ELE_RngGetRandom():

attestation_nonce_t nonce;
ELE_RngGetRandom(S3MU, (uint8_t*)&nonce, sizeof(nonce));

H3: Firmware Update Locks Sealed Data

AGREE / ACCEPT / PRODUCTION PLANNING — Split binding: only enforce HAB4 reg0 (SPL) + reg1 (bootloader). Add version field to blob header; implement seal_migrate() for v1-to-v2 transition.

#define SEAL_BIND_ROOT_OF_TRUST_REGS 2
/* Compare only reg0+reg1 on unseal; reg2/3 logged but not enforced */

H4: SRK Cached as VolatilePermanent

AGREE / ACCEPT / IMMEDIATE — Add SRK age tracking; force re-derive after configurable timeout (e.g. 1h); clear on security violation via watchdog callback.

void seal_security_violation_handler(void) {
    ELE_DeleteKey(S3MU, SEAL_KEY_GROUP, SEAL_SRK_HANDLE);
    SEC_WDOG_TriggerReset();
}

H5: Unauthenticated M7-to-M33 IPC

AGREE / ACCEPT / IMMEDIATE — Per-session MAC on IPC requests; rate limit (10 unseal/min); validate all message sizes.

status_t ipc_verify_request(ipc_msg_t *msg) {
    /* HMAC-SHA256 verify over {nonce || cmd || payload} */
    if (memcmp(msg->mac, expected_mac, 16) != 0)
        return kStatus_InvalidArgument;
    /* Rate limit unseal */
    if (msg->cmd == IPC_CMD_UNSEAL && unseal_count >= 10)
        return kStatus_RetryLater;
}

H6: Fault Injection on memcmp (seal.c:304)

AGREE / ACCEPT / IMMEDIATE — Hash-then-compare with redundant loop:

status_t seal_verify_hab4(const uint8_t a[128], const uint8_t b[128]) {
    uint8_t ha[32], hb[32];
    /* ELE_Hash(a) → ha; ELE_Hash(b) → hb */
    /* Compare hashes, then redundant byte-by-byte XOR in loop */
    uint32_t diff = 0;
    for (uint32_t i = 0; i < 128; i++) diff |= a[i] ^ b[i];
    return diff ? kStatus_SecurityViolation : kStatus_Success;
}

H7: Fixed, Unpatchable ELE Firmware

AGREE / ACCEPT AS DESIGN CONSTRAINT / ONGOING — Mitigate: call ELE_DeleteKey() for every ephemeral key; run ELE_SelfTest() on boot; track NXP advisories; plan HW upgrade path.

H8: JTAG Disable Not Enforced at Runtime

AGREE / ACCEPT / IMMEDIATE — Check fuse in seal_init():

uint32_t jtag_fuse = OCOTP_ReadFuseWord(OCOTP, kOCOTP_JTAG_DISABLE_INDEX);
if (!(jtag_fuse & kOCOTP_JTAG_DISABLED_MASK))
    return kStatus_SecurityViolation; /* refuse to operate */

H9: QSPI NOR Flash Accessible

AGREE / ACCEPT / PRODUCTION PLANNING — Append HMAC-SHA256 MAC to every blob; maintain replay-prevention counter in internal flash; store high-value blobs (OTA sigs, MAC keys) in internal flash only.


MEDIUM SEVERITY

M1: Chosen-Ciphertext Oracle — AGREE / ACCEPT / IMMEDIATE

Unify all decryption-path errors to kStatus_SecurityViolation (magic fail, HAB4 mismatch, AEAD fail all map to same code).

M2: Data Key in M33 SRAM (seal.c:172) — AGREE / ACCEPT / IMMEDIATE

Replace ELE_GetRandom() + import with ELE_GenerateKey() — key material never touches M33 SRAM.

M3: Non-Constant-Time memcmpAGREE / ACCEPT / IMMEDIATE

Covered by H6 fix (hash-then-compare replaces all HAB4 memcmp).

M4: Key Group Exhaustion — AGREE / ACCEPT / IMMEDIATE

Call ELE_DeleteKey() for every ephemeral data key after seal/unseal; monitor key count, warn at 90/100.

M5: No Seal Versioning/Migration — AGREE / ACCEPT / PRODUCTION

Covered by H3 fix (version field + seal_migrate() API).

M6: No Graceful Lifecycle Transition — AGREE / ACCEPT / PRODUCTION

Use kKeylifecycle_Closed_Locked; add seal_prepare_lifecycle_transition() to migrate data before lifecycle change.

M7: ELE FW Version / Silicon Rev Not in IKM — AGREE / ACCEPT / IMMEDIATE

Covered by H1 fix (extend IKM to include silicon_rev || firmware_version).

M8: Opaque Key Material — AGREE / ACCEPT AS DESIGN CONSTRAINT / ONGOING

Run ELE_SelfTest() on boot; verify attestation signatures; monitor NXP advisories.


LOW SEVERITY

L1: Variable ELE Timing — AGREE / ACCEPT AS DESIGN CONSTRAINT

Add fixed delay padding to equalize paths; document in threat model. Low priority.

L2: HAB4 Hash vs Full Regs in Wrap — AGREE / ACCEPT AS DESIGN CONSTRAINT

AEAD AAD already covers full 128B HAB4 registers. Hash in wrap payload is secondary. No action needed.


Summary

Priority Findings Count
IMMEDIATE H1,H2,H4,H5,H6 + M1,M2,M3,M4,M7 10
PRODUCTION PLANNING H3,H9 + M5,M6 4
DESIGN CONSTRAINT H7,M8,L1,L2 4

Bottom line: All 9 HIGH findings are valid. 8 fixable via code changes; 1 (ELE firmware) accepted as HW constraint. Immediate fixes have no blockers. Split binding (H3) requires migration tooling for production.

RT1180 Sealing Architecture — SGX-Style Data Sealing for Embedded MCU

Overview

Sealing architecture for the NXP i.MX RT1180 (M33 Secure / M7 Non-Secure, EdgeLock Enclave v0.1.1, HAB4) that binds persistent data encryption keys to HAB4 boot measurements + ELE device identity. Sealed data decrypts only on the exact device with the exact boot chain; any boot image change permanently locks the data.


1. Key Derivation Hierarchy

ELE Internal Root (NRK) — never leaves ELE ROM
         │
  ELE_DeriveKey(kKeyType_DERIVE, context)
  context = HAB4_reg0‖reg1‖reg2‖reg3‖attest_resp‖purpose
         │
  Seal Root Key (SRK) — AES-256, PersistentPermanent,
                        usage: Encrypt|Decrypt|Derive
         │
  Data Keys — AES-128/256, Volatile, from SRK + store_id

SRK derivation uses ELE_DeriveKey() with kKeyType_DERIVE (256-bit). Context concatenates all four HAB4 measurement registers (128 B), attestation response (272 B), a policy version constant, and "RT1180_SEAL" purpose string. Data keys are transient — never leave ELE.


2. Sealing Flow

Seal (Write)

  1. M33 calls HAB4_GetStatus() — must return success, else abort.
  2. HAB4_GetMeasurementReg(0..3, buf) — collect SPL, bootloader, app, monitor SHA-256 hashes (32 B each).
  3. ELE_RngGetRandom() → 4-word nonce. ELE_Attest(S3MU, nonce, response) → 272-byte device identity response.
  4. Derive SRK: ELE_DeriveKey() with context = measurements ‖ response ‖ policy ‖ purpose.
  5. Derive data key from SRK + store_id, or generate fresh.
  6. Encrypt payload: ELE_AeadEncrypt() (AES-CCM or AES-GCM).
  7. Write sealed blob: {policy_ver(4B) | nonce(16B) | attest_resp(272B) | aad=HAB4_hashes(128B) | ct+iv+mac} to flash.
  8. SRK/data key stay inside ELE; only ciphertext is persisted.

Unseal (Read)

  1. HAB4_GetStatus() — must pass.
  2. Read current HAB4 measurements.
  3. Load sealed blob; compare AAD measurements against live measurements. Mismatch → abort (boot chain changed).
  4. ELE_Attest() with stored nonce.
  5. Re-derive SRK with live measurements + new attestation response.
  6. Re-derive data key from SRK + store_id.
  7. ELE_AeadDecrypt() — AEAD auth covers AAD (HAB4 hashes), providing cryptographic binding.
  8. Return plaintext to caller.

3. M33 Secure World Orchestration

Layer Role
M33 Secure OS Hosts Sealing Service; owns SRK derivation, all ELE calls
ELE (via S3MU) All crypto: derive, encrypt, decrypt, attestation. Keys never leave ELE.
M7 Non-Secure Sends SEAL_REQ/UNSEAL_REQ via S3MU IPC. Never touches keys/plaintext.
S3MU IPC Trusted mailbox. M33 serializes requests to prevent key-derivation races.

SRK cached as VolatilePermanent — re-derived only on cold boot.


4. Sealed Data and Storage

Data Type Example Size Storage
Device config WiFi creds, calibration 128–2 KB QSPI flash
Secure elements OTA sigs, MAC keys 256 B Internal flash
User profiles Encrypted prefs Up to 16 KB External NVM

Blob format:

┌─ Header (288 B): policy_ver(4) ‖ nonce(16) ‖ attest_resp(272)
├─ AAD (128 B): HAB4 reg0–3 (32 B each)
└─ Ciphertext + IV + MAC (variable, AES-CCM/GCM)

5. Lifecycle Considerations

Lifecycle Seal Unseal
OEM Open ✅ — keys with kKeylifecycle_Open
OEM Closed ✅ — keys need kKeylifecycle_Closed flag
Field Return ❌ — ELE keystore inaccessible

Best practice: generate SRK with kKeylifecycle_Closed_Locked for cross-lifecycle compatibility, then lock the keystore group.


6. JTAG Disable

JTAG can expose SRK in SRAM or trigger arbitrary ELE ops. Protection:

  1. Fuse JTAG_DIS before production lifecycle — permanent debug disable.
  2. Secure debug authentication — only authorized certificates can halt core.
  3. CAAM security violation — ELE FW sets Sec_vio0 on JTAG connection, preventing key release.

Fuse JTAG disable during EL2GO provisioning, before sealing production data.


7. EL2GO Provisioning Integration

  1. SRKH burn: OEM SRKH burned into OCOTP fuses via EL2GO self-signed TLV blob.
  2. KEK import: Pre-shared KEKs imported via ELE_ImportKey() using EL2GO-signed TLV.
  3. Encrypted data storage: ELE FW v1.1.0 RT1180 B0 supports EL2GO encrypted data storage API.
  4. Sequence: EL2GO SRKH → EL2GO KEK → Seal Service init → First seal

EL2GO ensures only authorized OEM partners provision keys.


8. Error Handling

Error Source Response
HAB4 fail Boot measurement Abort immediately
AAD mismatch Boot chain changed Return error; log violation
Attest fail ELE HW fault Retry once; disable if persistent
SRK derive fail Corrupted context Fatal — halt sealing
AEAD decrypt fail Tampered blob Return zeroed buffer
Key group full >100 keys Return error; implement rotation
Lifecycle mismatch Key/device lifecycle Return error

After 5 consecutive unseal failures, trigger secure watchdog reset.


9. Performance

Operation Time
ELE_Attest() ~2–5 ms
ELE_DeriveKey() (SRK) ~1–3 ms
AES-CCM encrypt/decrypt (1 KB) ~0.5–1 ms
Full seal (cold SRK) ~5–10 ms
Full unseal (cold SRK) ~5–10 ms
Repeated seal/unseal (cached SRK) ~1–2 ms

SRK caching as VolatilePermanent eliminates attestation/derivation overhead after first call. ELE key group caching (FW v0.0.10+) further accelerates repeated ops.


v1.0 — ELE Crypto API v2.10.0, ELE FW v0.1.1 (RT1180 B0), HAB4, MCUX SDK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment