1 /** 2 * --------- 3 * |.##> <##.| Open Smart Card Development Platform (www.openscdp.org) 4 * |# #| 5 * |# #| Copyright (c) 1999-2009 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 28 load("apdu.js"); 29 load("securityenvironment.js"); 30 31 32 /** 33 * Create a File Control Parameter containing information about a file system node 34 * 35 * @class Class storing File Control Parameter for a file system node 36 * @constructor 37 */ 38 function FCP() { 39 } 40 41 42 /** File type for DF */ 43 FCP.DEDICATEDFILE = 0x38; 44 45 /** File type for transparent EF */ 46 FCP.TRANSPARENT = 0x01; 47 48 /** File type for record oriented EF with fixed record size */ 49 FCP.LINEARFIXED = 0x02; 50 51 /** File type for record oriented EF with variable record size */ 52 FCP.LINEARVARIABLE = 0x04; 53 54 55 /** 56 * Convert an integer value into an two byte ByteString 57 * 58 * @param {Number} val the value 59 * @type ByteString 60 * @return the 2 byte encoded value MSB||LSB 61 */ 62 FCP.short2bytestring = function(val) { 63 var bb = new ByteBuffer(); 64 bb.append(val >> 8); 65 bb.append(val & 0xFF); 66 return(bb.toByteString()); 67 } 68 69 70 71 /** 72 * Construct a new FCP object from parameters. 73 * 74 * <p>This function should never be called directly. Use newTransparentDF(), newDF() or newLinearEF() instead.</p> 75 * 76 * @param {String|ByteString} fid the file identifier (2 Bytes) 77 * @param {Number} sfi the short file identifier or -1 or 0 if not defined 78 * @param {Number} type the file type, one of FCP.DEDICATEDFILE, FCP.TRANSPARENT or FCP.LINEAR* 79 * @param {Boolean} shareable true, if file may be shared between logical channels 80 * @param {Boolean} internal true, if file is internal only and not externally selectable 81 * @param {ByteString} supl supplemental information 82 * @type FCP 83 * @return the newly constructed FCP object 84 */ 85 FCP.newFCP = function(fid, sfi, type, shareable, internal, supl) { 86 var fcp = new FCP(); 87 88 if (fid != null) { 89 if (typeof(fid) == "string") { 90 if (fid.length != 4) { 91 throw new GPError("FCP", GPError.INVALID_DATA, 0, "File Identifier must be 2 bytes"); 92 } 93 fcp.fid = new ByteString(fid, HEX); 94 } else if (fid instanceof ByteString) { 95 if (fid.length != 2) { 96 throw new GPError("FCP", GPError.INVALID_DATA, 0, "File Identifier must be 2 bytes"); 97 } 98 fcp.fid = fid; 99 } else { 100 throw new GPError("FCP", GPError.INVALID_TYPE, 0, "Argument must be of type String or ByteString"); 101 } 102 } 103 104 if (typeof(sfi) != "number") { 105 throw new GPError("FCP", GPError.INVALID_TYPE, 1, "Argument must be of type Number"); 106 } 107 if ((sfi >= -1) && (sfi <= 30)) { 108 if (sfi > 0) { 109 fcp.sfi = sfi; 110 } 111 } else { 112 throw new GPError("FCP", GPError.INVALID_DATA, 1, "SFI must be in the range 1 to 30 or 0 if not defined"); 113 } 114 115 if (typeof(type) != "number") { 116 throw new GPError("FCP", GPError.INVALID_TYPE, 2, "Argument must be of type Number"); 117 } 118 fcp.type = type; 119 120 if (typeof(shareable) != "boolean") { 121 throw new GPError("FCP", GPError.INVALID_TYPE, 3, "Argument must be of type Boolean"); 122 } 123 fcp.shareable = shareable; 124 125 if (typeof(internal) != "boolean") { 126 throw new GPError("FCP", GPError.INVALID_TYPE, 4, "Argument must be of type Boolean"); 127 } 128 fcp.internal = internal; 129 130 fcp.supl = supl; 131 return fcp; 132 } 133 134 135 136 /** 137 * Construct a new FCP object for an EF of type transparent. 138 * 139 * @param {String|ByteString} fid the file identifier (2 Bytes) 140 * @param {Number} sfi the short file identifier or -1 or 0 if not defined 141 * @param {Number} size the file size 142 * @param {ByteString} supl supplemental information 143 * @type FCP 144 * @return the newly constructed FCP object 145 */ 146 FCP.newTransparentEF = function(fid, sfi, size, supl) { 147 if (typeof(size) != "number") { 148 throw new GPError("FCP", GPError.INVALID_TYPE, 2, "Argument size must be of type Number"); 149 } 150 151 var fcp = FCP.newFCP(fid, sfi, FCP.TRANSPARENT, false, false, supl); 152 153 fcp.size = size; 154 return fcp; 155 } 156 157 158 159 /** 160 * Construct a new FCP object for a DF. 161 * 162 * @param {String|ByteString} fid the file identifier (2 Bytes) 163 * @param {ByteString} aid the DF's application identifier (DFName) 164 * @param {ByteString} supl supplemental information 165 * @type FCP 166 * @return the newly constructed FCP object 167 */ 168 FCP.newDF = function(fid, aid, supl) { 169 var fcp = FCP.newFCP(fid, -1, FCP.DEDICATEDFILE, false, false, supl); 170 171 if (aid != null) { 172 if ((typeof(aid) != "object") && !(aid instanceof(ByteString))) { 173 throw new GPError("FCP", GPError.INVALID_TYPE, 2, "Argument size must be of type Number"); 174 } 175 fcp.aid = aid; 176 } 177 178 return fcp; 179 } 180 181 182 183 /** 184 * Construct a new FCP object for an EF of type linear. 185 * 186 * @param {String|ByteString} fid the file identifier (2 Bytes) 187 * @param {Number} sfi the short file identifier or -1 or 0 if not defined 188 * @param {Number} type the file type, one of FCP.LINEARFIXED or FCP.LINEARVARIABLE 189 * @param {Number} recno the maximum number of records 190 * @param {Number} recsize the maximum or fixed record size 191 * @param {ByteString} supl supplemental information 192 * @type FCP 193 * @return the newly constructed FCP object 194 */ 195 FCP.newLinearEF = function(fid, sfi, type, recno, recsize, supl) { 196 if (typeof(recsize) != "number") { 197 throw new GPError("FCP", GPError.INVALID_TYPE, 3, "Argument recsize must be of type Number"); 198 } 199 if (typeof(recno) != "number") { 200 throw new GPError("FCP", GPError.INVALID_TYPE, 4, "Argument recno must be of type Number"); 201 } 202 203 var fcp = FCP.newFCP(fid, sfi, type, false, false, supl); 204 return fcp; 205 } 206 207 208 209 /** 210 * Returns the File Identifier (FID) 211 * 212 * @type ByteString 213 * @return the FID 214 */ 215 FCP.prototype.getFID = function() { 216 return this.fid; 217 } 218 219 220 221 /** 222 * Returns the Application Identifier (AID) 223 * 224 * @type ByteString 225 * @return the AID 226 */ 227 FCP.prototype.getAID = function() { 228 return this.aid; 229 } 230 231 232 233 /** 234 * Returns the Short File Identifier (SFI) 235 * 236 * @type Number 237 * @return the SFI 238 */ 239 FCP.prototype.getSFI = function() { 240 return this.sfi; 241 } 242 243 244 245 /** 246 * Returns the encoded FCP 247 * 248 * @type ByteString 249 * @return the encoded FCP 250 */ 251 FCP.prototype.getBytes = function() { 252 var fcp = new ASN1("fcp", 0x62); 253 254 if (typeof(this.size) != "undefined") { 255 fcp.add(new ASN1("fileSizeTransparent", 0x80, FCP.short2bytestring(this.size))); 256 } 257 258 var bb = new ByteBuffer(); 259 bb.append(this.type); 260 261 // ToDo: extra type bytes 262 263 fcp.add(new ASN1("fileDescriptor", 0x82, bb.toByteString())); 264 265 if (typeof(this.fid) != "undefined") { 266 fcp.add(new ASN1("fileIdentifier", 0x83, this.fid)); 267 } 268 269 if (typeof(this.aid) != "undefined") { 270 fcp.add(new ASN1("dFName", 0x84, this.aid)); 271 } 272 273 if (typeof(this.sfi) != "undefined") { 274 var bb = new ByteBuffer(); 275 bb.append(this.sfi << 3); 276 fcp.add(new ASN1("shortFileIdentifier", 0x88, bb.toByteString())); 277 } 278 279 return(fcp.getBytes()); 280 } 281 282 283 284 /** 285 * Returns the FCI 286 * 287 * @type ASN1 288 * @return the FCI 289 */ 290 FCP.prototype.getFCI = function() { 291 var fci = new ASN1("fci", 0x6F); 292 293 if (typeof(this.aid) != "undefined") { 294 fci.add(new ASN1("dFName", 0x84, this.aid)); 295 } 296 297 if (this.supl) { 298 fci.add(new ASN1(this.supl)); 299 } 300 301 return(fci); 302 } 303 304 305 306 /** 307 * Return a human readible string for this object 308 * 309 * @type String 310 * @return the string 311 */ 312 FCP.prototype.toString = function() { 313 var str = "FCP("; 314 315 for (var i in this) { 316 if (typeof(this[i]) != "function") { 317 str += i + "=" + this[i] + ","; 318 } 319 } 320 str += ")"; 321 return str; 322 } 323 324 325 326 /** 327 * Construct a file system node 328 * 329 * @class Abstract class for file system nodes 330 * @constructor 331 */ 332 function FSNode(fcp) { 333 this.parent = null; 334 this.fcp = fcp; 335 } 336 337 338 339 /** 340 * Sets the parent for this node 341 * 342 * @param {DF} the parent node 343 */ 344 FSNode.prototype.setParent = function(parent) { 345 if ((typeof(parent) != "object") && !(parent instanceof(DF))) { 346 throw new GPError("FSNode", GPError.INVALID_TYPE, 0, "Argument parent must be of type DF"); 347 } 348 this.parent = parent; 349 } 350 351 352 353 /** 354 * Gets the parent node for this node 355 * 356 * @type DF 357 * @returns the parent node 358 */ 359 FSNode.prototype.getParent = function() { 360 return this.parent; 361 } 362 363 364 365 /** 366 * Gets the file control parameter for this node 367 * 368 * @type FCP 369 * @returns the FCP 370 */ 371 FSNode.prototype.getFCP = function() { 372 return this.fcp; 373 } 374 375 376 377 /** 378 * Returns true if this is a DF 379 * 380 * @type boolean 381 * @return true if this is a DF 382 */ 383 FSNode.prototype.isDF = function() { 384 return (this instanceof DF); 385 } 386 387 388 389 /** 390 * Returns a human readible string 391 * 392 * @type String 393 * @return a string 394 */ 395 FSNode.prototype.toString = function() { 396 if (!this.fcp || !this.fcp.getFID()) { 397 return "FSNode"; 398 } 399 return this.fcp.getFID().toString(HEX); 400 } 401 402 403 404 /** 405 * Create a file system node that represents a transparent EF 406 * 407 * @class Class implementing a transparent EF 408 * @constructor 409 * @param {FCP} fcp the FCP for this EF 410 * @param {ByteString} contents the contents for this EF 411 */ 412 function TransparentEF(fcp, contents) { 413 if (!(fcp instanceof FCP)) { 414 throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument 1 must be of type FCP"); 415 } 416 417 if ((typeof(contents) != "undefined") && (contents != null) && !(contents instanceof ByteString)) { 418 throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument 2 must be of type ByteString"); 419 } 420 421 FSNode.call(this, fcp); 422 this.content = contents; 423 } 424 425 TransparentEF.prototype = new FSNode(); 426 TransparentEF.prototype.constructor = TransparentEF; 427 428 429 430 /** 431 * Reads data from a transparent EF 432 * 433 * @param {APDU} apdu the APDU used for reading 434 * @param {Number} offset the offset to read from 435 * @param {Number} length the length in bytes or 0 for all in short APDU or 65536 for all in extended APDUs 436 * @type ByteString 437 * @return the data read 438 */ 439 TransparentEF.prototype.readBinary = function(apdu, offset, length) { 440 if (typeof(offset) != "number") { 441 throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Offset must be type Number"); 442 } 443 if (typeof(length) != "number") { 444 throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Length must be type Number"); 445 } 446 447 if (offset >= this.content.length) { 448 throw new GPError("TransparentEF", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Offset out of range"); 449 } 450 451 var rlen = length; 452 if ((length == 0) || (length == 65536)) { 453 rlen = this.content.length - offset; 454 if ((length == 0) && (rlen > 256)) { 455 rlen = 256; 456 } 457 } 458 459 if (offset + rlen > this.content.length) { 460 apdu.setSW(APDU.SW_EOF); 461 rlen = this.content.length - offset; 462 } else { 463 apdu.setSW(APDU.SW_OK); 464 } 465 466 return this.content.bytes(offset, rlen); 467 } 468 469 470 471 /** 472 * Update data in transparent EF 473 * 474 * @param {APDU} apdu the APDU used for updating 475 * @param {Number} offset the offset to update 476 * @param {ByteString} data the data to write into the EF 477 */ 478 TransparentEF.prototype.updateBinary = function(apdu, offset, data) { 479 if (typeof(offset) != "number") { 480 throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Offset must be type Number"); 481 } 482 if ((typeof(data) != "object") || !(data instanceof ByteString)) { 483 throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Data must be a ByteString"); 484 } 485 486 if (offset + data.length > this.fcp.size) { 487 throw new GPError("TransparentEF", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Writing beyond file limit"); 488 } 489 490 if (this.content) { 491 if (offset > this.content.length) { 492 throw new GPError("TransparentEF", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Offset out of range"); 493 } 494 var newcontent = this.content.bytes(0, offset).concat(data); 495 if (this.content.length > newcontent.length) { 496 newcontent = newcontent.concat(this.content.bytes(newcontent.length)); 497 } 498 } else { 499 if (offset > 0) { 500 throw new GPError("TransparentEF", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Offset out of range"); 501 } 502 var newcontent = data; 503 } 504 505 this.content = newcontent; 506 apdu.setSW(APDU.SW_OK); 507 } 508 509 510 511 /** 512 * Creates a LinearEF 513 * 514 * @class Class implementing linear EFs 515 * @constructor 516 * @param {FCP} the file control parameter 517 * @param {ByteString[]} records the array of records 518 */ 519 function LinearEF(fcp, records) { 520 if (!(fcp instanceof FCP)) { 521 throw new GPError("LinearEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument 1 must be of type FCP"); 522 } 523 print(typeof(records)); 524 if ((typeof(records) != "undefined") && (records != null) && (typeof(records) != "object")) { 525 throw new GPError("LinearEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument 2 must be of type ByteString[]"); 526 } 527 528 FSNode.call(this, fcp); 529 this.records = records; 530 } 531 532 LinearEF.prototype = new FSNode(); 533 LinearEF.prototype.constructor = LinearEF; 534 535 536 537 /** 538 * Reads a record from a linear EF 539 * 540 * @param {APDU} apdu the APDU used for reading 541 * @param {Number} recno the record number 542 * @param {Number} qualifier the qualifier as encoded in bit b3 - b1 of P1 543 * @param {Number} length the length in bytes or 0 for all in short APDU or 65536 for all in extended APDUs 544 * @type ByteString 545 * @return the data read 546 */ 547 LinearEF.prototype.readRecord = function(apdu, recno, qualifier, length) { 548 if (typeof(recno) != "number") { 549 throw new GPError("LinearEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Record number must be type Number"); 550 } 551 if (typeof(qualifier) != "number") { 552 throw new GPError("LinearEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Qualifier must be type Number"); 553 } 554 555 if (recno == 0) { 556 throw new GPError("LinearEF", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Current record referencing with P1=00 not support"); 557 } 558 recno--; 559 560 if (recno >= this.records.length) { 561 throw new GPError("LinearEF", GPError.INVALID_DATA, APDU.SW_RECORDNOTFOUND, "Record number exeeds number of defined records"); 562 } 563 564 if (qualifier != 4) { 565 throw new GPError("LinearEF", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Only absolute record references supported"); 566 } 567 568 var record = this.records[recno]; 569 570 var rlen = length; 571 if ((length == 0) || (length == 65536)) { 572 rlen = record.length; 573 if ((length == 0) && (rlen > 256)) { 574 rlen = 256; 575 } 576 } 577 578 if (rlen > record.length) { 579 apdu.setSW(APDU.SW_EOF); 580 rlen = record.length; 581 } else { 582 apdu.setSW(APDU.SW_OK); 583 } 584 585 return record.left(rlen); 586 } 587 588 589 590 /** 591 * Creates a Dedicated File (DF) 592 * 593 * <p>The constructor supports as argument a list of child elements</p> 594 * 595 * @class Class implementing dedicated files 596 * @constructor 597 * @param {FCP} the file control parameter 598 */ 599 function DF(fcp) { 600 this.childs = new Array(); 601 this.fidmap = new Array(); 602 this.sfimap = new Array(); 603 this.aidmap = new Array(); 604 this.meta = new Array(); 605 606 FSNode.call(this, fcp); 607 608 if (arguments.length > 1) { 609 for (var i = 1; i < arguments.length; i++) { 610 var arg = arguments[i]; 611 this.add(arg); 612 } 613 } 614 } 615 616 DF.prototype = new FSNode(); 617 DF.prototype.constructor = DF; 618 619 620 621 /** 622 * Adds a new child node to the DF 623 * 624 * @param {FSNode} node the node to add 625 */ 626 DF.prototype.add = function(node) { 627 this.childs.push(node); 628 node.setParent(this); 629 630 var f = node.getFCP(); 631 632 var fid = f.getFID(); 633 if (fid) { 634 if (this.fidmap[fid]) { 635 throw new GPError("DF", GPError.INVALID_DATA, APDU.SW_FILEEXISTS, "Duplicate file identifier " + fid); 636 } 637 this.fidmap[fid] = node; 638 } 639 640 if (node.isDF()) { 641 var aid = f.getAID(); 642 if (aid) { 643 if (this.aidmap[aid]) { 644 throw new GPError("DF", GPError.INVALID_DATA, APDU.SW_FILEEXISTS, "Duplicate application identifier " + aid); 645 } 646 this.aidmap[aid] = node; 647 } 648 } else { 649 var sfi = f.getSFI(); 650 // print("Found SFI " + sfi); 651 if (typeof(sfi) != "undefined") { 652 if (this.sfimap[sfi]) { 653 throw new GPError("DF", GPError.INVALID_DATA, APDU.SW_FILEEXISTS, "Duplicate short file identifier " + sfi); 654 } 655 this.sfimap[sfi] = node; 656 } 657 } 658 659 } 660 661 662 663 /** 664 * Add meta information to DF 665 * 666 * @param {String} name name of meta information 667 * @param {Object} value value of meta information 668 */ 669 DF.prototype.addMeta = function(name, value) { 670 this.meta[name] = value; 671 } 672 673 674 675 /** 676 * Add object to DF 677 * 678 * @param {Object} o object to be added. Must have property type and id. 679 */ 680 DF.prototype.addObject = function(o) { 681 assert((typeof(o) == "object") && (o instanceof FileSystemIdObject), "Argument must be instance of FileSystemIdObject"); 682 if (typeof(this.meta[o.getType()]) == "undefined") { 683 this.meta[o.getType()] = []; 684 } 685 this.meta[o.getType()][o.getId()] = o; 686 } 687 688 689 690 /** 691 * Select a file contained in the DF using the file identifier 692 * 693 * @param {ByteString} the file identifier 694 * @type FSNode 695 * @return the found node or undefined 696 */ 697 DF.prototype.selectByFID = function(fid) { 698 return this.fidmap[fid]; 699 } 700 701 702 703 /** 704 * Select a file contained in the DF using the short file identifier 705 * 706 * @param {Number} the short file identifier 707 * @type FSNode 708 * @return the found node or undefined 709 */ 710 DF.prototype.selectBySFI = function(sfi) { 711 return this.sfimap[sfi]; 712 } 713 714 715 716 /** 717 * Select a DF contained in the DF using the application identifier 718 * 719 * @param {ByteString} the application identifier 720 * @type FSNode 721 * @return the found node or undefined 722 */ 723 DF.prototype.selectByAID = function(aid) { 724 return this.aidmap[aid]; 725 } 726 727 728 729 /** 730 * Dump the file system system recursively starting this this node 731 * 732 * @param {String} indent the string to prefix the output with 733 * @type String 734 * @return the dump 735 */ 736 DF.prototype.dump = function(indent) { 737 if (typeof(indent) == "undefined") { 738 indent = ""; 739 } 740 var str = indent + this.toString() + "\n"; 741 742 if (this instanceof DF) { 743 for (var i in this.meta) { 744 str += indent + " Meta:" + i + "\n"; 745 if (typeof(this.meta[i]) == "object") { 746 for each (e in this.meta[i]) { 747 if (e instanceof FileSystemIdObject) { 748 str += indent + " " + e.toString() + "\n"; 749 } 750 } 751 } 752 } 753 } 754 755 for (var i = 0; i < this.childs.length; i++) { 756 var c = this.childs[i]; 757 758 if (c instanceof DF) { 759 str += c.dump(" " + indent); 760 } else { 761 str += " " + indent + c.toString() + "\n"; 762 } 763 } 764 return str; 765 } 766 767 768 769 /** 770 * Create a file system object identifiable by an id 771 * 772 * @class Abstract class for file system objects identified by an identifier 773 * 774 * @param {String} name the human readable name of the object 775 * @param {Number} id the id 776 */ 777 function FileSystemIdObject(name, id) { 778 this.name = name; 779 this.id = id; 780 } 781 782 783 784 /** 785 * Return id of object 786 */ 787 FileSystemIdObject.prototype.getId = function() { 788 return this.id; 789 } 790 791 792 793 /** 794 * Return type of object 795 * @type string 796 * @return type of object 797 */ 798 FileSystemIdObject.prototype.getType = function() { 799 throw new GPError("FileSystemIdObject", GPError.NOT_IMPLEMENTED, 0, "Derived class must override getType()"); 800 } 801 802 803 804 /** 805 * Return human readable string 806 */ 807 FileSystemIdObject.prototype.toString = function() { 808 return this.name + "(" + this.id + ")"; 809 } 810 811 812 813 /** 814 * Create a file selector object 815 * 816 * @class Class implementing a file selector used to store information about the currently selected 817 * file system object and to process the SELECT APDU 818 * @constructor 819 * @param {DF} mf the master file 820 */ 821 function FileSelector(mf) { 822 if ((typeof(mf) != "object") && !(mf instanceof DF)) { 823 throw new GPError("FileSelector", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument 1 must be of type DF"); 824 } 825 826 this.mf = mf; 827 this.selectMF(); 828 829 this.se = { VEXK: new SecurityEnvironment(), CDIK: new SecurityEnvironment(), SMRES: new SecurityEnvironment(), SMCOM: new SecurityEnvironment()}; 830 this.globalAuthenticationState = []; 831 } 832 833 834 835 /** 836 * Returns the current EF, if any 837 * 838 * @type EF 839 * @return the current EF or null 840 */ 841 FileSelector.prototype.getCurrentEF = function() { 842 return this.currentEF; 843 } 844 845 846 847 /** 848 * Return the current security environment 849 * 850 * @type Object 851 * @returns Object with properties VEXK, CDIK, SMRES and SMCOM containing SecurityEnvironment objects 852 */ 853 FileSelector.prototype.getSecurityEnvironment = function() { 854 return this.se; 855 } 856 857 858 859 /** 860 * Return meta data associated with the current DF or MF 861 * 862 * @param {String} name the meta data name 863 * @type Object 864 * @returns The meta data 865 */ 866 FileSelector.prototype.getMeta = function(name) { 867 var meta; 868 869 if (this.currentDF) { 870 // print("DF selected: " + this.currentDF); 871 var meta = this.currentDF.meta[name]; 872 // print("Found: " + meta); 873 } 874 875 if (!meta) { 876 meta = this.mf.meta[name]; 877 } 878 return meta; 879 } 880 881 882 883 /** 884 * Return object of given type identified by id 885 * 886 * <p>If bit b8 in the id is 1, then the search will start in the current DF. If the object 887 * is not found, the search is continued in the MF. If the bit is not set, then the search 888 * will only look into the MF.</p> 889 * 890 * @param {String} type the type of the object 891 * @param {Number} id the id, bit b8 indicating local DF or global MF search 892 * @type {Object} 893 * @returns the object of the requested type or null if not found 894 */ 895 FileSelector.prototype.getObject = function(type, id) { 896 var olist; 897 898 if (id & 0x80) { 899 olist = this.currentDF.meta[type]; 900 if (olist) { 901 var o = olist[id & 0x7F]; 902 903 if (o) { 904 return o; 905 } 906 } 907 } 908 909 olist = this.mf.meta[type]; 910 if (olist) { 911 var o = olist[id & 0x7F]; 912 913 if (o) { 914 return o; 915 } 916 } 917 return null; 918 } 919 920 921 922 /** 923 * Enumerate objects of a defined type 924 * 925 * @param {String} type the type of the object 926 * @type {Number[]} 927 * @returns the list of objects found 928 */ 929 FileSelector.prototype.enumerateObjects = function(type) { 930 var idlist = []; 931 932 if (this.mf != this.currentDF) { 933 for each (var o in this.currentDF.meta[type]) { 934 idlist.push(o.getId()); 935 } 936 } 937 938 for each (var o in this.mf.meta[type]) { 939 idlist.push(o.getId()); 940 } 941 942 return idlist; 943 } 944 945 946 947 /** 948 * Add authenticated object to the list of authentication states for the local DF or global MF 949 * 950 * @param{boolean} global true if global state else local DF state 951 * @param{AuthenticationObject} ao the authentication object for which authentication was successfull 952 */ 953 FileSelector.prototype.addAuthenticationState = function(global, ao) { 954 if (global) { 955 this.globalAuthenticationState.push(ao); 956 } else { 957 this.localAuthenticationState.push(ao); 958 } 959 } 960 961 962 963 /** 964 * Add authenticated object to the list of authentication states for the local DF or global MF 965 * 966 * @param{boolean} global true if global state else local DF state 967 * @param{AuthenticationObject} ao the authentication object for which authentication was successfull 968 */ 969 FileSelector.prototype.isAuthenticated = function(global, ao) { 970 if (global) { 971 var list = this.globalAuthenticationState; 972 } else { 973 var list = this.localAuthenticationState; 974 } 975 for each (var aao in list) { 976 if (aao === ao) { 977 return true; 978 } 979 } 980 return false; 981 } 982 983 984 985 /** 986 * Select the MF 987 */ 988 FileSelector.prototype.selectMF = function() { 989 this.currentDF = this.mf; 990 this.currentEF = null; 991 this.localAuthenticationState = []; 992 993 return this.mf; 994 } 995 996 997 998 /** 999 * Select a DF entry by FID 1000 * 1001 * @param {ByteString} fid the file identifier 1002 * @param {boolean} check if file matches expected type EF or DF 1003 * @param {boolean} df true if the check must check for a DF type, else a EF type 1004 * @type FSNode 1005 * @return the selected file system node 1006 */ 1007 FileSelector.prototype.selectFID = function(fid, check, df) { 1008 var node = this.currentDF.selectByFID(fid); 1009 1010 if (!node) { 1011 throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "File " + fid + " not found"); 1012 } 1013 1014 if (check) { 1015 if ((df && !node.isDF()) || (!df && node.isDF())) { 1016 throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "File " + fid + " not found or not of matching type"); 1017 } 1018 } 1019 1020 if (node.isDF()) { 1021 this.currentDF = node; 1022 this.localAuthenticationState = []; 1023 this.currentEF = null; 1024 } else { 1025 this.currentEF = node; 1026 } 1027 return node; 1028 } 1029 1030 1031 1032 /** 1033 * Select a DF entry by SFI 1034 * 1035 * @param {Number} sfi the short file identifier 1036 * @type FSNode 1037 * @return the selected file system node 1038 */ 1039 FileSelector.prototype.selectSFI = function(sfi) { 1040 var node = this.currentDF.selectBySFI(sfi); 1041 1042 if (!node) { 1043 throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "File with SFI " + sfi + " not found"); 1044 } 1045 1046 this.currentEF = node; 1047 return node; 1048 } 1049 1050 1051 1052 /** 1053 * Processes the SELECT APDU 1054 * 1055 * <p>Supports in P1</p> 1056 * <ul> 1057 * <li>'00' with empty data to select the MF</li> 1058 * <li>'00' with "3F00" to select the MF</li> 1059 * <li>'00' with fid to select an entry in the current DF</li> 1060 * <li>'01' with fid to select a DF in the current DF</li> 1061 * <li>'02' with fid to select an EF in the current DF</li> 1062 * <li>'03' with empty data to select the parent</li> 1063 * </ul> 1064 * <p>Supports in P2</p> 1065 * <ul> 1066 * <li>'00' with P1=='00' return no data</li> 1067 * <li>'04' return FCP</li> 1068 * <li>'0C' return no data</li> 1069 * </ul> 1070 * @param {APDU} apdu the select APDU 1071 */ 1072 FileSelector.prototype.processSelectAPDU = function(apdu) { 1073 var node; 1074 1075 var p2 = apdu.getP2(); 1076 if ((p2 != 0x00) && (p2 != 0x04) && (p2 != 0x0C)) { 1077 throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Incorrect parameter P2 (" + p2.toString(16) + ")"); 1078 } 1079 1080 var data = apdu.getCData(); 1081 var p1 = apdu.getP1(); 1082 switch(p1) { 1083 case 0x00: 1084 if ((typeof(data) == "undefined") || (data.toString(HEX) == "3F00")) { 1085 node = this.selectMF(); 1086 } else { 1087 node = this.selectFID(data, false, false); 1088 } 1089 break; 1090 case 0x01: 1091 node = this.selectFID(data, true, true); 1092 break; 1093 case 0x02: 1094 node = this.selectFID(data, true, false); 1095 break; 1096 case 0x03: 1097 // ToDo data must be missing APDU.SW_INVLC 1098 if (this.currentEF) { 1099 this.currentEF = null; 1100 node = this.currentDF; 1101 } else { 1102 node = this.currentDF.getParent(); 1103 if (node) { 1104 this.currentDF = node; 1105 this.localAuthenticationState = []; 1106 } else { 1107 node = this.currentDF; 1108 } 1109 } 1110 break; 1111 case 0x04: 1112 node = this.mf.selectByAID(data); 1113 if (typeof(node) == "undefined") { 1114 throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "Application " + data + " not found"); 1115 } 1116 this.currentDF = node; 1117 this.currentEF = null; 1118 this.localAuthenticationState = []; 1119 break; 1120 default: 1121 throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Incorrect parameter P1 (" + p1.toString(16) + ")"); 1122 } 1123 1124 switch(p2) { 1125 case 0x00: 1126 apdu.setRData(node.getFCP().getFCI().getBytes()); 1127 break; 1128 case 0x04: 1129 apdu.setRData(node.getFCP().getBytes()); 1130 break; 1131 } 1132 1133 apdu.setSW(APDU.SW_OK); 1134 } 1135 1136 1137 1138 /** 1139 * Return a human readable string for this object 1140 */ 1141 FileSelector.prototype.toString = function() { 1142 var str = "FileSelector: Current DF=" + this.currentDF + " / Current EF=" + this.currentEF; 1143 if (this.globalAuthenticationState.length > 0) { 1144 str += "\nGlobally authenticated objects:"; 1145 for each (var aao in this.globalAuthenticationState) { 1146 str += "\n" + aao.toString(); 1147 } 1148 } 1149 if (this.localAuthenticationState.length > 0) { 1150 str += "\nLocally authenticated objects:"; 1151 for each (var aao in this.localAuthenticationState) { 1152 str += "\n" + aao.toString(); 1153 } 1154 } 1155 return str; 1156 } 1157 1158 1159 1160 FileSelector.test = function() { 1161 1162 var aid = new ByteString("A0000000010101", HEX); 1163 1164 var mf = new DF(FCP.newDF("3F00", null), 1165 new TransparentEF(FCP.newTransparentEF("2F00", -1, 100)), 1166 new TransparentEF(FCP.newTransparentEF("2F01", 0x17, 100)), 1167 new DF(FCP.newDF("DF01", aid), 1168 new TransparentEF(FCP.newTransparentEF("2F01", -1, 100)) 1169 ) 1170 ); 1171 1172 print(mf.dump("")); 1173 1174 assert(mf.isDF()); 1175 1176 var ef = mf.selectByFID(new ByteString("2F00", HEX)); 1177 assert(!ef.isDF()); 1178 assert(ef.getFCP().getFID().toString(HEX) == "2F00"); 1179 1180 var ef = mf.selectBySFI(0x17); 1181 assert(ef.getFCP().getFID().toString(HEX) == "2F01"); 1182 1183 var df = mf.selectByAID(aid); 1184 assert(df.getFCP().getFID().toString(HEX) == "DF01"); 1185 1186 var fs = new FileSelector(mf); 1187 1188 var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX)); 1189 fs.processSelectAPDU(a); 1190 print(fs); 1191 print(a); 1192 1193 var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("2F00", HEX)); 1194 fs.processSelectAPDU(a); 1195 print(fs); 1196 print(a); 1197 1198 var a = new APDU(0x00, 0xA4, 0x01, 0x0C, new ByteString("DF01", HEX)); 1199 fs.processSelectAPDU(a); 1200 print(fs); 1201 print(a); 1202 1203 var a = new APDU(0x00, 0xA4, 0x02, 0x0C, new ByteString("2F01", HEX)); 1204 fs.processSelectAPDU(a); 1205 print(fs); 1206 print(a); 1207 } 1208 1209 1210 // test(); 1211 1212