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