Skip to content

On-device GlobalPlatform card management via SCP02 — hardware-verified #25

@Amperstrand

Description

@Amperstrand

Summary

We've implemented and hardware-verified a full GlobalPlatform (GP) card management stack that runs entirely on the Specter-DIY STM32F469 firmware. This enables the device to manage installed JavaCard applets directly — no PC-based tools like GlobalPlatformPro required.

All 19/19 hardware tests pass on a real JCOP4 card, covering the complete applet lifecycle: install, verify, and delete.

What we built

Pure Python SCP02 Secure Channel

  • des3.py — Full DES/3DES implementation (FIPS 46-3) in pure Python, since MicroPython on STM32F469 has no DES/3DES hardware support (only AES via ucryptolib). Verified against pycryptodome on desktop.
  • scp02.py — SCP02 session management: INITIALIZE UPDATE, EXTERNAL AUTHENTICATE, session key derivation, and C-MAC wrapping. Verified byte-for-byte against a GlobalPlatformPro hardware trace captured from the same JCOP4 card.

GP Card Management Operations

  • registry.py — GET STATUS with auto-detection of both standard E3-tagged TLV responses and JCOP4's proprietary compact format. Finds applets by both top-level AID and module AIDs.
  • loader.py — INSTALL FOR LOAD, chunked LOAD, and INSTALL FOR INSTALL, all aligned byte-for-byte with GPPro trace output. LOAD uses 247-byte blocks (255 Lc minus 8-byte MAC).
  • deleter.py — DELETE with P2=0x80 for cascading deletion of related objects.
  • profiles.py — JCOP4 profile with GP default keys (404142...4F) and SCP02 configuration.

REPL Development & Testing Infrastructure

  • boot.py / main.py — VCP+MSC USB mode for REPL access, GUI skipped in HIL mode.
  • test_gp_flow.py — 9-test hardware test suite covering: SCP02 session, GET STATUS (4 element types), AID lookup, INSTALL FOR LOAD, chunked LOAD (831 bytes in 4 blocks, ~7.8s), INSTALL FOR INSTALL, verify installation, DELETE, verify deletion.
  • DGP (deployed CAP) files uploaded to /flash/gp/ via mpremote cp — avoids MemoryError from freezing large CAP files in firmware.

How we verified it

Every APDU format was validated against a GlobalPlatformPro hardware trace captured from the exact same JCOP4 card:

  1. SCP02 session — Session key derivation, card/host cryptogram computation, and MAC wrapping all match GPPro output byte-for-byte.
  2. INSTALL FOR LOAD — Data field format (pkg_aid | sd_aid | 0x00 0x00 0x00) and P1=0x02 match GPPro.
  3. LOAD blocks — C4 header encoding, block sizes (247/247/247/94 for 831 bytes), P1/P2 sequence values all match.
  4. INSTALL FOR INSTALL — Data field format with split privileges/install_params fields matches GPPro.
  5. DGP extraction — CAP ZIP decomposed into 9 component files (excluding Descriptor.cap), concatenated in GPPro's order. All components verified byte-identical.

What this means for the project

This is the foundation for managing installed applets from within the Specter-DIY firmware itself, rather than requiring a separate PC with Java/GPPro. The implications:

  • SeedKeeper provisioning — The full GP install flow can run on-device. Users can install the SeedKeeper applet directly from the Specter-DIY menu, without needing a PC with GlobalPlatformPro.
  • Applet management UI — List, install, and delete applets through the device's touchscreen interface.
  • Self-contained security — No need to expose card keys to a PC. The SCP02 session and all key material stay on the STM32.
  • Multiple card support — The GP stack is card-agnostic (profile-driven), so it can support other JavaCard types beyond JCOP4.

Current status

