Summary
Uninstall (delete) functionality for MemoryCard and SeedKeeper applets was removed from the GUI in the initial provisioning implementation. Users must use gp.jar (GlobalPlatformPro) to uninstall applets.
Why
- Risk: Uninstalling an applet also wipes any stored secrets on the card. This is destructive and irreversible.
- Complexity: Proper uninstall requires cascading delete (applet + package), session management, and error handling that adds significant surface area.
- Simplicity: For the initial proof-of-concept, install-only keeps the scope minimal and well-tested.
How to uninstall manually
# Install GlobalPlatformPro v25.10.20+
# https://github.com/martinpaljak/GlobalPlatformPro/releases
# Delete MemoryCard applet + package
java -jar gp.jar --delete B00B5111CB01
java -jar gp.jar --delete B00B5111CB --op201
# Delete SeedKeeper applet + package
java -jar gp.jar --delete 536565644b656570657200
java -jar gp.jar --delete 536565644b6565706572 --op201
Code for re-addition
The following methods were removed from src/specter.py and should be restored when this feature is re-implemented:
_delete_memorycard(self, silent=False)
async def _delete_memorycard(self, silent=False):
"""Delete MemoryCard applet from card."""
if not silent:
if not await self.gui.prompt(
"Delete MemoryCard?",
"This will remove the MemoryCard applet\n"
"and all its data from the card.\n\n"
"Are you sure?",
warning="This action cannot be undone!",
):
return
from gui.screens.provisioning import ProvisioningProgressScreen
from keystore.javacard.util import get_connection
from keystore.javacard.gp.profiles import JCOP4_PROFILE
from keystore.javacard.gp.scp02 import open_session
from keystore.javacard.gp.deleter import delete_aid
scr = ProvisioningProgressScreen("Delete MemoryCard")
await self.gui.load_screen(scr)
try:
conn = None
session = None
scr.set_step("Connecting to card...")
conn = get_connection()
_safe_connect(conn)
scr.set_step("Authenticating...")
session = open_session(conn, JCOP4_PROFILE)
scr.set_step("Deleting applet...")
applet_aid = unhexlify("B00B5111CB01")
delete_aid(session, applet_aid)
scr.set_step("Deleting package...")
package_aid = unhexlify("B00B5111CB")
try:
delete_aid(session, package_aid)
except Exception:
pass
scr.set_step("Ending session...")
if session is not None:
try:
session.end_session()
except Exception:
pass
try:
conn.disconnect()
except Exception:
pass
if silent:
return
scr.set_done()
await scr.result()
except Exception as e:
if session is not None:
try:
session.end_session()
except Exception:
pass
if conn is not None:
try:
conn.disconnect()
except Exception:
pass
if silent:
raise
scr.set_error("Delete failed:\n%s" % str(e))
await scr.result()
_delete_seedkeeper(self, silent=False)
async def _delete_seedkeeper(self, silent=False):
"""Delete SeedKeeper applet from card."""
if not silent:
if not await self.gui.prompt(
"Delete SeedKeeper?",
"This will remove the SeedKeeper applet\n"
"and all its secrets from the card.\n\n"
"Are you sure?",
warning="This action cannot be undone!",
):
return
from gui.screens.provisioning import ProvisioningProgressScreen
from keystore.javacard.util import get_connection
from keystore.javacard.gp.profiles import JCOP4_PROFILE
from keystore.javacard.gp.scp02 import open_session
from keystore.javacard.gp.deleter import delete_aid
from binascii import unhexlify
scr = ProvisioningProgressScreen("Delete SeedKeeper")
await self.gui.load_screen(scr)
try:
conn = None
session = None
scr.set_step("Connecting to card...")
conn = get_connection()
_safe_connect(conn)
scr.set_step("Authenticating...")
session = open_session(conn, JCOP4_PROFILE)
scr.set_step("Deleting applet...")
sk_inst = unhexlify("536565644b656570657200")
delete_aid(session, sk_inst)
scr.set_step("Deleting package...")
sk_pkg = unhexlify("536565644b6565706572")
try:
delete_aid(session, sk_pkg)
except Exception:
pass
scr.set_step("Ending session...")
if session is not None:
try:
session.end_session()
except Exception:
pass
try:
conn.disconnect()
except Exception:
pass
if silent:
return
scr.set_done()
await scr.result()
except Exception as e:
if session is not None:
try:
session.end_session()
except Exception:
pass
if conn is not None:
try:
conn.disconnect()
except Exception:
pass
if silent:
raise
scr.set_error("Delete failed:\n%s" % str(e))
await scr.result()
_install_seedkeeper(self)
async def _install_seedkeeper(self):
"""Install SeedKeeper applet from DGP file on filesystem."""
from keystore.javacard.gp.profiles import APPLET_AIDS
sk_info = APPLET_AIDS.get("seedkeeper")
dgp_path = sk_info["dgp_file"] if sk_info else "/flash/gp/SeedKeeper.dgp"
try:
f = open(dgp_path, "rb")
f.close()
except Exception:
await self.gui.alert(
"SeedKeeper.dgp not found",
"Copy the DGP file to the device:\n\n"
"mpremote cp SeedKeeper.dgp :%s" % dgp_path
)
return
from keystore.javacard.util import get_connection
from keystore.javacard.card_scanner import scan_card_applets
conn = get_connection()
scan = scan_card_applets(conn)
already_installed = "SeedKeeper" in scan.get("applets", [])
if already_installed:
if not await self.gui.prompt(
"SeedKeeper already installed",
"SeedKeeper is already on this card.\n\n"
"Reinstalling will delete existing secrets.\n\n"
"Continue?",
warning="All stored secrets will be lost!",
):
return
await self._delete_seedkeeper(silent=True)
if not await self.gui.prompt(
"Install SeedKeeper?",
"This will install the SeedKeeper applet\n"
"on the JavaCard.\n\n"
"The card will be modified.\n\n"
"Continue?",
):
return
from gui.screens.provisioning import ProvisioningProgressScreen
from keystore.javacard.gp.profiles import JCOP4_PROFILE
from keystore.javacard.gp.scp02 import open_session
from keystore.javacard.gp.loader import install_from_dgp, verify_install
from binascii import unhexlify
scr = ProvisioningProgressScreen("Install SeedKeeper")
await self.gui.load_screen(scr)
try:
scr.set_step("Loading DGP file...")
f = open(dgp_path, "rb")
dgp_data = f.read()
f.close()
scr.set_step("Connecting to card...")
conn = get_connection()
_safe_connect(conn)
scr.set_step("Authenticating...")
session = open_session(conn, JCOP4_PROFILE)
scr.set_step("Installing (%d bytes)..." % len(dgp_data))
sd_aid = unhexlify("A000000151000000")
pkg_aid = install_from_dgp(session, dgp_data, sd_aid)
scr.set_step("Verifying...")
sk_inst = unhexlify("536565644b656570657200")
if verify_install(session, sk_inst):
scr.set_done()
else:
scr.set_error("Verification failed")
try:
conn.disconnect()
except Exception:
pass
await scr.result()
except Exception as e:
try:
conn.disconnect()
except Exception:
pass
scr.set_error("Install failed:\n%s" % str(e))
await scr.result()
Requirements for re-addition
- Add warning about data loss in the confirmation prompt
- Show which applets/secrets will be deleted before confirmation
- Add "Use a different card" flow after delete (card swap)
- Consider adding a "factory reset" option that deletes ALL applets
- Test delete → reinstall → verify cycle thoroughly
- Test delete when card has stored mnemonic (confirm data is gone)
Summary
Uninstall (delete) functionality for MemoryCard and SeedKeeper applets was removed from the GUI in the initial provisioning implementation. Users must use
gp.jar(GlobalPlatformPro) to uninstall applets.Why
How to uninstall manually
Code for re-addition
The following methods were removed from
src/specter.pyand should be restored when this feature is re-implemented:_delete_memorycard(self, silent=False)_delete_seedkeeper(self, silent=False)_install_seedkeeper(self)Requirements for re-addition