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 Support for Global Platform SCP03 protocol 25 */ 26 27 var APDU = require('scsh/cardsim/APDU').APDU; 28 29 30 31 /** 32 * Class implementing support for Global Platform SCP03 secure messaging protocol 33 * 34 * @constructor 35 * @param {Crypto} crypto the crypto provider 36 * @param {Key} kenc the static secure channel encryption key 37 * @param {Key} kmac the static secure channel message authentication code key 38 * @param {Key} kdek the static data encryption key 39 * @param {ByteString) aid the AID to use for challenge calculation 40 */ 41 function GPSCP03(crypto, kenc, kmac, kdek, aid) { 42 this.crypto = crypto; 43 this.kenc = kenc; 44 this.kmac = kmac; 45 this.kdek = kdek; 46 this.chaining = ByteString.valueOf(0, 16); 47 this.enccnt = 1; 48 this.seclevel = 0; 49 this.keyVersion = 1; 50 this.parami = 0; 51 52 if (aid != undefined) { 53 assert(aid instanceof ByteString, "Argument aid must be ByteString"); 54 this.aid = aid; 55 } else { 56 this.aid = new ByteString("A000000151000000", HEX); 57 } 58 } 59 60 exports.GPSCP03 = GPSCP03; 61 62 63 64 /** 65 * Strip the ISO padding from dechipered cryptogram 66 * 67 * @param {ByteString} the plain text with padding 68 * @type ByteString 69 * @return the plain text without padding 70 */ 71 GPSCP03.stripPadding = function(plain) { 72 var i = plain.length - 1; 73 while ((i > 0) && (plain.byteAt(i) == 0)) { 74 i--; 75 } 76 if (plain.byteAt(i) != 0x80) { 77 throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "Padding check failed"); 78 } 79 return plain.left(i); 80 } 81 82 83 84 /** 85 * Reset the internal state 86 **/ 87 GPSCP03.prototype.reset = function() { 88 this.chaining = ByteString.valueOf(0, 16); 89 this.enccnt = 1; 90 this.seclevel = 0; 91 } 92 93 94 95 /** 96 * Called by the secure messaging wrapper to wrap the command APDU 97 */ 98 GPSCP03.prototype.wrap = function(apduToWrap, usageQualifier) { 99 100 if (this.seclevel & 0x01) { 101 var apdu = new APDU(apduToWrap); 102 103 if (apdu.hasCData()) { 104 if (this.seclevel & 0x02) { 105 var ivinp = ByteString.valueOf(this.enccnt, 16); 106 var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp); 107 var encinp = apdu.getCData().pad(Crypto.ISO9797_METHOD_2_16); 108 109 apdu.setCData(this.crypto.encrypt(this.skenc, Crypto.AES_CBC, encinp, iv)); 110 } 111 } else { 112 apdu.setCData(new ByteString("", HEX)); 113 } 114 115 var bb = new ByteBuffer(); 116 bb.append(this.chaining); 117 118 var cla = apdu.getCLA(); 119 if (cla & 0x40) { 120 // GP Format 121 cla = cla & 0xC0 | 0x40; 122 apdu.cla |= 0x40; 123 } else { 124 // ISO Format 125 cla = cla & 0x80 | 0x04; 126 apdu.cla |= 0x04; 127 } 128 bb.append(cla); 129 bb.append(apdu.getINS()); 130 bb.append(apdu.getP1()); 131 bb.append(apdu.getP2()); 132 133 var lc = apdu.getCData().length + 8; 134 if (apdu.isExtendedLength()) { 135 bb.append(0); 136 bb.append(ByteString.valueOf(lc, 2)); 137 } else { 138 bb.append(ByteString.valueOf(lc, 1)); 139 } 140 141 bb.append(apdu.getCData()); 142 var mac = this.crypto.sign(this.skmac, Crypto.AES_CMAC, bb.toByteString()); 143 144 this.chaining = mac; 145 146 apdu.setCData(apdu.getCData().concat(mac.left(8))); 147 148 if (this.clatransformation) { 149 apdu.setCLA(this.clatransformation(apdu.getCLA())); 150 } 151 152 apduToWrap = apdu.getCommandAPDU(); 153 } 154 return apduToWrap; 155 } 156 157 158 159 /** 160 * Called by the secure messaging wrapper to unwrap the response APDU 161 */ 162 GPSCP03.prototype.unwrap = function(apduToUnwrap, usageQualifier) { 163 if ((this.seclevel & 0x10) && (apduToUnwrap.length > 2)) { 164 var bb = new ByteBuffer(); 165 bb.append(this.chaining); 166 bb.append(apduToUnwrap.left(apduToUnwrap.length - 10)); 167 bb.append(apduToUnwrap.right(2)); 168 169 var mac = this.crypto.sign(this.skrmac, Crypto.AES_CMAC, bb.toByteString()).left(8); 170 171 if (!mac.equals(apduToUnwrap.bytes(apduToUnwrap.length - 10, 8))) { 172 throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "MAC on R-Data failed verification"); 173 } 174 175 if ((this.seclevel & 0x20) && (apduToUnwrap.length > 10)) { 176 var ivinp = ByteString.valueOf(0x80).concat(ByteString.valueOf(this.enccnt, 15)); 177 var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp); 178 179 var plain = this.crypto.decrypt(this.skenc, Crypto.AES_CBC, apduToUnwrap.left(apduToUnwrap.length - 10), iv); 180 apduToUnwrap = GPSCP03.stripPadding(plain).concat(apduToUnwrap.right(2)); 181 } else { 182 apduToUnwrap = apduToUnwrap.left(apduToUnwrap.length - 10).concat(apduToUnwrap.right(2)); 183 } 184 } 185 186 this.enccnt++; 187 188 return apduToUnwrap; 189 } 190 191 192 193 194 /** 195 * Counterpart to wrap and meant to be used in a simulator 196 */ 197 GPSCP03.prototype.unwrapCommandAPDU = function(apdu) { 198 if (this.seclevel == 0) { 199 throw new GPError(module.id, GPError.CRYPTO_FAILED, APDU.SW_CONDOFUSENOTSAT, "No secure messaging active"); 200 } 201 202 if (this.seclevel & 0x01) { 203 if (!apdu.hasCData() || apdu.getCData().length < 8) { 204 throw new GPError(module.id, GPError.CRYPTO_FAILED, APDU.SW_SMOBJMISSING, "MAC on C-Data missing"); 205 } 206 var bb = new ByteBuffer(); 207 bb.append(this.chaining); 208 209 var cla = apdu.getCLA(); 210 if (cla & 0x40) { 211 // GP Format 212 cla = cla & 0xC0 | 0x40; 213 } else { 214 // ISO Format 215 cla = cla & 0x80 | 0x04; 216 } 217 218 bb.append(cla); 219 bb.append(apdu.getINS()); 220 bb.append(apdu.getP1()); 221 bb.append(apdu.getP2()); 222 223 var lc = apdu.getCData().length; 224 if (apdu.isExtendedLength()) { 225 bb.append(0); 226 bb.append(ByteString.valueOf(lc, 2)); 227 } else { 228 bb.append(ByteString.valueOf(lc, 1)); 229 } 230 231 bb.append(apdu.getCData().left(lc - 8)); 232 233 var mac = this.crypto.sign(this.skmac, Crypto.AES_CMAC, bb.toByteString()); 234 235 if (!mac.left(8).equals(apdu.getCData().right(8))) { 236 throw new GPError(module.id, GPError.CRYPTO_FAILED, APDU.SW_SECSTATNOTSAT, "MAC on C-Data failed verification"); 237 } 238 239 this.chaining = mac; 240 241 apdu.setCData(apdu.getCData().left(lc - 8)); 242 243 if ((this.seclevel & 0x02) && (lc > 8)) { 244 var ivinp = ByteString.valueOf(this.enccnt, 16); 245 var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp); 246 247 var plain = this.crypto.decrypt(this.skenc, Crypto.AES_CBC, apdu.getCData(), iv); 248 apdu.setCData(GPSCP03.stripPadding(plain)); 249 } 250 251 } 252 } 253 254 255 256 /** 257 * Counterpart to unwrap and meant to be used in a simulator 258 */ 259 GPSCP03.prototype.wrapResponseAPDU = function(apdu) { 260 if (this.seclevel & 0x10) { 261 if ((this.seclevel & 0x20) && apdu.hasRData()) { 262 var ivinp = ByteString.valueOf(0x80).concat(ByteString.valueOf(this.enccnt, 15)); 263 var iv = this.crypto.encrypt(this.skenc, Crypto.AES_ECB, ivinp); 264 var plain = apdu.getRData().pad(Crypto.ISO9797_METHOD_2_16); 265 266 var cryptogram = this.crypto.encrypt(this.skenc, Crypto.AES_CBC, plain, iv); 267 apdu.setRData(cryptogram); 268 } 269 270 var bb = new ByteBuffer(); 271 bb.append(this.chaining); 272 273 bb.append(apdu.getResponseAPDU()); 274 275 var mac = this.crypto.sign(this.skrmac, Crypto.AES_CMAC, bb.toByteString()).left(8); 276 277 apdu.setRData(apdu.hasRData() ? apdu.getRData().concat(mac) : mac); 278 } 279 280 this.enccnt++; 281 } 282 283 284 285 GPSCP03.prototype.setCLATransformation = function(clatransformation) { 286 assert(typeof(clatransformation) == "function", "Argument clatransformation must be a function"); 287 this.clatransformation = clatransformation; 288 } 289 290 291 292 /** 293 * Issue INITIALIZE UPDATE command to card and parse response 294 * 295 * @param {Card} card the card to use 296 * @param {Number} keyVersion the version of the key to use (0 for default) 297 * @param {Number} keyId the key id to use (usually 0) 298 */ 299 GPSCP03.prototype.initializeUpdate = function(card, keyVersion, keyId) { 300 this.hostChallenge = this.crypto.generateRandom(8); 301 302 var cla = 0x80; 303 if (this.clatransformation) { 304 cla = this.clatransformation(cla); 305 } 306 307 var rsp = card.sendApdu(cla, 0x50, keyVersion, keyId, this.hostChallenge, 0, [0x9000]); 308 309 this.diversificationData = rsp.bytes(0, 10); 310 this.keyInformation = rsp.bytes(10, 3); 311 this.keyVersion = this.keyInformation.byteAt(0); 312 this.parami = this.keyInformation.byteAt(2); 313 this.cardChallenge = rsp.bytes(13, 8); 314 this.cardCryptogram = rsp.bytes(21, 8); 315 if (rsp.length > 29) { 316 this.sequenceCounter = rsp.bytes(29,3); 317 var context = this.sequenceCounter.concat(this.aid); 318 var ref = this.deriveKey(this.kenc, 2, 8, context); 319 assert(ref.equals(this.cardChallenge), "Pseudo-random card challenge does not match Kenc"); 320 } 321 } 322 323 324 325 /** 326 * Set the sequence number for pseudo-random card challenges 327 * 328 * @param {ByteString} cnt the last used counter value 329 */ 330 GPSCP03.prototype.setSequenceCounter = function(cnt) { 331 assert(cnt instanceof ByteString, "Argument cnt must be ByteString"); 332 assert(cnt.length == 3, "Argument cnt must be 3 bytes long"); 333 this.sequenceCounter = cnt; 334 } 335 336 337 338 /** 339 * Set the key version and protocol parameter 340 * 341 * @param {Number} version the key version indicated in INITIALIZE_UPDATE 342 * @param {Number} parami the i parameter for the SCP03 protocol (Default '00') 343 */ 344 GPSCP03.prototype.setKeyInfo = function(version, parami) { 345 assert(typeof(version) == "number", "Argument version must be of type number"); 346 if (typeof(parami) != "undefined") { 347 this.parami = parami; 348 } 349 this.keyVersion = version; 350 this.keyInformation = ByteString.valueOf(this.keyVersion).concat(ByteString.valueOf(0x03)).concat(ByteString.valueOf(this.parami)); 351 } 352 353 354 355 /** 356 * Determine random or pseudo-random card challenge 357 * 358 */ 359 GPSCP03.prototype.determineCardChallenge = function() { 360 if (typeof(this.sequenceCounter) == "undefined") { 361 this.cardChallenge = this.crypto.generateRandom(8); 362 } else { 363 var cnt = this.sequenceCounter.toUnsigned() + 1; 364 this.sequenceCounter = ByteString.valueOf(cnt, 3); 365 var context = this.sequenceCounter.concat(this.aid); 366 this.cardChallenge = this.deriveKey(this.kenc, 2, 8, context); 367 } 368 } 369 370 371 372 /** 373 * Handle processing of INITIALIZE UPDATE command 374 * 375 * @param {ByteString} hostCryptogram the cryptogram calculated at the host 376 */ 377 GPSCP03.prototype.handleInitializeUpdate = function(hostChallenge) { 378 this.hostChallenge = hostChallenge; 379 this.determineCardChallenge(); 380 381 this.diversificationData = new ByteString("0102030405060708090A", HEX); 382 383 if (typeof(this.keyInformation) == "undefined") { 384 this.keyInformation = new ByteString("010300", HEX); 385 } 386 387 this.deriveSessionKeys(); 388 var context = this.hostChallenge.concat(this.cardChallenge); 389 this.cardCryptogram = this.deriveKey(this.skmac, 0, 8, context); 390 391 this.seclevel = 1; 392 this.chaining = ByteString.valueOf(0, 16); 393 394 var bb = new ByteBuffer(); 395 bb.append(this.diversificationData); 396 bb.append(this.keyVersion); 397 bb.append(0x03); 398 bb.append(this.parami); 399 bb.append(this.cardChallenge); 400 bb.append(this.cardCryptogram); 401 if (this.sequenceCounter != undefined) { 402 bb.append(this.sequenceCounter); 403 } 404 return bb.toByteString(); 405 } 406 407 408 409 /** 410 * Issue EXTERNAL AUTHENTICATE command to card. 411 * 412 * @param {Card} card the card to use 413 * @param {Number} level the security level (a combination of bits B5 (R-ENC), B4 (R-MAC), B2 (C-ENC) and B1 (C-MAC)) 414 * @param {ByteString} hostCryptogram optional parameter, calculated internally if missing 415 */ 416 GPSCP03.prototype.externalAuthenticate = function(card, level, hostCryptogram) { 417 assert(card instanceof Card, "Argument card must be instance of Card"); 418 assert(typeof(level) == "number", "Argument level must be a number"); 419 assert((level & ~0x33) == 0, "Argument level must use only bits B5,B4,B2 or B1"); 420 assert((hostCryptogram == undefined) || (hostCryptogram instanceof ByteString), "hostCryptogram must be missing or ByteString"); 421 422 if (hostCryptogram == undefined) { 423 this.calculateHostCryptogram(); 424 } else { 425 this.hostCryptogram = hostCryptogram; 426 } 427 this.seclevel = 1; 428 429 card.sendSecMsgApdu(Card.ALL, 0x80, 0x82, level, 0x00, this.hostCryptogram, [0x9000]); 430 this.seclevel = level; 431 this.enccnt = 1; 432 } 433 434 435 436 /** 437 * Handle processing of PUT KEY command 438 * 439 * @param {ByteString} cdata the C-Data field of the APDU 440 */ 441 GPSCP03.prototype.handlePutKey = function(cdata) { 442 var version = cdata.byteAt(0); 443 cdata = cdata.bytes(1); 444 var kd = GPSCP03.parseKeyDataField(cdata); 445 var kenc = this.validateKeyBlock(kd); 446 cdata = cdata.bytes(kd.length); 447 448 var kd = GPSCP03.parseKeyDataField(cdata); 449 var kmac = this.validateKeyBlock(kd); 450 cdata = cdata.bytes(kd.length); 451 452 var kd = GPSCP03.parseKeyDataField(cdata); 453 var kdek = this.validateKeyBlock(kd); 454 455 this.keyVersion = version; 456 this.kenc = kenc; 457 this.kmac = kmac; 458 this.kdek = kdek; 459 } 460 461 462 463 /** 464 * Derive S-ENC, S-MAC and S-RMAC session keys 465 */ 466 GPSCP03.prototype.deriveSessionKeys = function() { 467 var context = this.hostChallenge.concat(this.cardChallenge); 468 469 var keysize = this.kmac.getSize() >> 3; 470 471 // Derive S-MAC 472 var ss = this.deriveKey(this.kmac, 6, keysize, context); 473 this.skmac = new Key(); 474 this.skmac.setComponent(Key.AES, ss); 475 476 // Derive S-RMAC 477 var ss = this.deriveKey(this.kmac, 7, keysize, context); 478 this.skrmac = new Key(); 479 this.skrmac.setComponent(Key.AES, ss); 480 481 // Derive S-ENC 482 var ss = this.deriveKey(this.kenc, 4, keysize, context); 483 this.skenc = new Key(); 484 this.skenc.setComponent(Key.AES, ss); 485 } 486 487 488 489 /** 490 * Verify card cryptogram 491 * 492 * @type boolean 493 * @return true if cryptogram is valid 494 */ 495 GPSCP03.prototype.verifyCardCryptogram = function() { 496 var context = this.hostChallenge.concat(this.cardChallenge); 497 498 var ss = this.deriveKey(this.skmac, 0, 8, context); 499 return ss.equals(this.cardCryptogram); 500 } 501 502 503 504 /** 505 * Calculate host cryptogram using the key derive method 506 * 507 * @type ByteString 508 * @return the 8 byte cryptogram 509 */ 510 GPSCP03.prototype.calculateHostCryptogram = function() { 511 var context = this.hostChallenge.concat(this.cardChallenge); 512 513 this.hostCryptogram = this.deriveKey(this.skmac, 1, 8, context); 514 return this.hostCryptogram; 515 } 516 517 518 519 /** 520 * Encrypt key and prepare key block for PUT KEY command 521 * 522 * @param {Key} dek the data encryption key 523 * @param {Key} key the key to wrap 524 * @type ByteString 525 * @return the key type, length, cryptogram and key check value 526 */ 527 GPSCP03.prototype.prepareKeyBlock = function(dek, key) { 528 assert(dek instanceof Key, "Argument dek must be instance of Key"); 529 assert(key instanceof Key, "Argument key must be instance of Key"); 530 531 var keysize = key.getSize() >> 3; 532 var ones = new ByteString("01010101010101010101010101010101", HEX); 533 var kcv = this.crypto.encrypt(key, Crypto.AES_ECB, ones).left(3); 534 535 var iv = ByteString.valueOf(0, 16); 536 var ck = this.crypto.encrypt(dek, Crypto.AES_CBC, key.getComponent(Key.AES), iv); 537 var ckbin = new ByteBuffer("88", HEX); 538 ckbin.append(keysize + 1); 539 ckbin.append(keysize); 540 ckbin.append(ck); 541 ckbin.append(3); 542 ckbin.append(kcv); 543 544 return ckbin.toByteString(); 545 } 546 547 548 549 /** 550 * Extract a key data field from C-Data in a PUT KEY APDU 551 * 552 * @param {ByteString} keyDataField the list of key data fields 553 * @type ByteString 554 * @return the extracted key data 555 */ 556 GPSCP03.parseKeyDataField = function(keyDataField) { 557 var i = keyDataField.byteAt(1); 558 if (i - 1 != keyDataField.byteAt(2)) { 559 throw new GPError(module.id, GPError.INVALID_DATA, APDU.SW_INVDATA, "Field length does not match"); 560 } 561 i += 2; 562 i += keyDataField.byteAt(i) + 1; 563 return keyDataField.left(i); 564 } 565 566 567 568 /** 569 * Decrypt and validate key blob containing AES key wrapped with DEK 570 * 571 * @param {ByteString} keyblob the wrapped key 572 * @type Key 573 * @return the unwrapped key 574 */ 575 GPSCP03.prototype.validateKeyBlock = function(keyblob) { 576 assert(keyblob instanceof ByteString, "Argument keyblob must be instance of ByteString"); 577 578 if (keyblob.byteAt(0) != 0x88) { 579 throw new GPError(module.id, GPError.INVALID_DATA, APDU.SW_INVDATA, "AES key blob must start with '88'"); 580 } 581 582 var l = keyblob.byteAt(1); 583 if (keyblob.length <= l + 2) { 584 throw new GPError(module.id, GPError.INVALID_DATA, APDU.SW_INVDATA, "Length exceeds key blob"); 585 } 586 587 var kcvlen = keyblob.byteAt(3 + l - 1); 588 var ck = keyblob.bytes(3, l - 1); 589 var iv = ByteString.valueOf(0, 16); 590 var plain = this.crypto.decrypt(this.kdek, Crypto.AES_CBC, ck, iv); 591 592 var key = new Key(); 593 key.setComponent(Key.AES, plain); 594 595 var ones = new ByteString("01010101010101010101010101010101", HEX); 596 var kcv = this.crypto.encrypt(key, Crypto.AES_ECB, ones).left(kcvlen); 597 598 if (!keyblob.right(3).equals(kcv)) { 599 throw new GPError(module.id, GPError.CRYPTO_FAILED, APDU.SW_SECSTATNOTSAT, "KCV does not match"); 600 } 601 return key; 602 } 603 604 605 606 /** 607 * Derive a session key or cryptogram 608 * 609 * @param {Key} key the master key 610 * @param {Number} ddc the data derivation constant 611 * @param {Number} size the size of byte of the resulting key or cryptogram 612 * @param {ByteString} context the context (usually hostChallenge || cardChallenge) 613 * @return the derived value 614 * @type ByteString 615 */ 616 GPSCP03.prototype.deriveKey = function(key, ddc, size, context) { 617 assert(key instanceof Key, "Argument key must be instance of Key"); 618 assert(typeof(ddc) == "number", "Argument ddc must be a number"); 619 assert(typeof(size) == "number", "Argument size must be a number"); 620 assert(context instanceof ByteString, "Argument context must be instance of ByteString"); 621 622 var dd = new ByteBuffer(); 623 var os = size; 624 var iter = 1; 625 while (os > 0) { 626 var dp = new ByteBuffer(); 627 dp.append(ByteString.valueOf(ddc, 12)); 628 dp.append(0); 629 dp.append(ByteString.valueOf(size << 3, 2)); 630 dp.append(ByteString.valueOf(iter)); 631 dp.append(context); 632 633 var mac = this.crypto.sign(key, Crypto.AES_CMAC, dp.toByteString()); 634 635 dd.append(mac.left(os > mac.length ? mac.length : os)); 636 os -= mac.length; 637 iter++; 638 } 639 return dd.toByteString(); 640 } 641 642 643 644 GPSCP03.test = function() { 645 var crypto = new Crypto(); 646 647 var kmac = new Key(); 648 kmac.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX)); 649 650 var kenc = new Key(); 651 kenc.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX)); 652 653 var kdek = new Key(); 654 kdek.setComponent(Key.AES, new ByteString("404142434445464748494a4b4c4d4e4f", HEX)); 655 656 var card = new Card(_scsh3.reader); 657 658 card.sendApdu(0x00, 0xA4, 0x04, 0x00, new ByteString("A000000151000000", HEX)); 659 660 var scp = new GPSCP03(crypto, kenc, kmac, kdek); 661 662 scp.initializeUpdate(card, 0, 0); 663 scp.deriveSessionKeys(); 664 assert(scp.verifyCardCryptogram()); 665 card.setCredential(scp); 666 scp.externalAuthenticate(card, 1); 667 668 var aid = new ByteString("E82B0601040181C31F0202", HEX); 669 var cdata = (new ASN1(0x4F, aid)).getBytes(); 670 671 card.sendSecMsgApdu(Card.ALL, 0x80, 0xE4, 0x00, 0x80, cdata, [0x9000, 0x6A88]); 672 card.sendSecMsgApdu(Card.ALL, 0x80, 0xE4, 0x00, 0x80, cdata, 0, [0x9000, 0x6A88]); 673 } 674