C-APDU constructor spec fixes (TS 102 221 / GPC v2.3 / TS 102 226)
SIM/USIM tab (TS 102 221): - Record P2 modes: 04/06/02 -> 04/02/03; P1=00 for next/previous - SELECT Le rules: USIM FID no Le, path/dfname P2=04+Le; SIM no Le - Path placeholder: 3F007FFF6FC5 -> 7FFF6FC5 - ACTIVATE/DEACTIVATE: 5-byte -> 4-byte case-1 - VERIFY PIN: dedicated input, Lc=08, FF-pad to 8 bytes - CHANGE PIN: old/new inputs, Lc=10, two 8-byte fields - ERASE BINARY removed from USIM tab RAM tab (GPC v2.3): - INSTALL: all 5 variants rewritten to strict LV + Le 00 - ELF/Module AID inputs for install-install - Privileges: 1 byte if only byte-1, else 3 bytes - InstallParams: C9 00 + toolkit TLV (mandatory) - LOAD/DELETE/GET STATUS: append Le 00 - GET STATUS P2: drop 40/42, keep 02/03/00 - GET DATA: 2F00/5031 -> case-4 5C00 data - SET STATUS: raw AID (no 4F), ISD card states, P1=60 - EXT AUTH: CLA 84, P1 security level, Lc=10 - INT AUTH: CLA 00 Toolkit (TS 102 226): - MSL: 01 <msl> -> 00 (no check) or 02 01 <msl> - SIM CA wrapped in EF: C9 00 EF <len> CA... - TAR length %3 validation - Menu IDs <= 7F validation Script Chaining (TS 102 226 Table 5.9a): - Removed Script ID + Additional Data fields - Emit strictly 83 01 <flags> - Deleted generateScriptIdHint Privilege label: Token Verification -> Token Management
This commit is contained in:
+261
-190
@@ -87,7 +87,7 @@
|
||||
</div>
|
||||
<div id="sim-sel-path-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Path (hex)">Path (hex)</label>
|
||||
<input id="sim-sel-path" class="font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="3F007FFF6FC5">
|
||||
<input id="sim-sel-path" class="font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="7FFF6FC5">
|
||||
</div>
|
||||
<div id="sim-sel-dfname-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">AID (hex)</label>
|
||||
@@ -108,9 +108,9 @@
|
||||
<div class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Selection mode (P2)">Selection mode (P2)</label>
|
||||
<select id="sim-rec-mode" onchange="updateP1P2Display('sim')" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
|
||||
<option value="04">Absolute (04)</option>
|
||||
<option value="06">Next (06)</option>
|
||||
<option value="02">Previous (02)</option>
|
||||
<option value="04">Absolute (04)</option>
|
||||
<option value="02">Next (02)</option>
|
||||
<option value="03">Previous (03)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,6 +143,21 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="sim-pin-val-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">PIN value</label>
|
||||
<input id="sim-pin-val" class="font-mono w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="1234" maxlength="8">
|
||||
</div>
|
||||
|
||||
<div id="sim-pin-old-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">Old PIN</label>
|
||||
<input id="sim-pin-old" class="font-mono w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="1234" maxlength="8">
|
||||
</div>
|
||||
|
||||
<div id="sim-pin-new-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">New PIN</label>
|
||||
<input id="sim-pin-new" class="font-mono w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="5678" maxlength="8">
|
||||
</div>
|
||||
|
||||
<div id="sim-override-row" class="mb-3">
|
||||
<div class="flex gap-2 mb-2">
|
||||
<div class="flex-1">
|
||||
@@ -178,7 +193,6 @@
|
||||
<option value="update-binary">UPDATE BINARY</option>
|
||||
<option value="read-record">READ RECORD</option>
|
||||
<option value="read-binary">READ BINARY</option>
|
||||
<option value="erase-binary">ERASE BINARY</option>
|
||||
<option value="activate-file">ACTIVATE FILE</option>
|
||||
<option value="deactivate-file">DEACTIVATE FILE</option>
|
||||
<option value="verify">VERIFY PIN</option>
|
||||
@@ -209,7 +223,7 @@
|
||||
</div>
|
||||
<div id="usim-sel-path-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Path (hex)">Path (hex)</label>
|
||||
<input id="usim-sel-path" class="font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="3F007FFF6FC5">
|
||||
<input id="usim-sel-path" class="font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="7FFF6FC5">
|
||||
</div>
|
||||
<div id="usim-sel-dfname-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">AID (hex)</label>
|
||||
@@ -230,9 +244,9 @@
|
||||
<div class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Selection mode (P2)">Selection mode (P2)</label>
|
||||
<select id="usim-rec-mode" onchange="updateP1P2Display('usim')" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
|
||||
<option value="04">Absolute (04)</option>
|
||||
<option value="06">Next (06)</option>
|
||||
<option value="02">Previous (02)</option>
|
||||
<option value="04">Absolute (04)</option>
|
||||
<option value="02">Next (02)</option>
|
||||
<option value="03">Previous (03)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,6 +279,21 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="usim-pin-val-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">PIN value</label>
|
||||
<input id="usim-pin-val" class="font-mono w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="1234" maxlength="8">
|
||||
</div>
|
||||
|
||||
<div id="usim-pin-old-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">Old PIN</label>
|
||||
<input id="usim-pin-old" class="font-mono w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="1234" maxlength="8">
|
||||
</div>
|
||||
|
||||
<div id="usim-pin-new-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">New PIN</label>
|
||||
<input id="usim-pin-new" class="font-mono w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="5678" maxlength="8">
|
||||
</div>
|
||||
|
||||
<div id="usim-override-row" class="mb-3">
|
||||
<div class="flex gap-2 mb-2">
|
||||
<div class="flex-1">
|
||||
@@ -341,6 +370,14 @@
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">SD AID (hex)</label>
|
||||
<input id="ram-sdaid" class="font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="A000000151000000">
|
||||
</div>
|
||||
<div id="ram-elfaid-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">ELF AID (hex)</label>
|
||||
<input id="ram-elfaid" class="font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="A000000151000000">
|
||||
</div>
|
||||
<div id="ram-modaid-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">Module AID (hex)</label>
|
||||
<input id="ram-modaid" class="font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="A000000151000000">
|
||||
</div>
|
||||
<div id="ram-priv-row" class="mb-3">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Privileges">Privileges</label>
|
||||
@@ -354,7 +391,7 @@
|
||||
<label class="flex items-center gap-1"><input type="checkbox" class="ram-priv-b1" value="40" onchange="updateRamPriv()"> DAP Verification</label>
|
||||
<label class="flex items-center gap-1"><input type="checkbox" class="ram-priv-b2" value="40" onchange="updateRamPriv()"> Authorized Management</label>
|
||||
<label class="flex items-center gap-1"><input type="checkbox" class="ram-priv-b1" value="20" onchange="updateRamPriv()"> Delegated Management</label>
|
||||
<label class="flex items-center gap-1"><input type="checkbox" class="ram-priv-b2" value="20" onchange="updateRamPriv()"> Token Verification</label>
|
||||
<label class="flex items-center gap-1"><input type="checkbox" class="ram-priv-b2" value="20" onchange="updateRamPriv()"> Token Management</label>
|
||||
<label class="flex items-center gap-1"><input type="checkbox" class="ram-priv-b1" value="10" onchange="updateRamPriv()"> Card Lock</label>
|
||||
<label class="flex items-center gap-1"><input type="checkbox" class="ram-priv-b2" value="10" onchange="updateRamPriv()"> Global Delete</label>
|
||||
<label class="flex items-center gap-1"><input type="checkbox" class="ram-priv-b1" value="08" onchange="updateRamPriv()"> Card Terminate</label>
|
||||
@@ -468,6 +505,14 @@
|
||||
<option value="E0">With encryption, last block</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="ram-authp1-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300">Security level (P1)</label>
|
||||
<select id="ram-authp1" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
|
||||
<option value="00">00 — No secure messaging</option>
|
||||
<option value="01">01 — C-MAC</option>
|
||||
<option value="03">03 — C-DEC + C-MAC</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="ram-del-mode-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="DELETE mode">DELETE mode</label>
|
||||
<select id="ram-del-mode" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
|
||||
@@ -487,10 +532,8 @@
|
||||
<div id="ram-gs-p2-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="GET STATUS format">GET STATUS format</label>
|
||||
<select id="ram-gs-p2" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
|
||||
<option value="02">02 — First/all, GP v2.3 TLV format</option>
|
||||
<option value="03">03 — Next, GP v2.3 TLV format</option>
|
||||
<option value="40">40 — First/all, GP v2.2 TLV format</option>
|
||||
<option value="42">42 — Next, GP v2.2 TLV format</option>
|
||||
<option value="02">02 — First/all, TLV format</option>
|
||||
<option value="03">03 — Next, TLV format</option>
|
||||
<option value="00">00 — First/all, raw format (deprecated)</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -519,13 +562,19 @@
|
||||
<select id="ram-ss-mode" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
|
||||
<option value="80">ISD (80)</option>
|
||||
<option value="40">Application or SSD (40)</option>
|
||||
<option value="60">SD + associated apps (60)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="ram-ss-state-row" class="mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="State">State</label>
|
||||
<select id="ram-ss-state" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
|
||||
<option value="00">Unlock (previous state)</option>
|
||||
<option value="80">Lock (LOCKED)</option>
|
||||
<option value="00">00 — Unlock (previous state)</option>
|
||||
<option value="80">80 — Lock (LOCKED)</option>
|
||||
<option value="01">01 — OP_READY</option>
|
||||
<option value="07">07 — INITIALIZED</option>
|
||||
<option value="0F">0F — SECURED</option>
|
||||
<option value="7F">7F — CARD_LOCKED</option>
|
||||
<option value="FF">FF — TERMINATED</option>
|
||||
</select>
|
||||
</div>
|
||||
<button onclick="genRam()" class="mb-3 px-5 py-2.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700" data-l10n="Generate APDU">Generate APDU</button>
|
||||
@@ -1052,7 +1101,7 @@ document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
const RECORD_OPS = new Set(['update-record', 'read-record']);
|
||||
const BINARY_OPS = new Set(['update-binary', 'read-binary', 'erase-binary']);
|
||||
const READ_OPS = new Set(['read-record', 'read-binary']);
|
||||
const DATA_OPS = new Set(['update-record', 'update-binary', 'verify', 'change']);
|
||||
const DATA_OPS = new Set(['update-record', 'update-binary']);
|
||||
const RECSIZE_OPS = new Set(['update-record']);
|
||||
const PIN_OPS = new Set(['verify', 'change']);
|
||||
|
||||
@@ -1065,6 +1114,9 @@ function updateSimUsimFields(mode) {
|
||||
document.getElementById(mode + '-data-row').style.display = DATA_OPS.has(cmd) ? '' : 'none';
|
||||
document.getElementById(mode + '-recsize-row').style.display = RECSIZE_OPS.has(cmd) ? '' : 'none';
|
||||
document.getElementById(mode + '-pin-row').style.display = PIN_OPS.has(cmd) ? '' : 'none';
|
||||
document.getElementById(mode + '-pin-val-row').style.display = cmd === 'verify' ? '' : 'none';
|
||||
document.getElementById(mode + '-pin-old-row').style.display = cmd === 'change' ? '' : 'none';
|
||||
document.getElementById(mode + '-pin-new-row').style.display = cmd === 'change' ? '' : 'none';
|
||||
if (cmd === 'select') {
|
||||
document.getElementById(mode + '-select-checkbox-row').style.display = 'none';
|
||||
} else {
|
||||
@@ -1099,8 +1151,19 @@ function updateP1P2Display(mode) {
|
||||
const preset = mode === 'sim' ? {selP1: 0x00, selP2: 0x00} : {selP1: 0x00, selP2: 0x0C};
|
||||
const cmd = document.getElementById(mode + '-cmd').value;
|
||||
if (cmd === 'select') {
|
||||
document.getElementById(mode + '-p1').value = preset.selP1.toString(16).padStart(2, '0').toUpperCase();
|
||||
document.getElementById(mode + '-p2').value = preset.selP2.toString(16).padStart(2, '0').toUpperCase();
|
||||
const method = document.getElementById(mode + '-sel-method').value;
|
||||
let p1, p2;
|
||||
if (method === 'fid' || method === 'chain') {
|
||||
p1 = preset.selP1; p2 = preset.selP2;
|
||||
} else if (method === 'path') {
|
||||
p1 = 0x08; p2 = mode === 'sim' ? 0x00 : 0x04;
|
||||
} else if (method === 'dfname') {
|
||||
p1 = 0x04; p2 = mode === 'sim' ? 0x00 : 0x04;
|
||||
} else {
|
||||
p1 = preset.selP1; p2 = preset.selP2;
|
||||
}
|
||||
document.getElementById(mode + '-p1').value = p1.toString(16).padStart(2, '0').toUpperCase();
|
||||
document.getElementById(mode + '-p2').value = p2.toString(16).padStart(2, '0').toUpperCase();
|
||||
return;
|
||||
}
|
||||
const record = parseInt(document.getElementById(mode + '-record').value) || 1;
|
||||
@@ -1110,7 +1173,7 @@ function updateP1P2Display(mode) {
|
||||
const pin = document.getElementById(mode + '-pin').value;
|
||||
let p1, p2;
|
||||
if (cmd === 'read-record' || cmd === 'update-record') {
|
||||
p1 = record; p2 = recMode;
|
||||
p1 = (recMode === 0x02 || recMode === 0x03) ? 0x00 : record; p2 = recMode;
|
||||
} else if (cmd === 'read-binary' || cmd === 'update-binary' || cmd === 'erase-binary') {
|
||||
p1 = (offset >> 8) & 0xFF; p2 = offset & 0xFF;
|
||||
} else if (cmd === 'activate-file' || cmd === 'deactivate-file') {
|
||||
@@ -1127,13 +1190,17 @@ function updateP1P2Display(mode) {
|
||||
// ===== RAM field visibility =====
|
||||
function updateRamFields() {
|
||||
const cmd = document.getElementById('ram-cmd').value;
|
||||
document.getElementById('ram-priv-row').style.display = cmd === 'install-install' ? '' : 'none';
|
||||
const showPriv = ['install-install', 'install-make-sel', 'install-reg-update'].includes(cmd);
|
||||
document.getElementById('ram-priv-row').style.display = showPriv ? '' : 'none';
|
||||
document.getElementById('ram-toolkit-row').style.display = cmd === 'install-install' ? '' : 'none';
|
||||
document.getElementById('ram-sdaid-row').style.display = cmd === 'install-load' ? '' : 'none';
|
||||
document.getElementById('ram-sdaid-row').style.display = ['install-load', 'install-reg-update', 'install-extradition'].includes(cmd) ? '' : 'none';
|
||||
document.getElementById('ram-elfaid-row').style.display = cmd === 'install-install' ? '' : 'none';
|
||||
document.getElementById('ram-modaid-row').style.display = cmd === 'install-install' ? '' : 'none';
|
||||
document.getElementById('ram-load-data-row').style.display = cmd === 'install-load' ? '' : 'none';
|
||||
document.getElementById('ram-data-row').style.display = ['load', 'store-data', 'ext-auth', 'int-auth'].includes(cmd) ? '' : 'none';
|
||||
document.getElementById('ram-block-row').style.display = ['load', 'store-data'].includes(cmd) ? '' : 'none';
|
||||
document.getElementById('ram-enc-row').style.display = cmd === 'store-data' ? '' : 'none';
|
||||
document.getElementById('ram-authp1-row').style.display = cmd === 'ext-auth' ? '' : 'none';
|
||||
document.getElementById('ram-del-mode-row').style.display = cmd === 'delete' ? '' : 'none';
|
||||
document.getElementById('ram-gs-mode-row').style.display = cmd === 'get-status' ? '' : 'none';
|
||||
document.getElementById('ram-gs-p2-row').style.display = cmd === 'get-status' ? '' : 'none';
|
||||
@@ -1318,22 +1385,22 @@ function buildApdu(cla, ins, p1, p2, p3, data) {
|
||||
let s = cla.toString(16).padStart(2, '0').toUpperCase() +
|
||||
ins.toString(16).padStart(2, '0').toUpperCase() +
|
||||
p1.toString(16).padStart(2, '0').toUpperCase() +
|
||||
p2.toString(16).padStart(2, '0').toUpperCase() +
|
||||
p3.toString(16).padStart(2, '0').toUpperCase();
|
||||
p2.toString(16).padStart(2, '0').toUpperCase();
|
||||
if (p3 !== null && p3 !== undefined) s += p3.toString(16).padStart(2, '0').toUpperCase();
|
||||
if (data) s += data.toUpperCase();
|
||||
return s;
|
||||
}
|
||||
|
||||
function buildSelect(cla, p1, p2, fid) {
|
||||
const data = fid.length === 4 ? fid : '0000';
|
||||
return buildApdu(cla, 0xA4, p1, p2, 0x02, data) + '00';
|
||||
return buildApdu(cla, 0xA4, p1, p2, 0x02, data);
|
||||
}
|
||||
|
||||
function buildOpNoCla(ins, p1, p2, p3, data) {
|
||||
let s = ins.toString(16).padStart(2, '0').toUpperCase() +
|
||||
p1.toString(16).padStart(2, '0').toUpperCase() +
|
||||
p2.toString(16).padStart(2, '0').toUpperCase() +
|
||||
p3.toString(16).padStart(2, '0').toUpperCase();
|
||||
p2.toString(16).padStart(2, '0').toUpperCase();
|
||||
if (p3 !== null && p3 !== undefined) s += p3.toString(16).padStart(2, '0').toUpperCase();
|
||||
if (data) s += data.toUpperCase();
|
||||
return s;
|
||||
}
|
||||
@@ -1366,6 +1433,7 @@ function getSelValue(mode) {
|
||||
}
|
||||
|
||||
function buildSelectApdu(mode, preset, selInfo) {
|
||||
const isSim = preset.cla === 0xA0;
|
||||
if (!selInfo || !selInfo.value) return '';
|
||||
if (selInfo.type === 'fid') {
|
||||
if (selInfo.value.length !== 4) return '';
|
||||
@@ -1373,11 +1441,17 @@ function buildSelectApdu(mode, preset, selInfo) {
|
||||
} else if (selInfo.type === 'path') {
|
||||
if (selInfo.value.length < 2 || selInfo.value.length % 2 !== 0) return '';
|
||||
const p3 = selInfo.value.length / 2;
|
||||
return buildApdu(preset.cla, 0xA4, 0x08, 0x00, p3, selInfo.value) + '00';
|
||||
const p2 = isSim ? 0x00 : 0x04;
|
||||
let apdu = buildApdu(preset.cla, 0xA4, 0x08, p2, p3, selInfo.value);
|
||||
if (!isSim) apdu += '00';
|
||||
return apdu;
|
||||
} else if (selInfo.type === 'dfname') {
|
||||
if (selInfo.value.length < 2) return '';
|
||||
const p3 = selInfo.value.length / 2;
|
||||
return buildApdu(preset.cla, 0xA4, 0x04, 0x00, p3, selInfo.value) + '00';
|
||||
const p2 = isSim ? 0x00 : 0x04;
|
||||
let apdu = buildApdu(preset.cla, 0xA4, 0x04, p2, p3, selInfo.value);
|
||||
if (!isSim) apdu += '00';
|
||||
return apdu;
|
||||
} else if (selInfo.type === 'chain') {
|
||||
const fids = selInfo.value.split(',').map(f => f.trim().replace(/[^0-9a-fA-F]/g, '')).filter(f => f.length === 4);
|
||||
if (fids.length === 0) return '';
|
||||
@@ -1426,22 +1500,31 @@ function genSimUsim(mode) {
|
||||
|
||||
let p1, p2, p3, data = null;
|
||||
if (cmd === 'read-record') {
|
||||
p1 = record; p2 = recMode; p3 = le;
|
||||
p1 = (recMode === 0x02 || recMode === 0x03) ? 0x00 : record; p2 = recMode; p3 = le;
|
||||
} else if (cmd === 'read-binary') {
|
||||
p1 = (offset >> 8) & 0xFF; p2 = offset & 0xFF; p3 = le;
|
||||
} else if (cmd === 'update-record') {
|
||||
if (!dataHex) { resultEl.value = 'Error: specify data'; return; }
|
||||
p1 = record; p2 = recMode; p3 = dataHex.length / 2; data = dataHex;
|
||||
p1 = (recMode === 0x02 || recMode === 0x03) ? 0x00 : record; p2 = recMode; p3 = dataHex.length / 2; data = dataHex;
|
||||
} else if (cmd === 'update-binary') {
|
||||
if (!dataHex) { resultEl.value = 'Error: specify data'; return; }
|
||||
p1 = (offset >> 8) & 0xFF; p2 = offset & 0xFF; p3 = dataHex.length / 2; data = dataHex;
|
||||
} else if (cmd === 'erase-binary') {
|
||||
p1 = (offset >> 8) & 0xFF; p2 = offset & 0xFF; p3 = 0x00;
|
||||
} else if (cmd === 'activate-file' || cmd === 'deactivate-file') {
|
||||
p1 = 0x00; p2 = 0x00; p3 = 0x00;
|
||||
} else if (cmd === 'verify' || cmd === 'change') {
|
||||
if (!dataHex) { resultEl.value = 'Error: specify data'; return; }
|
||||
p1 = 0x00; p2 = parseInt(pin, 16); p3 = dataHex.length / 2; data = dataHex;
|
||||
p1 = 0x00; p2 = 0x00; p3 = null;
|
||||
} else if (cmd === 'verify') {
|
||||
const pinVal = document.getElementById(mode + '-pin-val').value || '';
|
||||
if (!pinVal) { resultEl.value = 'Error: specify PIN value'; return; }
|
||||
const pinHex = Array.from(pinVal).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('').padEnd(16, 'F').slice(0, 16);
|
||||
p1 = 0x00; p2 = parseInt(pin, 16); p3 = 0x08; data = pinHex;
|
||||
} else if (cmd === 'change') {
|
||||
const oldPin = document.getElementById(mode + '-pin-old').value || '';
|
||||
const newPin = document.getElementById(mode + '-pin-new').value || '';
|
||||
if (!oldPin || !newPin) { resultEl.value = 'Error: specify old and new PIN'; return; }
|
||||
const oldHex = Array.from(oldPin).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('').padEnd(16, 'F').slice(0, 16);
|
||||
const newHex = Array.from(newPin).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('').padEnd(16, 'F').slice(0, 16);
|
||||
p1 = 0x00; p2 = parseInt(pin, 16); p3 = 0x10; data = oldHex + newHex;
|
||||
}
|
||||
|
||||
if (override) {
|
||||
@@ -1469,6 +1552,8 @@ function genRam() {
|
||||
const cmd = document.getElementById('ram-cmd').value;
|
||||
const aid = (document.getElementById('ram-aid').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
const sdAid = (document.getElementById('ram-sdaid').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
const elfAid = (document.getElementById('ram-elfaid').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
const modAid = (document.getElementById('ram-modaid').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
const loadData = (document.getElementById('ram-load-data').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
const ramData = (document.getElementById('ram-data').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
const blockNum = parseInt(document.getElementById('ram-block').value) || 0;
|
||||
@@ -1476,134 +1561,163 @@ function genRam() {
|
||||
const delMode = document.getElementById('ram-del-mode').value;
|
||||
const gsMode = document.getElementById('ram-gs-mode').value;
|
||||
const tagHex = (document.getElementById('ram-tag').value || '5F50').replace(/[^0-9a-fA-F]/g, '').padStart(4, '0').slice(0, 4);
|
||||
const ssMode = document.getElementById('ram-ss-mode').value;
|
||||
const ssState = document.getElementById('ram-ss-state').value;
|
||||
const ssMode = parseInt(document.getElementById('ram-ss-mode').value, 16);
|
||||
const ssState = parseInt(document.getElementById('ram-ss-state').value, 16);
|
||||
|
||||
const resultEl = document.getElementById('ram-result');
|
||||
|
||||
if (cmd === 'install-load' && !sdAid) { resultEl.value = 'Error: specify SD AID'; return; }
|
||||
const INSTALL_P1 = {
|
||||
'install-load': 0x02,
|
||||
'install-install': 0x0C,
|
||||
'install-make-sel': 0x08,
|
||||
'install-reg-update': 0x40,
|
||||
'install-extradition': 0x10,
|
||||
};
|
||||
|
||||
const INSTALL_P1 = {
|
||||
'install-load': 0x02,
|
||||
'install-install': 0x0C,
|
||||
'install-make-sel': 0x08,
|
||||
'install-reg-update': 0x40,
|
||||
'install-extradition': 0x10,
|
||||
};
|
||||
function lv(data) {
|
||||
if (!data) return '00';
|
||||
return berLenStr(data.length / 2) + data;
|
||||
}
|
||||
|
||||
function computePriv() {
|
||||
const privHex = document.getElementById('ram-priv').value;
|
||||
const b1 = parseInt(privHex.substr(0, 2), 16) || 0;
|
||||
const b2 = privHex.length >= 4 ? parseInt(privHex.substr(2, 2), 16) : 0;
|
||||
const b3 = privHex.length >= 6 ? parseInt(privHex.substr(4, 2), 16) : 0;
|
||||
if (b2 === 0 && b3 === 0) return b1.toString(16).padStart(2, '0').toUpperCase();
|
||||
return b1.toString(16).padStart(2, '0').toUpperCase() + b2.toString(16).padStart(2, '0').toUpperCase() + b3.toString(16).padStart(2, '0').toUpperCase();
|
||||
}
|
||||
|
||||
function buildToolkitParams() {
|
||||
if (!document.getElementById('ram-toolkit-enable').checked) return '';
|
||||
const tkMode = document.getElementById('ram-tk-mode').value;
|
||||
const priority = parseInt(document.getElementById('ram-tk-priority').value) || 0;
|
||||
const timers = parseInt(document.getElementById('ram-tk-timers').value) || 0;
|
||||
const textLen = parseInt(document.getElementById('ram-tk-textlen').value) || 0;
|
||||
const menus = parseInt(document.getElementById('ram-tk-menus').value) || 0;
|
||||
const firstPos = parseInt(document.getElementById('ram-tk-firstpos').value) || 0;
|
||||
const firstId = (document.getElementById('ram-tk-firstid').value || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2) || '00';
|
||||
const lastPos = parseInt(document.getElementById('ram-tk-lastpos').value) || 0;
|
||||
const lastId = (document.getElementById('ram-tk-lastid').value || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2) || '00';
|
||||
const channels = parseInt(document.getElementById('ram-tk-channels').value) || 0;
|
||||
const msl = document.getElementById('ram-tk-msl').value;
|
||||
const tar = (document.getElementById('ram-tk-tar').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
const adRaw = (document.getElementById('ram-tk-ad').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
const ad = adRaw.padStart(2, '0').slice(0, 2);
|
||||
const services = parseInt(document.getElementById('ram-tk-services').value) || 0;
|
||||
|
||||
if (tar && tar.length % 6 !== 0) { resultEl.value = 'Error: TAR must be 3-byte multiples'; return null; }
|
||||
if (parseInt(firstId, 16) > 0x7F || parseInt(lastId, 16) > 0x7F) { resultEl.value = 'Error: menu IDs must be <= 7F'; return null; }
|
||||
|
||||
let menuPairs = '';
|
||||
for (let i = 1; i <= menus; i++) {
|
||||
if (i === 1) menuPairs += firstPos.toString(16).padStart(2, '0') + firstId;
|
||||
else if (i === menus) menuPairs += lastPos.toString(16).padStart(2, '0') + lastId;
|
||||
else menuPairs += '0000';
|
||||
}
|
||||
|
||||
const mslField = msl === '00' ? '00' : '0201' + msl;
|
||||
|
||||
let tkPayload = '';
|
||||
if (tkMode === 'ca') {
|
||||
tkPayload = (adRaw ? '01' + ad : '00') +
|
||||
priority.toString(16).padStart(2, '0') +
|
||||
timers.toString(16).padStart(2, '0') +
|
||||
textLen.toString(16).padStart(2, '0') +
|
||||
menus.toString(16).padStart(2, '0') +
|
||||
menuPairs +
|
||||
channels.toString(16).padStart(2, '0') +
|
||||
mslField +
|
||||
(tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00');
|
||||
const caTlv = 'CA' + berLenStr(tkPayload.length / 2) + tkPayload;
|
||||
return 'EF' + berLenStr(caTlv.length / 2) + caTlv;
|
||||
} else {
|
||||
tkPayload = priority.toString(16).padStart(2, '0') +
|
||||
timers.toString(16).padStart(2, '0') +
|
||||
textLen.toString(16).padStart(2, '0') +
|
||||
menus.toString(16).padStart(2, '0') +
|
||||
menuPairs +
|
||||
channels.toString(16).padStart(2, '0') +
|
||||
mslField +
|
||||
(tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00') +
|
||||
services.toString(16).padStart(2, '0');
|
||||
const innerTlv = '80' + berLenStr(tkPayload.length / 2) + tkPayload;
|
||||
return 'EA' + berLenStr(innerTlv.length / 2) + innerTlv;
|
||||
}
|
||||
}
|
||||
|
||||
let apdu = '';
|
||||
if (cmd === 'install-load') {
|
||||
const sdAidLen = (sdAid.length / 2).toString(16).padStart(2, '0');
|
||||
const aidLen = (aid.length / 2).toString(16).padStart(2, '0');
|
||||
let dataField = 'C9' + sdAidLen + sdAid + '5F50' + aidLen + aid;
|
||||
if (loadData) {
|
||||
const ldLen = (loadData.length / 2).toString(16).padStart(2, '0');
|
||||
dataField += 'C4' + ldLen + loadData;
|
||||
}
|
||||
const dataLen = (dataField.length / 2).toString(16).padStart(2, '0');
|
||||
apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField);
|
||||
if (!aid) { resultEl.value = 'Error: specify Load File AID'; return; }
|
||||
let dataField = lv(aid) + lv(sdAid) + '00' + (loadData ? lv(loadData) : '00') + '00';
|
||||
const dataLen = berLenStr(dataField.length / 2);
|
||||
apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField) + '00';
|
||||
} else if (cmd === 'install-install') {
|
||||
const aidLen = (aid.length / 2).toString(16).padStart(2, '0');
|
||||
let dataField = '4F' + aidLen + aid;
|
||||
const privHex = document.getElementById('ram-priv').value;
|
||||
const privLen = (privHex.length / 2).toString(16).padStart(2, '0');
|
||||
dataField += 'C7' + privLen + privHex;
|
||||
if (document.getElementById('ram-toolkit-enable').checked) {
|
||||
const tkMode = document.getElementById('ram-tk-mode').value;
|
||||
const priority = parseInt(document.getElementById('ram-tk-priority').value) || 0;
|
||||
const timers = parseInt(document.getElementById('ram-tk-timers').value) || 0;
|
||||
const textLen = parseInt(document.getElementById('ram-tk-textlen').value) || 0;
|
||||
const menus = parseInt(document.getElementById('ram-tk-menus').value) || 0;
|
||||
const firstPos = parseInt(document.getElementById('ram-tk-firstpos').value) || 0;
|
||||
const firstId = (document.getElementById('ram-tk-firstid').value || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2) || '00';
|
||||
const lastPos = parseInt(document.getElementById('ram-tk-lastpos').value) || 0;
|
||||
const lastId = (document.getElementById('ram-tk-lastid').value || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2) || '00';
|
||||
const channels = parseInt(document.getElementById('ram-tk-channels').value) || 0;
|
||||
const msl = document.getElementById('ram-tk-msl').value;
|
||||
const tar = (document.getElementById('ram-tk-tar').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
const adRaw = (document.getElementById('ram-tk-ad').value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
const ad = adRaw.padStart(2, '0').slice(0, 2);
|
||||
const services = parseInt(document.getElementById('ram-tk-services').value) || 0;
|
||||
let menuPairs = '';
|
||||
for (let i = 1; i <= menus; i++) {
|
||||
if (i === 1) menuPairs += firstPos.toString(16).padStart(2, '0') + firstId;
|
||||
else if (i === menus) menuPairs += lastPos.toString(16).padStart(2, '0') + lastId;
|
||||
else menuPairs += '0000';
|
||||
}
|
||||
let tkPayload = '';
|
||||
if (tkMode === 'ca') {
|
||||
tkPayload = (adRaw ? '01' + ad : '00') +
|
||||
priority.toString(16).padStart(2, '0') +
|
||||
timers.toString(16).padStart(2, '0') +
|
||||
textLen.toString(16).padStart(2, '0') +
|
||||
menus.toString(16).padStart(2, '0') +
|
||||
menuPairs +
|
||||
channels.toString(16).padStart(2, '0') +
|
||||
'01' + msl +
|
||||
(tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00');
|
||||
dataField += 'CA' + berLenStr(tkPayload.length / 2) + tkPayload;
|
||||
} else {
|
||||
tkPayload = priority.toString(16).padStart(2, '0') +
|
||||
timers.toString(16).padStart(2, '0') +
|
||||
textLen.toString(16).padStart(2, '0') +
|
||||
menus.toString(16).padStart(2, '0') +
|
||||
menuPairs +
|
||||
channels.toString(16).padStart(2, '0') +
|
||||
'01' + msl +
|
||||
(tar ? (tar.length / 2).toString(16).padStart(2, '0') + tar : '00') +
|
||||
services.toString(16).padStart(2, '0');
|
||||
const innerTlv = '80' + berLenStr(tkPayload.length / 2) + tkPayload;
|
||||
dataField += 'EA' + berLenStr(innerTlv.length / 2) + innerTlv;
|
||||
}
|
||||
}
|
||||
const dataLen = (dataField.length / 2).toString(16).padStart(2, '0');
|
||||
apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField);
|
||||
if (!aid) { resultEl.value = 'Error: specify AID'; return; }
|
||||
const privBytes = computePriv();
|
||||
let installParams = 'C900';
|
||||
const tkParams = buildToolkitParams();
|
||||
if (tkParams === null) return;
|
||||
if (tkParams) installParams += tkParams;
|
||||
let dataField = lv(elfAid) + lv(modAid) + lv(aid) + lv(privBytes) + lv(installParams) + '00';
|
||||
const dataLen = berLenStr(dataField.length / 2);
|
||||
apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField) + '00';
|
||||
} else if (cmd === 'install-make-sel') {
|
||||
if (!aid) { resultEl.value = 'Error: specify AID'; return; }
|
||||
const aidLen = (aid.length / 2).toString(16).padStart(2, '0');
|
||||
const dataField = '4F' + aidLen + aid;
|
||||
const dataLen = (dataField.length / 2).toString(16).padStart(2, '0');
|
||||
apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField);
|
||||
} else if (cmd === 'install-reg-update' || cmd === 'install-extradition') {
|
||||
const privBytes = computePriv();
|
||||
let dataField = '00' + '00' + lv(aid) + lv(privBytes) + '00' + '00';
|
||||
const dataLen = berLenStr(dataField.length / 2);
|
||||
apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField) + '00';
|
||||
} else if (cmd === 'install-reg-update') {
|
||||
if (!aid) { resultEl.value = 'Error: specify AID'; return; }
|
||||
const aidLen = (aid.length / 2).toString(16).padStart(2, '0');
|
||||
const dataField = '4F' + aidLen + aid;
|
||||
const dataLen = (dataField.length / 2).toString(16).padStart(2, '0');
|
||||
apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField);
|
||||
const privBytes = computePriv();
|
||||
let dataField = lv(sdAid) + '00' + lv(aid) + lv(privBytes) + '00' + '00';
|
||||
const dataLen = berLenStr(dataField.length / 2);
|
||||
apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField) + '00';
|
||||
} else if (cmd === 'install-extradition') {
|
||||
if (!sdAid || sdAid.length < 10 || sdAid.length > 20) { resultEl.value = 'Error: specify SD AID (5-10 bytes)'; return; }
|
||||
if (!aid) { resultEl.value = 'Error: specify AID'; return; }
|
||||
let dataField = lv(sdAid) + '00' + lv(aid) + '00' + '00' + '00';
|
||||
const dataLen = berLenStr(dataField.length / 2);
|
||||
apdu = buildApdu(0x80, 0xE6, INSTALL_P1[cmd], 0x00, parseInt(dataLen, 16), dataField) + '00';
|
||||
} else if (cmd === 'load') {
|
||||
if (!ramData) { resultEl.value = 'Error: specify data'; return; }
|
||||
const dataLen = (ramData.length / 2).toString(16).padStart(2, '0');
|
||||
apdu = buildApdu(0x80, 0xE8, 0x80, blockNum, parseInt(dataLen, 16), ramData);
|
||||
const dataLen = berLenStr(ramData.length / 2);
|
||||
apdu = buildApdu(0x80, 0xE8, 0x80, blockNum, parseInt(dataLen, 16), ramData) + '00';
|
||||
} else if (cmd === 'delete') {
|
||||
if (!aid) { resultEl.value = 'Error: specify AID'; return; }
|
||||
const aidLen = (aid.length / 2).toString(16).padStart(2, '0');
|
||||
const dataField = '4F' + aidLen + aid;
|
||||
const dataLen = (dataField.length / 2).toString(16).padStart(2, '0');
|
||||
apdu = buildApdu(0x80, 0xE4, 0x00, parseInt(delMode, 16), parseInt(dataLen, 16), dataField);
|
||||
const dataField = '4F' + berLenStr(aid.length / 2) + aid;
|
||||
const dataLen = berLenStr(dataField.length / 2);
|
||||
apdu = buildApdu(0x80, 0xE4, 0x00, parseInt(delMode, 16), parseInt(dataLen, 16), dataField) + '00';
|
||||
} else if (cmd === 'get-status') {
|
||||
const gsP2 = document.getElementById('ram-gs-p2').value;
|
||||
apdu = buildApdu(0x80, 0xF2, parseInt(gsMode, 16), parseInt(gsP2, 16), 0x02, '4F00');
|
||||
apdu = buildApdu(0x80, 0xF2, parseInt(gsMode, 16), parseInt(gsP2, 16), 0x02, '4F00') + '00';
|
||||
} else if (cmd === 'get-data') {
|
||||
const tag = parseInt(tagHex, 16);
|
||||
apdu = buildApdu(0x80, 0xCA, (tag >> 8) & 0xFF, tag & 0xFF, 0x00, null);
|
||||
if (tagHex === '2F00' || tagHex === '5031') {
|
||||
apdu = buildApdu(0x80, 0xCA, (tag >> 8) & 0xFF, tag & 0xFF, 0x02, '5C00') + '00';
|
||||
} else {
|
||||
apdu = buildApdu(0x80, 0xCA, (tag >> 8) & 0xFF, tag & 0xFF, 0x00, null) + '00';
|
||||
}
|
||||
} else if (cmd === 'store-data') {
|
||||
if (!ramData) { resultEl.value = 'Error: specify data'; return; }
|
||||
const dataLen = (ramData.length / 2).toString(16).padStart(2, '0');
|
||||
const dataLen = berLenStr(ramData.length / 2);
|
||||
apdu = buildApdu(0x80, 0xE2, parseInt(enc, 16), blockNum, parseInt(dataLen, 16), ramData);
|
||||
} else if (cmd === 'set-status') {
|
||||
if (!aid) { resultEl.value = 'Error: specify AID'; return; }
|
||||
const aidLen = (aid.length / 2).toString(16).padStart(2, '0');
|
||||
const dataField = '4F' + aidLen + aid;
|
||||
const dataLen = (dataField.length / 2).toString(16).padStart(2, '0');
|
||||
apdu = buildApdu(0x80, 0xF0, parseInt(ssMode, 16), parseInt(ssState, 16), parseInt(dataLen, 16), dataField);
|
||||
if (ssMode === 0x80) {
|
||||
apdu = buildApdu(0x80, 0xF0, 0x80, ssState, 0x00, null);
|
||||
} else {
|
||||
if (!aid) { resultEl.value = 'Error: specify AID'; return; }
|
||||
apdu = buildApdu(0x80, 0xF0, ssMode, ssState, aid.length / 2, aid);
|
||||
}
|
||||
} else if (cmd === 'ext-auth') {
|
||||
if (!ramData) { resultEl.value = 'Error: specify data (host cryptogram)'; return; }
|
||||
const dataLen = (ramData.length / 2).toString(16).padStart(2, '0');
|
||||
apdu = buildApdu(0x80, 0x82, 0x00, 0x00, parseInt(dataLen, 16), ramData);
|
||||
if (!ramData || ramData.length !== 32) { resultEl.value = 'Error: specify 16-byte data (host cryptogram + MAC)'; return; }
|
||||
const authP1 = parseInt(document.getElementById('ram-authp1').value, 16);
|
||||
apdu = buildApdu(0x84, 0x82, authP1, 0x00, 0x10, ramData);
|
||||
} else if (cmd === 'int-auth') {
|
||||
if (!ramData) { resultEl.value = 'Error: specify data (challenge)'; return; }
|
||||
const dataLen = (ramData.length / 2).toString(16).padStart(2, '0');
|
||||
apdu = buildApdu(0x80, 0x88, 0x00, 0x00, parseInt(dataLen, 16), ramData) + '00';
|
||||
const dataLen = berLenStr(ramData.length / 2);
|
||||
apdu = buildApdu(0x00, 0x88, 0x00, 0x00, parseInt(dataLen, 16), ramData) + '00';
|
||||
}
|
||||
|
||||
resultEl.value = apdu || 'Error: unknown command';
|
||||
@@ -2306,24 +2420,6 @@ function buildScriptChainingRow(rowIdx, hint = '') {
|
||||
Keep chaining info across reset (RFM)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 items-center">
|
||||
<span class="text-xs text-gray-600 dark:text-slate-400">Script ID:</span>
|
||||
<input class="chaining-script-id font-mono text-xs w-20 border border-gray-300 dark:border-slate-600 rounded px-2 py-1 dark:bg-slate-700 dark:text-slate-300"
|
||||
placeholder="${hint || ''}"
|
||||
maxlength="8"
|
||||
oninput="updateBerRow(this)">
|
||||
<span class="text-xs text-gray-400">(hex, 1-4 bytes)</span>
|
||||
<button type="button" class="text-xs text-blue-600 hover:underline"
|
||||
onclick="generateScriptIdHint(${rowIdx})">Hint</button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 items-center">
|
||||
<span class="text-xs text-gray-600 dark:text-slate-400">Additional Data:</span>
|
||||
<input class="chaining-additional font-mono text-xs flex-1 border border-gray-300 dark:border-slate-600 rounded px-2 py-1 dark:bg-slate-700 dark:text-slate-300"
|
||||
placeholder="hex (optional)"
|
||||
oninput="updateBerRow(this)">
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -2341,31 +2437,13 @@ function updateChainingFlags(rowIdx) {
|
||||
updateBerRow(document.querySelector(`[data-ber-idx="${rowIdx}"] .ber-type`));
|
||||
}
|
||||
|
||||
function generateScriptIdHint(rowIdx) {
|
||||
const row = document.querySelector(`[data-ber-idx="${rowIdx}"]`);
|
||||
if (!row) return;
|
||||
const scriptIdInput = row.querySelector('.chaining-script-id');
|
||||
if (!scriptIdInput) return;
|
||||
|
||||
const allIds = Array.from(document.querySelectorAll('.chaining-script-id'))
|
||||
.map(input => input.value || input.placeholder)
|
||||
.filter(id => id && id !== '');
|
||||
|
||||
const lastId = allIds.length ? allIds[allIds.length - 1] : '00000000';
|
||||
const num = parseInt(lastId, 16) + 1;
|
||||
const nextId = num.toString(16).padStart(8, '0').toUpperCase();
|
||||
scriptIdInput.placeholder = nextId;
|
||||
}
|
||||
|
||||
function genScriptChainingValue(row, tag) {
|
||||
const rowIdx = row.dataset.berIdx;
|
||||
const firstRadio = row.querySelector(`input[name="chaining-${rowIdx}"][value="first"]`);
|
||||
const intermediateRadio = row.querySelector(`input[name="chaining-${rowIdx}"][value="intermediary"]`);
|
||||
const lastCheckbox = row.querySelector(`input[name="chaining-${rowIdx}-last"]`);
|
||||
const keepCheckbox = row.querySelector(`input[name="chaining-${rowIdx}-keep"]`);
|
||||
const scriptIdInput = row.querySelector('.chaining-script-id');
|
||||
const additionalInput = row.querySelector('.chaining-additional');
|
||||
|
||||
|
||||
let flags = '';
|
||||
if (firstRadio && firstRadio.checked) {
|
||||
flags = (keepCheckbox && keepCheckbox.checked) ? '11' : '01';
|
||||
@@ -2373,21 +2451,8 @@ function genScriptChainingValue(row, tag) {
|
||||
flags = (lastCheckbox && lastCheckbox.checked) ? '03' : '02';
|
||||
}
|
||||
if (!flags) return '';
|
||||
|
||||
let value = flags;
|
||||
|
||||
const scriptId = (scriptIdInput.value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
if (scriptId) {
|
||||
value += scriptId;
|
||||
}
|
||||
|
||||
const additional = (additionalInput.value || '').replace(/[^0-9a-fA-F]/g, '');
|
||||
if (additional) {
|
||||
value += additional;
|
||||
}
|
||||
|
||||
if (!value || value === '00') return '';
|
||||
return tag + berLenStr(value.length/2) + value;
|
||||
|
||||
return tag + '01' + flags;
|
||||
}
|
||||
|
||||
function genBerPcValue(row, tag) {
|
||||
@@ -3096,7 +3161,7 @@ const LIFECYCLE_MAP = {
|
||||
|
||||
const PRIVILEGE_NAMES = [
|
||||
['Security Domain', 'DAP Verification', 'Delegated Management', 'Card Lock', 'Card Terminate', 'Card Reset', 'CVM Management', 'Mandated DAP Verification'],
|
||||
['Trusted Path', 'Authorized Management', 'Token Verification', 'Global Delete', 'Global Lock', 'Global Registry', 'Final Application', 'Global Service'],
|
||||
['Trusted Path', 'Authorized Management', 'Token Management', 'Global Delete', 'Global Lock', 'Global Registry', 'Final Application', 'Global Service'],
|
||||
['Receipt Generation', 'Ciphered Load File Data Block', 'Contactless Activation', 'Contactless Self-Activation'],
|
||||
];
|
||||
|
||||
@@ -5377,6 +5442,12 @@ const LANG_RU = {
|
||||
'Record size (bytes)': 'Размер записи (байт)',
|
||||
'Allow P1/P2 editing': 'Разрешить редактирование P1/P2',
|
||||
'Privileges': 'Привилегии',
|
||||
'PIN value': 'Значение PIN',
|
||||
'Old PIN': 'Старый PIN',
|
||||
'New PIN': 'Новый PIN',
|
||||
'ELF AID (hex)': 'ELF AID (hex)',
|
||||
'Module AID (hex)': 'AID модуля (hex)',
|
||||
'Security level (P1)': 'Уровень безопасности (P1)',
|
||||
'Byte 1': 'Байт 1',
|
||||
'Byte 2': 'Байт 2',
|
||||
'SIM/UICC Toolkit parameters': 'Параметры SIM/UICC Toolkit',
|
||||
|
||||
@@ -87,7 +87,7 @@ function genRamApdu() {
|
||||
|
||||
test('UICC toolkit nested inside EA (m=2, services 0)', () => {
|
||||
const apdu = genRamApdu();
|
||||
assert.ok(apdu.includes('EA128010000000020101020200011603B0000100'), apdu);
|
||||
assert.ok(apdu.includes('EA13801100000002010102020002011603B0000100'), apdu);
|
||||
});
|
||||
|
||||
test('UICC m=1 emits single pair', () => {
|
||||
@@ -111,7 +111,7 @@ test('UICC m=1 emits single pair', () => {
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
assert.ok(apdu.includes('EA10800E00000001010100011603B0000100'), apdu);
|
||||
assert.ok(apdu.includes('EA11800F0000000101010002011603B0000100'), apdu);
|
||||
});
|
||||
|
||||
test('UICC m=3 fills middle pair with 0000', () => {
|
||||
@@ -135,7 +135,7 @@ test('UICC m=3 fills middle pair with 0000', () => {
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
assert.ok(apdu.includes('EA1480120000000301010000030300011603B0000100'), apdu);
|
||||
assert.ok(apdu.includes('EA158013000000030101000003030002011603B0000100'), apdu);
|
||||
});
|
||||
|
||||
test('UICC services 7 appended as final byte', () => {
|
||||
@@ -159,7 +159,7 @@ test('UICC services 7 appended as final byte', () => {
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '7');
|
||||
const apdu = genRamResult();
|
||||
assert.ok(apdu.includes('EA128010000000020101020200011603B0000107'), apdu);
|
||||
assert.ok(apdu.includes('EA13801100000002010102020002011603B0000107'), apdu);
|
||||
});
|
||||
|
||||
test('SIM (CA) access domain FIRST, no services byte', () => {
|
||||
@@ -183,7 +183,7 @@ test('SIM (CA) access domain FIRST, no services byte', () => {
|
||||
set('ram-tk-ad', '5A');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
assert.ok(apdu.includes('CA11015A000000020101020200011603B00001'), apdu);
|
||||
assert.ok(apdu.includes('EF14CA12015A00000002010102020002011603B00001'), apdu);
|
||||
});
|
||||
|
||||
test('SIM (CA) blank access domain emits length byte 00', () => {
|
||||
@@ -207,7 +207,7 @@ test('SIM (CA) blank access domain emits length byte 00', () => {
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
assert.ok(apdu.includes('CA1000000000020101020200011603B00001'), apdu);
|
||||
assert.ok(apdu.includes('EF13CA110000000002010102020002011603B00001'), apdu);
|
||||
});
|
||||
|
||||
test('SIM (CA) m=3, no TAR, blank access domain', () => {
|
||||
@@ -231,7 +231,7 @@ test('SIM (CA) m=3, no TAR, blank access domain', () => {
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
assert.ok(apdu.includes('CA0F000000000301010000030300011600'), apdu);
|
||||
assert.ok(apdu.includes('EF12CA1000000000030101000003030002011600'), apdu);
|
||||
});
|
||||
|
||||
test('UICC m=60 uses long-form BER lengths (EA 81 87 / inner 81 84)', () => {
|
||||
@@ -255,7 +255,7 @@ test('UICC m=60 uses long-form BER lengths (EA 81 87 / inner 81 84)', () => {
|
||||
set('ram-tk-ad', '');
|
||||
set('ram-tk-services', '0');
|
||||
const apdu = genRamResult();
|
||||
assert.ok(apdu.includes('EA8187808184'), apdu);
|
||||
assert.ok(apdu.includes('EA8188808185'), apdu);
|
||||
});
|
||||
|
||||
test('LOAD P1 fixed to 80 (last block)', () => {
|
||||
|
||||
Reference in New Issue
Block a user