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 the PACE/SAC protocol for both card and terminal 25 */ 26 27 28 29 /** 30 * Create a PACEInfo object 31 * 32 * @class <p>This class encodes and decodes PACEInfo objects.</p> 33 * <p>The class implements the following ASN.1 syntax:</p> 34 * <pre> 35 * PACEInfo ::= SEQUENCE { 36 * protocol OBJECT IDENTIFIER, 37 * version INTEGER, -- MUST be 1 or 2 38 * parameterId INTEGER OPTIONAL 39 * } 40 * </pre> 41 * @constructor 42 * @param {ASN1} the optional tlv structure to initialize the object 43 */ 44 function PACEInfo(tlv) { 45 if (tlv && (tlv instanceof ASN1)) { 46 assert(tlv.isconstructed); 47 assert(tlv.elements >= 2); 48 49 var i = 0; 50 var t = tlv.get(i++); 51 assert(t.tag == ASN1.OBJECT_IDENTIFIER); 52 this.protocol = t.value; 53 54 var t = tlv.get(i++); 55 assert(t.tag == ASN1.INTEGER); 56 this.version = t.value.toSigned(); 57 58 if (i < tlv.elements) { 59 var t = tlv.get(i++); 60 assert(t.tag == ASN1.INTEGER); 61 this.parameterId = t.value.toSigned(); 62 } 63 } 64 } 65 66 67 68 /** 69 * Convert object to TLV structure 70 * 71 * @return the TLV structure 72 * @type ASN1 73 */ 74 PACEInfo.prototype.toTLV = function() { 75 var t = new ASN1(ASN1.SEQUENCE); 76 77 t.add(new ASN1(ASN1.OBJECT_IDENTIFIER, this.protocol)); 78 79 var bb = new ByteBuffer(); 80 bb.append(this.version); 81 t.add(new ASN1(ASN1.INTEGER, bb.toByteString())); 82 83 if (typeof(this.parameterId) != "undefined") { 84 var bb = new ByteBuffer(); 85 bb.append(this.parameterId); 86 t.add(new ASN1(ASN1.INTEGER, bb.toByteString())); 87 } 88 return t; 89 } 90 91 92 93 PACEInfo.prototype.toString = function() { 94 return "PACEInfo(protocol=" + this.protocol + ", version=" + this.version + ", parameterId=" + this.protocolId + ")"; 95 } 96 97 98 99 /** 100 * Create a PACEDomainParameterInfo object 101 * 102 * @class <p>This class encodes and decodes PACEDomainParameterInfo objects.</p> 103 * <p>The class implements the following ASN.1 syntax:</p> 104 * <pre> 105 * PACEDomainParameterInfo ::= SEQUENCE { 106 * protocol OBJECT IDENTIFIER(id-PACE-DH | id-PACE-ECDH), 107 * domainParameter AlgorithmIdentifier, 108 * parameterId INTEGER OPTIONAL 109 * } 110 * </pre> 111 * @constructor 112 * @param {ASN1} the optional tlv structure to initialize the object 113 */ 114 function PACEDomainParameterInfo(tlv) { 115 if (tlv && (tlv instanceof ASN1)) { 116 assert(tlv.isconstructed); 117 assert(tlv.elements >= 2); 118 119 var i = 0; 120 var t = tlv.get(i++); 121 assert(t.tag == ASN1.OBJECT_IDENTIFIER); 122 this.protocol = t.value; 123 124 var t = tlv.get(i++); 125 assert(t.tag == ASN1.SEQUENCE); 126 127 if (t.elements > 0) { 128 this.domainParameter = ECCUtils.decodeECParameters(t.get(1)); 129 } else { 130 this.domainParameter = new Key(); 131 this.domainParameter.setComponent(Key.ECC_CURVE_OID, new ByteString("brainpoolP256r1", OID)); 132 } 133 134 if (i < tlv.elements) { 135 var t = tlv.get(i++); 136 assert(t.tag == ASN1.INTEGER); 137 this.parameterId = t.value.toSigned(); 138 } 139 140 } 141 } 142 143 144 145 /** 146 * Convert object to TLV structure 147 * 148 * @return the TLV structure 149 * @type ASN1 150 */ 151 PACEDomainParameterInfo.prototype.toTLV = function() { 152 var t = new ASN1(ASN1.SEQUENCE); 153 154 t.add(new ASN1(ASN1.OBJECT_IDENTIFIER, this.protocol)); 155 156 var c = new ASN1(ASN1.SEQUENCE); 157 if (this.standardizedDomainParameter) { 158 c.add(new ASN1(ASN1.OBJECT_IDENTIFIER, new ByteString("standardizedDomainParameter", OID))); 159 c.add(new ASN1(ASN1.INTEGER, ByteString.valueOf(this.standardizedDomainParameter))); 160 } else { 161 162 } 163 t.add(c); 164 165 if (typeof(this.parameterId) != "undefined") { 166 var bb = new ByteBuffer(); 167 bb.append(this.parameterId); 168 t.add(new ASN1(ASN1.INTEGER, bb.toByteString())); 169 } 170 return t; 171 } 172 173 174 175 PACEDomainParameterInfo.getStandardizedDomainParameter = function(id) { 176 var key = new Key(); 177 key.setComponent(Key.ECC_CURVE_OID, new ByteString("brainpoolP256r1", OID)); 178 return key; 179 } 180 181 182 183 PACEDomainParameterInfo.prototype.toString = function() { 184 return "PACEDomainParameterInfo(protocol=" + this.protocol + ", parameterId=" + this.protocolId + ")"; 185 } 186 187 188 189 /** 190 * Create a PACE protocol object 191 * 192 * @class This class implements the PACE protocol 193 * 194 * @constructor 195 * 196 * @param {Crypto} crypto the crypto provider 197 * @param {ByteString} algo the algorithm OID 198 * @param {Key} domainparam the key object holding ECC domain parameter 199 * @param {Number} version protocol version (1 or 2) 200 */ 201 function PACE(crypto, algo, domparam, version) { 202 this.crypto = crypto; 203 this.algo = algo.toString(OID); 204 this.domparam = domparam; 205 206 if (typeof(version) != "undefined") { 207 this.version = version; 208 } else { 209 this.version = 1; 210 } 211 212 if (this.algo == PACE.id_PACE_ECDH_GM_3DES_CBC_CBC) { 213 this.symalgo = Key.DES; 214 } else { 215 this.symalgo = Key.AES; 216 } 217 218 this.sym = Crypto.AES; 219 } 220 221 222 223 /** 224 * Return algorithm type 225 * 226 * @type Number 227 * @returns Either Key.DES or Key.AES 228 */ 229 PACE.prototype.getSymmetricAlgorithm = function() { 230 return this.symalgo; 231 } 232 233 234 235 /** 236 * Derive key from input parameter, counter and optional nonce 237 * 238 * @param {ByteString} input the first part of the hash input 239 * @param {Number} counter the counter value 240 * @param {nonce} the optional nonce inserted between the input and the counter 241 * @return the key object 242 * @type Key 243 */ 244 PACE.prototype.deriveKey = function(input, counter, nonce) { 245 if (typeof(nonce) != "undefined") { 246 input = input.concat(nonce); 247 } 248 249 var bb = new ByteBuffer("000000", HEX); 250 bb.append(counter); 251 252 input = input.concat(bb.toByteString()); 253 254 var key = new Key(); 255 256 if (this.algo == PACE.id_PACE_ECDH_GM_3DES_CBC_CBC) { 257 var digest = this.crypto.digest(Crypto.SHA_1, input); 258 key.setComponent(Key.DES, digest.left(16)); 259 } else if (this.algo == PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_128) { 260 var digest = this.crypto.digest(Crypto.SHA_1, input); 261 key.setComponent(Key.AES, digest.left(16)); 262 } else if (this.algo == PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_192) { 263 var digest = this.crypto.digest(Crypto.SHA_256, input); 264 key.setComponent(Key.AES, digest.left(24)); 265 } else if (this.algo == PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_256) { 266 var digest = this.crypto.digest(Crypto.SHA_256, input); 267 key.setComponent(Key.AES, digest); 268 } else { 269 throw new GPError("pace", GPError.INVALID_MECH, 0x6A80, "Algorithm not supported"); 270 } 271 return key; 272 } 273 274 275 276 /** 277 * Set the password and derive the PACE key. 278 * @param {ByteString} pwd the PACE password (Hash Value for MRZ and ASCII string for others) 279 * @return the PACE key. 280 */ 281 PACE.prototype.setPassword = function(pwd) { 282 this.pacekey = this.deriveKey(pwd, 3); 283 } 284 285 286 287 /** 288 * Generate nonce and encrypt using PACE key. 289 * @return the encrypted nonce 290 * @type ByteString 291 */ 292 PACE.prototype.getEncryptedNonce = function() { 293 this.nonce = this.crypto.generateRandom(16); 294 if (this.symalgo == Key.DES) { 295 var encnonce = this.crypto.encrypt(this.pacekey, Crypto.DES_CBC, this.nonce); 296 } else { 297 var encnonce = this.crypto.encrypt(this.pacekey, Crypto.AES_ECB, this.nonce); 298 } 299 return encnonce; 300 } 301 302 303 304 /** 305 * Decrypt and store nonce using PACE key. 306 * 307 * @param {ByteString} nonce the encrypted nonce 308 */ 309 PACE.prototype.decryptNonce = function(encnonce) { 310 if (this.symalgo == Key.DES) { 311 this.nonce = this.crypto.decrypt(this.pacekey, Crypto.DES_CBC, encnonce); 312 } else { 313 this.nonce = this.crypto.decrypt(this.pacekey, Crypto.AES_ECB, encnonce); 314 } 315 } 316 317 318 319 /** 320 * Returns true, if the nonce is known. 321 * @return true if the nonce is known 322 * @type Boolean 323 */ 324 PACE.prototype.hasNonce = function() { 325 return (typeof(this.nonce) != "undefined"); 326 } 327 328 329 330 /** 331 * Generate ephemeral ECC key pair. 332 * 333 * @param domainParameter the domain parameter for the key pair 334 * @return the ephemeral public key 335 * @type Key 336 */ 337 PACE.prototype.generateEphemeralKeyPair = function(domainParameter) { 338 this.prk = new Key(domainParameter); 339 this.prk.setType(Key.PRIVATE); 340 341 this.puk = new Key(domainParameter); 342 this.puk.setType(Key.PUBLIC); 343 344 this.crypto.generateKeyPair(Crypto.EC, this.puk, this.prk); 345 346 return this.puk; 347 } 348 349 350 351 /** 352 * Generates and returns the mapping data for this instance 353 * @return the mapping data 354 * @type ByteString 355 */ 356 PACE.prototype.getMappingData = function() { 357 if (typeof(this.prk) == "undefined") { 358 this.generateEphemeralKeyPair(this.domparam); 359 } 360 361 var ecpk = new ByteString("04", HEX); 362 ecpk = ecpk.concat(this.puk.getComponent(Key.ECC_QX)); 363 ecpk = ecpk.concat(this.puk.getComponent(Key.ECC_QY)); 364 return ecpk; 365 } 366 367 368 369 /** 370 * Performs the mapping operation with mapping data from the other side 371 * 372 */ 373 PACE.prototype.performMapping = function(mappingData) { 374 if (mappingData.byteAt(0) != 0x04) 375 throw new GPError("PACE", GPError.INVALID_DATA, 0x6A80, "Public key must start with '04'"); 376 377 if (typeof(this.nonce) == "undefined") 378 throw new GPError("PACE", GPError.INVALID_MECH, 0x6985, "Nonce is not yet defined"); 379 380 var l = (mappingData.length - 1) >> 1; 381 if (l != this.prk.getComponent(Key.ECC_P).length) { 382 throw new GPError("PACE", GPError.INVALID_DATA, 0, "Public key size does not match private key size"); 383 } 384 385 var h = this.crypto.decrypt(this.prk, Crypto.ECDHP, mappingData.bytes(1)); 386 387 var l = h.length >> 1; 388 var H = new Key(this.domparam); 389 H.setComponent(Key.ECC_QX, h.bytes(0, l)); 390 H.setComponent(Key.ECC_QY, h.bytes(l, l)); 391 392 var G = new Key(this.domparam); 393 // Copy generator point into public key point 394 G.setComponent(Key.ECC_QX, G.getComponent(Key.ECC_GX)); 395 G.setComponent(Key.ECC_QY, G.getComponent(Key.ECC_GY)); 396 397 // Calculate G' = s * G + P, where P is initially stored in H and 398 // G' is finally stored in H. 399 this.crypto.deriveKey(G, Crypto.EC_MULTIPLY_ADD, this.nonce, H); 400 401 // Create new domain parameter with G' 402 this.ephDomParam = new Key(this.domparam); 403 this.ephDomParam.setComponent(Key.ECC_GX, H.getComponent(Key.ECC_QX)); 404 this.ephDomParam.setComponent(Key.ECC_GY, H.getComponent(Key.ECC_QY)); 405 } 406 407 408 409 /** 410 * Returns the ephemeral public key based on the new domain parameter 411 * 412 * @return the encoded public key 413 * @type ByteString 414 */ 415 PACE.prototype.getEphemeralPublicKey = function() { 416 this.generateEphemeralKeyPair(this.ephDomParam); 417 var ecpk = new ByteString("04", HEX); 418 ecpk = ecpk.concat(this.puk.getComponent(Key.ECC_QX)); 419 ecpk = ecpk.concat(this.puk.getComponent(Key.ECC_QY)); 420 return ecpk; 421 } 422 423 424 425 /** 426 * Performs the mapping operation with mapping data from the other side 427 * 428 * @param {ByteString} publicKey the public key in encoded format 429 */ 430 PACE.prototype.performKeyAgreement = function(publicKey) { 431 if (publicKey.byteAt(0) != 0x04) 432 throw new GPError("PACE", GPError.INVALID_DATA, 0x6A80, "Public key must start with '04'"); 433 434 if (typeof(this.nonce) == "undefined") 435 throw new GPError("PACE", GPError.INVALID_MECH, 0x6985, "Nonce is not yet defined"); 436 437 var l = (publicKey.length - 1) >> 1; 438 if (l != this.prk.getComponent(Key.ECC_P).length) { 439 throw new GPError("PACE", GPError.INVALID_DATA, 0, "Public key size does not match private key size"); 440 } 441 442 this.otherPuK = new Key(this.ephDomParam); 443 this.otherPuK.setComponent(Key.ECC_QX, publicKey.bytes( 1, l)); 444 this.otherPuK.setComponent(Key.ECC_QY, publicKey.bytes(l + 1, l)); 445 446 var k = this.crypto.decrypt(this.prk, Crypto.ECDH, publicKey.bytes(1)); 447 GPSystem.trace("Shared Secret K:"); 448 GPSystem.trace(k); 449 this.kenc = this.deriveKey(k, 1); 450 this.kmac = this.deriveKey(k, 2); 451 452 } 453 454 455 456 /** 457 * Strips leading zeros of a ByteString 458 * 459 * @param {ByteString} value the ByteString value 460 * @return the stripped ByteString object, may be an empty ByteString 461 * @type ByteString 462 */ 463 PACE.stripLeadingZeros = function(value) { 464 var i = 0; 465 for (; (i < value.length) && (value.byteAt(i) == 0); i++); 466 467 return value.right(value.length - i); 468 } 469 470 471 472 /** 473 * Encode an ECC public key in the format defined by the EAC 2.0 specification 474 * 475 * @param {String} oid the object identifier to encode 476 * @param {Key} key the EC public key 477 * @param {Boolean} withDP true to encode domain parameter as well 478 * @type ASN1 479 * @returns the ASN1 encoded public key object 480 */ 481 PACE.encodePublicKey = function(oid, key, withDP) { 482 483 var t = new ASN1("ecPublicKey", 0x7F49); 484 t.add(new ASN1("objectIdentifier", ASN1.OBJECT_IDENTIFIER, new ByteString(oid, OID))); 485 if (withDP) { 486 t.add(new ASN1("primeModulus", 0x81, key.getComponent(Key.ECC_P))); 487 t.add(new ASN1("firstCoefficient", 0x82, key.getComponent(Key.ECC_A))); 488 t.add(new ASN1("secondCoefficient", 0x83, key.getComponent(Key.ECC_B))); 489 490 var point = new ByteString("04", HEX); 491 point = point.concat(key.getComponent(Key.ECC_GX)); 492 point = point.concat(key.getComponent(Key.ECC_GY)); 493 t.add(new ASN1("basePoint", 0x84, point)); 494 495 t.add(new ASN1("orderOfTheBasePoint", 0x85, key.getComponent(Key.ECC_N))); 496 } 497 var point = new ByteString("04", HEX); 498 point = point.concat(key.getComponent(Key.ECC_QX)); 499 point = point.concat(key.getComponent(Key.ECC_QY)); 500 t.add(new ASN1("publicPoint", 0x86, point)); 501 502 if (withDP) { 503 var cofactor = key.getComponent(Key.ECC_H); 504 cofactor = PACE.stripLeadingZeros(cofactor); 505 506 t.add(new ASN1("cofactor", 0x87, cofactor)); 507 } 508 509 return t; 510 } 511 512 513 514 /** 515 * Calculate the authentication token over the public key received from 516 * the other side 517 * 518 * @return the MAC over the authentication data 519 * @type ByteString 520 */ 521 PACE.prototype.calculateAuthenticationToken = function() { 522 var t = PACE.encodePublicKey(this.algo, this.otherPuK, (this.version == 1)); 523 GPSystem.trace("Authentication Token:"); 524 GPSystem.trace(t); 525 526 if (this.symalgo == Key.DES) { 527 var inp = t.getBytes().pad(Crypto.ISO9797_METHOD_2); 528 var at = this.crypto.sign(this.kmac, Crypto.DES_MAC_EMV, inp); 529 } else { 530 var at = this.crypto.sign(this.kmac, Crypto.AES_CMAC, t.getBytes()).left(8); 531 } 532 533 return at; 534 } 535 536 537 538 /** 539 * Calculate and verify the authentication token over the public key received from 540 * the other side 541 * 542 * @param {ByteString} the MAC over the authentication data 543 * @return true if the MAC is valid 544 * @type Boolean 545 */ 546 PACE.prototype.verifyAuthenticationToken = function(authToken) { 547 var t = PACE.encodePublicKey(this.algo, this.puk, (this.version == 1)); 548 GPSystem.trace("Authentication Token:"); 549 GPSystem.trace(t); 550 551 if (this.symalgo == Key.DES) { 552 var inp = t.getBytes().pad(Crypto.ISO9797_METHOD_2); 553 var at = this.crypto.sign(this.kmac, Crypto.DES_MAC_EMV, inp); 554 } else { 555 var at = this.crypto.sign(this.kmac, Crypto.AES_CMAC, t.getBytes()).left(8); 556 } 557 558 return at.equals(authToken); 559 } 560 561 562 563 /** 564 * Returns true, if the mapping has been performed. 565 * @return true if the mapping has been performed 566 * @type Boolean 567 */ 568 PACE.prototype.hasMapping = function() { 569 return (typeof(this.ephDomParam) != "undefined"); 570 } 571 572 573 574 /** 575 * Describe key 576 * @param {Key} the key 577 * @return the string describing the key 578 * @type String 579 */ 580 PACE.keyToString = function(key) { 581 var str = ""; 582 var kval = key.getComponent(Key.AES); 583 if (kval) { 584 str += "(AES) " + kval + "\n"; 585 } 586 var kval = key.getComponent(Key.DES); 587 if (kval) { 588 str += "(DES) " + kval + "\n"; 589 } 590 return str; 591 } 592 593 594 595 /** 596 * Returns a human readable presentation of the current pace state. 597 * return {String} the object information 598 */ 599 PACE.prototype.toString = function() { 600 var str = "Algorithm " + this.algo + "\n"; 601 602 if (typeof(this.pacekey) != "undefined") { 603 str += "PACE Key " + PACE.keyToString(this.pacekey); 604 } 605 606 if (typeof(this.nonce) != "undefined") { 607 str += "Nonce " + this.nonce + "\n"; 608 } 609 610 if (typeof(this.ephDomParam) != "undefined") { 611 str += "Point G' " + this.ephDomParam.getComponent(Key.ECC_GX) + " " + this.ephDomParam.getComponent(Key.ECC_GY) + "\n"; 612 } 613 614 if (typeof(this.kenc) != "undefined") { 615 str += "Kenc " + PACE.keyToString(this.kenc); 616 } 617 618 if (typeof(this.kmac) != "undefined") { 619 str += "Kmac" + PACE.keyToString(this.kmac); 620 } 621 622 return str; 623 } 624 625 626 PACE.bsi_de = "0.4.0.127.0.7"; 627 PACE.id_PACE = PACE.bsi_de + ".2.2.4"; 628 PACE.id_PACE_ECDH_GM = PACE.id_PACE + ".2"; 629 PACE.id_PACE_ECDH_GM_3DES_CBC_CBC = PACE.id_PACE_ECDH_GM + ".1"; 630 PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_128 = PACE.id_PACE_ECDH_GM + ".2"; 631 PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_192 = PACE.id_PACE_ECDH_GM + ".3"; 632 PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_256 = PACE.id_PACE_ECDH_GM + ".4"; 633 634 PACE.id_roles = PACE.bsi_de + ".3.1.2"; 635 PACE.id_IS = PACE.id_roles + ".1"; 636 637