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 Implementation of an eID specific command interpreter 25 */ 26 27 load("../cardsim/commandinterpreter.js"); 28 load("../icao/pace.js"); 29 load("tools/eccutils.js"); 30 31 32 /** 33 * Create a command interpreter 34 * 35 * @class Class implementing a command interpreter that handles ISO 7816-4 command APDUs 36 * @constructor 37 * @param {FileSelector} fileSelector the file selector object 38 */ 39 function eIDCommandInterpreter(fileSelector) { 40 CommandInterpreter.call(this, fileSelector); 41 42 this.pacedp = new Key(); 43 this.pacedp.setComponent(Key.ECC_CURVE_OID, new ByteString("1.3.36.3.3.2.8.1.1.7", OID)); 44 this.challenge = null; 45 this.crypto = new Crypto(); 46 this.trustedDVCA = null; 47 this.trustedTerminal = null; 48 this.effectiveRights = null; 49 this.lastINS = 0; 50 } 51 52 53 // Inherit from CommandInterpreter 54 eIDCommandInterpreter.prototype = new CommandInterpreter(); 55 eIDCommandInterpreter.constructor = eIDCommandInterpreter; 56 57 58 59 /** 60 * Determine if the terminal has been authenticated 61 * 62 * @type boolean 63 * @return true if authenticated 64 */ 65 eIDCommandInterpreter.prototype.isAuthenticatedTerminal = function() { 66 return (this.effectiveRights != null); 67 } 68 69 70 71 /** 72 * Return terminal role from CHAT 73 * @type ByteString 74 * @return the object identifier value 75 */ 76 eIDCommandInterpreter.prototype.getTerminalRole = function() { 77 if (this.isAuthenticatedTerminal()) { 78 return this.trustedTerminal.getCHAT().get(0).value; 79 } 80 } 81 82 83 84 /** 85 * Determine the current date 86 * 87 * @type Date 88 * @return the current Date 89 */ 90 eIDCommandInterpreter.prototype.getDate = function() { 91 var dateobj = this.fileSelector.getMeta("currentDate"); 92 // print("getDate() = " + dateobj.currentDate); 93 return dateobj.currentDate; 94 } 95 96 97 98 /** 99 * Set the current date 100 * 101 * @param {Date} date the new date 102 */ 103 eIDCommandInterpreter.prototype.setDate = function(date) { 104 var dateobj = this.fileSelector.getMeta("currentDate"); 105 // print("setDate() = " + date); 106 dateobj.currentDate = date; 107 } 108 109 110 111 /** 112 * Update EF.CVCA to indicate new trust anchor for id-IS 113 * 114 * @param {Date} date the new date 115 */ 116 eIDCommandInterpreter.prototype.updateEFCVCA = function(content) { 117 var ef = this.fileSelector.getMeta("efCVCA"); 118 ef.content = content; 119 // print(ef.content); 120 } 121 122 123 124 /** 125 * Process GENERAL AUTHENTICATE command 126 * 127 * @param {APDU} the apdu 128 */ 129 eIDCommandInterpreter.prototype.generalAuthenticate = function(apdu) { 130 var a = new ASN1(apdu.getCData()); 131 132 if (a.tag != 0x7C) 133 throw new GPError("EACSIM", GPError.INVALID_DATA, 0, "Body must contain data element 0x7C"); 134 135 if (a.elements > 0) { 136 var ddtag = a.get(0).tag; 137 if (ddtag == 0x80) { 138 this.performChipAuthenticationV2(apdu); 139 return; 140 } 141 if ((ddtag == 0xA0) || (ddtag == 0xA2)) { 142 this.performRestrictedIdentification(apdu); 143 return; 144 } 145 } 146 147 this.performPACE(apdu); 148 } 149 150 151 152 /** 153 * Process GENERAL AUTHENTICATE command to perform PACE 154 * 155 * @param {APDU} the apdu 156 */ 157 eIDCommandInterpreter.prototype.performPACE = function(apdu) { 158 159 var a = new ASN1(apdu.getCData()); 160 var response = new ASN1(0x7C); 161 162 if (a.elements == 0) { // 1st General Authenticate 163 if (!apdu.isChained()) { 164 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Command must be chained"); 165 } 166 167 var se = this.fileSelector.getSecurityEnvironment().VEXK; 168 169 if (!se.t.AT) { 170 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Security environment not set"); 171 } 172 173 // print(se); 174 175 var protocol = se.t.AT.find(0x80); 176 if (!protocol) { 177 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No protocol defined in security environment"); 178 } 179 var protocol = protocol.value; 180 // print("Protocol: " + protocol); 181 182 var keyid = se.t.AT.find(0x83); 183 if (!keyid) { 184 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No key id defined in security environment"); 185 } 186 var keyid = keyid.value.toUnsigned(); 187 // print("KeyID: " + keyid); 188 189 var chat = se.t.AT.find(0x7F4C); 190 this.chat = chat; 191 192 this.paceao = this.fileSelector.getObject(AuthenticationObject.TYPE_PACE, keyid); 193 if (!this.paceao) { 194 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PACE password not found"); 195 } 196 197 if (this.paceao.isBlocked()) { 198 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_AUTHMETHLOCKED, "PACE password blocked"); 199 } 200 201 if (this.paceao.isSuspended()) { 202 if (!this.fileSelector.isAuthenticated(true, this.paceao.unsuspendAuthenticationObject)) { 203 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "PACE password suspended"); 204 } 205 } 206 207 this.paceao.decreaseRetryCounter(); 208 209 var paceInfo = this.fileSelector.getMeta("paceInfo"); 210 assert(paceInfo, "paceInfo must be defined in meta data"); 211 212 this.pace = new PACE(this.crypto, protocol, this.pacedp, paceInfo.version); 213 this.pace.setPassword(this.paceao.value); 214 var encnonce = this.pace.getEncryptedNonce(); 215 response.add(new ASN1(0x80, encnonce)); 216 } else { 217 if (!this.pace) 218 throw new GPError("EACSIM", GPError.INVALID_MECH, APDU.SW_CONDOFUSENOTSAT, "PACE must have been initialized"); 219 220 if (a.elements != 1) 221 throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_INVDATA, "Dynamic Authentication Data may only contain 1 element"); 222 223 a = a.get(0); 224 225 switch(a.tag) { 226 case 0x81: 227 if (!apdu.isChained()) 228 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Command must be chained"); 229 230 if ((this.lastINS != APDU.INS_GENERAL_AUTHENTICATE) || !this.pace.hasNonce()) 231 throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Invalid sequence. First GA missing"); 232 233 if (this.pace.hasMapping()) 234 throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Invalid sequence. Steps was already performed"); 235 236 if (a.value.byteAt(0) != 0x04) 237 throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_INVDATA, "Public key does not start with '04'"); 238 239 var mappingData = this.pace.getMappingData(); 240 response.add(new ASN1(0x82, mappingData)); 241 242 this.pace.performMapping(a.value); 243 break; 244 case 0x83: 245 if (!apdu.isChained()) 246 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Command must be chained"); 247 248 if ((this.lastINS != APDU.INS_GENERAL_AUTHENTICATE) || (!this.pace.hasMapping())) 249 throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Invalid sequence. Second GA missing"); 250 251 if (a.value.byteAt(0) != 0x04) 252 throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_INVDATA, "Public key does not start with '04'"); 253 254 var ephKey = this.pace.getEphemeralPublicKey(); 255 response.add(new ASN1(0x84, ephKey)); 256 257 // Store idPICC for later terminal authentication 258 this.idPICC = ephKey.bytes(1, (ephKey.length - 1) >> 1); 259 260 this.pace.performKeyAgreement(a.value); 261 break; 262 case 0x85: 263 if (apdu.isChained()) 264 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_LASTCMDEXPECTED, "Last PACE command must not be chained"); 265 266 if (this.lastINS != APDU.INS_GENERAL_AUTHENTICATE) 267 throw new GPError("EACSIM", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Invalid sequence. Second GA missing"); 268 269 if (!this.pace.verifyAuthenticationToken(a.value)) { 270 var sw = APDU.SW_WARNINGNVCHG; 271 if (this.paceao.initialretrycounter) { 272 sw |= 0xC0 + this.paceao.retrycounter; 273 } 274 throw new GPError("EACSIM", GPError.INVALID_DATA, sw, "Verification of authentication token failed"); 275 } 276 277 this.paceao.restoreRetryCounter(); 278 this.fileSelector.addAuthenticationState(true, this.paceao); 279 // print(this.fileSelector); 280 // print(this.fileSelector.isAuthenticated(true, this.paceao)); 281 282 var authToken = this.pace.calculateAuthenticationToken(); 283 284 response.add(new ASN1(0x86, authToken)); 285 if (this.chat) { 286 var pkiid = this.chat.get(0).value.right(1).toUnsigned(); 287 var anchor = this.fileSelector.getObject(TrustAnchor.TYPE, pkiid); 288 if (!anchor) { 289 throw new GPError("EACSIM", GPError.INVALID_DATA, 0, "Invalid PKI in chat"); 290 } 291 anchor.addCARforPACE(response); 292 } 293 294 var symalgo = this.pace.getSymmetricAlgorithm(); 295 296 if (symalgo == Key.AES) { 297 var sm = new SecureChannel(this.crypto); 298 sm.setSendSequenceCounterPolicy(IsoSecureChannel.SSC_SYNC_ENC_POLICY); 299 sm.setMacKey(this.pace.kmac); 300 sm.setEncKey(this.pace.kenc); 301 sm.setMACSendSequenceCounter(new ByteString("00000000000000000000000000000000", HEX)); 302 } else { 303 var sm = new SecureChannel(this.crypto); 304 sm.setMacKey(this.pace.kmac); 305 sm.setEncKey(this.pace.kenc); 306 sm.setMACSendSequenceCounter(new ByteString("0000000000000000", HEX)); 307 } 308 this.setSecureChannel(sm); 309 310 break; 311 default: 312 throw new GPError("EACSIM", GPError.INVALID_DATA, 0, "Unsupported Dynamic Authentication Data"); 313 } 314 } 315 316 apdu.setRData(response.getBytes()); 317 apdu.setSW(APDU.SW_OK); 318 } 319 320 321 322 /** 323 * Intercept MANAGE SE for PACE to determine status of PIN 324 * 325 * @param {APDU} the apdu 326 */ 327 eIDCommandInterpreter.prototype.determinePINStatus = function(apdu) { 328 var tlv = new ASN1(apdu.getP2(), apdu.getCData()); 329 tlv = new ASN1(tlv.getBytes()); // Dirty trick to deserialize as TLV tree 330 var keyref = tlv.find(0x83); 331 if (!keyref) { 332 return; 333 } 334 335 var paceao = this.fileSelector.getObject(AuthenticationObject.TYPE_PACE, keyref.value.toUnsigned()); 336 if (!paceao) { 337 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PACE password not found"); 338 } 339 if (!paceao.isActive) { 340 apdu.setSW(APDU.SW_INVFILE); 341 } else { 342 if (paceao.initialretrycounter) { 343 if (paceao.retrycounter != paceao.initialretrycounter) { 344 apdu.setSW(APDU.SW_WARNINGCOUNT + paceao.retrycounter); 345 } 346 } 347 } 348 } 349 350 351 352 /** 353 * Process GENERAL AUTHENTICATE command to perform chip authentication in version 1 354 * 355 * @param {APDU} the apdu 356 */ 357 eIDCommandInterpreter.prototype.performChipAuthenticationV1 = function(apdu) { 358 359 var a = new ASN1(0x30, apdu.getCData()); 360 a = new ASN1(a.getBytes()); 361 362 if ((a.elements == 0) || (a.elements > 2)) { 363 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Command data must contain 1..2 TLV elements"); 364 } 365 366 if (a.get(0).tag != 0x91) { 367 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Public key data elements must have tag '91'"); 368 } 369 370 var chipAuthenticationInfo = this.fileSelector.getMeta("chipAuthenticationInfo"); 371 var chipAuthenticationPublicKey = this.fileSelector.getMeta("chipAuthenticationPublicKey"); 372 var chipAuthenticationPrivateKey = this.fileSelector.getMeta("chipAuthenticationPrivateKey"); 373 374 assert(chipAuthenticationInfo); 375 assert(chipAuthenticationPublicKey); 376 assert(chipAuthenticationPrivateKey); 377 378 // ToDo: Select key based on MSE SET 379 var ca = new ChipAuthentication(this.crypto, chipAuthenticationInfo.protocol, chipAuthenticationPublicKey); 380 381 ca.setKeyPair(chipAuthenticationPrivateKey, chipAuthenticationPublicKey); 382 383 var puk = a.get(0).value; 384 ca.performKeyAgreement(puk); 385 386 this.idIFD = puk.bytes(1).left(puk.length >> 1); 387 388 var sm = new SecureChannel(this.crypto); 389 sm.setMacKey(ca.kmac); 390 sm.setEncKey(ca.kenc); 391 sm.setMACSendSequenceCounter(new ByteString("0000000000000000", HEX)); 392 this.setSecureChannel(sm); 393 394 apdu.setSW(APDU.SW_OK); 395 } 396 397 398 399 /** 400 * Process GENERAL AUTHENTICATE command to perform chip authentication in version 2 401 * 402 * @param {APDU} the apdu 403 */ 404 eIDCommandInterpreter.prototype.performChipAuthenticationV2 = function(apdu) { 405 406 if (apdu.isChained()) { 407 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in GENERAL AUTHENTICATE for chip authentication"); 408 } 409 410 var a = new ASN1(apdu.getCData()); 411 var response = new ASN1(0x7C); 412 413 var chipAuthenticationInfo = this.fileSelector.getMeta("groupChipAuthenticationInfo"); 414 var chipAuthenticationPublicKey = this.fileSelector.getMeta("groupChipAuthenticationPublicKey"); 415 var chipAuthenticationPrivateKey = this.fileSelector.getMeta("groupChipAuthenticationPrivateKey"); 416 417 assert(chipAuthenticationInfo); 418 assert(chipAuthenticationPublicKey); 419 assert(chipAuthenticationPrivateKey); 420 421 var uniqueChipAuthenticationInfo = this.fileSelector.getMeta("uniqueChipAuthenticationInfo"); 422 var uniqueChipAuthenticationPublicKey = this.fileSelector.getMeta("uniqueChipAuthenticationPublicKey"); 423 var uniqueChipAuthenticationPrivateKey = this.fileSelector.getMeta("uniqueChipAuthenticationPrivateKey"); 424 425 var se = this.fileSelector.getSecurityEnvironment().CDIK; 426 427 if (!se.t.AT) { 428 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "No security environment found"); 429 } 430 431 var keyref = se.t.AT.find(0x84); 432 if (keyref) { 433 var uniqueChipAuthenticationInfo = this.fileSelector.getMeta("uniqueChipAuthenticationInfo"); 434 if (uniqueChipAuthenticationInfo && (uniqueChipAuthenticationInfo.keyId == keyref.value.toUnsigned())) { 435 var ac = this.fileSelector.getMeta("accessController"); 436 437 if (ac && !ac.checkRight(this, apdu, 3)) { 438 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "No privileged terminal right to access unique chip authentication key"); 439 } 440 441 chipAuthenticationInfo = uniqueChipAuthenticationInfo; 442 chipAuthenticationPublicKey = this.fileSelector.getMeta("uniqueChipAuthenticationPublicKey"); 443 chipAuthenticationPrivateKey = this.fileSelector.getMeta("uniqueChipAuthenticationPrivateKey"); 444 } else { 445 if (chipAuthenticationInfo.keyId && (keyref.value.toUnsigned() != chipAuthenticationInfo.keyId)) { 446 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Key id " + keyref.value.toUnsigned() + " does not match a chip authentication key"); 447 } 448 } 449 } 450 451 // Extract idIFD and make sure it's the same used in TA 452 var idIFD = a.get(0).value.bytes(1); // Skip '04' and extract public key 453 idIFD = idIFD.left(idIFD.length >> 1); 454 455 if (!idIFD.equals(this.idIFD)) { 456 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Terminal public key does not match key signed in terminal authentication"); 457 } 458 459 var ca = new ChipAuthentication(this.crypto, chipAuthenticationInfo.protocol, chipAuthenticationPublicKey); 460 461 ca.setKeyPair(chipAuthenticationPrivateKey, chipAuthenticationPublicKey); 462 463 var nonce = this.crypto.generateRandom(8); 464 465 ca.performKeyAgreement(a.get(0).value, nonce); 466 var token = ca.calculateAuthenticationToken(); 467 468 response.add(new ASN1(0x81, nonce)); 469 response.add(new ASN1(0x82, token)); 470 471 apdu.setRData(response.getBytes()); 472 473 if (ca.algo.equals(ChipAuthentication.id_CA_ECDH_3DES_CBC_CBC)) { 474 // print("DES"); 475 var sm = new SecureChannel(this.crypto); 476 sm.setMacKey(ca.kmac); 477 sm.setEncKey(ca.kenc); 478 sm.setMACSendSequenceCounter(new ByteString("0000000000000000", HEX)); 479 } else { 480 // print("AES"); 481 var sm = new SecureChannel(this.crypto); 482 sm.setSendSequenceCounterPolicy(IsoSecureChannel.SSC_SYNC_ENC_POLICY); 483 sm.setMacKey(ca.kmac); 484 sm.setEncKey(ca.kenc); 485 sm.setMACSendSequenceCounter(new ByteString("00000000000000000000000000000000", HEX)); 486 } 487 this.setSecureChannel(sm); 488 489 apdu.setSW(APDU.SW_OK); 490 } 491 492 493 494 /** 495 * Locate public key either in trust anchor, trusted DVCA or trusted terminal 496 * 497 * @param {PublicKeyReference} keyid the public key reference to look for 498 * @type object 499 * @return object with properties level (issuer is 0-CVCA, 1-DVCA or 2-Terminal) and anchor (Trust Anchor) 500 */ 501 eIDCommandInterpreter.prototype.locatePublicKey = function(keyid) { 502 var idlist = this.fileSelector.enumerateObjects(TrustAnchor.TYPE); 503 // print(idlist); 504 505 for each (var i in idlist) { 506 var anchor = this.fileSelector.getObject(TrustAnchor.TYPE, i); 507 // print(anchor.root.getCHR().toString()); 508 if (anchor.isIssuer(keyid)) { 509 return { level: 0, anchor: anchor }; 510 } 511 } 512 513 if (this.trustedDVCA && (this.trustedDVCA.getCHR().equals(keyid))) { 514 var r = this.locatePublicKey(this.trustedDVCA.getCAR()); 515 r.level = 1; 516 return r; 517 } 518 519 if (this.trustedTerminal && (this.trustedTerminal.getCHR().equals(keyid))) { 520 var r = this.locatePublicKey(this.trustedDVCA.getCAR()); 521 r.level = 2; 522 return r; 523 } 524 return null; 525 } 526 527 528 529 /** 530 * Process PSO VERIFY CERTIFICATE command 531 * 532 * @param {APDU} the apdu 533 */ 534 eIDCommandInterpreter.prototype.verifyCertificate = function(apdu) { 535 536 if (apdu.isChained()) { 537 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in PSO VERIFY CERTIFICATE"); 538 } 539 540 if (!apdu.hasCData()) { 541 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Command data expected in PSO VERIFY CERTIFICATE"); 542 } 543 544 if ((apdu.getP1() != 0x00) || (apdu.getP2() != 0xBE)) { 545 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2"); 546 } 547 548 // Reconstruct CVC 549 var a = new ASN1(0x7F21, apdu.getCData()); 550 try { 551 a = new ASN1(a.getBytes()); 552 } 553 catch(e) { 554 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid certificate format"); 555 } 556 557 // Do some basic format checking 558 if ((a.elements != 2) || (a.get(0).tag != 0x7F4e) || (a.get(1).tag != 0x5F37)) { 559 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid certificate format"); 560 } 561 562 var cvc = new CVC(a); 563 564 // print(cvc); 565 566 // Determine public key for checking CVC signature 567 var se = this.fileSelector.getSecurityEnvironment().VEXK; 568 // print(se); 569 570 if (!se.t.DST) { 571 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No public key set in MANAGE SE for verification"); 572 } 573 574 var keyref = se.t.DST.find(0x83); 575 if (!keyref) { 576 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No public key set in MANAGE SE for verification"); 577 } 578 579 var keyid = new PublicKeyReference(keyref.value); 580 // print("KeyID: " + keyid); 581 582 var rc = this.locatePublicKey(keyid); 583 if (rc == null) { 584 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Referenced public key " + keyid + " not found"); 585 } 586 587 var anchor = rc.anchor; 588 // print(anchor); 589 // print(rc.level); 590 591 switch(rc.level) { 592 case 0: 593 anchor.validateCertificateIssuedByCVCA(this.crypto, cvc, this); 594 595 var chat = cvc.getCHAT(); 596 var certtype = chat.get(1).value.byteAt(0) & 0xC0; 597 598 if ((certtype == 0x80) || (certtype == 0x40)) { 599 this.trustedDVCA = cvc; 600 } 601 break; 602 case 1: 603 anchor.validateCertificateIssuedByDVCA(this.crypto, cvc, this.trustedDVCA, this); 604 this.trustedTerminal = cvc; 605 break; 606 default: 607 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Referenced public key " + keyid + " is a terminal key"); 608 } 609 610 apdu.setSW(APDU.SW_OK); 611 } 612 613 614 615 /** 616 * Process EXTERNAL AUTHENTICATE command to perform terminal authentication 617 * 618 * @param {APDU} the apdu 619 * @param {SecurityEnvironment} se the security environment for external authentication 620 */ 621 eIDCommandInterpreter.prototype.externalAuthenticateForTA = function(apdu, se) { 622 if (apdu.isChained()) { 623 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in EXTERNAL AUTHENTICATE"); 624 } 625 626 if (!apdu.hasCData()) { 627 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Command data expected in EXTERNAL AUTHENTICATE"); 628 } 629 630 if ((apdu.getP1() != 0x00) || (apdu.getP2() != 0x00)) { 631 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2"); 632 } 633 634 if (!apdu.isSecureMessaging()) { 635 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Terminal authentication can only be performed with secure messaging"); 636 } 637 638 if (this.isAuthenticatedTerminal()) { 639 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Terminal authentication can only be performed once in a session"); 640 } 641 642 if (!this.challenge) { 643 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Must obtain challenge before external authenticate"); 644 } 645 646 if (this.challenge.length < 8) { 647 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Challenge must be larger or equal 8 bytes"); 648 } 649 650 // Invalidate challenge 651 var challenge = this.challenge; 652 this.challenge = null; 653 654 if (this.trustedTerminal == null) { 655 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No terminal certificate found"); 656 } 657 658 if (this.chat) { 659 var tchat = this.trustedTerminal.getCHAT(); 660 if (!tchat.get(0).value.equals(this.chat.get(0).value)) { 661 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "CHAT in terminal certificate does not match CHAT in PACE"); 662 } 663 if (tchat.get(1).value.length != this.chat.get(1).value.length) { 664 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "CHAT in terminal certificate has different length than CHAT in PACE"); 665 } 666 } 667 668 var keyref = se.t.AT.find(0x83); 669 if (!keyref) { 670 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No public key set in MANAGE SE for terminal authentication"); 671 } 672 673 var keyid = new PublicKeyReference(keyref.value); 674 // print("KeyID: " + keyid); 675 676 var rc = this.locatePublicKey(keyid); 677 if (rc == null) { 678 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Referenced public key " + keyid + " not found"); 679 } 680 681 if (rc.level != 2) { 682 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Referenced public key is not a terminal key"); 683 } 684 685 var dp = rc.anchor.getPublicKeyFor(this.trustedDVCA.getCAR()); 686 var puk = this.trustedTerminal.getPublicKey(dp); 687 688 if (typeof(this.idIFD) == "undefined") { // CA not already performed ? Then we do EAC 2.x 689 var cakey = se.t.AT.find(0x91); 690 if (!cakey) { 691 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "No chip authentication ephemeral key found"); 692 } 693 this.idIFD = cakey.value; 694 } 695 696 if (typeof(this.idPICC) == "undefined") { 697 this.idPICC = this.fileSelector.getMeta("idPICC"); 698 } 699 700 var bb = new ByteBuffer(); 701 bb.append(this.idPICC); 702 bb.append(challenge); 703 bb.append(this.idIFD); 704 705 var auxdata = se.t.AT.find(0x67); 706 707 if (auxdata) { 708 bb.append(auxdata.getBytes()); 709 } 710 var signatureInput = bb.toByteString(); 711 712 var signature = ECCUtils.wrapSignature(apdu.getCData()); 713 var mech = CVC.getSignatureMech(this.trustedTerminal.getPublicKeyOID(dp)); 714 if (!this.crypto.verify(puk, mech, signatureInput, signature)) { 715 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WARNINGNVCHG, "Verification of terminal authentication signature failed"); 716 } 717 718 // Determine effective rights 719 var cvc = rc.anchor.getCertificateFor(this.trustedDVCA.getCAR()); 720 var er = cvc.getCHAT().get(1).value; 721 722 er = er.and(this.trustedDVCA.getCHAT().get(1).value); 723 er = er.and(this.trustedTerminal.getCHAT().get(1).value); 724 725 if (this.chat) { 726 er = er.and(this.chat.get(1).value); 727 } 728 729 // print("Effective rights : " + er); 730 this.effectiveRights = er; 731 732 apdu.setSW(APDU.SW_OK); 733 } 734 735 736 737 /** 738 * Process GENERAL AUTHENTICATE command to perform restricted identification 739 * 740 * @param {APDU} the apdu 741 */ 742 eIDCommandInterpreter.prototype.performRestrictedIdentification = function(apdu) { 743 if (apdu.isChained()) { 744 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in GENERAL AUTHENTICATE for chip authentication"); 745 } 746 747 if (!apdu.hasLe()) { 748 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Wrong length - missing Le field"); 749 } 750 751 if (apdu.isChained()) { 752 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in "); 753 } 754 755 var rikeys = this.fileSelector.getMeta("RIKeys"); 756 assert(rikeys, "No RI keys defined im meta data"); 757 758 var se = this.fileSelector.getSecurityEnvironment().CDIK; 759 // print(se); 760 761 var keyid = 0; 762 if (se.t.AT) { 763 var keyref = se.t.AT.find(0x84); 764 if (keyref) { 765 keyid = keyref.value.toUnsigned(); 766 } 767 } 768 769 if (!rikeys[keyid]) { 770 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Restricted identification key not found"); 771 } 772 773 var ri = rikeys[keyid]; 774 GPSystem.trace("Selected key " + keyid + " for restricted identification(authorizedOnly=" + (ri.authorizedOnly ? "true" : "false") + ")"); 775 776 var ac = this.fileSelector.getMeta("accessController"); 777 778 if (ac && ri.authorizedOnly) { 779 if (!ac.checkRight(this, apdu, 2)) { 780 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Restricted identification requires right in id-AT"); 781 } 782 } 783 784 var a = new ASN1(apdu.getCData()); 785 var rido = a.get(0); 786 787 var response = new ASN1(0x7C); 788 789 var pk = new ASN1(0x7F49, rido.value); 790 pk = new ASN1(pk.getBytes()); // Rebuild tree after replacing tag 791 792 // print(pk); 793 var mech = CVC.getHashMech(this.trustedDVCA.getPublicKeyOID()); 794 var hash = this.crypto.digest(mech, pk.getBytes()); 795 // print(hash); 796 797 var ext = this.trustedTerminal.getExtension(new ByteString("id-sector", OID)); 798 if (!ext) { 799 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Terminal certificate does not contain a sector-id"); 800 } 801 802 if (rido.tag == 0xA0) { 803 var stag = 0x80; 804 var rtag = 0x81; 805 } else if (rido.tag == 0xA2) { 806 var stag = 0x81; 807 var rtag = 0x83; 808 } else { 809 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Dynamic data for restricted identification requires either 'A0' or 'A2' data element"); 810 } 811 812 var hashdo = ext.find(stag); 813 if (!hashdo) { 814 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Sector-id extension does not contain a hash value"); 815 } 816 817 if (!hashdo.value.equals(hash)) { 818 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Sector-id does not match provided public key"); 819 } 820 821 var sectorPuK = new Key(); 822 sectorPuK.setType(Key.PUBLIC); 823 CVC.decodeECPublicKey(pk, sectorPuK); 824 825 var inp = sectorPuK.getComponent(Key.ECC_QX).concat(sectorPuK.getComponent(Key.ECC_QY)); 826 var id = this.crypto.digest(mech, this.crypto.decrypt(ri.prk, Crypto.ECDH, inp)); 827 828 response.add(new ASN1(rtag, id)); 829 830 apdu.setRData(response.getBytes()); 831 apdu.setSW(APDU.SW_OK); 832 } 833 834 835 836 /** 837 * Process GET CHALLENGE command 838 * 839 * @param {APDU} the apdu 840 */ 841 eIDCommandInterpreter.prototype.getChallenge = function(apdu) { 842 if (!apdu.hasLe()) { 843 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Wrong length - missing Le field"); 844 } 845 846 if (apdu.isChained()) { 847 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in GET CHALLENGE"); 848 } 849 850 if (apdu.hasCData()) { 851 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Command data not expected in GET CHALLENGE"); 852 } 853 854 var l = apdu.getNe(); 855 this.challenge = this.crypto.generateRandom(l); 856 apdu.setRData(this.challenge); 857 858 apdu.setSW(APDU.SW_OK); 859 } 860 861 862 863 /** 864 * Performs an EXTERNAL AUTHENTICATE command for BAC 865 * 866 * @param {APDU} the apdu 867 */ 868 eIDCommandInterpreter.prototype.externalAuthenticateForBAC = function(apdu) { 869 if (!apdu.hasLe()) { 870 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Wrong length - missing Le field"); 871 } 872 873 if (apdu.isChained()) { 874 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in EXTERNAL AUTHENTICATE"); 875 } 876 877 if (!apdu.hasCData()) { 878 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command requires C-data"); 879 } 880 881 if ((apdu.getP1() != 0x00) || (apdu.getP2() != 0x00)) { 882 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2"); 883 } 884 885 if (!this.challenge) { 886 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Must obtain challenge before external authenticate"); 887 } 888 889 if (this.challenge.length < 8) { 890 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CONDOFUSENOTSAT, "Challenge must be larger or equal 8 bytes"); 891 } 892 893 var challenge = this.challenge; 894 this.challenge = null; 895 896 var cryptogram = apdu.getCData(); 897 898 if (cryptogram.length != 40) { 899 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Cryptogram must be 40 bytes long"); 900 } 901 902 var k_enc_bac = this.fileSelector.getMeta("KENC"); 903 var k_mac_bac = this.fileSelector.getMeta("KMAC"); 904 905 if (!k_enc_bac || !k_mac_bac) { 906 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_GENERALERROR, "No K_ENC or K_MAC defined for DF"); 907 } 908 909 var mac = cryptogram.right(8); 910 cryptogram = cryptogram.left(32); 911 912 if (!this.crypto.verify(k_mac_bac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2), mac)) { 913 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WARNINGNVCHG, "Authentication failed"); 914 } 915 916 var plain = this.crypto.decrypt(k_enc_bac, Crypto.DES_CBC, cryptogram, new ByteString("0000000000000000", HEX)); 917 918 var rndifd = plain.bytes(0, 8); 919 var rndicc = plain.bytes(8, 8); 920 var kifd = plain.bytes(16, 16); 921 922 if (!rndicc.equals(challenge)) { 923 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WARNINGNVCHG, "RNDicc in cryptogram does not match last response in GET CHALLENGE"); 924 } 925 926 var kicc = this.crypto.generateRandom(16); 927 var plain = rndicc.concat(rndifd).concat(kicc); 928 929 var cryptogram = this.crypto.encrypt(k_enc_bac, Crypto.DES_CBC, plain, new ByteString("0000000000000000", HEX)); 930 var mac = this.crypto.sign(k_mac_bac, Crypto.DES_MAC_EMV, cryptogram.pad(Crypto.ISO9797_METHOD_2)); 931 932 apdu.setRData(cryptogram.concat(mac)); 933 934 keyinp = kicc.xor(kifd); 935 936 var hashin = keyinp.concat(new ByteString("00000001", HEX)); 937 var kencval = this.crypto.digest(Crypto.SHA_1, hashin); 938 kencval = kencval.bytes(0, 16); 939 var kenc = new Key(); 940 kenc.setComponent(Key.DES, kencval); 941 942 var hashin = keyinp.concat(new ByteString("00000002", HEX)); 943 var kmacval = this.crypto.digest(Crypto.SHA_1, hashin); 944 kmacval = kmacval.bytes(0, 16); 945 var kmac = new Key(); 946 kmac.setComponent(Key.DES, kmacval); 947 948 var ssc = rndicc.bytes(4, 4).concat(rndifd.bytes(4, 4)); 949 950 var sm = new SecureChannel(this.crypto); 951 sm.setMacKey(kmac); 952 sm.setEncKey(kenc); 953 sm.setMACSendSequenceCounter(ssc); 954 this.setSecureChannel(sm); 955 956 apdu.setSW(APDU.SW_OK); 957 } 958 959 960 961 /** 962 * Process VERIFY(AD) 963 * 964 * @param {APDU} the apdu 965 */ 966 eIDCommandInterpreter.prototype.verifyAuxiliaryData = function(apdu) { 967 if (apdu.isChained()) { 968 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in VERIFY(AUX)"); 969 } 970 971 if (!apdu.hasCData()) { 972 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command requires C-data"); 973 } 974 975 if ((apdu.getP1() != 0x80) || (apdu.getP2() != 0x00)) { 976 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2"); 977 } 978 979 var oid = apdu.getCData(); 980 if ((oid.byteAt(0) != 0x06) || (oid.byteAt(1) != oid.length - 2)) { 981 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Malformed object identifier in C-Data"); 982 } 983 oid = oid.bytes(2); 984 985 if (!oid.equals(new ByteString("id-DateOfExpiry", OID)) && 986 !oid.equals(new ByteString("id-CommunityID", OID)) && 987 !oid.equals(new ByteString("id-DateOfBirth", OID))) { 988 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Unknown object identifier in C-Data"); 989 } 990 991 var se = this.fileSelector.getSecurityEnvironment().VEXK; 992 993 if (!se.t.AT) { 994 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "No security environment found"); 995 } 996 997 var ad = se.t.AT.find(0x67); 998 if (!ad) { 999 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "No auxiliary data found in security environment"); 1000 } 1001 1002 var refdata; 1003 for (var i = 0; i < ad.elements; i++) { 1004 var ade = ad.get(i); 1005 if (ade.tag == 0x73) { 1006 if ((ade.elements != 2) || (ade.get(0).tag != ASN1.OBJECT_IDENTIFIER) || (ade.get(1).tag != 0x53)) { 1007 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Malformed auxiliary data found in security environment"); 1008 } 1009 if (ade.get(0).value.equals(oid)) { 1010 var refdata = ade.get(1).value; 1011 break; 1012 } 1013 } 1014 } 1015 1016 // print("RefData: " + refdata.toString(ASCII)); 1017 1018 var ac = this.fileSelector.getMeta("accessController"); 1019 1020 if (oid.equals(new ByteString("id-CommunityID", OID))) { 1021 if (ac && !ac.checkRight(this, apdu, 1)) { 1022 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Community ID Verification not allowed"); 1023 } 1024 var id = this.fileSelector.getMeta("CommunityID"); 1025 assert(id, "Community ID not defined as part of meta data"); 1026 id = new ByteString(id, ASCII); 1027 if (id.startsWith(refdata) == refdata.length) { 1028 apdu.setSW(APDU.SW_OK); 1029 } else { 1030 apdu.setSW(APDU.SW_WARNINGNVCHG); 1031 } 1032 } else if (oid.equals(new ByteString("id-DateOfExpiry", OID))) { 1033 var doe = this.fileSelector.getMeta("DateOfExpiry"); 1034 assert(doe, "Data of expiry not defined as part of meta data"); 1035 if (refdata.toString(ASCII) <= doe) { 1036 apdu.setSW(APDU.SW_OK); 1037 } else { 1038 apdu.setSW(APDU.SW_WARNINGNVCHG); 1039 } 1040 } else { 1041 if (ac && !ac.checkRight(this, apdu, 0)) { 1042 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Age Verification not allowed"); 1043 } 1044 var dob = this.fileSelector.getMeta("DateOfBirth"); 1045 assert(dob, "Data of birth not defined as part of meta data"); 1046 if (refdata.toString(ASCII) >= dob) { 1047 apdu.setSW(APDU.SW_OK); 1048 } else { 1049 apdu.setSW(APDU.SW_WARNINGNVCHG); 1050 } 1051 } 1052 } 1053 1054 1055 1056 /** 1057 * Performs an EXTERNAL AUTHENTICATE command 1058 * 1059 * @param {APDU} the apdu 1060 */ 1061 eIDCommandInterpreter.prototype.externalAuthenticate = function(apdu) { 1062 var se = this.fileSelector.getSecurityEnvironment().VEXK; 1063 1064 if (se.t.AT) { 1065 this.externalAuthenticateForTA(apdu, se); 1066 } else { 1067 this.externalAuthenticateForBAC(apdu); 1068 } 1069 } 1070 1071 1072 1073 /** 1074 * Performs an ACTIVATE/DEACTIVATE command 1075 * 1076 * @param {APDU} the apdu 1077 */ 1078 eIDCommandInterpreter.prototype.manageActiveState = function(apdu) { 1079 if (apdu.isChained()) { 1080 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in command"); 1081 } 1082 if (apdu.hasCData()) { 1083 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must not have C-data"); 1084 } 1085 if (apdu.getP1() != 0x10) { 1086 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2"); 1087 } 1088 1089 var pacekeys = this.fileSelector.getMeta(AuthenticationObject.TYPE_PACE); 1090 assert(pacekeys, "No PACE authentication objects defined"); 1091 1092 var paceao = pacekeys[apdu.getP2()]; 1093 if (!paceao) { 1094 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PACE password not found"); 1095 } 1096 1097 var ac = this.fileSelector.getMeta("accessController"); 1098 if (!(ac && ac.checkRight(this, apdu, 5))) { 1099 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Activate requires right 5 in id-AT"); 1100 } 1101 1102 if (apdu.getINS() == APDU.INS_ACTIVATE) { 1103 paceao.activate(); 1104 } else { 1105 paceao.deactivate(); 1106 } 1107 1108 apdu.setSW(APDU.SW_OK); 1109 } 1110 1111 1112 1113 /** 1114 * Performs a RESET RETRY COUNTER command for PACE keys 1115 * 1116 * @param {APDU} the apdu 1117 */ 1118 eIDCommandInterpreter.prototype.resetRetryCounterPACE = function(apdu) { 1119 if (apdu.isChained()) { 1120 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in command"); 1121 } 1122 var p1 = apdu.getP1(); 1123 1124 if (p1 == 0x02) { 1125 if (!apdu.hasCData()) { 1126 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must have C-data"); 1127 } 1128 } else if (p1 == 0x03) { 1129 if (apdu.hasCData()) { 1130 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must not have C-data"); 1131 } 1132 } else { 1133 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2"); 1134 } 1135 1136 var pacekeys = this.fileSelector.getMeta(AuthenticationObject.TYPE_PACE); 1137 assert(pacekeys, "No PACE authentication objects defined"); 1138 1139 var paceao = pacekeys[apdu.getP2()]; 1140 if (!paceao) { 1141 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PACE password not found"); 1142 } 1143 1144 if (!apdu.isSecureMessaging()) { 1145 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Secure messaging required for reset retry counter"); 1146 } 1147 1148 var ac = this.fileSelector.getMeta("accessController"); 1149 1150 if (!( this.fileSelector.isAuthenticated(true, paceao) || 1151 ((typeof(paceao.unblockAuthenticationObject) != "undefined") && this.fileSelector.isAuthenticated(true, paceao.unblockAuthenticationObject)) || 1152 (ac && ac.checkRight(this, apdu, 5)) )) { 1153 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_SECSTATNOTSAT, "Security condition for reset retry counter not satisfied"); 1154 } 1155 paceao.resetRetryCounter(apdu.getCData()); 1156 1157 apdu.setSW(APDU.SW_OK); 1158 } 1159 1160 1161 1162 /** 1163 * Performs a TERMINATE(PIN) command 1164 * 1165 * @param {APDU} the apdu 1166 */ 1167 eIDCommandInterpreter.prototype.terminatePIN = function(apdu) { 1168 if (apdu.hasCData()) { 1169 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must not have C-data"); 1170 } 1171 1172 var pinao = this.fileSelector.getObject(AuthenticationObject.TYPE_PIN, apdu.getP2()); 1173 if (!pinao) { 1174 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "PIN with reference " + apdu.getP2() + " not found"); 1175 } 1176 1177 pinao.terminate(); 1178 apdu.setSW(APDU.SW_OK); 1179 } 1180 1181 1182 1183 /** 1184 * Performs a TERMINATE(Key) command 1185 * 1186 * @param {APDU} the apdu 1187 */ 1188 eIDCommandInterpreter.prototype.terminateKey = function(apdu) { 1189 if (!apdu.hasCData()) { 1190 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must have C-data"); 1191 } 1192 var crt = new ASN1(apdu.getCData()); 1193 1194 if (crt.tag != 0xB6) { 1195 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Digital Signature Template (DST 'B6') not found"); 1196 } 1197 1198 var ref = crt.find(0x84); 1199 if (!ref) { 1200 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Private key reference object '84' not found"); 1201 } 1202 var id = ref.value.toUnsigned(); 1203 1204 var key = this.fileSelector.getObject(SignatureKey.TYPE_KEY, id); 1205 if (!key) { 1206 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Key with reference " + id + " not found"); 1207 } 1208 1209 if (!key.useAuthenticationObject.isTerminated) { 1210 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_REFDATANOTUSABLE, "Authentication object is not terminated"); 1211 } 1212 1213 key.terminate(); 1214 apdu.setSW(APDU.SW_OK); 1215 } 1216 1217 1218 1219 /** 1220 * Performs a TERMINATE command 1221 * 1222 * @param {APDU} the apdu 1223 */ 1224 eIDCommandInterpreter.prototype.terminate = function(apdu) { 1225 if (apdu.isChained()) { 1226 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_CHAINNOTSUPPORTED, "Chaining not supported in command"); 1227 } 1228 1229 switch(apdu.getP1()) { 1230 case 0x10: 1231 this.terminatePIN(apdu); 1232 break; 1233 case 0x21: 1234 this.terminateKey(apdu); 1235 break; 1236 default: 1237 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2"); 1238 } 1239 1240 apdu.setSW(APDU.SW_OK); 1241 } 1242 1243 1244 1245 /** 1246 * Performs a TERMINATE(Key) command 1247 * 1248 * @param {APDU} the apdu 1249 */ 1250 eIDCommandInterpreter.prototype.generateAsymmetricKeyPair = function(apdu) { 1251 if (!apdu.hasCData()) { 1252 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must have C-data"); 1253 } 1254 if ((apdu.getP1() != 0x82) || (apdu.getP2() != 0x00)) { 1255 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2"); 1256 } 1257 var a = new ASN1(0x30, apdu.getCData()); 1258 a = new ASN1(a.getBytes()); 1259 // print(a); 1260 1261 var crt = a.get(0); 1262 if (crt.tag != 0xB6) { 1263 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Digital Signature Template (DST 'B6') not found"); 1264 } 1265 1266 var ref = crt.find(0x84); 1267 if (!ref) { 1268 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Private key reference object '84' not found"); 1269 } 1270 var id = ref.value.toUnsigned(); 1271 1272 var keyobj = this.fileSelector.getObject(SignatureKey.TYPE_KEY, id); 1273 if (!keyobj) { 1274 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Key with reference " + id + " not found"); 1275 } 1276 1277 var dpt = a.get(1); 1278 if (dpt.tag != 0x7F49) { 1279 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Private key reference object '84' not found"); 1280 } 1281 1282 if (dpt.elements != 7) { 1283 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INVDATA, "Public key must contain 7 elements"); 1284 } 1285 1286 var puk = new Key(); 1287 puk.setType(Key.PUBLIC); 1288 puk.setComponent(Key.ECC_P, dpt.find(0x81).value); 1289 puk.setComponent(Key.ECC_A, dpt.find(0x82).value); 1290 puk.setComponent(Key.ECC_B, dpt.find(0x83).value); 1291 var g = dpt.find(0x84).value.bytes(1); 1292 puk.setComponent(Key.ECC_GX, g.left(g.length >> 1)); 1293 puk.setComponent(Key.ECC_GY, g.right(g.length >> 1)); 1294 puk.setComponent(Key.ECC_N, dpt.find(0x85).value); 1295 puk.setComponent(Key.ECC_H, dpt.find(0x87).value); 1296 1297 keyobj.privateKey = new Key(); 1298 keyobj.privateKey.setType(Key.PRIVATE); 1299 keyobj.isTerminated = false; 1300 1301 this.crypto.generateKeyPair(Crypto.EC, puk, keyobj.privateKey); 1302 1303 var encpuk = PACE.encodePublicKey("ecdsa-plain-signatures", puk, true); 1304 apdu.setRData(encpuk.getBytes()); 1305 1306 apdu.setSW(APDU.SW_OK); 1307 } 1308 1309 1310 1311 /** 1312 * Performs a COMPUTE DIGITAL SIGNATURE command 1313 * 1314 * @param {APDU} the apdu 1315 */ 1316 eIDCommandInterpreter.prototype.computeDigitalSignature = function(apdu) { 1317 if (!apdu.hasCData()) { 1318 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Command must have C-data"); 1319 } 1320 if (apdu.getP2() != 0x9A) { 1321 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Invalid P1 or P2"); 1322 } 1323 1324 var se = this.fileSelector.getSecurityEnvironment().CDIK; 1325 // print(se); 1326 1327 var keyid = 0x81; 1328 if (se.t.DST) { 1329 var keyref = se.t.DST.find(0x84); 1330 if (keyref) { 1331 keyid = keyref.value.toUnsigned(); 1332 } 1333 } 1334 1335 // print("KeyID: " + keyid); 1336 1337 var keyobj = this.fileSelector.getObject(SignatureKey.TYPE_KEY, keyid); 1338 if (!keyobj) { 1339 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_RDNOTFOUND, "Key with reference " + keyid + " not found"); 1340 } 1341 1342 if (keyobj.isTerminated) { 1343 throw new GPError("CommandInterpreter", GPError.INVALID_DATA, APDU.SW_REFDATANOTUSABLE, "Key is terminated"); 1344 } 1345 1346 var signature = this.crypto.sign(keyobj.privateKey, Crypto.ECDSA, apdu.getCData()); 1347 1348 apdu.setRData(ECCUtils.unwrapSignature(signature), keyobj.privateKey.getComponent(Key.ECC_P).length); 1349 apdu.setSW(APDU.SW_OK); 1350 } 1351 1352 1353 1354 /** 1355 * Dispatch to command handler based in INS byte in APDU 1356 * 1357 * @param {APDU} apdu the apdu 1358 * @param {Number} ins the normalized instruction code 1359 */ 1360 eIDCommandInterpreter.prototype.dispatch = function(apdu, ins) { 1361 if (!apdu.isISO() && (ins != APDU.INS_VERIFY)) { 1362 apdu.setSW(APDU.SW_INVCLA); 1363 return; 1364 } 1365 1366 switch(ins) { 1367 case APDU.INS_GENERAL_AUTHENTICATE: 1368 this.generalAuthenticate(apdu); 1369 break; 1370 case APDU.INS_GET_CHALLENGE: 1371 this.getChallenge(apdu); 1372 break; 1373 case APDU.INS_EXTERNAL_AUTHENTICATE: 1374 this.externalAuthenticate(apdu); 1375 break; 1376 case APDU.INS_MANAGE_SE: 1377 if ((apdu.getP1() == 0x41) && (apdu.getP2() == 0xA6)) { 1378 this.performChipAuthenticationV1(apdu); 1379 } else { 1380 CommandInterpreter.prototype.dispatch.call(this, apdu, ins); 1381 if ((apdu.getP1() == 0xC1) && (apdu.getP2() == 0xA4)) { 1382 this.determinePINStatus(apdu); 1383 } 1384 } 1385 break; 1386 case APDU.INS_VERIFY: 1387 if ((apdu.getCLA() & 0x80) == 0x80) { 1388 this.verifyAuxiliaryData(apdu); 1389 } else { 1390 CommandInterpreter.prototype.dispatch.call(this, apdu, ins); 1391 } 1392 break; 1393 case APDU.INS_RESET_RETRY_COUNTER: 1394 if (!(apdu.getP2() & 0x80)) { 1395 this.resetRetryCounterPACE(apdu); // PACE eID-PIN 1396 } else { 1397 CommandInterpreter.prototype.dispatch.call(this, apdu, ins); // eSign PIN 1398 } 1399 break; 1400 case APDU.INS_ACTIVATE: 1401 this.manageActiveState(apdu); 1402 break; 1403 case APDU.INS_DEACTIVATE: 1404 this.manageActiveState(apdu); 1405 break; 1406 case APDU.INS_TERMINATE: 1407 this.terminate(apdu); 1408 break; 1409 case APDU.INS_PSO: 1410 if (apdu.getP2() == APDU.INS_VERIFY_CERTIFICATE) { 1411 this.verifyCertificate(apdu); 1412 } else if (apdu.getP1() == APDU.INS_COMPUTE_DIGITAL_SIGN) { 1413 this.computeDigitalSignature(apdu); 1414 } else { 1415 CommandInterpreter.prototype.dispatch.call(this, apdu, ins); 1416 } 1417 break; 1418 case APDU.INS_GENERATE_KEY_PAIR: 1419 this.generateAsymmetricKeyPair(apdu); 1420 break; 1421 default: 1422 CommandInterpreter.prototype.dispatch.call(this, apdu, ins); 1423 } 1424 1425 this.lastINS = ins; 1426 } 1427