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