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 The EMV class contains necessary functions for transaction processing 25 */ 26 27 28 29 /** 30 * EMV class constructor 31 * @class This class implements functions for the EMV tansaction process 32 * @constructor 33 * @param {object} card the card object 34 * @param {object} crypto the crypto object 35 */ 36 function EMV(card, crypto) { 37 this.card = card; 38 this.crypto = crypto; 39 this.cardDE = new Array(); 40 this.terminalDE = new Array(); 41 42 this.terminalDE[EMV.UN] = crypto.generateRandom(4); 43 44 this.terminalDE[0x9F33] = new ByteString("2028C0", HEX); 45 this.terminalDE[0x9F1A] = new ByteString("0276", HEX); 46 this.terminalDE[0x9F35] = new ByteString("15", HEX); 47 this.terminalDE[0x9F40] = new ByteString("0200000000", HEX); 48 49 this.verbose = false; 50 } 51 52 53 54 // Constants 55 56 EMV.PSE1 = new ByteString("1PAY.SYS.DDF01", ASCII); 57 EMV.PSE2 = new ByteString("2PAY.SYS.DDF01", ASCII); 58 59 EMV.INS_GET_PROCESSING_OPTIONS = 0xA8; 60 61 EMV.AID = 0x4F; 62 EMV.LABEL = 0x50; 63 EMV.FCI = 0x6F; 64 EMV.TEMPLATE = 0x70; 65 EMV.RMTF2 = 0x77; 66 EMV.RMTF1 = 0x80; 67 EMV.AIP = 0x82; 68 EMV.DFNAME = 0x84; 69 EMV.PRIORITY = 0x87; 70 EMV.SFI = 0x88; 71 EMV.CDOL1 = 0x8C; 72 EMV.CDOL2 = 0x8D; 73 EMV.CAPKI = 0x8F; 74 EMV.AFL = 0x94; 75 EMV.FCI_ISSUER = 0xA5; 76 EMV.UN = 0x9F37; 77 EMV.PDOL = 0x9F38; 78 EMV.SDATL = 0x9F4A; 79 EMV.FCI_ISSUER_DISCRETIONARY_DATA = 0xBF0C; 80 EMV.DIRECTORY_ENTRY = 0x61; 81 82 EMV.AIDLIST = new Array(); 83 EMV.AIDLIST[0] = { aid : "A00000002501", partial : true, name : "AMEX" }; 84 EMV.AIDLIST[1] = { aid : "A0000000031010", partial : false, name : "VISA" }; 85 EMV.AIDLIST[2] = { aid : "A0000000041010", partial : false, name : "MC" }; 86 87 EMV.TAGLIST = new Array(); 88 EMV.TAGLIST[EMV.UN] = { name : "Unpredictable Number" }; 89 EMV.TAGLIST[EMV.CAPKI] = { name : "Certification Authority Public Key Index" }; 90 EMV.TAGLIST[EMV.SDATL] = { name : "Static Data Authentication Tag List" }; 91 EMV.TAGLIST[EMV.CDOL1] = { name : "Card Risk Management Data Object List 1" }; 92 EMV.TAGLIST[EMV.CDOL2] = { name : "Card Risk Management Data Object List 2" }; 93 94 //EMV.pdol = 0x9F38179F1A0200009F33030000009F3501009F40050000000000; 95 96 97 98 /** 99 * Log message if verbosity is enabled 100 * 101 * @param {String} msg the message to log 102 */ 103 EMV.prototype.log = function(msg) { 104 if (this.verbose) { 105 GPSystem.trace(msg); 106 } 107 } 108 109 110 111 /** 112 * Return cardDE 113 * 114 * @return the cardDE array 115 * @type Array 116 */ 117 EMV.prototype.getCardDataElements = function() { 118 return this.cardDE; 119 } 120 121 122 123 /** 124 * Send SELECT APDU 125 * 126 * @param {object} dfname the PSE AID 127 * @param {boolean} first the selection options 128 * @return the FCI 129 * @type ByteString 130 */ 131 EMV.prototype.select = function(dfname, first) { 132 var fci = this.card.sendApdu(0x00, 0xA4, 0x04, (first ? 0x00 : 0x02), dfname, 0x00); 133 return(fci); 134 } 135 136 137 138 /** 139 * Send READ RECORD APDU 140 * 141 * @param {number} sfi the Short File Identifier 142 * @param {number} recno the record number 143 * @return the corresponding record or empty ByteString if no data was read 144 * @type ByteString 145 */ 146 EMV.prototype.readRecord = function(sfi, recno) { 147 var data = this.card.sendApdu(0x00, 0xB2, recno, (sfi << 3) | 0x04, 0); 148 if (this.card.SW1 == 0x6C) { 149 var data = this.card.sendApdu(0x00, 0xB2, recno, (sfi << 3) | 0x04, this.card.SW2); 150 } 151 152 return(data); 153 } 154 155 156 157 /** 158 * Create a Data Object List related ByteString 159 * @param {object} dol the Data Object List 160 * @return ByteString related to the DOL 161 * @type ByteString 162 */ 163 EMV.prototype.createDOL = function(dol) { 164 this.log("createDOL() called with " + dol.toString(HEX)); 165 var dolenc = new ByteBuffer(); 166 while (dol.length > 0) { 167 var b = dol.byteAt(0); 168 if ((b & 0x1F) == 0x1F) { 169 var tag = dol.left(2).toUnsigned(); 170 var length = dol.byteAt(2); 171 var dol = dol.bytes(3); //Remove Tag and Length Byte 172 } else { 173 var tag = dol.left(1).toUnsigned(); 174 var length = dol.byteAt(1); 175 var dol = dol.bytes(2); //Remove Tag and Length Byte 176 } 177 this.log("Tag: " + tag.toString(HEX)); 178 var addDolenc = this.terminalDE[tag]; 179 if (typeof(addDolenc) != "undefined") { 180 // ToDo: Padding 181 assert(length == addDolenc.length); 182 dolenc.append(addDolenc); 183 } 184 } 185 dolenc = dolenc.toByteString(); 186 //print("Return this dolenc: " + dolenc); 187 188 return(dolenc); 189 } 190 191 192 193 /** 194 * Send GET PROCESSING OPTION APDU 195 * 196 * @param {ByteString} pdol the Processing Data Object List 197 * @return the Application Interchange Profile and the Application File Locator 198 * @type ByteString 199 */ 200 EMV.prototype.getProcessingOptions = function(pdol) { 201 this.log("getProcessingOptions() called"); 202 203 if (pdol == null) { 204 var pdol = new ByteString("8300", HEX); // OTHER 205 //var pdol = new ByteString("830B0000000000000000000000", HEX); // VISA 206 //var pdol = new ByteString("830B2028C00276160200000000", HEX); // VISA mit generate ac support 207 //var pdol = new ByteString("830B2028C00276150200000000", HEX); 208 } 209 var data = this.card.sendApdu(0x80, 0xA8, 0x00, 0x00, pdol, 0, [0x9000]); 210 211 return(data); 212 } 213 214 215 216 /** 217 * Select and read Payment System Environment on either 218 * contact or contactless card 219 * 220 * @param {boolean} contactless the PSE AID 221 */ 222 EMV.prototype.selectPSE = function(contactless) { 223 this.log("selectPSE() called"); 224 225 this.PSE = null; 226 var dfname = (contactless ? EMV.PSE2 : EMV.PSE1); 227 var fci = this.select(dfname, true); 228 if (this.card.SW != 0x9000) { 229 this.log("No PAY.SYS.DDF01 found"); 230 return; 231 } 232 233 if (fci.length == 0) { 234 this.log("No " + dfname.toString(ASCII) + " found"); 235 return; 236 } 237 238 // Decode FCI Template 239 var tl = new TLVList(fci, TLV.EMV); 240 var t = tl.index(0); 241 if (t.getTag() != EMV.FCI) { 242 throw new GPError("EMV", GPError.INVALID_DATA, t.getTAG(), "FCI does not contain tag 6F"); 243 } 244 245 var tl = new TLVList(t.getValue(), TLV.EMV); 246 if (tl.length < 2) { 247 throw new GPError("EMV", GPError.INVALID_DATA, 0, "FCI must contain at least two elements"); 248 } 249 250 if (contactless) { 251 // Decode FCI Proprietary Template 252 t = tl.find(EMV.FCI_ISSUER); 253 if (!t) { 254 throw new GPError("EMV", GPError.INVALID_DATA, 0, "Could not find FCI Proprietary Template in FCI"); 255 } 256 257 var tl = new TLVList(t.getValue(), TLV.EMV); 258 if (tl.length < 1) { 259 throw new GPError("EMV", GPError.INVALID_DATA, 0, "FCI Proprietary Template does not contains any objects"); 260 } 261 262 // Decode FCI Issuer Discretionary Data 263 t = tl.index(0); 264 if (t.getTag() != EMV.FCI_ISSUER_DISCRETIONARY_DATA) { 265 throw new GPError("EMV", GPError.INVALID_DATA, t.getTAG(), "FCI does not contain FCI Issuer Discretionary Data (BF0C)"); 266 } 267 268 tl = new TLVList(t.getValue(), TLV.EMV); 269 270 this.PSE = new Array(); 271 272 for (var i = 0; i < tl.length; i++) { 273 t = tl.index(i); 274 if (t.getTag() != EMV.DIRECTORY_ENTRY) { 275 throw new GPError("EMV", GPError.INVALID_DATA, t.getTAG(), "FCI Issuer Discretionary Data does not contain a valid entry with tag 61"); 276 } 277 this.log("Payment System Directory Entry:"); 278 this.log(t.getValue()); 279 this.PSE.push(new TLVList(t.getValue(), TLV.EMV)); 280 } 281 } else { 282 // Decode DF Name 283 t = tl.index(0); 284 285 if (t.getTag() != EMV.DFNAME) { 286 throw new GPError("EMV", GPError.INVALID_DATA, t.getTAG(), "PSE DDF FCI Template does not contain tag 84"); 287 } 288 289 // Decode FCI Proprietary Template 290 t = tl.index(1); 291 if (t.getTag() != EMV.FCI_ISSUER) { 292 throw new GPError("EMV", GPError.INVALID_DATA, t.getTAG(), "PSE DDF FCI Template does not contain tag A5"); 293 } 294 295 var tl = new TLVList(t.getValue(), TLV.EMV); 296 297 // Decode SFI of the Directory Elementary File 298 t = tl.index(0); 299 if (t.getTag() != EMV.SFI) { 300 throw new GPError("EMV", GPError.INVALID_DATA, t.getTAG(), "PSE DDF FCI Proprietary Template does not contain tag 88"); 301 } 302 303 var sfi = t.getValue(); 304 assert(sfi.length == 1); 305 sfi = sfi.byteAt(0); 306 307 this.PSE = new Array(); 308 309 // Read all records from Directory Elementary File 310 var recno = 1; 311 do { 312 var data = this.readRecord(sfi, recno++); 313 314 if (data.length > 0) { 315 var tl = new TLVList(data, TLV.EMV); 316 if (tl.length != 1) { 317 throw new GPError("EMV", GPError.INVALID_DATA, 0, "Payment System Directory Tag 70 must contain only one entry"); 318 } 319 320 var t = tl.index(0); 321 if (t.getTag() != EMV.TEMPLATE) { 322 throw new GPError("EMV", GPError.INVALID_DATA, t.getTAG(), "PSE DDF FCI Proprietary Template does not contain tag 88"); 323 } 324 325 var tl = new TLVList(t.getValue(), TLV.EMV); 326 for (var i = 0; i < tl.length; i++) { 327 var t = tl.index(i); 328 if (t.getTag() != 0x61) { 329 throw new GPError("EMV", GPError.INVALID_DATA, t.getTAG(), "Payment System Directory Entry must use tag 61"); 330 } 331 332 this.log("Payment System Directory Entry:"); 333 this.log(t.getValue()); 334 this.PSE.push(new TLVList(t.getValue(), TLV.EMV)); 335 } 336 } 337 } while (data.length > 0); 338 } 339 } 340 341 342 343 /** 344 * Return array of PSE entries or null if none defined 345 * @return the PSE array 346 * @type Array 347 */ 348 EMV.prototype.getPSE = function() { 349 return this.PSE; 350 } 351 352 353 354 /** 355 * Return AID of application with highest priority or null if no PSE defined 356 * @return the AID 357 * @type ByteString 358 */ 359 EMV.prototype.getAID = function() { 360 this.log("getAID() called"); 361 362 var prio = 0xFFFF; 363 var aid = null; 364 var pse = this.getPSE(); 365 if (pse == null) { 366 this.log("No PSE found"); 367 return null; 368 } 369 370 // Iterate through PSE entries 371 for (var i = 0; i < pse.length; i++) { 372 var t = pse[i].find(EMV.AID); 373 if (!t) { 374 throw new GPError("EMV", GPError.INVALID_DATA, 0, "Could not find an AID in PSE entry"); 375 } 376 var entryAid = t.getValue(); 377 378 var entryPrio = 0xFFFE; 379 var t = pse[i].find(EMV.PRIORITY); 380 if (t != null) { 381 entryPrio = t.getValue().toUnsigned(); 382 entryPrio &= 0x0F; 383 } 384 if (entryPrio < prio) { 385 prio = entryPrio; 386 aid = entryAid; 387 } 388 } 389 return aid; 390 } 391 392 393 394 /** 395 * Select application and return FCI 396 * @param {ByteString} aid the Application Identifier 397 */ 398 EMV.prototype.selectADF = function(aid) { 399 this.log("selectADF() called"); 400 var fci = this.select(aid, true); 401 if (this.card.SW != 0x9000) { 402 throw new GPError("EMV", GPError.INVALID_DATA, 0, "Could not select ADF with AID " + aid.toString(HEX)); 403 } 404 this.decodeFCI(fci); 405 this.cardDE[EMV.AID] = aid; 406 } 407 408 409 410 /** 411 * Decode the A5 Template from the FCI 412 * @param {ByteString} fci the File Control Informations 413 */ 414 EMV.prototype.decodeFCI = function(fci) { 415 this.log("decodeFCI() called"); 416 417 var fcitlv = new ASN1(fci); 418 var a5 = fcitlv.find(0xA5); 419 420 if (a5 != null) { 421 for (var i = 0; i < a5.elements; i++) { 422 this.cardDE[a5.get(i).tag] = a5.get(i).value; 423 this.log("Found data element " + a5.get(i).tag.toString(HEX) + " = " + a5.get(i).value.toString(HEX)); 424 } 425 } 426 } 427 428 429 430 /** 431 * Try a list of predefined AID in order to select an application 432 */ 433 EMV.prototype.tryAID = function() { 434 this.log("tryAID() called"); 435 436 for (var i = 0; i < EMV.AIDLIST.length; i++) { 437 var le = EMV.AIDLIST[i]; 438 var aid = new ByteString(le.aid, HEX); 439 var fci = this.select(aid, true); 440 441 if (fci.length > 0) { 442 this.cardDE[EMV.AID] = aid; 443 this.decodeFCI(fci); 444 } 445 } 446 } 447 448 449 450 /** 451 * Add elements from ByteString into the cardDE array 452 * @param {TLVList} tlvlist 453 */ 454 EMV.prototype.addCardDEFromList = function(tlvlist) { 455 this.log("addCardDEFromList() called"); 456 for (var i = 0; i < tlvlist.length; i++) { 457 var t = tlvlist.index(i); 458 if (t.getTag() != 0) { 459 this.log("Found data element " + t.getTag().toString(16) + " = " + t.getValue().toString(HEX)); 460 this.cardDE[t.getTag()] = t.getValue(); 461 } 462 } 463 } 464 465 466 467 /** 468 * Inform the ICC that a new transaction is beginning. 469 * Store AIP and AFL into the cardDE array. 470 */ 471 EMV.prototype.initApplProc = function() { 472 this.log("initApplProc() called"); 473 474 var pdol = this.cardDE[EMV.PDOL]; 475 var pdolenc = null; 476 477 if (typeof(pdol) != "undefined") { 478 pdolenc = this.createDOL(pdol); 479 var length = pdolenc.length 480 var length = length.toString(HEX); 481 if (pdolenc.length <= 0xF) { 482 length = "0".concat(length); 483 } 484 var length = new ByteString(length, HEX); 485 pdolenc = new ByteString("83", HEX).concat(length).concat(pdolenc); 486 print(pdolenc); 487 } 488 489 var data = this.getProcessingOptions(pdolenc); 490 491 var tl = new TLVList(data, TLV.EMV); 492 if (tl.length != 1) { 493 throw new GPError("EMV", GPError.INVALID_DATA, 0, "Invalid format in GET PROCESSING OPTIONS response"); 494 } 495 496 var t = tl.index(0); 497 if (t.getTag() == EMV.RMTF1) { // Format 1 498 this.cardDE[EMV.AIP] = t.getValue().left(2); 499 this.cardDE[EMV.AFL] = t.getValue().bytes(2); 500 } else if (t.getTag() == EMV.RMTF2) { 501 tl = new TLVList(t.getValue(), TLV.EMV); 502 if (tl.length < 2) { 503 throw new GPError("EMV", GPError.INVALID_DATA, 0, "At least two entries tag 77 of GET PROCESSING OPTIONS response expected"); 504 } 505 this.addCardDEFromList(tl); 506 } else { 507 throw new GPError("EMV", GPError.INVALID_DATA, 0, "Invalid tag in GET PROCESSING OPTIONS response"); 508 } 509 } 510 511 512 513 /** 514 * Read application data as indicated in the Application File Locator. 515 * Collect input to data authentication. 516 * 517 */ 518 EMV.prototype.readApplData = function() { 519 print("<-----Read application data as indicated in the Application File Locator.------"); 520 print("---------------------Collect input to data authentication.---------------------"); 521 // Application File Locator must exist 522 assert(typeof(this.cardDE[EMV.AFL]) != "undefined"); 523 var afl = this.cardDE[EMV.AFL]; 524 525 // Must be a multiple of 4 526 assert((afl.length & 0x03) == 0); 527 528 // Collect input to data authentication 529 var da = new ByteBuffer(); 530 531 while(afl.length > 0) { 532 var sfi = afl.byteAt(0) >> 3; // Short file identifier 533 var srec = afl.byteAt(1); // Start record 534 var erec = afl.byteAt(2); // End record 535 var dar = afl.byteAt(3); // Number of records included in data authentication 536 537 for (; srec <= erec; srec++) { 538 // Read all indicated records 539 var data = this.readRecord(sfi, srec); 540 print("Record No. " + srec); 541 print(data); 542 543 // Decode template 544 var tl = new TLVList(data, TLV.EMV); 545 assert(tl.length == 1); 546 var t = tl.index(0); 547 assert(t.getTag() == EMV.TEMPLATE); 548 549 // Add data authentication input 550 if (dar > 0) { 551 if (sfi <= 10) { // Only value 552 da.append(t.getValue()); 553 } else { // Full template 554 da.append(data); 555 } 556 dar--; 557 } 558 559 // Add card based data elements to internal list 560 var tl = new TLVList(t.getValue(), TLV.EMV); 561 this.addCardDEFromList(tl); 562 } 563 564 // Continue with next entry in AFL 565 afl = afl.bytes(4); 566 } 567 this.daInput = da.toByteString(); 568 print(this.daInput); 569 print("------------------------------------------------------------------------------>\n"); 570 } 571 572 573 574 /** 575 * Return the Data Authentication Input 576 * @return the Data Authentication Input 577 * @type ByteString 578 */ 579 EMV.prototype.getDAInput = function() { 580 return this.daInput; 581 } 582 583 584 585 /** 586 * Send GENERATE APPLICATION CRYPTOGRAM APDU 587 */ 588 EMV.prototype.generateAC = function() { 589 /* 590 p1 591 0x00 = AAC = reject transaction 592 0x40 = TC = proceed offline 593 0x80 = ARQC = go online 594 */ 595 596 var p1 = 0x40; 597 598 var authorisedAmount = new ByteString("000000000001", HEX); 599 var secondaryAmount = new ByteString("000000000000", HEX); 600 var tvr = new ByteString("0000000000", HEX); 601 var transCurrencyCode = new ByteString("0978", HEX); 602 var transDate = new ByteString("090730", HEX); 603 var transType = new ByteString("21", HEX); 604 var unpredictableNumber = crypto.generateRandom(4); 605 var iccDynamicNumber = card.sendApdu(0x00, 0x84, 0x00, 0x00, 0x00); 606 var DataAuthCode = this.cardDE[0x9F45]; 607 608 var Data = authorisedAmount.concat(secondaryAmount).concat(tvr).concat(transCurrencyCode).concat(transDate).concat(transType).concat(unpredictableNumber).concat(iccDynamicNumber).concat(DataAuthCode); 609 610 var generateAC = card.sendApdu(0x80, 0xAE, p1, 0x00, Data, 0x00); 611 } 612