      1 /**
      2  *  ---------
      3  * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
      4  * |#       #|
      5  * |#       #|  Copyright (c) 1999-2018 CardContact Software & System Consulting
      6  * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
      7  *  ---------
      8  *
      9  *  This file is part of OpenSCDP.
     10  *
     11  *  OpenSCDP is free software; you can redistribute it and/or modify
     12  *  it under the terms of the GNU General Public License version 2 as
     13  *  published by the Free Software Foundation.
     14  *
     15  *  OpenSCDP is distributed in the hope that it will be useful,
     16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18  *  GNU General Public License for more details.
     19  *
     20  *  You should have received a copy of the GNU General Public License
     21  *  along with OpenSCDP; if not, write to the Free Software
     22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     23  *
     24  * @fileoverview Implementation of a ISO 7816-4 file system simulation
     25  */
     26
     27 var APDU                = require('scsh/cardsim/APDU').APDU;
     28 var FCP                 = require('scsh/cardsim/FCP').FCP;
     29 var DF                  = require('scsh/cardsim/DF').DF
     30 var TransparentEF       = require('scsh/cardsim/TransparentEF').TransparentEF;
     31 var SecurityEnvironment = require('scsh/cardsim/SecurityEnvironment').SecurityEnvironment;
     32
     33
     34
     35 /**
     36  * Create a file selector object
     37  *
     38  * @class Class implementing a file selector used to store information about the currently selected
     39  *        file system object and to process the SELECT APDU
     40  * @constructor
     41  * @param {DF} mf the master file
     42  */
     43 function FileSelector(mf) {
     44 	if ((typeof(mf) != "object") && !(mf instanceof DF)) {
     45 		throw new GPError("FileSelector", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument 1 must be of type DF");
     46 	}
     47
     48 	this.mf = mf;
     49 	this.selectMF();
     50
     51 	this.se = { VEXK: new SecurityEnvironment(), CDIK: new SecurityEnvironment(), SMRES: new SecurityEnvironment(), SMCOM: new SecurityEnvironment()};
     52 	this.globalAuthenticationState = [];
     53 }
     54
     55 exports.FileSelector = FileSelector;
     56
     57
     58
     59 /**
     60  * Returns the current EF, if any
     61  *
     62  * @type EF
     63  * @return the current EF or null
     64  */
     65 FileSelector.prototype.getCurrentEF = function() {
     66 	return this.currentEF;
     67 }
     68
     69
     70
     71 /**
     72  * Return the current security environment
     73  *
     74  * @type Object
     75  * @returns Object with properties VEXK, CDIK, SMRES and SMCOM containing SecurityEnvironment objects
     76  */
     77 FileSelector.prototype.getSecurityEnvironment = function() {
     78 	return this.se;
     79 }
     80
     81
     82
     83 /**
     84  * Return meta data associated with the current DF or MF
     85  *
     86  * @param {String} name the meta data name
     87  * @type Object
     88  * @returns The meta data
     89  */
     90 FileSelector.prototype.getMeta = function(name) {
     91 	var meta;
     92
     93 	if (this.currentDF) {
     94 //		print("DF selected: " + this.currentDF);
     95 		var meta = this.currentDF.meta[name];
     96 //		print("Found: " + meta);
     97 	}
     98
     99 	if (!meta) {
    100 		meta = this.mf.meta[name];
    101 	}
    102 	return meta;
    103 }
    104
    105
    106
    107 /**
    108  * Return object of given type identified by id
    109  *
    110  * <p>If bit b8 in the id is 1, then the search will start in the current DF. If the object
    111  *    is not found, the search is continued in the MF. If the bit is not set, then the search
    112  *    will only look into the MF.</p>
    113  *
    114  * @param {String} type the type of the object
    115  * @param {Number} id the id, bit b8 indicating local DF or global MF search
    116  * @type {Object}
    117  * @returns the object of the requested type or null if not found
    118  */
    119 FileSelector.prototype.getObject = function(type, id) {
    120 	var olist;
    121
    122 	if (id & 0x80) {
    123 		olist = this.currentDF.meta[type];
    124 		if (olist) {
    125 			var o = olist[id & 0x7F];
    126
    127 			if (o) {
    128 				return o;
    129 			}
    130 		}
    131 	}
    132
    133 	olist = this.mf.meta[type];
    134 	if (olist) {
    135 		var o = olist[id & 0x7F];
    136
    137 		if (o) {
    138 			return o;
    139 		}
    140 	}
    141 	return null;
    142 }
    143
    144
    145
    146 /**
    147  * Enumerate objects of a defined type
    148  *
    149  * @param {String} type the type of the object
    150  * @type {Number[]}
    151  * @returns the list of objects found
    152  */
    153 FileSelector.prototype.enumerateObjects = function(type) {
    154 	var idlist = [];
    155
    156 	if (this.mf != this.currentDF) {
    157 		for each (var o in this.currentDF.meta[type]) {
    158 			idlist.push(o.getId());
    159 		}
    160 	}
    161
    162 	for each (var o in this.mf.meta[type]) {
    163 		idlist.push(o.getId());
    164 	}
    165
    166 	return idlist;
    167 }
    168
    169
    170
    171 /**
    172  * Add authenticated object to the list of authentication states for the local DF or global MF
    173  *
    174  * @param{boolean} global true if global state else local DF state
    175  * @param{AuthenticationObject} ao the authentication object for which authentication was successfull
    176  */
    177 FileSelector.prototype.addAuthenticationState = function(global, ao) {
    178 	if (global) {
    179 		this.globalAuthenticationState.push(ao);
    180 	} else {
    181 		this.localAuthenticationState.push(ao);
    182 	}
    183 }
    184
    185
    186
    187 /**
    188  * Add authenticated object to the list of authentication states for the local DF or global MF
    189  *
    190  * @param{boolean} global true if global state else local DF state
    191  * @param{AuthenticationObject} ao the authentication object for which authentication was successfull
    192  */
    193 FileSelector.prototype.isAuthenticated = function(global, ao) {
    194 	if (global) {
    195 		var list = this.globalAuthenticationState;
    196 	} else {
    197 		var list = this.localAuthenticationState;
    198 	}
    199 	for each (var aao in list) {
    200 		if (aao === ao) {
    201 			return true;
    202 		}
    203 	}
    204 	return false;
    205 }
    206
    207
    208
    209 /**
    210  * Select the MF
    211  */
    212 FileSelector.prototype.selectMF = function() {
    213 	this.currentDF = this.mf;
    214 	this.currentEF = null;
    215 	this.localAuthenticationState = [];
    216
    217 	return this.mf;
    218 }
    219
    220
    221
    222 /**
    223  * Select a DF entry by FID
    224  *
    225  * @param {ByteString} fid the file identifier
    226  * @param {boolean} check if file matches expected type EF or DF
    227  * @param {boolean} df true if the check must check for a DF type, else a EF type
    228  * @type FSNode
    229  * @return the selected file system node
    230  */
    231 FileSelector.prototype.selectFID = function(fid, check, df) {
    232 	var node = this.currentDF.selectByFID(fid);
    233
    234 	if (!node) {
    235 		throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "File " + fid + " not found");
    236 	}
    237
    238 	if (check) {
    239 		if ((df && !node.isDF()) || (!df && node.isDF())) {
    240 			throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "File " + fid + " not found or not of matching type");
    241 		}
    242 	}
    243
    244 	if (node.isDF()) {
    245 		this.currentDF = node;
    246 		this.localAuthenticationState = [];
    247 		this.currentEF = null;
    248 	} else {
    249 		this.currentEF = node;
    250 	}
    251 	return node;
    252 }
    253
    254
    255
    256 /**
    257  * Select a DF entry by SFI
    258  *
    259  * @param {Number} sfi the short file identifier
    260  * @type FSNode
    261  * @return the selected file system node
    262  */
    263 FileSelector.prototype.selectSFI = function(sfi) {
    264 	var node = this.currentDF.selectBySFI(sfi);
    265
    266 	if (!node) {
    267 		throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "File with SFI " + sfi + " not found");
    268 	}
    269
    270 	this.currentEF = node;
    271 	return node;
    272 }
    273
    274
    275
    276 /**
    277  * Processes the SELECT APDU
    278  *
    279  * <p>Supports in P1</p>
    280  * <ul>
    281  *  <li>'00' with empty data to select the MF</li>
    282  *  <li>'00' with "3F00" to select the MF</li>
    283  *  <li>'00' with fid to select an entry in the current DF</li>
    284  *  <li>'01' with fid to select a DF in the current DF</li>
    285  *  <li>'02' with fid to select an EF in the current DF</li>
    286  *  <li>'03' with empty data to select the parent</li>
    287  * </ul>
    288  * <p>Supports in P2</p>
    289  * <ul>
    290  *  <li>'00' with P1=='00' return no data</li>
    291  *  <li>'04' return FCP</li>
    292  *  <li>'0C' return no data</li>
    293  * </ul>
    294  * @param {APDU} apdu the select APDU
    295  */
    296 FileSelector.prototype.processSelectAPDU = function(apdu) {
    297 	var node;
    298
    299 	var p2 = apdu.getP2();
    300 	if ((p2 != 0x00) && (p2 != 0x04) && (p2 != 0x0C)) {
    301 		throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Incorrect parameter P2 (" + p2.toString(16) + ")");
    302 	}
    303
    304 	var data = apdu.getCData();
    305 	var p1 = apdu.getP1();
    306 	switch(p1) {
    307 	case 0x00:
    308 		if ((typeof(data) == "undefined") || (data.toString(HEX) == "3F00")) {
    309 			node = this.selectMF();
    310 		} else {
    311 			node = this.selectFID(data, false, false);
    312 		}
    313 		break;
    314 	case 0x01:
    315 		node = this.selectFID(data, true, true);
    316 		break;
    317 	case 0x02:
    318 		node = this.selectFID(data, true, false);
    319 		break;
    320 	case 0x03:
    321 		if (typeof(data) != "undefined") {
    322 			throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.APDU.SW_INVDATA, "Empty C-Data expected");
    323 		}
    324
    325 		node = this.currentDF.getParent();
    326 		if (node) {
    327 			this.currentDF = node;
    328 			this.localAuthenticationState = [];
    329 		} else {
    330 			node = this.currentDF;
    331 		}
    332 		this.currentEF = null;
    333 		break;
    334 	case 0x04:
    335 		node = this.mf.selectByAID(data);
    336 		if (typeof(node) == "undefined") {
    337 			throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "Application " + data + " not found");
    338 		}
    339 		this.currentDF = node;
    340 		this.currentEF = null;
    341 		this.localAuthenticationState = [];
    342 		break;
    343 	default:
    344 		throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Incorrect parameter P1 (" + p1.toString(16) + ")");
    345 	}
    346
    347 	switch(p2) {
    348 	case 0x00:
    349 		apdu.setRData(node.getFCP().getFCI().getBytes());
    350 		break;
    351 	case 0x04:
    352 		apdu.setRData(node.getFCP().getBytes());
    353 		break;
    354 	}
    355
    356 	apdu.setSW(APDU.SW_OK);
    357 }
    358
    359
    360
    361 /**
    362  * Return a human readable string for this object
    363  */
    364 FileSelector.prototype.toString = function() {
    365 	var str = "FileSelector: Current DF=" + this.currentDF + " / Current EF=" + this.currentEF;
    366 	if (this.globalAuthenticationState.length > 0) {
    367 		str += "\nGlobally authenticated objects:";
    368 		for each (var aao in this.globalAuthenticationState) {
    369 			str += "\n" + aao.toString();
    370 		}
    371 	}
    372 	if (this.localAuthenticationState.length > 0) {
    373 		str += "\nLocally authenticated objects:";
    374 		for each (var aao in this.localAuthenticationState) {
    375 			str += "\n" + aao.toString();
    376 		}
    377 	}
    378 	return str;
    379 }
    380
    381
    382
    383 FileSelector.test = function() {
    384
    385 	var aid = new ByteString("A0000000010101", HEX);
    386
    387 	var mf = new DF(FCP.newDF("3F00", null),
    388 						new TransparentEF(FCP.newTransparentEF("2F00", -1, 100)),
    389 						new TransparentEF(FCP.newTransparentEF("2F01", 0x17, 100)),
    390 						new DF(FCP.newDF("DF01", aid),
    391 							new TransparentEF(FCP.newTransparentEF("2F01", -1, 100))
    392 						)
    393 					);
    394
    395 	print(mf.dump(""));
    396
    397 	assert(mf.isDF());
    398
    399 	var ef = mf.selectByFID(new ByteString("2F00", HEX));
    400 	assert(!ef.isDF());
    401 	assert(ef.getFCP().getFID().toString(HEX) == "2F00");
    402
    403 	var ef = mf.selectBySFI(0x17);
    404 	assert(ef.getFCP().getFID().toString(HEX) == "2F01");
    405
    406 	var df = mf.selectByAID(aid);
    407 	assert(df.getFCP().getFID().toString(HEX) == "DF01");
    408
    409 	var fs = new FileSelector(mf);
    410
    411 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX));
    412 	fs.processSelectAPDU(a);
    413 	print(fs);
    414 	print(a);
    415
    416 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("2F00", HEX));
    417 	fs.processSelectAPDU(a);
    418 	print(fs);
    419 	print(a);
    420
    421 	var a = new APDU(0x00, 0xA4, 0x01, 0x0C, new ByteString("DF01", HEX));
    422 	fs.processSelectAPDU(a);
    423 	print(fs);
    424 	print(a);
    425
    426 	var a = new APDU(0x00, 0xA4, 0x02, 0x0C, new ByteString("2F01", HEX));
    427 	fs.processSelectAPDU(a);
    428 	print(fs);
    429 	print(a);
    430 }
    431
    432
    433 // test();
    434
    435