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
 25  * Simple CRL generator class
 26  */
 27 
 28 PKIXCommon = require("scsh/x509/PKIXCommon").PKIXCommon;
 29 
 30 
 31 
 32 /**
 33  * Create a Certificate Revocation List (CRL) generator.
 34  *
 35  * @class Class implementing a CRL certificate generator
 36  * @constructor
 37  *
 38  * @param {Crypto} crypto the crypto provider to use for signing operations
 39  */
 40 function CRLGenerator(crypto) {
 41 	this.crypto = crypto;
 42 	this.reset();
 43 }
 44 
 45 exports.CRLGenerator = CRLGenerator;
 46 
 47 
 48 CRLGenerator.unspecified = 0;
 49 CRLGenerator.keyCompromise = 1;
 50 CRLGenerator.cACompromise = 2;
 51 CRLGenerator.affiliationChanged = 3;
 52 CRLGenerator.superseded = 4;
 53 CRLGenerator.cessationOfOperation = 5;
 54 CRLGenerator.certificateHold = 6;
 55 CRLGenerator.removeFromCRL = 8;
 56 CRLGenerator.privilegeWithdrawn = 9;
 57 CRLGenerator.aACompromise = 10;
 58 
 59 
 60 
 61 /**
 62  * Resets all internal state variables.
 63  *
 64  */
 65 CRLGenerator.prototype.reset = function() {
 66 	this.extensions = [];
 67 	this.revokedCertificates = [];
 68 	this.nextUpdate = null;
 69 }
 70 
 71 
 72 
 73 /**
 74  * Sets the isser name.
 75  *
 76  * <p>The issuer name must be a JavaScript object containing the properties:</p>
 77  * <ul>
 78  *  <li>C - the country</li>
 79  *  <li>O - the organization</li>
 80  *  <li>OU - the organization unit</li>
 81  *  <li>CN - the common name</li>
 82  * </ul>
 83  * <p>Example:</p>
 84  * <pre>
 85  *	var issuer = { C:"UT", O:"ACME Corporation", CN:"Test-CA" };
 86  * </pre>
 87  * @param {Object} issuer the issuer name
 88  */
 89 CRLGenerator.prototype.setIssuer = function(issuer) {
 90 	this.issuer = issuer;
 91 }
 92 
 93 
 94 
 95 /**
 96  * Sets the timestamp for this CRL
 97  *
 98  * @param {Date} datetime the current date and time
 99  */
