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