fix: correct the GP privilege decode and remove the duplicate (v3.6.28)

The RAM explorer's ramFmtPrivileges used a private table with wrong bits and
invented names ("Cum Deletion Ctr", "GSM Card Binding", "Global PIN",
"RFM"/"CFM") and no case for 0x80: a live ISD 9E printed four wrong names
and dropped Security Domain.  Per GP Card Spec v2.3.1 11.1.2 Tables
11-7/8/9, 9E = Security Domain, Card Lock, Card Terminate, Card Reset, CVM
Management.

- ramFmtPrivileges now delegates to decodePrivileges (one spec table).
- decodePrivileges decodes byte 1's DAP pair per Table 11-7: b8+b7 = DAP
  Verification, b8+b7+b1 = Mandated DAP Verification; the flag bits alone
  are no privilege.
- The file had two decodePrivileges copies (the runtime used the later one,
  the C-APDU parser test the earlier); the duplicate and its private table
  are gone - the single implementation plus PRIVILEGE_NAMES live in the
  parser section, which the test evaluates (converted to var like PARSE_INS).
- Tests: 9E/9A live values, the DAP pair, byte 2/3 spot checks; the
  PRIVILEGE_NAMES spec pin moved to apdu_parse.

659 frontend / 504 Python green; version 3.6.28; sw simple-v301.
This commit is contained in:
2026-09-28 23:29:01 +03:00
parent 85ba2a709d
commit 90fe409dec
7 changed files with 74 additions and 72 deletions
+19 -1
View File
@@ -50,7 +50,9 @@ for (const c of consts) {
}
const start = html.indexOf('// ===== C-APDU Parser =====');
const end = html.indexOf('// ===== Response Parser =====');
eval(prefix + html.slice(start, end).replace(/^const PARSE_INS = /m, 'var PARSE_INS = '));
eval(prefix + html.slice(start, end)
.replace(/^const PARSE_INS = /m, 'var PARSE_INS = ')
.replace(/^const PRIVILEGE_NAMES = /m, 'var PRIVILEGE_NAMES = '));
test('Compact RAM INSTALL [for install] UICC', () => {
const tree = parseHexTree('80E60C00214F08A000000151000000C70100EA128010000000020101020200011603B0000100');
@@ -227,10 +229,26 @@ test('decodeTextData explicit DCS 08 with malformed payload returns ?', () => {
assert.strictEqual(decodeTextData('0841'), '?');
});
test('PRIVILEGE_NAMES matches GPC v2.3 Tables 11-7/11-8/11-9', () => {
assert.strictEqual(PRIVILEGE_NAMES[0][7], 'Mandated DAP Verification');
assert.strictEqual(PRIVILEGE_NAMES[1][2], 'Token Verification');
assert.strictEqual(PRIVILEGE_NAMES[1][7], 'Global Service');
assert.strictEqual(PRIVILEGE_NAMES[2][0], 'Receipt Generation');
assert.strictEqual(PRIVILEGE_NAMES[2][3], 'Contactless Self-Activation');
});
test('decodePrivileges byte 2 b6 is Token Verification per GPC v2.3 Table 11-8', () => {
assert.ok(decodePrivileges('0020').includes('Token Verification'));
});
test('decodePrivileges byte 1: DAP Verification vs Mandated DAP (GP Table 11-7)', () => {
assert.strictEqual(decodePrivileges('80'), 'Security Domain');
assert.strictEqual(decodePrivileges('C0'), 'Security Domain, DAP Verification');
assert.strictEqual(decodePrivileges('C1'), 'Security Domain, Mandated DAP Verification');
assert.strictEqual(decodePrivileges('40'), 'None'); // b7 alone is no privilege
assert.strictEqual(decodePrivileges('01'), 'None'); // b1 alone is only the flag
});
test('decodePrivileges', () => {
assert.ok(decodePrivileges('00').includes('None'));
assert.ok(decodePrivileges('80').includes('Security Domain'));
+20 -1
View File
@@ -25,7 +25,7 @@ function extractFunc(src, name) {
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramStepLine', 'ramGetStatusApdu', 'ramDeleteApdu',
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'parseTLV', '_parseE3Entry',
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute',
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges',
'jcAidNorm', 'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
let code = '';
for (const f of FNS) {
@@ -35,6 +35,8 @@ const m = html.match(/const _chains = \{\};/);
if (m) code += m[0].replace(/^const /, 'var ') + '\n';
const lc = html.match(/const RAM_LIFECYCLE = \{[\s\S]*?\n\};/);
if (lc) code += lc[0].replace(/^const /, 'var ') + '\n';
const pn = html.match(/const PRIVILEGE_NAMES = \[[\s\S]*?\n\];/);
if (pn) code += pn[0].replace(/^const /, 'var ') + '\n';
const an = html.match(/const JC_AID_NAMES = \{[\s\S]*?\n\};/);
if (an) code += an[0].replace(/^const /, 'var ') + '\n';
const ar = html.match(/const JC_AID_RIDS = \{[\s\S]*?\n\};/);
@@ -231,6 +233,23 @@ test('ramRenderExploreHtml annotates standard package AIDs, vendor AIDs stay bar
assert.ok(out.includes('(GlobalPlatform RID)'), out);
});
test('ramFmtPrivileges decodes the GP privilege bytes (live ISD values)', () => {
global.t = s => s;
// GP Table 11-7: 0x9E = Security Domain + Card Lock + Card Terminate +
// Card Reset + CVM Management (the live ISD privilege byte)
assert.strictEqual(ramFmtPrivileges('9E'),
'Security Domain, Card Lock, Card Terminate, Card Reset, CVM Management');
assert.strictEqual(ramFmtPrivileges('9A'),
'Security Domain, Card Lock, Card Terminate, CVM Management');
// DAP Verification (b8+b7) vs Mandated DAP Verification (b8+b7+b1)
assert.strictEqual(ramFmtPrivileges('C0'), 'Security Domain, DAP Verification');
assert.strictEqual(ramFmtPrivileges('C1'), 'Security Domain, Mandated DAP Verification');
// byte 2 / byte 3 spot checks
assert.strictEqual(ramFmtPrivileges('0001'), 'Global Service');
assert.strictEqual(ramFmtPrivileges('000080'), 'Receipt Generation');
delete global.t;
});
test('ramFmtPrivileges uses the translated (none) placeholder', () => {
global.t = s => 'XX' + s;
assert.strictEqual(ramFmtPrivileges(''), 'XX(none)');
-8
View File
@@ -48,14 +48,6 @@ test('LIFECYCLE_MAP per GPC v2.3 life cycles', () => {
assert.strictEqual(LIFECYCLE_MAP['83'], 'LOCKED (SD)');
});
test('PRIVILEGE_NAMES matches GPC v2.3 Tables 11-7/11-8/11-9', () => {
assert.strictEqual(PRIVILEGE_NAMES[0][7], 'Mandated DAP Verification');
assert.strictEqual(PRIVILEGE_NAMES[1][2], 'Token Verification');
assert.strictEqual(PRIVILEGE_NAMES[1][7], 'Global Service');
assert.strictEqual(PRIVILEGE_NAMES[2][0], 'Receipt Generation');
assert.strictEqual(PRIVILEGE_NAMES[2][3], 'Contactless Self-Activation');
});
test('TS 51.011 SIM families resolve (94xx/98xx/92xx/9Exx/9Fxx)', () => {
assert.ok(lookupSw('94', '04', 'uicc').includes('not found'));
assert.ok(lookupSw('94', '00', 'uicc').includes('No EF selected'));