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