Files
J3R180_Batching/profiles.py
2026-02-09 15:19:21 -08:00

110 lines
3.8 KiB
Python

"""
Applet validation profiles for the j3r180 batch provisioning tool.
Each profile defines how to validate a specific applet after installation.
Profiles are matched to CAP files automatically via their applet AID.
To add a new profile:
1. Subclass AppletProfile
2. Implement name and validate()
3. Register it in APPLET_PROFILES keyed by the applet AID (hex string)
"""
from abc import ABC, abstractmethod
from smartcard.Exceptions import CardConnectionException, NoCardException
from smartcard.util import toHexString
class AppletProfile(ABC):
"""Base class for applet-specific validation profiles."""
@property
@abstractmethod
def name(self) -> str:
"""Human-readable name for logging/display."""
...
@abstractmethod
def validate(self, connection) -> dict[str, str]:
"""
Validate the applet on a live pyscard connection.
Must return a dict of named results:
{"installed": "yes", "public_key": "04AB..."} -- success
{"installed": "no", "error": "SELECT failed"} -- failure
Must NOT raise exceptions.
"""
...
class DefaultProfile(AppletProfile):
"""Fallback profile for CAPs without a registered profile. SELECT-only check."""
def __init__(self, aid: list[int]):
self._aid = aid
self._name = f"Applet {bytes(aid).hex().upper()}"
@property
def name(self) -> str:
return self._name
def validate(self, connection) -> dict[str, str]:
select_apdu = [0x00, 0xA4, 0x04, 0x00, len(self._aid)] + self._aid
try:
response, sw1, sw2 = connection.transmit(select_apdu)
except (CardConnectionException, NoCardException):
return {"installed": "no", "error": "Card communication error"}
if (sw1, sw2) == (0x90, 0x00):
return {"installed": "yes"}
return {"installed": "no", "error": f"SELECT failed (SW={sw1:02X}{sw2:02X})"}
class TeslaVCSEC(AppletProfile):
"""Profile for the Tesla VCSEC identity applet (TeslaIdent.cap)."""
PACKAGE_AID = [0xF4, 0x65, 0x73, 0x6C, 0x61, 0x4C, 0x6F, 0x67, 0x69, 0x63]
GET_PUBLIC_KEY_APDU = [0x80, 0x04, 0x00, 0x00, 0x00]
@property
def name(self) -> str:
return "Tesla VCSEC"
def validate(self, connection) -> dict[str, str]:
select_apdu = [0x00, 0xA4, 0x04, 0x00, len(self.PACKAGE_AID)] + self.PACKAGE_AID
try:
response, sw1, sw2 = connection.transmit(select_apdu)
except (CardConnectionException, NoCardException):
return {"installed": "no", "error": "Card communication error during SELECT"}
if (sw1, sw2) != (0x90, 0x00):
return {"installed": "no", "error": f"SELECT failed (SW={sw1:02X}{sw2:02X})"}
try:
response, sw1, sw2 = connection.transmit(self.GET_PUBLIC_KEY_APDU)
except (CardConnectionException, NoCardException):
return {"installed": "yes", "error": "Card communication error reading public key"}
if (sw1, sw2) != (0x90, 0x00):
return {"installed": "yes", "error": f"GET PUBLIC KEY failed (SW={sw1:02X}{sw2:02X})"}
pubkey_hex = toHexString(response).replace(" ", "")
return {"installed": "yes", "public_key": pubkey_hex}
# ---------------------------------------------------------------------------
# Registry: applet AID (hex string) → profile instance
# Keys are the APPLET-level AID (as returned by extract_applet_aids()),
# not the package AID.
# ---------------------------------------------------------------------------
APPLET_PROFILES: dict[str, AppletProfile] = {
"F465736C614C6F67696330303201": TeslaVCSEC(),
}
def get_profile(aid_bytes: list[int]) -> AppletProfile | None:
"""Look up a registered profile for the given applet AID."""
aid_hex = bytes(aid_bytes).hex().upper()
return APPLET_PROFILES.get(aid_hex)