An open-source, spec-driven implementation of Secure JTAG authentication and access control for automotive embedded systems — running in pure Python simulation today, targeting real hardware tomorrow.
Modern automotive ECUs are required by ISO/SAE 21434 and UNECE R155 to protect their JTAG debug ports in production. An attacker with unrestricted JTAG access can dump firmware, extract cryptographic keys, and bypass secure boot — a direct threat to vehicle safety and OEM intellectual property.
This lab implements Secure JTAG: a challenge-response authentication gate that controls who can open the debug port, at what privilege level, and for how long. The design mirrors the debug access control architectures used in production automotive SoCs such as Renesas RH850, NXP S32K3, and STMicroelectronics SPC5xx / STM32H7.
| Phase 1 — Simulation | Phase 2 — Hardware | |
|---|---|---|
| Status | ✅ Complete — All tests GREEN | Planned |
| Platform | Pure Python, any OS | STM32F407G-DISC1 + SparkFun ATECC608A |
| Crypto | Software ECDSA-P256 (cryptography lib) |
ATECC608A hardware secure element |
| Entropy | secrets.token_bytes() |
ATECC608A TRNG |
| Transport | In-process function calls | JSON-RPC over localhost TCP (port 9000) |
| Requirements | Python 3.10+ | + pyOCD, CryptoAuthLib, OpenOCD |
The design enforces a two-process trust boundary that mirrors the hardware isolation between an ECU's application core and its dedicated debug security controller:
┌─────────────────────────────────────────────────────────────────────┐
│ Process 1 — Software Side (UNTRUSTED — mirrors ECU application FW) │
│ │
│ sim/protocol/client.py ← JSON-RPC client │
│ sim/core/auth_engine.py ← challenge/response coordination │
│ sim/core/session_manager.py ← session token tracking │
│ sim/core/access_policy.py ← RBAC permission enforcement │
│ sim/core/audit_log.py ← tamper-evident hash-chained log │
└──────────────────────────────┬──────────────────────────────────────┘
│ JSON-RPC only — no shared memory
│ localhost TCP port 9000
│
┌──────────────────────────────┴──────────────────────────────────────┐
│ Process 2 — Secure Debug Controller (TRUSTED — mirrors HW root) │
│ │
│ sim/protocol/server.py ← JSON-RPC dispatcher │
│ sim/hal/atecc_base.py ← abstract crypto interface │
│ sim/hal/atecc_simulator.py ← Phase 1 software shim │
│ sim/hal/atecc_device.py ← Phase 2 real chip (locked) │
│ sim/core/secure_debug_controller.py ← JTAG state machine + policy │
│ sim/core/lockout_manager.py ← brute-force protection │
│ sim/core/tamper_monitor.py ← tamper detection & response │
│ sim/core/lifecycle_manager.py ← DEVELOPMENT→DECOMMISSION │
└─────────────────────────────────────────────────────────────────────┘
The golden rule: Process 1 has zero access to key material, lock state, or security counters except through defined RPC protocol messages. This models the bus isolation between an ECU host CPU and a real HSM or secure element.
| Area | Test Cases | Key behaviours verified |
|---|---|---|
| Lock state | TC-01, TC-02, TC-03 | LOCKED on power-up, any reset, and during early boot |
| Challenge generation | TC-04 | Fresh 32-byte nonce per attempt, ECDSA-P256 |
| Authentication | TC-05, TC-06 | Valid ECDSA sig → session token; invalid sig → reject + audit |
| Replay protection | TC-07 | Spent-nonce set blocks replayed challenge-response pairs |
| Lockout | TC-08, TC-09 | 5 failures → 1-hour lockout; persists across power cycle |
| RBAC | TC-10, TC-11, TC-12 | READ_ONLY / DIAGNOSTICS / ENGINEERING; PROTECTED_OPS always denied |
| Session timeout | TC-13 | 5-minute TTL; expired token → LOCKED + audit entry |
| Relock | TC-14 | Explicit relock zeroizes session and volatile material |
| Tamper response | TC-15, TC-16 | Any tamper → LOCKED + zeroize + TAMPER_DETECTED audit |
| Fault injection | TC-17 | Malformed sigs, oversized params, concurrent threads — no state corruption |
| Permanent disable | TC-18 | One-way DECOMMISSION brick — irreversible gate on all ops |
| Audit log | TC-19 | Hash-chained entries, FIFO eviction, no-clear API |
| Concurrency | TC-20 | Single active session enforced under concurrent authentication |
- Python 3.10 or newer
- Git
Double-click launch.bat in the project root. It will:
- Create the
.venvvirtual environment if it doesn't already exist - Install (or update) all Phase 1 dependencies from
requirements.txt - Start the real backend — the Secure Debug Controller (
sim/protocol/server.py, port 9000) and the browser WebSocket bridge (sim/protocol/ws_bridge.py, port 8765) — each in its own titled window - Open the live Secure JTAG Debug Monitor dashboard in your default browser, already connected to that real backend
- Leave you in a terminal window with the virtual environment already activated, ready to run
pytestor the reference CLI client
No manual setup required — this is the fastest way for a new user to get the lab running. To stop the backend, close the two titled windows it opens.
launch.bat
Raspberry Pi / hardware-only packages (
RPi.GPIO,smbus2,cryptoauthlib) are marked Linux-only inrequirements.txtand are automatically skipped on Windows — Phase 1 needs none of them.
git clone https://github.com/venaychawda/SecureJTAG.git
cd SecureJTAG
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
pip install -r requirements.txtpytest tests/ -v --tb=shortExpected output — all tests GREEN:
tests/test_TC01_power_up_lock.py PASSED
tests/test_TC02_reset_lock.py PASSED
tests/test_TC03_early_boot_lock.py PASSED
tests/test_TC04_challenge_generation.py PASSED
tests/test_TC05_valid_auth.py PASSED
tests/test_TC06_invalid_auth.py PASSED
tests/test_TC07_replay_protection.py PASSED
tests/test_TC08_lockout.py PASSED
tests/test_TC09_lockout_persistence.py PASSED
tests/test_TC10_rbac_limited.py PASSED
tests/test_TC11_rbac_engineer.py PASSED
tests/test_TC12_secure_asset_protection.py PASSED
tests/test_TC13_session_timeout.py PASSED
tests/test_TC14_relock_reset.py PASSED
tests/test_TC15_tamper_response.py PASSED
tests/test_TC16_zeroization.py PASSED
tests/test_TC17_fault_injection.py PASSED
tests/test_TC18_permanent_disable.py PASSED
tests/test_TC19_audit_log.py PASSED
tests/test_TC20_concurrent_sessions.py PASSED
20 passed
pytest tests/test_TC05_valid_auth.py -vpytest tests/ -v --cov=sim --cov-report=term-missingStart the Secure Debug Controller and software client in separate terminals to observe the JSON-RPC trust boundary live:
# Terminal 1 — Secure Debug Controller (trusted process)
python -m sim.protocol.server
# Terminal 2 — Software side (untrusted process)
python -m sim.protocol.client --role DIAGNOSTICSserver.py listens on 127.0.0.1:9000 and speaks the exact JSON-RPC message contract in sim/protocol/messages.py. On first run it generates a demo ECDSA-P256 debug-tool key pair (sim/protocol/.debug_identity.json, git-ignored, never committed) and enrols the public half into the controller — this stands in for the out-of-band certificate provisioning a real debug tool would receive before ever touching a vehicle's JTAG port. client.py reads that same identity to sign challenges for real, over the real socket.
This repo ships two HTML dashboards that look alike but work very differently. Pick the right one for what you're demonstrating.
A self-contained mockup: all state (lock status, role, TTL, audit log) lives in a JavaScript variable in the page itself. It never talks to the Python backend, needs no server, and works by double-clicking the file — which is exactly why it's what GitHub Pages hosts as the public Live Demo. It's useful for a quick, zero-setup walkthrough of the state machine's shape, but it does not exercise any real code in sim/.
docs/index.html
Double-click the file or drag it into a browser tab. Local-only — not wired to a backend.
A genuine client of the real backend: every button click sends an actual JSON-RPC message over a real socket to the actual SecureDebugController running in sim/protocol/server.py, and every response you see (session state, RBAC decisions, audit log entries) was computed by the real Python code the pytest suite tests. It performs real ECDSA-P256 signing in the browser via the native Web Crypto API, using the private key handed to it once by the WebSocket bridge.
Browser (docs/SecureJTAG.html)
│ WebSocket ws://127.0.0.1:8765
▼
sim/protocol/ws_bridge.py (relays JSON-RPC; hands the browser its local debug-tool identity)
│ TCP JSON-RPC 127.0.0.1:9000
▼
sim/protocol/server.py → SecureDebugController → ATECCSimulator
Browsers cannot open raw TCP sockets — only WebSocket/HTTP — so ws_bridge.py exists purely to relay WebSocket frames to the real TCP JSON-RPC server byte-for-byte. It also plays the role of "the debug engineer's laptop": it reads the locally-provisioned private key from disk and gives it to the page once per connection, over a small bridge-local message that never touches the real P1↔P2 wire and isn't part of the documented message contract.
Requires the backend running (launch.bat starts it automatically, or manually):
python -m sim.protocol.server # Terminal 1 — Process 2, port 9000
python -m sim.protocol.ws_bridge # Terminal 2 — browser bridge, port 8765Then open docs/SecureJTAG.html. Because it depends on a locally running Python backend, it is not part of the GitHub Pages live demo — it only works when run from a local clone.
- JTAG lock/unlock state, active session role, TTL, lifecycle
- Authenticate with a valid signature, or a deliberately bogus key to see
AUTH_FAIL - Relock, tamper trigger, lifecycle transitions, permanent disable
- RBAC-gated debug operations (try
HALT_CPUasREAD_ONLY— denied; asENGINEERING— permitted) - Live audit log stream
Production automotive ECUs — such as the Renesas RH850/U2A, NXP S32K3, and STM32H7 — implement Secure JTAG inside dedicated hardware security modules (HSMs). That silicon is expensive, export-controlled, and requires NDA access. This lab replaces it with a ~$25 discovery board and an ~$8 breakout — and the security model is architecturally identical:
| Automotive ECU | This Lab | Why it is equivalent |
|---|---|---|
| HSM / dedicated security core | STM32F407G-DISC1 running secure_debug_controller.py in an isolated process |
Same trust boundary: application software cannot directly access HSM state |
| Hardware Secure Element (key store) | SparkFun ATECC608A DEV-18077 | The ATECC608A is the same Microchip IC family used as a companion HSM in automotive designs |
| Hardware TRNG | ATECC608A internal TRNG | NIST SP 800-90B compliant — same standard required by ISO 26262 and ISO/SAE 21434 |
| ECDSA-P256 verification | ATECC608A Sign/Verify commands | Identical NIST P-256 curve and API used in production secure debug controllers |
| Non-volatile lockout counter | ATECC608A monotonic counter | Hardware-backed, survives power loss — prevents brute-force even across reboots |
| JTAG TAP controller hardware gate | STM32 RDP (Readout Protection) bits + pyOCD TCK/TMS control | Same principle: a hardware bit blocks the debug TAP regardless of software state |
| ISO/SAE 21434 audit trail | Hash-chained AuditLog (tamper-evident, no-clear API) |
Implements the evidence-chain requirement from TARA artefacts |
| Role-based debug privileges | READ_ONLY / DIAGNOSTICS / ENGINEERING RBAC | Directly maps to OEM debug manifest role definitions |
| OEM lifecycle management | DEVELOPMENT → PRODUCTION → SERVICE → DECOMMISSION | Standard ECU lifecycle model per ISO/SAE 21434 Section 11 |
| OEM challenge-response protocol | JSON-RPC over TCP (replacing SPI/I²C physical bus) | Same protocol semantics — only the transport medium differs |
ISO/SAE 21434 (Road Vehicles — Cybersecurity Engineering) and UNECE Regulation 155 require OEMs to document and test debug access control as part of the vehicle cybersecurity management system. The docs/traceability_matrix.md in this repo follows exactly the requirement → test case → verification evidence chain demanded by a TARA (Threat Analysis and Risk Assessment) artefact — giving learners a complete, regulation-aligned example at a fraction of the cost of proprietary toolchains.
SecureJTAG/
├── sim/
│ ├── hal/
│ │ ├── atecc_base.py # Abstract crypto interface (ABC)
│ │ ├── atecc_simulator.py # Phase 1 — software crypto shim
│ │ └── atecc_device.py # Phase 2 — real ATECC608A (locked)
│ ├── core/
│ │ ├── secure_debug_controller.py # JTAG state machine + policy
│ │ ├── session_manager.py # Session tokens and TTL
│ │ ├── access_policy.py # RBAC permission tables
│ │ ├── audit_log.py # Hash-chained tamper-evident log
│ │ ├── lockout_manager.py # Brute-force counter + lockout
│ │ ├── tamper_monitor.py # Tamper detection and response
│ │ └── lifecycle_manager.py # Lifecycle state machine
│ ├── protocol/
│ │ ├── client.py # Process 1 — reference RPC client (CLI)
│ │ ├── server.py # Process 2 — RPC dispatcher + demo identity provisioning
│ │ ├── ws_bridge.py # Browser WebSocket <-> TCP relay for SecureJTAG.html
│ │ ├── messages.py # Message schema constants
│ │ └── .debug_identity.json # Generated on first server.py run — git-ignored
│ └── config/
│ ├── device_policy.json # Policy constants (timeouts, roles)
│ └── lifecycle_states.json # Valid state transitions
├── tests/
│ ├── conftest.py # Shared pytest fixtures
│ └── test_TC01_*.py … test_TC20_*.py # One file per TC-ID
├── specs/
│ └── requirements.csv # Source of truth — READ ONLY (Added to .gitignore)
├── docs/
│ ├── traceability_matrix.md # REQ → TC → status mapping
│ ├── index.html # Offline simulation dashboard (GitHub Pages live demo)
│ ├── SecureJTAG.html # Live dashboard — real server-client app (local only)
│ └── images/
│ └── hardware_wiring.png # Phase 2 wiring photo (Phase 2)
├── firmware/ # Phase 2 only (locked during Phase 1)
├── host/ # Phase 2 only (locked during Phase 1)
├── requirements.txt
├── launch.bat # Windows one-click setup + dashboard launcher
└── README.md
| Artefact | Location |
|---|---|
| Requirement → test case → status matrix | docs/traceability_matrix.md |