1 /*
  2  *  ---------
  3  * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
  4  * |#       #|  
  5  * |#       #|  Copyright (c) 1999-2006 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  *  Tools for accessing a ICAO conform Machine Readable Travel Document
 25  */
 26 
 27 
 28 
 29 /*
 30  * Calculate a single Basic Access Control (BAC) key from the second
 31  * line of the Machine Readable Zone (MRZ).
 32  *
 33  * The function extracts the Document Number, Date of Birth and Date of Expiration
 34  * from the second line of the machine readable zone
 35  *
 36  * E.g. MRZ of Silver Data Set
 37  *   P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<
 38  *   L898902C<3UTO6908061F9406236ZE184226B<<<<<14
 39  *   '-DocNo--'   '-DoB-' '-DoE-'
 40  *
 41  * This extract is then hashed, concatenated with a key number and
 42  * hashed again.
 43   
 44  * crypto	Crypto object used for hashing
 45  * mrz2		String containing second line of MRZ
 46  * keyno	Number of key to calculate (1 for Kenc and 2 for Kmac)
 47  *
 48  * Returns	Key object
 49  */
 50 
 51 function calculateBACKey(crypto, mrz2, keyno) {
 52 
 53 	// Convert to byte string
 54 	var strbin = new ByteString(mrz2, ASCII);
 55 
 56 	// Extract Document Number, Date of Birth and Date of Expiration
 57 	var hash_input = strbin.bytes(0, 10);
 58 	hash_input = hash_input.concat(strbin.bytes(13, 7));
 59 	hash_input = hash_input.concat(strbin.bytes(21, 7));
 60 	print("Hash Input   : " + hash_input.toString(ASCII));
 61 
 62 	// Hash input	
 63 	var mrz_hash = crypto.digest(Crypto.SHA_1, hash_input);
 64 	print("MRZ Hash     : " + mrz_hash);
 65 
 66 	// Extract first 16 byte and append 00000001 or 00000002
 67 	var bb = new ByteBuffer(mrz_hash.bytes(0, 16));
 68 	bb.append(new ByteString("000000", HEX));
 69 	bb.append(keyno);
 70 
 71 	// Hash again to calculate key value	
 72 	var keyval = crypto.digest(Crypto.SHA_1, bb.toByteString());
 73 	keyval = keyval.bytes(0, 16);
 74 	print("Value of Key : " + keyval);
 75 	var key = new Key();
 76 	key.setComponent(Key.DES, keyval);
 77 
 78 	return key;
 79 }
 80 
 81 
 82 
 83 /*
 84  * Calculate a single Basic Access Control (BAC) key from a 3-line
 85  * Machine Readable Zone (MRZ).
 86  *
 87  * The function extracts the Document Number, Date of Birth and Date of Expiration
 88  * from the second line of the machine readable zone
 89  *
 90  * E.g. MRZ of Silver Data Set
 91  *   I<UTOL898902C<3<<<<<<<<<<<<<<<
 92  *        '-DocNo--'
 93  *   6908061F9406236UTO<<<<<<<<<<<1
 94  *   '-DoB-' '-DoE-'
 95  *   ERIKSON<<ANNA<MARIA<<<<<<<<<<<
 96  *
 97  * This extract is then hashed, concatenated with a key number and
 98  * hashed again.
 99   
100  * crypto	Crypto object used for hashing
101  * mrz		String containing second line of MRZ
102  * keyno	Number of key to calculate (1 for Kenc and 2 for Kmac)
103  *
104  * Returns	Key object
105  */
106 function calculateBACKeyFrom3LineMRZ(crypto, mrz, keyno) {
107 
108 	// Convert to byte string
109 	var strbin = new ByteString(mrz, ASCII);
110 
111 	// Extract Document Number, Date of Birth and Date of Expiration
112 	var hash_input = strbin.bytes(5, 10);
113 	hash_input = hash_input.concat(strbin.bytes(30, 7));
114 	hash_input = hash_input.concat(strbin.bytes(38, 7));
115 	print("Hash Input   : " + hash_input.toString(ASCII));
116 
117 	// Hash input	
118 	var mrz_hash = crypto.digest(Crypto.SHA_1, hash_input);
119 	print("MRZ Hash     : " + mrz_hash);
120 
121 	// Extract first 16 byte and append 00000001 or 00000002
122 	var bb = new ByteBuffer(mrz_hash.bytes(0, 16));
123 	bb.append(new ByteString("000000", HEX));
124 	bb.append(keyno);
125 
126 	// Hash again to calculate key value	
127 	var keyval = crypto.digest(Crypto.SHA_1, bb.toByteString());
128 	keyval = keyval.bytes(0, 16);
129 	print("Value of Key : " + keyval);
130 	var key = new Key();
131 	key.setComponent(Key.DES, keyval);
132 
133 	return key;
134 }
135 
136 
137 
138 // The SecureChannel object is required as a credential for the CardFile objects
139 // SecureChannel objects must at least implement a wrap() method. The unwrap()
140 // method is optional, but called when defined
141 
142 function SecureChannel(crypto, kenc, kmac, ssc) {
143 	this.crypto = crypto;
144 	this.kenc = kenc;
145 	this.kmac = kmac;
146 	this.ssc = ssc;
147 	this.trace = false;
148 }
149 
150 
151 
152 SecureChannel.prototype.enableTrace = function () {
153 	this.trace = true;
154 }
155 
156 
157 
158 //
159 // Increment send sequence counter
160 //
161 SecureChannel.prototype.incssc = function () {
162 	var c = this.ssc.bytes(4, 4).toUnsigned() + 1;
163 	bb = new ByteBuffer(this.ssc.bytes(0, 4));
164 	bb.append((c >> 24) & 0xFF);
165 	bb.append((c >> 16) & 0xFF);
166 	bb.append((c >>  8) & 0xFF);
167 	bb.append((c      ) & 0xFF);
168 	this.ssc = bb.toByteString();
169 }
170 
171 
172 
173 //
174 // Wrap command-APDU with secure messaging
175 //
176 SecureChannel.prototype.wrap = function(apduToWrap) {
177 	if (this.trace) {
178 		print("Command-APDU to wrap :");
179 		print(apduToWrap);
180 	}
181 
182 	var b = new ByteBuffer();
183 	var macb = new ByteBuffer();
184 
185 	// Transform CLA byte and add header	
186 	var cla = apduToWrap.byteAt(0);
187 	cla |= 0x0C;
188 	b.append(cla);
189 	b.append(apduToWrap.bytes(1, 3));
190 
191 	this.incssc();
192 	macb.append(this.ssc);
193 	macb.append(b.toByteString().pad(Crypto.ISO9797_METHOD_2));
194 
195 	var do87 = null;
196 
197 	var le = apduToWrap.bytes(apduToWrap.length - 1, 1);
198 	
199 	if (apduToWrap.length > 5) {
200 		var lc = apduToWrap.byteAt(4);
201 		var plain = apduToWrap.bytes(5, lc);
202 		plain = plain.pad(Crypto.ISO9797_METHOD_2);
203 		if (this.trace) {
204 			print("Input to cipher:");
205 			print(plain);
206 		}
207 		
208 		var cipher = this.crypto.encrypt(this.kenc, Crypto.DES_CBC, plain, new ByteString("0000000000000000", HEX));
209 		do87 = new ByteString("01", HEX);
210 		do87 = do87.concat(cipher);
211 		do87 = new TLV(0x87, do87, TLV.EMV);
212 		do87 = do87.getTLV();
213 		
214 		macb.append(do87);
215 		
216 		if (apduToWrap.length == 5 + lc) {
217 			le = new ByteString("", HEX);
218 		}
219 	} else if (apduToWrap.length == 4) {
220 		le = new ByteString("", HEX);
221 	}
222 
223 	var do97;
224 	if (le.length > 0) {	
225 		do97 = new ByteString("9701", HEX);
226 		do97 = do97.concat(le);
227 		macb.append(do97);
228 	} else {
229 		do97 = new ByteString("", HEX);
230 	}
231 	
232 	if (this.trace) {
233 		print("Input to MAC calculation :");
234 	}
235 	
236 	var macinput = macb.toByteString().pad(Crypto.ISO9797_METHOD_2);
237 	if (this.trace) {
238 		print(macinput);
239 	}
240 	
241 	var mac = this.crypto.sign(this.kmac, Crypto.DES_MAC_EMV, macinput);
242 	if (this.trace) {
243 		print("Calculated MAC :");
244 		print(mac);
245 	}
246 	
247 	var macdo = new ByteString("8E08", HEX);
248 	macdo = macdo.concat(mac);
249 	
250 	if (do87 != null) {
251 		b.append(do87.length + do97.length + macdo.length);
252 		b.append(do87);
253 	} else {
254 		b.append(do97.length + macdo.length);
255 	}
256 
257 	b.append(do97);
258 	b.append(macdo);
259 	
260 	if (le.length > 0) {
261 		b.append(0);
262 	}
263 	
264 	if (this.trace) {
265 		print("Wrapped Command-APDU :");
266 		print(b.toByteString());
267 	}
268 	
269 	return(b.toByteString());
270 }
271 
272 
273 
274 //
275 // Unwrap response-APDU with secure messaging
276 //
277 SecureChannel.prototype.unwrap = function(apduToUnwrap) {
278 	if (this.trace) {
279 		print("Response-APDU to unwrap :");
280 		print(apduToUnwrap);
281 	}
282 	
283 	if (apduToUnwrap.length == 2) {
284 		return(apduToUnwrap);
285 	}
286 	
287 	var b = new ByteBuffer();
288 	var macb = new ByteBuffer();
289 
290 	this.incssc();
291 
292 	macb.append(this.ssc);
293 
294 	var tl = new TLVList(apduToUnwrap.left(apduToUnwrap.length - 2), TLV.EMV);
295 	
296 	var mac = null;
297 	for (i = 0; i < tl.length; i++) {
298 		var t = tl.index(i);
299 		
300 		if (t.getTag() == 0x8E) {
301 			mac = t.getValue();
302 		} else {
303 			macb.append(t.getTLV());
304 		}
305 	}
306 	
307 	if (mac == null) {
308 		throw new GPError("SecureChannelCredential", GPError.OBJECT_NOT_FOUND, 0, "MAC data object missing");
309 	}
310 
311 	if (this.trace) {
312 		print(macb.toByteString());
313 	}
314 	
315 	if (!this.crypto.verify(this.kmac, Crypto.DES_MAC_EMV, macb.toByteString().pad(Crypto.ISO9797_METHOD_2), mac)) {
316 		throw new GPError("SecureChannelCredential", GPError.CRYPTO_FAILED, 0, "MAC verification failed");
317 	}
318 
319 	var t = tl.find(0x87);
320 	if (t != null) {
321 		var cryptogram = t.getValue();
322 		var padding = cryptogram.byteAt(0);
323 		cryptogram = cryptogram.right(cryptogram.length - 1);
324 
325 		if (padding != 0x01) {
326 			throw new GPError("SecureChannelCredential", GPError.INVALID_MECH, padding, "Unsupported padding mode " + padding + " in cryptogram");
327 		}
328 		
329 		var plain = this.crypto.decrypt(this.kenc, Crypto.DES_CBC, cryptogram, new ByteString("0000000000000000", HEX));
330 		for (i = plain.length - 1; (i > 0) && (plain.byteAt(i) != 0x80); i--);
331 		
332 		b.append(plain.left(i));
333 	}
334 
335 	var t = tl.find(0x81);
336 	
337 	if (t != null) {
338 		b.append(t.getValue());
339 	}
340 	
341 	var t = tl.find(0x99);
342 	if (t == null) {
343 		b.append(apduToUnwrap.right(2));
344 	} else {
345 		b.append(t.getValue());
346 	}
347 	
348 	if (this.trace) {
349 		print("Unwrapped Response-APDU :");
350 		print(b.toByteString());
351 	}
352 	return(b.toByteString());
353 }
354 
355 
356 /*
357  * Open secure channel using basic access control keys
358  *
359  * card		Card object for access to passport
360  * crypto	Crypto object to be used for cryptographic operations
361  * kenc		Kenc key
362  * kmac		Kmac key
363  *
364  * Returns	Open secure channel object
365  */
366  
367 function openSecureChannel(card, crypto, kenc, kmac) {
368 
369 	// Perform mutual authentication procedure
370 	print("Performing mutual authentication");
371 	var rndicc = card.sendApdu(0x00, 0x84, 0x00, 0x00, 0x08, [0x9000]);
372 
373 	var rndifd = crypto.generateRandom(8);
374 	var kifd = crypto.generateRandom(16);
375 
376 	var plain = rndifd.concat(rndicc).concat(kifd);
377 	print("Plain Block  : " + plain);
378 
379 	var cryptogram = crypto.encrypt(kenc, Crypto.DES_CBC, plain, new ByteString("0000000000000000", HEX));
380 	print("Cryptogram   : " + cryptogram);
381 
382 	var mac = crypto.sign(kmac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2));
383 	print("MAC          : " + mac);
384 
385 	var autresp = card.sendApdu(0x00, 0x82, 0x00, 0x00, cryptogram.concat(mac), 0);
386 	
387 	if (card.SW != 0x9000) {
388 		print("Mutual authenticate failed with " + card.SW.toString(16) + " \"" + card.SWMSG + "\". MRZ correct ?");
389 		throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card did not accept MAC");
390 	}
391 	
392 	print("Response     : " + autresp);
393 
394 	cryptogram = autresp.bytes(0, 32);
395 	mac = autresp.bytes(32, 8);
396 
397 	if (!crypto.verify(kmac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2), mac)) {
398 		throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card MAC did not verify correctly");
399 	}
400 
401 	plain = crypto.decrypt(kenc, Crypto.DES_CBC, cryptogram, new ByteString("0000000000000000", HEX));
402 	print("Plain Block  : " + plain);
403 
404 	if (!plain.bytes(0, 8).equals(rndicc)) {
405 		throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card response does not contain matching RND.ICC");
406 	}
407 
408 	if (!plain.bytes(8, 8).equals(rndifd)) {
409 		throw new GPError("MutualAuthentication", GPError.CRYPTO_FAILED, 0, "Card response does not contain matching RND.IFD");
410 	}
411 
412 	var kicc = plain.bytes(16, 16);
413 	keyinp = kicc.xor(kifd);
414 
415 	var hashin = keyinp.concat(new ByteString("00000001", HEX));
416 	var kencval = crypto.digest(Crypto.SHA_1, hashin);
417 	kencval = kencval.bytes(0, 16);
418 	print("Kenc         : " + kencval);
419 	var kenc = new Key();
420 	kenc.setComponent(Key.DES, kencval);
421 
422 	var hashin = keyinp.concat(new ByteString("00000002", HEX));
423 	var kmacval = crypto.digest(Crypto.SHA_1, hashin);
424 	kmacval = kmacval.bytes(0, 16);
425 	print("Kmac         : " + kmacval);
426 	var kmac = new Key();
427 	kmac.setComponent(Key.DES, kmacval);
428 
429 	var ssc = rndicc.bytes(4, 4).concat(rndifd.bytes(4, 4));
430 	print("SSC          : " + ssc);
431 
432 // Disable to use script-secure messaging secure messaging
433 	var sc = new IsoSecureChannel(crypto);
434 	sc.setEncKey(kenc);
435 	sc.setMacKey(kmac);
436 	sc.setSendSequenceCounter(ssc);
437 	return sc;
438 //
439 
440 // Enable to use script-secure messaging secure messaging
441 //	return new SecureChannel(crypto, kenc, kmac, ssc);
442 //
443 }
444 
445 
446 
447 /*
448  * Write a byte string object to file
449  *
450  * The filename is mapped to the location of the script
451  *
452  * name		Name of file
453  * content	ByteString content for file
454  *
455  */
456  
457 function writeFileOnDisk(name, content) {
458 
459 	// Map filename
460 	var filename = GPSystem.mapFilename(name, GPSystem.USR);
461 	print("Writing " + filename);
462 
463 	var file = new java.io.FileOutputStream(filename);
464 	file.write(content);
465 	file.close();
466 }
467 
468 
469 
470 /*
471  * Read a byte string object from file
472  *
473  * The filename is mapped to the location of the script
474  *
475  * name		Name of file
476  *
477  */
478  
479 function readFileFromDisk(name) {
480 
481 	// Map filename
482 	var filename = GPSystem.mapFilename(name, GPSystem.USR);
483 	print("Reading " + filename);
484 
485 	var file = new java.io.FileInputStream(filename);
486 	
487 	var content = new ByteBuffer();
488 	var buffer = new ByteString("                                                                                                                                                                                                                                                                ", ASCII);
489 	var len;
490 	
491 	while ((len = file.read(buffer)) >= 0) {
492 		content.append(buffer.bytes(0, len));
493 	}
494 	
495 	file.close();
496 	return(content.toByteString());
497 }
498 
499 
500 /*
501  * Extract the length of the file from the TLV encoding at the beginning of the
502  * file
503  *
504  * header	First bytes read from file
505  *
506  * Return	Total length of TLV object
507  */
508  
509 function lengthFromHeader(header) {
510 	var value;
511 	
512 	value = header.byteAt(1);
513 	
514 	if (value > 0x82) {
515 		throw new GPError("lengthfromheader()", GPError.INVALID_DATA, value, "");
516 	}
517 	
518 	switch(value) {
519 		case 0x81:
520 			value = header.byteAt(2) + 1;
521 			break;
522 		case 0x82:
523 			value = (header.byteAt(2) << 8) + header.byteAt(3) + 2;
524 			break;
525 	}
526 	return value + 2;
527 }
528