Skip to content

SeedKeeper card EEPROM full: bulk delete/import leaves orphaned secrets #22

@Amperstrand

Description

@Amperstrand

Problem

When importing or deleting secrets rapidly on the SeedKeeper card, the card's EEPROM becomes corrupted with orphaned secret entries. After ~25+ duplicate imports in a single session, all subsequent operations return 0x9C06 (SW_UNAUTHORIZED) — even after correct PIN verification and secure channel initialization.

Error code: 0x9C06 (SW_UNAUTHORIZED)

From the SeedKeeper applet source (SeedKeeper.java):

/** Required operation was not authorized because of a lack of privileges */
private final static short SW_UNAUTHORIZED = (short) 0x9C06;

From pysatochip's CardConnector.py, 0x9C06 triggers automatic PIN re-verification:

# PIN authentication is required
if (sw1==0x9C) and (sw2==0x06):
    (response, sw1, sw2)= self.card_verify_PIN_simple()

However, in our case, PIN re-verification doesn't help — the card's EEPROM object manager is corrupted.

Root cause

The ObjectManager on the SeedKeeper card allocates fixed-size EEPROM slots for each secret. When secrets are deleted, the slot may not be properly freed (or the free list becomes corrupted). After many rapid import/delete cycles:

  1. Card EEPROM fills up with orphaned secret entries
  2. INS_LIST_SECRET_HEADERS (0xA6) fails with 0x9C06
  3. INS_DELETE_SECRET (0xA5) also fails with 0x9C06
  4. Even INS_IMPORT_SECRET (0xA1) may fail
  5. The only recovery is physical card removal/reinsertion (full power cycle clears volatile state but NOT EEPROM corruption)

Reproduction

  1. Import the same secret 25+ times via TEST_IMPORT_SECRET:<hex>:abandon
  2. Try to delete all secrets via TEST_DELETE_SECRET:<id> for each
  3. After ~25 deletes, all subsequent operations return 0x9C06
  4. TEST_CARD_RESET (GPIO power cycle) does NOT fix it
  5. Only physical card removal and reinsertion recovers the card

SeedKeeper error code reference

From the applet source, the relevant SW codes are:

Code Constant Meaning
0x63cX SW_PIN_FAILED Wrong PIN, X attempts remaining
0x9c01 SW_NO_MEMORY_LEFT EEPROM full
0x9c03 SW_OPERATION_NOT_ALLOWED Operation not allowed
0x9c04 SW_SETUP_NOT_DONE Setup not complete
0x9c05 SW_UNSUPPORTED_FEATURE Feature not supported
0x9c06 SW_UNAUTHORIZED PIN/auth required
0x9c08 SW_OBJECT_NOT_FOUND Object missing
0x9c12 SW_SEQUENCE_END No more data
0x9c20 SW_SECURE_CHANNEL_REQUIRED Secure channel needed
0x9c21 SW_SECURE_CHANNEL_UNINITIALIZED Secure channel not init
0x9c23 SW_SECURE_CHANNEL_WRONG_MAC Bad MAC in secure channel
0x9c30 SW_LOCK_ERROR Lock mechanism error
0x9c31 SW_EXPORT_NOT_ALLOWED Export not allowed
0x9c32 SW_IMPORTED_DATA_TOO_LONG Secret too long
0x9c36 SW_USAGE_NOT_ALLOWED Usage not allowed
0x9c38 SW_WRONG_SECRET_TYPE Wrong secret type

Fixes needed

1. Rate-limit HIL test imports (firmware)

In _load_seedkeeper(), the retry loop imported the abandon secret multiple times before detecting the "No secrets" screen. Add a guard:

# Only import if TEST_SECRETS shows empty
resp = self.gui.command("TEST_SECRETS", timeout=2)
if b"OK:SECRETS:" in resp and resp.split(b"OK:SECRETS:")[1].strip():
    # Secrets exist, don't import
    pass
else:
    self.card_import_bip39(...)

2. Add 0x9C06 handling to secure_request

In satochip_securechannel.py, detect 0x9C06 and provide a meaningful error message:

if sw == 0x9C06:
    raise ISOException("9c06", "Operation not authorized - card may need physical reset")

3. Add TEST_WIPE_SECRETS HIL command

A dedicated command to delete all BIP39 secrets in a single operation, with proper error handling for EEPROM full state:

TEST_WIPE_SECRETS → deletes all secrets, returns OK:WIPED_SECRETS or ERR:EEPROM_FULL

4. Consider batch delete in firmware

Instead of individual INS_DELETE_SECRET calls, implement a single "delete all BIP39" flow that minimizes EEPROM writes.

Workaround

Physical card removal and reinsertion is the only reliable recovery. GPIO power cycling (TEST_CARD_RESET) does NOT clear EEPROM corruption.

Environment

  • SeedKeeper card: protocol v0.2, applet v0.1
  • Tested on STM32F469 Discovery board with HIL test framework
  • st-flash v1.8.0, openocd v0.12.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: mediumShould be addressed, improves qualityseedkeeperSeedKeeper keystore support

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions