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 A X509 certificate generator class following RFC5280
 25  */
 26 
 27 PKIXCommon = require("scsh/x509/PKIXCommon").PKIXCommon;
 28 
 29 
 30 
 31 /**
 32  * Create a X.509 certificate generator.
 33  *
 34  * @class Class implementing a X.509 certificate generator
 35  * @constructor
 36  *
 37  * @param {Crypto} crypto the crypto provider to use for signing operations
 38  */
 39 function X509CertificateGenerator(crypto) {
 40 	this.crypto = crypto;
 41 	this.encodeECDomainParameter = true;
 42 	this.reset();
 43 }
 44 
 45 exports.X509CertificateGenerator = X509CertificateGenerator;
 46 
 47 
 48 
 49 /**
 50  * Resets all internal state variables.
 51  *
 52  */
 53 X509CertificateGenerator.prototype.reset = function() {
 54 	this.extensions = new Array();
 55 
 56 }
 57 
 58 
 59 
 60 /**
 61  * Sets the serial number.
 62  *
 63  * @param {ByteString} serialNumber the serial number for the certificate
 64  */
 65 X509CertificateGenerator.prototype.setSerialNumber = function(serialNumber) {
 66 	this.serialNumber = serialNumber;
 67 }
 68 
 69 
 70 
 71 /**
 72  * Sets the isser name.
 73  *
 74  * <p>The issuer name must be a JavaScript object containing the properties:</p>
 75  * <ul>
 76  *  <li>C - the country</li>
 77  *  <li>O - the organization</li>
 78  *  <li>OU - the organization unit</li>
 79  *  <li>CN - the common name</li>
 80  * </ul>
 81  * <p>Example:</p>
 82  * <pre>
 83  *	var issuer = { C:"UT", O:"ACME Corporation", CN:"Test-CA" };
 84  * </pre>
 85  * @param {Object} issuer the issuer name
 86  */
 87 X509CertificateGenerator.prototype.setIssuer = function(issuer) {
 88 	this.issuer = issuer;
 89 }
 90 
 91 
 92 
 93 /**
 94  * Sets the effective date for the certificate.
 95  *
 96  * @param {String or Date} date the date in format YYMMDDHHMMSSZ
 97  */
 98 X509CertificateGenerator.prototype.setNotBefore = function(date) {
 99 	this.notBefore = date;
100 }
101 
102 
103 
104 /**
105  * Sets the expiration date for the certificate.
106  *
107  * @param {String or Date} date the date in format YYMMDDHHMMSSZ
108  */
109 X509CertificateGenerator.prototype.setNotAfter = function(date) {
110 	this.notAfter = date;
111 }
112 
113 
114 
115 /**
116  * Sets the subject name.
117  *
118  * <p>The subject name must be a JavaScript object containing the properties:</p>
119  * <ul>
120  *  <li>C - the country</li>
121  *  <li>O - the organization</li>
122  *  <li>OU - the organization unit</li>
123  *  <li>CN - the common name</li>
124  * </ul>
125  * <p>Example:</p>
126  * <pre>
127  *	var subject = { C:"UT", O:"ACME Corporation", CN:"Joe Doe" };
128  * </pre>
129  * @param {Object} subject the subject name
130  */
131 X509CertificateGenerator.prototype.setSubject = function(subject) {
132 	this.subject = subject;
133 }
134 
135 
136 
137 /**
138  * Sets the subjects public key
139  *
140  * <p>The methods accepts ECC and RSA Public Keys.</p>
141  *
142  * @param {Key} publicKey the subjects public key
143  */
144 X509CertificateGenerator.prototype.setPublicKey = function(publicKey) {
145 	this.publicKey = publicKey;
146 }
147 
148 
149 
150 /**
151  * Sets the signature algorithm. Currently only Crypto.RSA is supported
152  *
153  * @param {Number} alg the signature algorithm, only Crypto.RSA supported
154  */
155 X509CertificateGenerator.prototype.setSignatureAlgorithm = function(alg) {
156 	this.signatureAlgorithm = alg;
157 }
158 
159 
160 
161 /**
162  * Adds an extension to the certificate
163  *
164  * <p>The structure is defined as:</p>
165  * <pre>
166  *    Extension  ::=  SEQUENCE  {
167  *        extnID      OBJECT IDENTIFIER,
168  *        critical    BOOLEAN DEFAULT FALSE,
169  *        extnValue   OCTET STRING
170  *                    -- contains the DER encoding of an ASN.1 value
171  *                    -- corresponding to the extension type identified
172  *                    -- by extnID
173  *        }
174  *</pre>
175  * @param {String} extnID the extensions object identifier
176  * @param {Boolean} critical the extension is critical
177  * @param {ByteString} the extension value as ByteString
178  */
179 X509CertificateGenerator.prototype.addExtension = function(extnID, critical, extnValue) {
180 	var t = new ASN1("extension", ASN1.SEQUENCE,
181 				new ASN1("extnID", ASN1.OBJECT_IDENTIFIER, new ByteString(extnID, OID))
182 			);
183 
184 	if (critical) {
185 		t.add(new ASN1("critical", ASN1.BOOLEAN, new ByteString("FF", HEX)));
186 	}
187 
188 	t.add(new ASN1("extnValue", ASN1.OCTET_STRING, extnValue));
189 	this.extensions.push(t);
190 
191 }
192 
193 
194 
195 /**
196  * Adds the key usage extension.
197  *
198  * <p>The following flags are defined:</p>
199  * <pre>
200  * PKIXCommon.digitalSignature = 0x0080;
201  * PKIXCommon.nonRepudiation   = 0x0040;
202  * PKIXCommon.keyEncipherment  = 0x0020;
203  * PKIXCommon.dataEncipherment = 0x0010;
204  * PKIXCommon.keyAgreement     = 0x0008;
205  * PKIXCommon.keyCertSign      = 0x0004;
206  * PKIXCommon.cRLSign          = 0x0002;
207  * PKIXCommon.encipherOnly     = 0x0001;
208  * PKIXCommon.decipherOnly     = 0x8000;
209  * </pre>
210  * @param {Number} the key usage flags as combination of the flags defined above.
211  */
212 X509CertificateGenerator.prototype.addKeyUsageExtension = function(flags) {
213 	var t = new ASN1(ASN1.BIT_STRING, PKIXCommon.bitstringForInteger(flags));
214 	this.addExtension("2.5.29.15", true, t.getBytes());
215 }
216 
217 // Deprecated: Use PKIXCommon. instead
218 X509CertificateGenerator.digitalSignature	= 0x0080;
219 X509CertificateGenerator.nonRepudiation		= 0x0040;
220 X509CertificateGenerator.keyEncipherment	= 0x0020;
221 X509CertificateGenerator.dataEncipherment	= 0x0010;
222 X509CertificateGenerator.keyAgreement		= 0x0008;
223 X509CertificateGenerator.keyCertSign		= 0x0004;
224 X509CertificateGenerator.cRLSign			= 0x0002;
225 X509CertificateGenerator.encipherOnly		= 0x0001;
226 X509CertificateGenerator.decipherOnly		= 0x8000;
227 
228 
229 
230 /**
231  * Adds the BasicConstraints extension.
232  *
233  * @param {Boolean} cA the certificate belongs to a CA
234  * @param {Number} pathLenConstraint the maximum number of subordinate CA certificates
235  */
236 X509CertificateGenerator.prototype.addBasicConstraintsExtension = function(cA, pathLenConstraint) {
237 	var t = new ASN1("BasicConstraints",ASN1.SEQUENCE);
238 	if (cA) {
239 		t.add(new ASN1("cA", ASN1.BOOLEAN, new ByteString("FF", HEX)));
240 	}
241 	if (pathLenConstraint >= 0) {
242 		var bb = new ByteBuffer();
243 		bb.append(pathLenConstraint);
244 		t.add(new ASN1("pathLenConstraint", ASN1.INTEGER, bb.toByteString()));
245 	}
246 	this.addExtension("2.5.29.19", true, t.getBytes());
247 }
248 
249 
250 
251 /**
252  * Adds the subject public key identifier extension based on the certificates subject key.
253  *
254  * <p>The key identifier is calculated as SHA-1 hash over the contents of the
255  * subject public key (Without tag, length and number of unused bits.</p>
256  */
257 X509CertificateGenerator.prototype.addSubjectKeyIdentifierExtension = function() {
258 	var spi = this.getSubjectPublicKeyInfo();
259 	var keyvalue = spi.get(1).value.bytes(1);
260 	var crypto = new Crypto();
261 	var hash = crypto.digest(Crypto.SHA_1, keyvalue);
262 
263 	var t = new ASN1(ASN1.OCTET_STRING, hash);
264 	this.addExtension("2.5.29.14", false, t.getBytes());
265 }
266 
267 
268 
269 /**
270  * Adds the authority public key identifier extension based on the issuers key.
271  *
272  * <p>The key identifier is calculated as SHA-1 hash over the contents of the
273  * issuer public key (Without tag, length and number of unused bits.</p>
274  *
275  * @param {Key/ByteString} publicKeyOrId the authority subject key or authority key identifier
276  */
277 X509CertificateGenerator.prototype.addAuthorityKeyIdentifierExtension = function(publicKeyOrId) {
278 	if (publicKeyOrId instanceof Key) {
279 		if (publicKeyOrId.getComponent(Key.MODULUS)) {
280 			var spi = PKIXCommon.createRSASubjectPublicKeyInfo(publicKeyOrId);
281 		} else {
282 			var spi = PKIXCommon.createECSubjectPublicKeyInfo(publicKeyOrId, this.encodeECDomainParameter);
283 		}
284 
285 		var keyvalue = spi.get(1).value.bytes(1);
286 		var crypto = new Crypto();
287 		var hash = crypto.digest(Crypto.SHA_1, keyvalue);
288 	} else {
289 		var hash = publicKeyOrId;
290 	}
291 
292 	var t = new ASN1(ASN1.SEQUENCE,
293 					new ASN1(0x80, hash)
294 	);
295 	this.addExtension("2.5.29.35", false, t.getBytes());
296 }
297 
298 
299 
300 /**
301  * Adds the CRL distribution point URLs.
302  *
303  * @param {String[]} url a list of URLs
304  */
305 X509CertificateGenerator.prototype.addCRLDistributionPointURL = function(url) {
306 	var t = new ASN1("cRLDistributionPoints", ASN1.SEQUENCE);
307 
308 	for (var i = 0; i < url.length; i++) {
309 		t.add(	new ASN1("cRLDistributionPoint", ASN1.SEQUENCE,
310 					new ASN1("distributionPoint", 0xA0,
311 						new ASN1("fullName", 0xA0,
312 							new ASN1("uniformResourceIdentifier", 0x86, new ByteString(url[i], ASCII))
313 						)
314 					)
315 				));
316 	}
317 	this.addExtension("id-ce-cRLDistributionPoints", false, t.getBytes());
318 }
319 
320 
321 
322 /**
323  * Adds the extended key usage extension
324  *
325  * @param {String[]} oids the list of object identifier names
326  * @param {Boolean} critical the extension is critical
327  */
328 X509CertificateGenerator.prototype.addExtendedKeyUsages = function(oids, critical) {
329 	var t = new ASN1("extKeyUsages", ASN1.SEQUENCE);
330 
331 	for (var i = 0; i < oids.length; i++) {
332 		t.add( new ASN1("keyPurposeId", ASN1.OBJECT_IDENTIFIER, new ByteString(oids[i], OID) ));
333 	}
334 	this.addExtension("id-ce-extKeyUsage", critical, t.getBytes());
335 }
336 
337 
338 
339 /**
340  * Gets the issuer name as TLV object
341  *
342  * @return the issuer RDNSequence
343  * @type ASN1
344  */
345 X509CertificateGenerator.prototype.getIssuer = function() {
346 	if (this.issuer instanceof ASN1) {
347 		return this.issuer;
348 	} else {
349 		return PKIXCommon.encodeName(this.issuer);
350 	}
351 }
352 
353 
354 
355 /**
356  * Gets the certificate validity as TLV object
357  *
358  * @return the certificates validity
359  * @type ASN1
360  */
361 X509CertificateGenerator.prototype.getValidity = function() {
362 	var t = new ASN1("validity", ASN1.SEQUENCE);
363 
364 	var ts = this.notBefore;
365 	if (typeof(ts) != "string") {
366 		ts = PKIXCommon.dtoUTC(this.notBefore);
367 	}
368 	t.add(new ASN1("notBefore", ASN1.UTCTime, new ByteString(ts, ASCII)));
369 
370 	var ts = this.notAfter;
371 	if (typeof(ts) != "string") {
372 		ts = PKIXCommon.dtoUTC(this.notAfter);
373 	}
374 	t.add(new ASN1("notAfter", ASN1.UTCTime, new ByteString(ts, ASCII)));
375 	return t;
376 }
377 
378 
379 
380 /**
381  * Gets the subject name as TLV object
382  *
383  * @return the issuer RDNSequence
384  * @type ASN1
385  */
386 X509CertificateGenerator.prototype.getSubject = function() {
387 	if (this.subject instanceof ASN1) {
388 		return this.subject;
389 	} else {
390 		return PKIXCommon.encodeName(this.subject);
391 	}
392 }
393 
394 
395 
396 /**
397  * Gets the subject's public key as TLV object
398  *
399  * @return the subject's public key info
400  * @type ASN1
401  */
402 X509CertificateGenerator.prototype.getSubjectPublicKeyInfo = function() {
403 	if (this.publicKey.getComponent(Key.MODULUS)) {
404 		return PKIXCommon.createRSASubjectPublicKeyInfo(this.publicKey);
405 	} else {
406 		return PKIXCommon.createECSubjectPublicKeyInfo(this.publicKey, this.encodeECDomainParameter);
407 	}
408 }
409 
410 
411 
412 /**
413  * Gets the certificate extension as TLV object
414  *
415  * @return the certificate extensions
416  * @type ASN1
417  */
418 X509CertificateGenerator.prototype.getExtensions = function() {
419 	var t = new ASN1("extensions", 0xA3);
420 	var s = new ASN1("extensions", ASN1.SEQUENCE);
421 
422 	t.add(s);
423 
424 	for (var i = 0; i < this.extensions.length; i++) {
425 		s.add(this.extensions[i]);
426 	}
427 	return t;
428 }
429 
430 
431 
432 /**
433  * Gets the part of the certificate that will be signed
434  *
435  * @return the TBSCertificate part
436  * @type ASN1
437  */
438 X509CertificateGenerator.prototype.getTbsCertificate = function() {
439 	var t = new ASN1("tbsCertificate", ASN1.SEQUENCE);
440 	t.add(new ASN1("version", 0xA0,
441 			new ASN1("version", ASN1.INTEGER, new ByteString("02", HEX))));
442 	t.add(new ASN1("serialNumber", ASN1.INTEGER, PKIXCommon.convertUnsignedInteger(this.serialNumber)));
443 	t.add(this.getSignatureAlgorithm());
444 	t.add(this.getIssuer());
445 	t.add(this.getValidity());
446 	t.add(this.getSubject());
447 	t.add(this.getSubjectPublicKeyInfo());
448 	t.add(this.getExtensions());
449 	return t;
450 }
451 
452 
453 
454 /**
455  * Gets the signature algorithm TLV object
456  *
457  * @return the signature algorithm object
458  * @type ASN1
459  */
460 X509CertificateGenerator.prototype.getSignatureAlgorithm = function() {
461 	return PKIXCommon.encodeSignatureAlgorithm(this.signatureAlgorithm);
462 }
463 
464 
465 
466 /**
467  * Generates the certificate.
468  *
469  * @return the generated certificate
470  * @type X509
471  */
472 X509CertificateGenerator.prototype.generateX509Certificate = function(privateKey) {
473 	var certificate = new ASN1("certificate", ASN1.SEQUENCE);
474 
475 	var tbs = this.getTbsCertificate();
476 	certificate.add(tbs);
477 	certificate.add(this.getSignatureAlgorithm());
478 
479 	var signature = this.crypto.sign(privateKey, this.signatureAlgorithm, tbs.getBytes());
480 	signature = (new ByteString("00", HEX)).concat(signature);
481 
482 	var signatureValue = new ASN1("signatureValue", ASN1.BIT_STRING, signature);
483 	certificate.add(signatureValue);
484 
485 //	print(certificate);
486 	return new X509(certificate.getBytes());
487 }
488 
489 
490 
491 X509CertificateGenerator.RSATest = function() {
492 
493 	var crypto = new Crypto();
494 
495 	var caPrivateKey = new Key();
496 	caPrivateKey.setType(Key.PRIVATE);
497 
498 	var caPublicKey = new Key();
499 	caPublicKey.setType(Key.PUBLIC);
500 	caPublicKey.setSize(1024);
501 
502 	crypto.generateKeyPair(Crypto.RSA, caPublicKey, caPrivateKey);
503 
504 //	var caPrivKey = new Key("profiles/kp_rsa_private.xml");
505 
506 	var x = new X509CertificateGenerator(crypto);
507 
508 	x.reset();
509 	x.setSerialNumber(new ByteString("01", HEX));
510 	x.setSignatureAlgorithm(Crypto.RSA);
511 	var issuer = { C:"UT", O:"ACME Corporation", CN:"Test-CA" };
512 	x.setIssuer(issuer);
513 	x.setNotBefore("060825120000Z");
514 	x.setNotAfter("160825120000Z");
515 	var subject = { C:"UT", O:"Utopia CA", OU:"ACME Corporation", CN:"Joe Doe" };
516 	x.setSubject(subject);
517 
518 	x.setPublicKey(caPublicKey);
519 
520 	x.addKeyUsageExtension(	PKIXCommon.digitalSignature |
521 							PKIXCommon.keyCertSign |
522 							PKIXCommon.cRLSign );
523 
524 	x.addBasicConstraintsExtension(true, 0);
525 	x.addSubjectKeyIdentifierExtension();
526 	x.addAuthorityKeyIdentifierExtension(caPublicKey);
527 
528 	var cert = x.generateX509Certificate(caPrivateKey);
529 	var fn = GPSystem.mapFilename("cert_rsa.cer", GPSystem.USR);
530 	PKIXCommon.writeFileToDisk(fn, cert.getBytes());
531 
532 	cert.verifyWith(cert);
533 
534 	print(cert);
535 }
536 
537 
538 
539 X509CertificateGenerator.ECCTest = function() {
540 
541 	var crypto = new Crypto();
542 
543 	var caPrivateKey = new Key();
544 	caPrivateKey.setType(Key.PRIVATE);
545 
546 	var caPublicKey = new Key();
547 	caPublicKey.setType(Key.PUBLIC);
548 	caPublicKey.setComponent(Key.ECC_CURVE_OID, new ByteString("brainpoolP256r1", OID));
549 
550 	crypto.generateKeyPair(Crypto.EC, caPublicKey, caPrivateKey);
551 
552 	var x = new X509CertificateGenerator(crypto);
553 
554 	x.reset();
555 	x.setSerialNumber(new ByteString("01", HEX));
556 	x.setSignatureAlgorithm(Crypto.ECDSA_SHA256);
557 	var issuer = { C:"UT", O:"ACME Corporation", CN:"Test-CA" };
558 	x.setIssuer(issuer);
559 	var t = new Date();
560 	x.setNotBefore(t);
561 	x.setNotAfter(PKIXCommon.addDays(t, 180));
562 	var subject = { C:"UT", O:"Utopia CA", OU:"ACME Corporation", CN:"Joe Doe" };
563 	x.setSubject(subject);
564 
565 	x.setPublicKey(caPublicKey);
566 
567 	x.addKeyUsageExtension(	PKIXCommon.digitalSignature |
568 							PKIXCommon.keyCertSign |
569 							PKIXCommon.cRLSign );
570 
571 	x.addBasicConstraintsExtension(true, 0);
572 	x.addSubjectKeyIdentifierExtension();
573 	x.addAuthorityKeyIdentifierExtension(caPublicKey);
574 
575 	var cert = x.generateX509Certificate(caPrivateKey);
576 
577 	var fn = GPSystem.mapFilename("cert_ecc.cer", GPSystem.USR);
578 	PKIXCommon.writeFileToDisk(fn, cert.getBytes());
579 
580 	cert.verifyWith(cert);
581 
582 	print(cert);
583 	print(new ASN1(cert.getBytes()));
584 }
585