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 Basic helper functions to convert PKCS#8 data to GP keys and vice versa 25 */ 26 27 28 29 /** 30 * Empty constructor 31 */ 32 function PKCS8() { 33 } 34 35 if (typeof(exports) != "undefined") 36 exports.PKCS8 = PKCS8; 37 38 39 PKCS8.idEcPublicKey = new ByteString("id-ecPublicKey", OID); 40 PKCS8.rsaEncryption = new ByteString("1.2.840.113549.1.1.1", OID); 41 42 /** 43 * Convert x/y coordinates to uncompressed format 44 * 45 * @param {ByteString} x the x coordinate 46 * @param {ByteString} y the y coordinate 47 * @type ByteString 48 * @return ByteString containing compressed format 49 * 50 */ 51 PKCS8.encodeUncompressedECPoint = function(x,y) { 52 53 var bb = new ByteBuffer(); 54 55 // uncompressed encoding 56 bb.append(new ByteString("04", HEX)); 57 bb.append(x); 58 bb.append(y); 59 60 return bb.toByteString(); 61 } 62 63 64 65 /** 66 * Convert uncompressed format to x and y coordinates 67 * 68 * @param {ByteString} compressed point 69 * @type Object 70 * @return Object with ByteString properties x and y 71 * 72 */ 73 PKCS8.decodeUncompressedECPoint = function(uncompressedPoint) { 74 75 // Determine the size of the coordinates ignoring the indicator byte '04' 76 var length = uncompressedPoint.length - 1; 77 78 var sizeOfCoordinate = length >> 1; 79 80 var xValue = uncompressedPoint.bytes(1, sizeOfCoordinate); 81 var yValue = uncompressedPoint.bytes(1 + sizeOfCoordinate, sizeOfCoordinate); 82 83 return { x:xValue, y:yValue }; 84 } 85 86 87 88 /** 89 * Integer to octet string conversion 90 * 91 * @param {ByteString} value the encoded integer value 92 * @param {Number} the number of digits 93 * @type ByteString 94 * @return the truncated or padded result 95 */ 96 PKCS8.I2O = function(value, length) { 97 if (value.length > length) { 98 value = value.right(length); 99 } 100 while (value.length < length) { 101 value = PKCS8.PAD.left((length - value.length - 1 & 15) + 1).concat(value); 102 } 103 return value; 104 } 105 PKCS8.PAD = new ByteString("00000000000000000000000000000000", HEX); 106 107 108 109 /** 110 * Strips leading zeros of a ByteString 111 * 112 * @param {ByteString} value the ByteString value 113 * @return the stripped ByteString object, may be an empty ByteString 114 * @type ByteString 115 */ 116 PKCS8.stripLeadingZeros = function(value, size) { 117 if (typeof(size) == "undefined") { 118 var limit = value.length; 119 } else { 120 limit = value.length - size; 121 } 122 123 var i = 0; 124 for (; (i < limit) && (value.byteAt(i) == 0); i++); 125 126 return value.bytes(i); 127 } 128 129 130 131 /** 132 * Removes leading zeros and prepends a single '00' to ByteStrings which have the most significant bit set. 133 * 134 * This prevent interpretation of the integer representation if converted into 135 * a signed ASN1 INTEGER. 136 * 137 * @param {ByteString} value the value to convert 138 * @return the converted value 139 * @type ByteString 140 */ 141 PKCS8.convertUnsignedInteger = function(value) { 142 assert(value.length > 0); 143 144 var i = 0; 145 for (; (i < value.length - 1) && (value.byteAt(i) == 0); i++); 146 147 if (value.byteAt(i) >= 0x80) { 148 value = (new ByteString("00", HEX)).concat(value.bytes(i)); 149 } else { 150 value = value.bytes(i); 151 } 152 153 return value; 154 } 155 156 157 158 /** 159 * Encode a given GP ECC private key as specified by the PKCS#8 format 160 * 161 * @param {Key} the private key object that should be encoded 162 * @return the encoded PKCS#8 private key 163 * @type ByteString 164 */ 165 PKCS8.encodeECCKeyUsingPKCS8Format = function(privateKey) { 166 var privateKeyInfo = new ASN1(ASN1.SEQUENCE); 167 168 // Set the version number - must be zero 169 privateKeyInfo.add(new ASN1(ASN1.INTEGER, new ByteString("00", HEX))); 170 171 var privateKeyAlgorithm = new ASN1(ASN1.SEQUENCE); 172 privateKeyAlgorithm.add(new ASN1(ASN1.OBJECT_IDENTIFIER, PKCS8.idEcPublicKey)); 173 174 var domainInfo = new ASN1(ASN1.SEQUENCE); 175 176 // Cofactor - must be 1 177 domainInfo.add(new ASN1(ASN1.INTEGER, PKCS8.stripLeadingZeros(privateKey.getComponent(Key.ECC_H)))); 178 179 var field = new ASN1(ASN1.SEQUENCE); 180 181 // we are using a prime field 182 field.add(new ASN1(ASN1.OBJECT_IDENTIFIER, new ByteString("prime-field", OID))); // prime field 183 184 var primeOrder = privateKey.getComponent(Key.ECC_P); 185 if (primeOrder.byteAt(0) >= 0x80) { // signed int? -> add 0x00 186 field.add(new ASN1(ASN1.INTEGER, new ByteString("00", HEX).concat(privateKey.getComponent(Key.ECC_P)))); 187 } else { 188 field.add(new ASN1(ASN1.INTEGER, privateKey.getComponent(Key.ECC_P))); 189 } 190 191 domainInfo.add(field); 192 193 // Coefficients a and b 194 var coeff = new ASN1(ASN1.SEQUENCE); 195 196 // first coefficient 197 coeff.add(new ASN1(ASN1.OCTET_STRING, privateKey.getComponent(Key.ECC_A))); 198 199 // second coefficient 200 coeff.add(new ASN1(ASN1.OCTET_STRING, privateKey.getComponent(Key.ECC_B))); 201 202 domainInfo.add(coeff); 203 204 // Base point (uncompressed) 205 var gx = privateKey.getComponent(Key.ECC_GX); 206 var gy = privateKey.getComponent(Key.ECC_GY); 207 208 domainInfo.add(new ASN1(ASN1.OCTET_STRING, PKCS8.encodeUncompressedECPoint(gx, gy))); 209 210 // group order generated by the base point 211 var groupOrder = privateKey.getComponent(Key.ECC_N); 212 if (groupOrder.byteAt(0) >= 0x80) { // signed int? -> add 0x00 213 domainInfo.add(new ASN1(ASN1.INTEGER, new ByteString("00", HEX).concat(privateKey.getComponent(Key.ECC_N)))); 214 } else { 215 domainInfo.add(new ASN1(ASN1.INTEGER, privateKey.getComponent(Key.ECC_N))); 216 } 217 218 privateKeyAlgorithm.add(domainInfo); 219 220 // encode the key information 221 privateKeyInfo.add(privateKeyAlgorithm); 222 223 // encode the private key 224 var encodedPrivateKey = new ASN1(ASN1.OCTET_STRING); 225 226 var pk = privateKey.getComponent(Key.ECC_D); 227 var key = new ASN1(ASN1.SEQUENCE); 228 key.add(new ASN1(ASN1.INTEGER, new ByteString("01", HEX))); 229 key.add(new ASN1(ASN1.OCTET_STRING, pk)); 230 231 encodedPrivateKey.add(key); 232 233 privateKeyInfo.add(encodedPrivateKey); 234 235 // print(privateKeyInfo); 236 return privateKeyInfo.getBytes(); 237 } 238 239 240 241 /** 242 * Encode RSA private key as defined in PKCS#1 243 * 244 * RSAPrivateKey ::= SEQUENCE { 245 * version Version, 246 * modulus INTEGER, -- n 247 * publicExponent INTEGER, -- e 248 * privateExponent INTEGER, -- d 249 * prime1 INTEGER, -- p 250 * prime2 INTEGER, -- q 251 * exponent1 INTEGER, -- d mod (p-1) 252 * exponent2 INTEGER, -- d mod (q-1) 253 * coefficient INTEGER, -- (inverse of q) mod p 254 * otherPrimeInfos OtherPrimeInfos OPTIONAL 255 * } 256 * @param {Key} privateKey the private RSA key in CRT format 257 * @type ByteString 258 * @return the encoded RSA key 259 */ 260 PKCS8.encodeRSAKey = function(privateKey, publicKey) { 261 var rsaPrivateKey = 262 new ASN1(ASN1.SEQUENCE); 263 264 rsaPrivateKey.add(new ASN1(ASN1.INTEGER, ByteString.valueOf(0))); 265 if (typeof(publicKey) != "undefined") { 266 rsaPrivateKey.add(new ASN1(ASN1.INTEGER, PKCS8.convertUnsignedInteger(publicKey.getComponent(Key.MODULUS)))); 267 rsaPrivateKey.add(new ASN1(ASN1.INTEGER, PKCS8.convertUnsignedInteger(publicKey.getComponent(Key.EXPONENT)))); 268 } else { 269 rsaPrivateKey.add(new ASN1(ASN1.INTEGER, ByteString.valueOf(0))); 270 rsaPrivateKey.add(new ASN1(ASN1.INTEGER, ByteString.valueOf(0))); 271 } 272 rsaPrivateKey.add(new ASN1(ASN1.INTEGER, ByteString.valueOf(0))); // Private Exponent not at interface for CRT format 273 rsaPrivateKey.add(new ASN1(ASN1.INTEGER, PKCS8.convertUnsignedInteger(privateKey.getComponent(Key.CRT_P)))); 274 rsaPrivateKey.add(new ASN1(ASN1.INTEGER, PKCS8.convertUnsignedInteger(privateKey.getComponent(Key.CRT_Q)))); 275 rsaPrivateKey.add(new ASN1(ASN1.INTEGER, PKCS8.convertUnsignedInteger(privateKey.getComponent(Key.CRT_DP1)))); 276 rsaPrivateKey.add(new ASN1(ASN1.INTEGER, PKCS8.convertUnsignedInteger(privateKey.getComponent(Key.CRT_DQ1)))); 277 rsaPrivateKey.add(new ASN1(ASN1.INTEGER, PKCS8.convertUnsignedInteger(privateKey.getComponent(Key.CRT_PQ)))); 278 279 return rsaPrivateKey.getBytes(); 280 } 281 282 283 284 /** 285 * Encode a given GP RSA private key as specified by the PKCS#8 format 286 * 287 * @param {Key} the private key object that should be encoded 288 * @return the encoded PKCS#8 private key 289 * @type ByteString 290 */ 291 PKCS8.encodeRSAKeyUsingPKCS8Format = function(privateKey, publicKey) { 292 var privateKeyInfo = new ASN1(ASN1.SEQUENCE); 293 294 // Set the version number - must be zero 295 privateKeyInfo.add(new ASN1(ASN1.INTEGER, new ByteString("00", HEX))); 296 297 var privateKeyAlgorithm = new ASN1(ASN1.SEQUENCE); 298 privateKeyAlgorithm.add(new ASN1(ASN1.OBJECT_IDENTIFIER, PKCS8.rsaEncryption)); 299 privateKeyAlgorithm.add(new ASN1(ASN1.NULL)); 300 301 // encode the key information 302 privateKeyInfo.add(privateKeyAlgorithm); 303 304 // encode the private key 305 var encodedPrivateKey = new ASN1(ASN1.OCTET_STRING, PKCS8.encodeRSAKey(privateKey, publicKey)); 306 307 privateKeyInfo.add(encodedPrivateKey); 308 309 // print(privateKeyInfo); 310 return privateKeyInfo.getBytes(); 311 } 312 313 314 315 /** 316 * Encode a given GP private key as specified by the PKCS#8 format 317 * 318 * For now we only support the encoding of ECC private keys in a prime field 319 * 320 * @param {Key} the private key object that should be encoded 321 * @return the encoded PKCS#8 private key 322 * @type ByteString 323 */ 324 PKCS8.encodeKeyUsingPKCS8Format = function(privateKey, publicKey) { 325 326 assert(privateKey.getType() == Key.PRIVATE); 327 if (typeof(privateKey.getComponent(Key.ECC_P)) != "undefined") { 328 return PKCS8.encodeECCKeyUsingPKCS8Format(privateKey); 329 } else { 330 return PKCS8.encodeRSAKeyUsingPKCS8Format(privateKey, publicKey); 331 } 332 } 333 334 335 336 /** 337 * Decode a given PKCS#8 ECC private key from the given TLV objects and create a GP key object 338 * 339 * For now we only support the decoding of ECC private keys in a prime field 340 * 341 * @param {ASN1} algparam the algorithm parameter from AlgorithmInfo 342 * @param {ASN1} privateKey the privateKey element from the PKCS#8 structure 343 * @return the GP key object 344 * @type Key 345 */ 346 PKCS8.decodeECCKeyFromPKCS8Format = function(domainParameter, encodedKey) { 347 348 var key = new Key(); 349 350 key.setType(Key.PRIVATE); 351 352 if (domainParameter.tag == ASN1.OBJECT_IDENTIFIER) { 353 key.setComponent(Key.ECC_CURVE_OID, domainParameter.value); 354 } else { 355 // Decode the domain parameters 356 var cofactor = domainParameter.get(0); 357 key.setComponent(Key.ECC_H, cofactor.value); 358 359 var order = domainParameter.get(1).get(1); 360 key.setComponent(Key.ECC_P, PKCS8.stripLeadingZeros(order.value)); 361 362 var coeff_A = domainParameter.get(2).get(0); 363 key.setComponent(Key.ECC_A, coeff_A.value); 364 365 var coeff_B = domainParameter.get(2).get(1); 366 key.setComponent(Key.ECC_B, coeff_B.value); 367 368 var generatorPoint = domainParameter.get(3).value; 369 370 var coordinates = PKCS8.decodeUncompressedECPoint(generatorPoint); 371 372 key.setComponent(Key.ECC_GX, coordinates.x); 373 key.setComponent(Key.ECC_GY, coordinates.y); 374 375 var groupOrder = domainParameter.get(4); 376 377 key.setComponent(Key.ECC_N, PKCS8.stripLeadingZeros(groupOrder.value)); 378 } 379 380 key.setComponent(Key.ECC_D, encodedKey.get(1).value); 381 382 return key; 383 } 384 385 386 387 /** 388 * Decode a PKCS#1 encoded RSA private key from the given TLV objects and create a GP key object with the private key 389 * 390 * @param {ASN1} algparam the algorithm parameter from AlgorithmInfo 391 * @param {ASN1} privateKey the privateKey element from the PKCS#8 structure 392 * @return the GP key object 393 * @type Key 394 */ 395 PKCS8.decodeRSAPrivateKeyFromPKCS1Format = function(privateKey) { 396 397 var key = new Key(); 398 399 key.setType(Key.PRIVATE); 400 401 assert(privateKey.tag == ASN1.SEQUENCE); 402 assert(privateKey.isconstructed); 403 assert(privateKey.elements >= 9); 404 405 for (var i = 0; i < 9; i++) { 406 var e = privateKey.get(i); 407 assert(e.tag == ASN1.INTEGER); 408 assert(!e.isconstructed); 409 } 410 411 assert(privateKey.get(0).value.toUnsigned() == 0); 412 413 var p = PKCS8.stripLeadingZeros(privateKey.get(4).value); 414 var l = p.length; 415 key.setComponent(Key.CRT_P, p); 416 key.setComponent(Key.CRT_Q, PKCS8.I2O(privateKey.get(5).value, l)); 417 key.setComponent(Key.CRT_DP1, PKCS8.I2O(privateKey.get(6).value, l)); 418 key.setComponent(Key.CRT_DQ1, PKCS8.I2O(privateKey.get(7).value, l)); 419 key.setComponent(Key.CRT_PQ, PKCS8.I2O(privateKey.get(8).value, l)); 420 421 return key; 422 } 423 424 425 426 /** 427 * Decode a PKCS#1 encoded RSA private key from the given TLV objects and create a GP key object with the public key 428 * 429 * @param {ASN1} algparam the algorithm parameter from AlgorithmInfo 430 * @param {ASN1} privateKey the privateKey element from the PKCS#8 structure 431 * @return the GP key object 432 * @type Key 433 */ 434 PKCS8.decodeRSAPublicKeyFromPKCS1Format = function(privateKey) { 435 436 var key = new Key(); 437 438 key.setType(Key.MODULUS); 439 440 assert(privateKey.tag == ASN1.SEQUENCE); 441 assert(privateKey.isconstructed); 442 assert(privateKey.elements >= 9); 443 444 for (var i = 0; i < 9; i++) { 445 var e = privateKey.get(i); 446 assert(e.tag == ASN1.INTEGER); 447 assert(!e.isconstructed); 448 } 449 450 assert(privateKey.get(0).value.toUnsigned() == 0); 451 452 key.setComponent(Key.MODULUS, PKCS8.stripLeadingZeros(privateKey.get(1).value)); 453 key.setComponent(Key.EXPONENT, PKCS8.stripLeadingZeros(privateKey.get(2).value)); 454 455 return key; 456 } 457 458 459 460 /** 461 * Decode a given PKCS#8 RSA private key from the given TLV objects and create a GP key object 462 * 463 * @param {ASN1} algparam the algorithm parameter from AlgorithmInfo 464 * @param {ASN1} privateKey the privateKey element from the PKCS#8 structure 465 * @return the GP key object 466 * @type Key 467 */ 468 PKCS8.decodeRSAKeyFromPKCS8Format = function(algparam, privateKey) { 469 470 var key = new Key(); 471 472 key.setType(Key.PRIVATE); 473 474 assert(algparam.tag == ASN1.NULL); 475 assert(!algparam.isconstructed); 476 assert(algparam.length == 0); 477 478 return PKCS8.decodeRSAPrivateKeyFromPKCS1Format(privateKey); 479 } 480 481 482 483 /** 484 * Decode a given PKCS#8 private key from the given ByteString and create a GP key object 485 * 486 * For now we only support the decoding of ECC private keys in a prime field 487 * 488 * @param {ByteString} the private key object in PKCS#8 format 489 * @return the GP key object 490 * @type Key 491 */ 492 PKCS8.decodeKeyFromPKCS8Format = function(encodedKey) { 493 var p8 = new ASN1(encodedKey); 494 495 assert(p8.isconstructed); 496 assert(p8.elements >= 3); 497 498 var version = p8.get(0); 499 assert(version.tag == ASN1.INTEGER); 500 assert(version.value.toUnsigned() == 0); 501 502 var pkai = p8.get(1); 503 assert(pkai.tag == ASN1.SEQUENCE); 504 assert(pkai.isconstructed); 505 assert(pkai.elements == 2); 506 var keytype = pkai.get(0); 507 508 assert(keytype.tag == ASN1.OBJECT_IDENTIFIER); 509 510 var algparam = pkai.get(1); 511 512 var privateKey = p8.get(2); 513 assert(privateKey.tag == ASN1.OCTET_STRING); 514 if (privateKey.isconstructed) { 515 privateKey = privateKey.get(0); 516 } else { 517 privateKey = new ASN1(privateKey.value); 518 } 519 520 if (keytype.value.equals(PKCS8.rsaEncryption)) { 521 return PKCS8.decodeRSAKeyFromPKCS8Format(algparam, privateKey); 522 } else if (keytype.value.equals(PKCS8.idEcPublicKey)) { 523 return PKCS8.decodeECCKeyFromPKCS8Format(algparam, privateKey); 524 } else { 525 throw new Error("Unknown key type " + keytype.value.toString(OID)); 526 } 527 } 528 529 530 531 /** 532 * Simple self-test 533 */ 534 PKCS8.test = function() { 535 536 // Set OID for EC curve 537 var ecCurve = "1.3.36.3.3.2.8.1.1.7"; 538 539 var crypto = new Crypto("BC"); 540 541 // Create empty public key object 542 var pubKey = new Key(); 543 pubKey.setType(Key.PUBLIC); 544 pubKey.setComponent(Key.ECC_CURVE_OID, new ByteString(ecCurve, OID)); 545 546 // Create empty private key object 547 var priKey = new Key(); 548 priKey.setType(Key.PRIVATE); 549 priKey.setComponent(Key.ECC_CURVE_OID, new ByteString(ecCurve, OID)); 550 551 // Generate key pair 552 crypto.generateKeyPair(Crypto.EC, pubKey, priKey); 553 554 // Encode 555 var p8Key = PKCS8.encodeKeyUsingPKCS8Format(priKey); 556 557 // Decode 558 var decodedKeyObject = PKCS8.decodeKeyFromPKCS8Format(p8Key); 559 560 // Compare 561 assert(decodedKeyObject.getComponent(Key.ECC_D).equals(priKey.getComponent(Key.ECC_D))); 562 563 assert(decodedKeyObject.getComponent(Key.ECC_GX).equals(priKey.getComponent(Key.ECC_GX))); 564 assert(decodedKeyObject.getComponent(Key.ECC_GY).equals(priKey.getComponent(Key.ECC_GY))); 565 assert(decodedKeyObject.getComponent(Key.ECC_A).equals(pubKey.getComponent(Key.ECC_A))); 566 assert(decodedKeyObject.getComponent(Key.ECC_B).equals(pubKey.getComponent(Key.ECC_B))); 567 568 // Encode 569 var refp8Key = PKCS8.encodeKeyUsingPKCS8Format(decodedKeyObject); 570 571 // Compare 572 assert(p8Key.equals(refp8Key)); 573 574 575 // Create empty public key object 576 var pubKey = new Key(); 577 pubKey.setType(Key.PUBLIC); 578 pubKey.setSize(1024); 579 580 // Create empty private key object 581 var priKey = new Key(); 582 priKey.setType(Key.PRIVATE); 583 584 // Generate key pair 585 crypto.generateKeyPair(Crypto.RSA, pubKey, priKey); 586 587 // Encode 588 var p8Key = PKCS8.encodeKeyUsingPKCS8Format(priKey); 589 590 // Decode 591 var decodedKeyObject = PKCS8.decodeKeyFromPKCS8Format(p8Key); 592 593 // Compare 594 assert(decodedKeyObject.getComponent(Key.CRT_P).equals(priKey.getComponent(Key.CRT_P))); 595 assert(decodedKeyObject.getComponent(Key.CRT_Q).equals(priKey.getComponent(Key.CRT_Q))); 596 assert(decodedKeyObject.getComponent(Key.CRT_DP1).equals(priKey.getComponent(Key.CRT_DP1))); 597 assert(decodedKeyObject.getComponent(Key.CRT_DQ1).equals(priKey.getComponent(Key.CRT_DQ1))); 598 assert(decodedKeyObject.getComponent(Key.CRT_PQ).equals(priKey.getComponent(Key.CRT_PQ))); 599 600 // Encode 601 var refp8Key = PKCS8.encodeKeyUsingPKCS8Format(decodedKeyObject); 602 603 // Compare 604 assert(p8Key.equals(refp8Key)); 605 } 606