100 CRLGenerator.prototype.setThisUpdate = function(datetime) {
101 	this.thisUpdate = datetime;
102 }
103 
104 
105 
106 /**
107  * Sets the timestamp for the next update
108  *
109  * @param {Date} datetime the date and time of the next update
110  */
111 CRLGenerator.prototype.setNextUpdate = function(datetime) {
112 	this.nextUpdate = datetime;
113 }
114 
115 
116 
117 /**
118  * Sets the signature algorithm.
119  *
120  * @param {Number} alg the signature algorithm, must be one of Crypto.RSA, Crypto.RSA_SHA256 or Crypto.ECDSA_SHA256
121  */
122 CRLGenerator.prototype.setSignatureAlgorithm = function(alg) {
123 	this.signatureAlgorithm = alg;
124 }
125 
126 
127 
128 /**
129  * Adds an extension to the CRL
130  *
131  * <p>The structure is defined as:</p>
132  * <pre>
133  *    Extension  ::=  SEQUENCE  {
134  *        extnID      OBJECT IDENTIFIER,
135  *        critical    BOOLEAN DEFAULT FALSE,
136  *        extnValue   OCTET STRING
137  *                    -- contains the DER encoding of an ASN.1 value
138  *                    -- corresponding to the extension type identified
139  *                    -- by extnID
140  *        }
141  *</pre>
142  * @param {String} extnID the extensions object identifier
143  * @param {Boolean} critical the extension is critical
144  * @param {ByteString} the extension value as ByteString
145  */
146 CRLGenerator.prototype.addExtension = function(extnID, critical, extnValue) {
147 	var t = new ASN1("extension", ASN1.SEQUENCE,
148 				new ASN1("extnID", ASN1.OBJECT_IDENTIFIER, new ByteString(extnID, OID))
149 			);
150 
151 	if (critical) {
152 		t.add(new ASN1("critical", ASN1.BOOLEAN, new ByteString("FF", HEX)));
153 	}
154 
155 	t.add(new ASN1("extnValue", ASN1.OCTET_STRING, extnValue));
156 	this.extensions.push(t);
157 
158 }
159 
160 
161 
162 /**
163  * Adds the authority public key identifier extension based on the issuers key.
164  *
165  * <p>The key identifier is calculated as SHA-1 hash over the contents of the
166  * issuer public key (Without tag, length and number of unused bits.</p>
167  */
168 CRLGenerator.prototype.addAuthorityKeyIdentifierExtension = function(publicKey) {
169 	if (publicKey.getComponent(Key.MODULUS)) {
170 		var spi = PKIXCommon.createRSASubjectPublicKeyInfo(publicKey);
171 	} else {
172 		var spi = PKIXCommon.createECSubjectPublicKeyInfo(publicKey, this.encodeECDomainParameter);
173 	}
174 
175 	var keyvalue = spi.get(1).value.bytes(1);
176 	var hash = this.crypto.digest(Crypto.SHA_1, keyvalue);
177 
178 	var t = new ASN1(ASN1.SEQUENCE,
179 					new ASN1(0x80, hash)
180 				);
181 	this.addExtension("id-ce-authorityKeyIdentifier", false, t.getBytes());
182 }
183 
184 
185 
186 /**
187  * Adds the CRL number extension.
188  *
189  */
190 CRLGenerator.prototype.addCRLNumberExtension = function(crlnumber) {
191 	var t = new ASN1(ASN1.INTEGER,
192 					PKIXCommon.convertUnsignedInteger(ByteString.valueOf(crlnumber))
193 				);
194 	this.addExtension("id-ce-cRLNumber", false, t.getBytes());
195 }
196 
197 
198 
199 /**
200  * Add a revoked certificate to the list. This adds the complete DER encoded structure.
201  *
202  * @param {ASN1} revokedCertificate the information related to the revoked certificate
203  */
204 CRLGenerator.prototype.addRevokedCertificate = function(revokedCertificate) {
205 	this.revokedCertificates.push(revokedCertificate);
206 }
207 
208 
209 
210 CRLGenerator.prototype.createInvalidityDateExt = function(invalidityDate) {
211 	var t = new ASN1("extension", ASN1.SEQUENCE,
212 			new ASN1("extnID", ASN1.OBJECT_IDENTIFIER,
213 				 new ByteString("id-ce-invalidityDate", OID)));
214 
215 	var extnValue = new ASN1("invalidityDate", ASN1.GeneralizedTime,
216 				 new ByteString(PKIXCommon.dtoUTCFullYear(invalidityDate), ASCII));
217 
218 	t.add(new ASN1("extnValue", ASN1.OCTET_STRING, extnValue.getBytes()));
219 
220 	return t;
221 }
222 
223 
224 /**
225  * Add a revoked certificate to the list. This adds the complete DER encoded structure.
226  *
227  * @param {ByteString} serial the serial number of the certificate to revoke
228  * @param {Date} timestamp the revocation time, optional, default is now
229  * @param {Number} reason the revocation reason
230  * @param {ASN1} ext the crl entry extensions
231  */
232 CRLGenerator.prototype.revokeCertificate = function(serial, timestamp, reason, ext) {
233 	if (typeof(timestamp) == "undefined") {
234 		timestamp = new Date();
235 	}
236 	var t = new ASN1("revokedCertificate", ASN1.SEQUENCE,
237 				new ASN1("userCertificate", ASN1.INTEGER, PKIXCommon.convertUnsignedInteger(serial)),
238 				new ASN1("revocationTime", ASN1.UTCTime, new ByteString(PKIXCommon.dtoUTC(timestamp), ASCII))
239 				);
240 
241 	if (typeof(ext) == "undefined") {
242 		ext = new ASN1("crlExtensions", ASN1.SEQUENCE);
243 	}
244 
245 	if ((typeof(reason) != "undefined") && (reason != -1)) {
246 		ext.add(new ASN1("reason", ASN1.SEQUENCE,
247 					new ASN1("extnID", ASN1.OBJECT_IDENTIFIER, new ByteString("id-ce-cRLReasons", OID)),
248 					new ASN1("extnValue", ASN1.OCTET_STRING,
249 						(new ASN1("cRLReason", ASN1.ENUMERATED, ByteString.valueOf(reason))).getBytes())
250 					)
251 				);
252 	}
253 
254 	if (ext.length > 0) {
255 		t.add(ext);
256 	}
257 
258 	this.revokedCertificates.push(t);
259 }
260 
261 
262 
263 /**
264  * Load list of revoked certificates from an existing CRL
265  *
266  * @param {ByteString} crlbin the DER encoded CRL
267  * @type Number
268  * @return the value of the CRLNumber extension, 0 if extension not defined or -1 if file could not be loaded
269  */
270 CRLGenerator.prototype.loadCRLEntries = function(crlbin) {
271 	var crlnumber = 0;
272 
273 	try	{
274 		var crl = new ASN1(crlbin);
275 		print(crl);
276 		var tbs = crl.get(0);
277 		var i = 0;
278 		if ((i < tbs.elements) && (tbs.get(i).tag == ASN1.INTEGER)) {		// Skip version if present
279 			i++;
280 		}
281 		i += 3;		// Skip signature, issuer, thisUpdate
282 		if ((i < tbs.elements) && (tbs.get(i).tag == ASN1.UTCTime)) {		// nextUpdate if present
283 			i++;
284 		}
285 		if ((i < tbs.elements) && (tbs.get(i).tag == ASN1.SEQUENCE)) {
286 			for (var j = 0; j < tbs.get(i).elements; j++) {
287 				this.revokedCertificates.push(tbs.get(i).get(j));
288 			}
289 			i++;
290 		}
291 		if ((i < tbs.elements) && (tbs.get(i).tag == 0xA0)) {
292 			var l = tbs.get(i).get(0);
293 			var oid = new ByteString("id-ce-cRLNumber", OID);
294 			for (var j = 0; j < l.elements; j++) {
295 				var ext = l.get(j);
296 				if (ext.get(0).value.equals(oid)) {
297 					var extval = new ASN1(ext.get(1).value);
298 					crlnumber = extval.value.toUnsigned();
299 				}
300 			}
301 
302 		}
303 	}
304 	catch(e) {
305 		GPSystem.trace(e);
306 		return -1;
307 	}
308 	return crlnumber;
309 }
310 
311 
312 
313 /**
314  * Gets the issuer name as TLV object
315  *
316  * @return the issuer RDNSequence
317  * @type ASN1
318  */
319 CRLGenerator.prototype.getIssuer = function() {
320 	if (this.issuer instanceof ASN1) {
321 		return this.issuer;
322 	} else {
323 		return PKIXCommon.encodeName(this.issuer);
324 	}
325 }
326 
327 
328 
329 /**
330  * Gets the thisUpdate TLV object
331  *
332  * @return the thisUpdate UTC encoded time
333  * @type ASN1
334  */
335 CRLGenerator.prototype.getThisUpdate = function() {
336 	var t = new ASN1("thisUpdate", ASN1.UTCTime, new ByteString(PKIXCommon.dtoUTC(this.thisUpdate), ASCII));
337 	return t;
338 }
339 
340 
341 
342 /**
343  * Gets the nextUpdate TLV object
344  *
345  * @return the nextUpdate UTC encoded time
346  * @type ASN1
347  */
348 CRLGenerator.prototype.getNextUpdate = function() {
349 	var t = new ASN1("nextUpdate", ASN1.UTCTime, new ByteString(PKIXCommon.dtoUTC(this.nextUpdate), ASCII));
350 	return t;
351 }
352 
353 
354 
355 /**
356  * Gets the signature algorithm TLV object
357  *
358  * @return the signature algorithm object
359  * @type ASN1
360  */
361 CRLGenerator.prototype.getSignatureAlgorithm = function() {
362 	return PKIXCommon.encodeSignatureAlgorithm(this.signatureAlgorithm);
363 }
364 
365 
366 
367 /**
368  * Gets revoked certificates
369  *
370  * @return the list of revoked certificates
371  * @type ASN1
372  */
373 CRLGenerator.prototype.getRevokedCertificates = function() {
374 	var t = new ASN1("revokedCertificates", ASN1.SEQUENCE);
375 	for (var i = 0; i < this.revokedCertificates.length; i++) {
376 		t.add(this.revokedCertificates[i]);
377 	}
378 	return t;
379 }
380 
381 
382 
383 /**
384  * Gets the CRL extension as TLV object
385  *
386  * @return the CRL extensions
387  * @type ASN1
388  */
389 CRLGenerator.prototype.getExtensions = function() {
390 	var t = new ASN1("extensions", 0xA0);
391 	var s = new ASN1("extensions", ASN1.SEQUENCE);
392 
393 	t.add(s);
394 
395 	for (var i = 0; i < this.extensions.length; i++) {
396 		s.add(this.extensions[i]);
397 	}
398 	return t;
399 }
400 
401 
402 
403 /**
404  * Gets the part of the CRL that will be signed
405  *
406  * @return the TBSCertificate part
407  * @type ASN1
408  */
409 CRLGenerator.prototype.getTbsCertificateList = function() {
410 	var t = new ASN1("tbsCertList", ASN1.SEQUENCE);
411 	t.add(new ASN1("version", ASN1.INTEGER, new ByteString("01", HEX)));
412 	t.add(this.getSignatureAlgorithm());
413 	t.add(this.getIssuer());
414 	t.add(this.getThisUpdate());
415 	if (this.nextUpdate != null) {
416 		t.add(this.getNextUpdate());
417 	}
418 	if (this.revokedCertificates.length > 0) {
419 		t.add(this.getRevokedCertificates());
420 	}
421 	if (this.extensions.length > 0) {
422 		t.add(this.getExtensions());
423 	}
424 	return t;
425 }
426 
427 
428 
429 /**
430  * Generates the certificate.
431  *
432  * @return the generated certificate
433  * @type X509
434  */
435 CRLGenerator.prototype.generateCRL = function(privateKey) {
436 	var certlist = new ASN1("certificateList", ASN1.SEQUENCE);
437 
438 	var tbs = this.getTbsCertificateList();
439 	certlist.add(tbs);
440 	certlist.add(this.getSignatureAlgorithm());
441 
442 	var signature = this.crypto.sign(privateKey, this.signatureAlgorithm, tbs.getBytes());
443 	signature = (new ByteString("00", HEX)).concat(signature);
444 
445 	var signatureValue = new ASN1("signatureValue", ASN1.BIT_STRING, signature);
446 	certlist.add(signatureValue);
447 
448 	return certlist;
449 }
450 
451 
452 
453 CRLGenerator.test = function() {
454 
455 	var crypto = new Crypto();
456 
457 	var caPrivateKey = new Key();
458 	caPrivateKey.setType(Key.PRIVATE);
459 
460 	var caPublicKey = new Key();
461 	caPublicKey.setType(Key.PUBLIC);
462 	caPublicKey.setSize(1024);
463 
464 	crypto.generateKeyPair(Crypto.RSA, caPublicKey, caPrivateKey);
465 
466 	var x = new CRLGenerator(crypto);
467 
468 	x.reset();
469 	x.setSignatureAlgorithm(Crypto.RSA);
470 	var issuer = [{ C:"UT" },{ O:"ACME Corporation" },{ CN:"Test-CA" }];
471 	x.setIssuer(issuer);
472 	var now = new Date();
473 	x.setThisUpdate(now);
474 	var nextMonth = new Date();
475 	nextMonth.setMonth((now.getMonth() + 1) % 12);
476 	x.setNextUpdate(nextMonth);
477 	x.addAuthorityKeyIdentifierExtension(caPublicKey);
478 	x.addCRLNumberExtension(11);
479 
480 	x.revokeCertificate(new ByteString("01", HEX));
481 	x.revokeCertificate(new ByteString("80", HEX), new Date());
482 	x.revokeCertificate(new ByteString("80", HEX), new Date(), CRLGenerator.keyCompromise);
483 	x.revokeCertificate(new ByteString("80", HEX), new Date(), CRLGenerator.superseded, new ASN1("crlExtensions", ASN1.SEQUENCE));
484 
485 	var extensions = new ASN1("crlExtensions", ASN1.SEQUENCE);
486 	extensions.add(x.createInvalidityDateExt(new Date()));
487  	x.revokeCertificate(new ByteString("80", HEX), new Date(), CRLGenerator.superseded, extensions);
488 
489 	var crl = x.generateCRL(caPrivateKey);
490 
491 	var fn = GPSystem.mapFilename("crl.crl", GPSystem.USR);
492 	PKIXCommon.writeFileToDisk(fn, crl.getBytes());
493 
494 	print(crl);
495 
496 	var x = new CRLGenerator(crypto);
497 	x.reset();
498 	x.setSignatureAlgorithm(Crypto.RSA);
499 	var issuer = [{ C:"UT" },{ O:"ACME Corporation" },{ CN:"Test-CA" }];
500 	x.setIssuer(issuer);
501 	var now = new Date();
502 	x.setThisUpdate(now);
503 	var nextMonth = new Date();
504 	nextMonth.setMonth((now.getMonth() + 1) % 12);
505 	x.setNextUpdate(nextMonth);
506 	var crlnumber = x.loadCRLEntries(crl.getBytes());
507 	assert(crlnumber == 11);
508 	x.addAuthorityKeyIdentifierExtension(caPublicKey);
509 	x.addCRLNumberExtension(crlnumber + 1);
510 
511 	x.revokeCertificate(crypto.generateRandom(8), new Date(), CRLGenerator.keyCompromise);
512 	var crl = x.generateCRL(caPrivateKey);
513 //	PKIXCommon.writeFileToDisk(fn, crl.getBytes());
514 
515 	print(crl);
516 }
517 
518 
519