C-APDU parser + Response parser alignment with updated spec registry
PARSE_INS: +24 command labels per TS 102 221 table 10.5 / TS 102 222 /
GPC (TERMINAL PROFILE, ENVELOPE, FETCH, TERMINAL RESPONSE, SEARCH RECORD,
INCREASE, RETRIEVE DATA, PUT/SET DATA, GET CHALLENGE, AUTHENTICATE odd
INS, SUSPEND UICC, TERMINAL CAPABILITY, MANAGE SECURE CHANNEL, TRANSACT
DATA, GET IDENTITY, EXCHANGE CAPABILITIES, MANAGE LSI, CREATE FILE,
RESIZE FILE 'D4' (registry fixes 'D8' typo), INITIALIZE UPDATE, PUT KEY,
MANAGE CHANNEL; DISABLE/ENABLE/UNBLOCK PIN). INS '88' relabelled
AUTHENTICATE per TS 102 221 §11.1.16.
describeP1P2: SELECT P2 response field per ETSI b5-b3 ('001' FCP /
'011' none); READ/UPDATE BINARY offset / SFI-mode descriptors.
Response parser maps:
- LIFECYCLE_MAP: SD PERSONALIZED is '1F'; '0F' = SECURED (card);
'01' dual-context OP_READY (card) / LOADED (ELF)
- SW_MAP.uicc: add '91XX' wildcard (proactive lengths vary)
- SW_MAP.generic: add 6A85 / 6A89 / 6A8A; 63CX notes blocked/no-counter
- SW_MAP.gp: reword stale 6310 (P2=42 option was removed as RFU)
This commit is contained in:
+42
-7
@@ -2544,16 +2544,42 @@ const PARSE_INS = {
|
||||
'04': {name:'DEACTIVATE FILE', data:'none'},
|
||||
'20': {name:'VERIFY', data:'lc'},
|
||||
'24': {name:'CHANGE', data:'lc'},
|
||||
'26': {name:'DISABLE PIN', data:'lc'},
|
||||
'28': {name:'ENABLE PIN', data:'lc'},
|
||||
'2C': {name:'UNBLOCK PIN', data:'lc'},
|
||||
'E6': {name:'INSTALL', data:'lc'},
|
||||
'E8': {name:'LOAD', data:'lc'},
|
||||
'D4': {name:'RESIZE FILE', data:'lc'},
|
||||
'E0': {name:'CREATE FILE', data:'lc'},
|
||||
'E4': {name:'DELETE', data:'lc'},
|
||||
'F2': {name:'GET STATUS', data:'lc'},
|
||||
'CA': {name:'GET DATA', data:'none'},
|
||||
'DA': {name:'PUT DATA', data:'lc'},
|
||||
'CB': {name:'RETRIEVE DATA (GET DATA)', data:'le'},
|
||||
'DB': {name:'SET DATA', data:'lc'},
|
||||
'A2': {name:'SEARCH RECORD', data:'lc'},
|
||||
'32': {name:'INCREASE', data:'lc'},
|
||||
'84': {name:'GET CHALLENGE', data:'le'},
|
||||
'88': {name:'AUTHENTICATE', data:'lc'},
|
||||
'89': {name:'AUTHENTICATE (odd INS)', data:'lc'},
|
||||
'E2': {name:'STORE DATA', data:'lc'},
|
||||
'F0': {name:'SET STATUS', data:'lc'},
|
||||
'82': {name:'EXTERNAL AUTHENTICATE', data:'lc'},
|
||||
'88': {name:'INTERNAL AUTHENTICATE', data:'lc'},
|
||||
'C0': {name:'GET RESPONSE', data:'le'},
|
||||
'10': {name:'TERMINAL PROFILE', data:'lc'},
|
||||
'C2': {name:'ENVELOPE', data:'lc'},
|
||||
'12': {name:'FETCH', data:'le'},
|
||||
'14': {name:'TERMINAL RESPONSE', data:'lc'},
|
||||
'76': {name:'SUSPEND UICC', data:'lc'},
|
||||
'AA': {name:'TERMINAL CAPABILITY', data:'lc'},
|
||||
'73': {name:'MANAGE SECURE CHANNEL', data:'lc'},
|
||||
'75': {name:'TRANSACT DATA', data:'lc'},
|
||||
'78': {name:'GET IDENTITY', data:'le'},
|
||||
'7A': {name:'EXCHANGE CAPABILITIES', data:'lc'},
|
||||
'7C': {name:'MANAGE LSI', data:'lc'},
|
||||
'50': {name:'INITIALIZE UPDATE', data:'lc'},
|
||||
'D8': {name:'PUT KEY', data:'lc'},
|
||||
'70': {name:'MANAGE CHANNEL', data:'none'},
|
||||
};
|
||||
|
||||
function parseBerLen(hex, i) {
|
||||
@@ -2905,13 +2931,17 @@ function describeP1P2(ins, p1, p2) {
|
||||
let d1 = '', d2 = '';
|
||||
if (ins === 'A4') {
|
||||
d1 = {'00':'by FID','01':'child DF','03':'parent DF','04':'by DF name (AID)','08':'path from MF','09':'path from current DF'}[p1] || '';
|
||||
const resp = (v2 >> 2) & 0x03;
|
||||
const resp = (v2 >> 2) & 0x07;
|
||||
if (resp === 0x01) d2 = 'return FCP template';
|
||||
else if (resp === 0x03) d2 = 'no response data';
|
||||
if (v1 === 0x04) {
|
||||
const occName = {'00':'first or only occurrence','01':'last occurrence','10':'next occurrence','11':'previous occurrence'}[(v2 & 0x03).toString(2).padStart(2,'0')];
|
||||
if (occName) d2 += (d2 ? ', ' : '') + occName;
|
||||
}
|
||||
} else if (ins === 'B0' || ins === 'D6') {
|
||||
if (v1 & 0x80) d1 = 'SFI mode, SFI ' + (v1 & 0x1F);
|
||||
else d1 = 'offset high';
|
||||
d2 = 'offset low';
|
||||
} else if (ins === 'B2' || ins === 'DC') {
|
||||
const modeKey = (v2 & 0x07).toString(16).padStart(2, '0');
|
||||
d2 = {'02':'next record','03':'previous record','04':'absolute/current'}[modeKey] || '';
|
||||
@@ -3262,7 +3292,7 @@ const SW_MAP = {
|
||||
'62F3': 'Warning: Response data available',
|
||||
'63F1': 'Warning: More data expected',
|
||||
'63F2': 'Warning: More data expected and proactive command pending',
|
||||
'63CX': 'Verification failed, X retries remaining (SW2 & 0x0F = tries)',
|
||||
'63CX': 'Verification failed, X retries remaining (SW2 & 0x0F = tries; X=0: no counter provided / blocked for VERIFY)',
|
||||
'6400': 'No information given, state of NV memory unchanged',
|
||||
'6500': 'No information given, state of NV memory changed',
|
||||
'6581': 'Memory problem',
|
||||
@@ -3285,7 +3315,10 @@ const SW_MAP = {
|
||||
'6A84': 'Not enough memory space',
|
||||
'6A86': 'Incorrect parameters P1-P2',
|
||||
'6A87': 'Lc inconsistent with P1-P2',
|
||||
'6A85': 'Nc inconsistent with TLV data object',
|
||||
'6A88': 'Referenced data not found',
|
||||
'6A89': 'File already exists',
|
||||
'6A8A': 'DF name already exists',
|
||||
'6B00': 'Wrong parameter(s) P1-P2',
|
||||
'6D00': 'Instruction code not supported or invalid',
|
||||
'6E00': 'Class not supported',
|
||||
@@ -3296,7 +3329,7 @@ const SW_MAP = {
|
||||
'9864': 'Minimum UICC suspension time is too long',
|
||||
},
|
||||
gp: {
|
||||
'6310': 'More data available — send GET STATUS [next] (P2=42)',
|
||||
'6310': 'More data available — issue GET STATUS [get next occurrence]',
|
||||
'6581': 'Memory failure',
|
||||
'6A80': 'Incorrect values in command data',
|
||||
'6A81': 'Function not supported (card Life Cycle State is CARD_LOCKED)',
|
||||
@@ -3313,7 +3346,8 @@ const SW_MAP = {
|
||||
'6283': 'Warning: Card Life Cycle State is CARD_LOCKED',
|
||||
},
|
||||
uicc: {
|
||||
'9100': 'Normal ending with proactive UICC command pending (SW2 = length of response data)',
|
||||
'91XX': 'Normal ending with proactive UICC command pending (SW2 = length of response data)',
|
||||
'9100': 'Normal ending with proactive UICC command pending (no response data)',
|
||||
'92XX': 'Normal ending with ongoing data transfer session info',
|
||||
'9300': 'SIM Application Toolkit is busy',
|
||||
'62F1': 'More data available (ENVELOPE, RETRIEVE DATA, SET DATA)',
|
||||
@@ -3381,10 +3415,11 @@ const SW_CMD_NAMES = {
|
||||
};
|
||||
|
||||
const LIFECYCLE_MAP = {
|
||||
'01': 'LOADED (ELF)',
|
||||
'01': 'OP_READY (card) / LOADED (ELF)',
|
||||
'03': 'INSTALLED',
|
||||
'07': 'SELECTABLE',
|
||||
'0F': 'PERSONALIZED (SD) / SECURED (card)',
|
||||
'0F': 'SECURED (card)',
|
||||
'1F': 'PERSONALIZED (SD)',
|
||||
'7F': 'CARD_LOCKED',
|
||||
'FF': 'TERMINATED',
|
||||
'83': 'LOCKED (SD)',
|
||||
|
||||
@@ -140,7 +140,7 @@ test('INTERNAL AUTHENTICATE', () => {
|
||||
assert.strictEqual(tree.children.length, 1);
|
||||
const apdu = tree.children[0];
|
||||
assert.strictEqual(apdu.label, 'APDU');
|
||||
assert.ok(findNode(apdu, 'INS').desc.includes('INTERNAL AUTHENTICATE'));
|
||||
assert.ok(findNode(apdu, 'INS').desc.includes('AUTHENTICATE'));
|
||||
assert.ok(findNode(apdu, 'Data'));
|
||||
});
|
||||
|
||||
@@ -327,4 +327,36 @@ test('CLA 84-87 labeled GlobalPlatform secure messaging', () => {
|
||||
const tree = parseHexTree('8482030010' + '00112233445566778899AABBCCDDEEFF');
|
||||
const apdu = tree.children[0];
|
||||
assert.strictEqual(findNode(apdu, 'CLA').desc, 'GlobalPlatform (secure messaging)');
|
||||
});
|
||||
});
|
||||
test('New PARSE_INS labels decode (FETCH le, RESIZE D4, RETRIEVE CB, AUTH 89)', () => {
|
||||
const fetchTree = parseHexTree('8012000020');
|
||||
assert.ok(findNode(fetchTree.children[0], 'INS').desc.includes('FETCH'));
|
||||
assert.ok(findNode(fetchTree.children[0], 'Le'));
|
||||
|
||||
const resizeTree = parseHexTree('80D4000004' + '00000FA0');
|
||||
assert.ok(findNode(resizeTree.children[0], 'INS').desc.includes('RESIZE FILE'));
|
||||
assert.strictEqual(findNode(resizeTree.children[0], 'Data').desc, '00000FA0');
|
||||
|
||||
const retrTree = parseHexTree('80CB000000');
|
||||
assert.ok(findNode(retrTree.children[0], 'INS').desc.includes('RETRIEVE DATA'));
|
||||
|
||||
const authTree = parseHexTree('0089000008' + '0011223344556677');
|
||||
assert.ok(findNode(authTree.children[0], 'INS').desc.includes('odd INS'));
|
||||
});
|
||||
|
||||
test('READ BINARY SFI mode and offset descriptors', () => {
|
||||
const sfi = parseHexTree('00B0830602');
|
||||
const p1sfi = findNode(sfi.children[0], 'P1').desc;
|
||||
assert.ok(p1sfi.includes('SFI') && p1sfi.includes('3'), p1sfi);
|
||||
assert.strictEqual(findNode(sfi.children[0], 'P2').desc, 'offset low');
|
||||
|
||||
const off = parseHexTree('00B0010202');
|
||||
assert.strictEqual(findNode(off.children[0], 'P1').desc, 'offset high');
|
||||
});
|
||||
|
||||
test('SELECT P2 response field per ETSI b5-b3', () => {
|
||||
const fcp = parseHexTree('00A40804047FFF6FC500');
|
||||
assert.ok(findNode(fcp.children[0], 'P2').desc.includes('FCP'));
|
||||
const none = parseHexTree('00A4000C026F3B');
|
||||
assert.ok(findNode(none.children[0], 'P2').desc.includes('no response data'));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user