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