Initial commit
This commit is contained in:
109
profiles.py
Normal file
109
profiles.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user