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 		// ToDo data must be missing APDU.SW_INVLC
322 		if (this.currentEF) {
323 			this.currentEF = null;
324 			node = this.currentDF;
325 		} else {
326 			node = this.currentDF.getParent();
327 			if (node) {
328 				this.currentDF = node;
329 				this.localAuthenticationState = [];
330 			} else {
331 				node = this.currentDF;
332 			}
333 		}
334 		break;
335 	case 0x04:
336 		node = this.mf.selectByAID(data);
337 		if (typeof(node) == "undefined") {
338 			throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "Application " + data + " not found");
339 		}
340 		this.currentDF = node;
341 		this.currentEF = null;
342 		this.localAuthenticationState = [];
343 		break;
344 	default:
345 		throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Incorrect parameter P1 (" + p1.toString(16) + ")");
346 	}
347 
348 	switch(p2) {
349 	case 0x00:
350 		apdu.setRData(node.getFCP().getFCI().getBytes());
351 		break;
352 	case 0x04:
353 		apdu.setRData(node.getFCP().getBytes());
354 		break;
355 	}
356 
357 	apdu.setSW(APDU.SW_OK);
358 }
359 
360 
361 
362 /**
363  * Return a human readable string for this object
364  */
365 FileSelector.prototype.toString = function() {
366 	var str = "FileSelector: Current DF=" + this.currentDF + " / Current EF=" + this.currentEF;
367 	if (this.globalAuthenticationState.length > 0) {
368 		str += "\nGlobally authenticated objects:";
369 		for each (var aao in this.globalAuthenticationState) {
370 			str += "\n" + aao.toString();
371 		}
372 	}
373 	if (this.localAuthenticationState.length > 0) {
374 		str += "\nLocally authenticated objects:";
375 		for each (var aao in this.localAuthenticationState) {
376 			str += "\n" + aao.toString();
377 		}
378 	}
379 	return str;
380 }
381 
382 
383 
384 FileSelector.test = function() {
385 
386 	var aid = new ByteString("A0000000010101", HEX);
387 
388 	var mf = new DF(FCP.newDF("3F00", null),
389 						new TransparentEF(FCP.newTransparentEF("2F00", -1, 100)),
390 						new TransparentEF(FCP.newTransparentEF("2F01", 0x17, 100)),
391 						new DF(FCP.newDF("DF01", aid),
392 							new TransparentEF(FCP.newTransparentEF("2F01", -1, 100))
393 						)
394 					);
395 
396 	print(mf.dump(""));
397 
398 	assert(mf.isDF());
399 
400 	var ef = mf.selectByFID(new ByteString("2F00", HEX));
401 	assert(!ef.isDF());
402 	assert(ef.getFCP().getFID().toString(HEX) == "2F00");
403 
404 	var ef = mf.selectBySFI(0x17);
405 	assert(ef.getFCP().getFID().toString(HEX) == "2F01");
406 
407 	var df = mf.selectByAID(aid);
408 	assert(df.getFCP().getFID().toString(HEX) == "DF01");
409 
410 	var fs = new FileSelector(mf);
411 
412 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX));
413 	fs.processSelectAPDU(a);
414 	print(fs);
415 	print(a);
416 
417 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("2F00", HEX));
418 	fs.processSelectAPDU(a);
419 	print(fs);
420 	print(a);
421 
422 	var a = new APDU(0x00, 0xA4, 0x01, 0x0C, new ByteString("DF01", HEX));
423 	fs.processSelectAPDU(a);
424 	print(fs);
425 	print(a);
426 
427 	var a = new APDU(0x00, 0xA4, 0x02, 0x0C, new ByteString("2F01", HEX));
428 	fs.processSelectAPDU(a);
429 	print(fs);
430 	print(a);
431 }
432 
433 
434 // test();
435 
436