Component Status Notes
Pure Python DES/3DES Done Verified against pycryptodome
SCP02 secure channel Done Verified against GPPro trace
GET STATUS / Registry Done E3 + JCOP4 compact format
INSTALL FOR LOAD Done Byte-exact match with GPPro
Chunked LOAD Done 247-byte blocks, ~7.8s for 831B
INSTALL FOR INSTALL Done Byte-exact match with GPPro
DELETE Done Cascading delete (P2=0x80)
Hardware test suite Done 19/19 passing
HIL commands Done gp_init, gp_status, gp_delete, gp_install, gp_probe, gp_verify
GUI provisioning screens Done (M6) Progress + details screens exist
MicroPython compat Done No bytes.fromhex(), no time.sleep()

Branch: gp-repl-tests (branched from applet-manager, which branched from seedkeeper)

Roadmap

Phase 1 — Merge and integrate (next)

  • Merge gp-repl-tests into applet-manager or a unified branch
  • Revert REPL-only boot.py/main.py changes (keep GUI, add REPL as opt-in)
  • Remove teapot_cap.py (MemoryError when frozen, not needed for production)
  • Update specter.py provisioning flow to use scp02 instead of scp03 imports
  • Clean up unused SCP03 code paths or mark as deprecated

Phase 2 — On-device SeedKeeper provisioning

  • Bundle SeedKeeper CAP as DGP file (like TeapotApplet)
  • Add SeedKeeper install flow to GUI provisioning screens
  • Add "Manage Applets" screen to list installed applets from registry
  • Add "Delete Applet" confirmation flow in GUI
  • Test full SeedKeeper install-verify-delete cycle on hardware

Phase 3 — SCP02 performance optimization

  • Profile pure Python DES/3DES — current LOAD takes ~7.8s for 831 bytes
  • Consider C usermod for DES/3DES if performance is unacceptable
  • Implement SCP02 R-MAC for response authentication
  • Implement SCP02 encryption (currently C-MAC only, no encryption)

Phase 4 — Advanced features

  • Key diversification support (KDF for non-default keys)
  • Key update protocol (SET STATUS for new keys)
  • Support for additional card profiles (NXP JCOP, Infineon, etc.)
  • CAP file upload over USB (receive DGP from host, store to /flash/gp/)
  • Multiple AID management (install several applets, switch between them)

Phase 5 — Integration with Specter-DIY workflows

  • Auto-detect and initialize SeedKeeper on card insert
  • Seamless SeedKeeper keystore integration after on-device install
  • Firmware update flow that preserves installed applets
  • Factory reset that cleans up GP state

Test results

==================================================
GP Card Management Hardware Test
==================================================
--- Test 1: SCP02 Session ---
[PASS] Card present          ATR: 3bd518ff8191fe1fc38073c821100a
[PASS] SCP02 session
--- Test 2: GET STATUS ---
[PASS] GET STATUS parsed
[PASS] ISD lifecycle         LC=07
[PASS] ISD privileges        9e
[PASS] Apps found            1 entries
[PASS] Load files found      1 entries
[PASS] Packages found        1 entries
--- Test 3: AID Lookup ---
[PASS] SatoChip found
[PASS] MemoryCard absent
[PASS] TeapotApplet pre-check  installed=False
--- Test 4: INSTALL FOR LOAD ---
[PASS] INSTALL for load
--- Test 5: LOAD CAP ---
[PASS] CAP file loaded       831 bytes from /flash/gp/TeapotApplet.dgp
[PASS] LOAD CAP              831 bytes in 4 blocks, 7829 ms
--- Test 6: INSTALL FOR INSTALL ---
[PASS] INSTALL for install
--- Test 7: Verify Installation ---
[PASS] TeapotApplet installed
--- Test 8: DELETE ---
[PASS] DELETE instance       SW=9000
[PASS] DELETE package        SW=9000
--- Test 9: Verify Deletion ---
[PASS] TeapotApplet removed
==================================================
Results: 19/19 passed, 0 failed
ALL TESTS PASSED
==================================================

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature creepIncreases merge complexity; scope should be reduced or deferredpriority: lowNice to have, not blockingseedkeeperSeedKeeper keystore support

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions