diff --git a/frontend/index.html b/frontend/index.html index 7c6772b..26a7eec 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -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)', diff --git a/frontend/tests/apdu_parse.test.js b/frontend/tests/apdu_parse.test.js index 82836bd..9e11846 100644 --- a/frontend/tests/apdu_parse.test.js +++ b/frontend/tests/apdu_parse.test.js @@ -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)'); -}); \ No newline at end of file +}); +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')); +});