      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 var 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