      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 Issuer for X.509 Certificates
     25  */
     26
     27 var X509Signer = require('scsh/x509/X509Signer').X509Signer;
     28
     29 var PKIXCommon = require("scsh/x509/PKIXCommon").PKIXCommon;
     30 var X509CertificateGenerator = require("scsh/x509/X509CertificateGenerator").X509CertificateGenerator;
     31 var CRLGenerator = require("scsh/x509/CRLGenerator").CRLGenerator;
     32
     33
     34
     35 /**
     36  * Create a certification authority that issues X.509 certificates and CRLs
     37  *
     38  * @class Class implementing a certification authority issuing X.509 certificates and CRLs
     39  * @constructor
     40  * @param {DAOFactory} daof the factory that can create the required data access objects
     41  * @param {CryptoProviderFactory} cpf factory implementing getCryptoProvider() used to get access to crypto providers
     42  * @param {Holder} holder the holder object for this signer the database
     43  */
     44 function X509CertificateIssuer(daof, cpf, holder) {
     45 	X509Signer.call(this, daof, cpf, holder);
     46 	this.crldp = [];
     47 }
     48
     49 X509CertificateIssuer.prototype = Object.create(X509Signer.prototype);
     50 X509CertificateIssuer.constructor = X509CertificateIssuer;
     51
     52 exports.X509CertificateIssuer = X509CertificateIssuer;
     53
     54
     55
     56 /**
     57  * Create a new certificate issuer
     58  *
     59  * @param {DAOFactory} daof the factory that can create the required data access objects
     60  * @param {String/Number} pathOrHolderId the path of holderIDs (eg. "/UTCVCA/UTDVCA/UTTERM") or the holderId from the database
     61  * @param {Number} certtype optional argument, default Holder.X509
     62  * @type Number
     63  * @return the newly created holder id
     64  */
     65 X509CertificateIssuer.createCertificateIssuer = function(daof, pathOrHolderId, certtype, template) {
     66 	return X509Signer.createSigner(daof, pathOrHolderId, certtype, template);
     67 }
     68
     69
     70
     71 /**
     72  * Add a CRL distribution point to issued certificates
     73  *
     74  * @param {String} crldp the URL of the distribution point
     75  */
     76 X509CertificateIssuer.prototype.addCRLDistributionPoint = function(crldp) {
     77 	this.crldp.push(crldp);
     78 }
     79
     80
     81
     82 /**
     83  * Create a new randomly generated certificate serial number
     84  *
     85  * @private
     86  * @type ByteString
     87  * @return a 8 byte bytestring that resembles an unsigned integer
     88  */
     89 X509CertificateIssuer.prototype.newSerialNumber = function() {
     90 	var crypto = new Crypto();
     91 	var serial = crypto.generateRandom(8);
     92
     93 	// Strip first bit to make integer unsigned
     94 	if (serial.byteAt(0) > 0x7F) {
     95 		serial = ByteString.valueOf(serial.byteAt(0) & 0x7F).concat(serial.bytes(1));
     96 	}
     97 	return serial;
     98 }
     99
    100
    101
    102 /**
    103  * Issue a self-signed certificate for the given keyId.
    104  *
    105  * The key must have been previously generated using the newSigner() method
    106  *
    107  * @param {ByteString} keyId the subject key identifier
    108  * @param {Number} srId service request id to be stored with issued certificate
    109  */
    110 X509CertificateIssuer.prototype.issueSelfSignedCertificate = function(keyId, srId) {
    111
    112 	if (keyId) {
    113 		var signerDAO = this.daof.getSignerDAO();
    114 		var signer = signerDAO.getSignerByKeyId(this.holder, keyId);
    115 	} else {
    116 		var signer = this.signer;
    117 	}
    118
    119 	if (!signer) {
    120 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "No signer with keyId " + keyId + " found");
    121 	}
    122
    123 	var cp = this.cpf.getCryptoProvider(signer.keyDomain, true);
    124
    125 	if (!cp) {
    126 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "Signer is offline (Key Domain : " + this.signer.keyDomain + ")");
    127 	}
    128
    129 	try	{
    130 		var prk = cp.getPrivateKeyByKeyId(signer.keyId, signer.keyblob);
    131
    132 		if (prk == null) {
    133 			throw new GPError(module.id, GPError.INVALID_DATA, 1, "No key with keyId " + signer.keyId + " found");
    134 		}
    135
    136 		var req = this.getRequest(signer.keyId);
    137
    138 		if (req == null) {
    139 			throw new GPError(module.id, GPError.INVALID_DATA, 1, "No request with keyId " + signer.keyId + " found");
    140 		}
    141
    142 		var pub = req.getPublicKey();
    143
    144 		if (typeof(this.policy.restrictPublicKey) != "undefined") {
    145 			pub.algorithmIdentifier = PKIXCommon.restrictedPublicKeyAlgorithmIdentifier(this.policy.restrictPublicKey);
    146 		}
    147
    148 		var gen = new X509CertificateGenerator(cp.getCrypto());
    149 		gen.encodeECDomainParameter = false;
    150
    151 		gen.reset();
    152 		gen.setSerialNumber(this.newSerialNumber());
    153 		gen.setSignatureAlgorithm(this.policy.signatureAlgorithm);
    154 		var subject = req.getSubject();
    155 		gen.setIssuer(subject);
    156 		var ced = new Date();
    157 		var cxd = PKIXCommon.addDays(ced, this.policy.validityDaysSelfSigned);
    158 		gen.setNotBefore(ced);
    159 		gen.setNotAfter(cxd);
    160 		gen.setSubject(subject);
    161 		gen.setPublicKey(pub);
    162 		gen.addSubjectKeyIdentifierExtension();
    163 		gen.addKeyUsageExtension(PKIXCommon.keyCertSign | PKIXCommon.cRLSign );
    164 		gen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint);
    165
    166 		var cert = gen.generateX509Certificate(prk);
    167
    168 		var id = this.storeCertificate(cert, true, signer.keyId, srId);
    169 	}
    170 	finally {
    171 		cp.release();
    172 	}
    173
    174 	return { id: id, cert: cert };
    175 }
    176
    177
    178
    179 /**
    180  * Issue a new certificate for the given subject and public key
    181  *
    182  * @param {Number/String/Object} certholder the holder id, path or object
    183  * @param {Key} pubkey the public key
    184  * @param {Object} subject in ASN1 format or a format accepted by PKIXCommon.encodeName()
    185  * @param {Object[]} extensions array of certificate extensions objects with properties oid{String}, critical{boolean} and value{ByteString}
    186  * @param {Number} srId service request id to be stored with issued certificate
    187  * @type X509
    188  * @return the newly generated certificate
    189  */
    190 X509CertificateIssuer.prototype.issueCertificate = function(certholder, pubkey, subject, extensions, srId) {
    191 	assert(pubkey instanceof Key, "Argument pubkey must be instance of Key");
    192
    193 	if (typeof(certholder) != "object") {
    194 		var holderdao = this.daof.getHolderDAO();
    195
    196 		if (typeof(pathOrHolderId) == "string") {
    197 			certholder = holderdao.getHolder(certholder);
    198 		} else {
    199 			certholder = holderdao.getHolderById(certholder);
    200 		}
    201 	}
    202
    203 	if (!certholder) {
    204 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "Holder " + certholder + " not found");
    205 	}
    206
    207 	var icert = this.getSignerCertificate();
    208
    209 	if (!icert) {
    210 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "No active signer found");
    211 	}
    212
    213 	var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true);
    214
    215 	if (!cp) {
    216 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "Signer is offline (Key Domain : " + this.signer.keyDomain + ")");
    217 	}
    218
    219 	try	{
    220 		var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob);
    221
    222 		if (prk == null) {
    223 			throw new GPError(module.id, GPError.INVALID_DATA, 1, "No key with keyId " + this.signer.keyId + " found");
    224 		}
    225
    226 		var issuer = PKIXCommon.getSubjectAsASN1(icert);
    227
    228 		var gen = new X509CertificateGenerator(cp.getCrypto());
    229 		gen.encodeECDomainParameter = false;
    230
    231 		gen.reset();
    232 		gen.setSerialNumber(this.newSerialNumber());
    233 		gen.setSignatureAlgorithm(this.policy.signatureAlgorithm);
    234 		gen.setIssuer(issuer);
    235 		var ced = new Date();
    236 		var cxd = PKIXCommon.addDays(ced, this.policy.validityDaysCertificates);
    237 		gen.setNotBefore(ced);
    238 		gen.setNotAfter(cxd);
    239 		gen.setSubject(subject);
    240 		gen.setPublicKey(pubkey);
    241 		gen.addSubjectKeyIdentifierExtension();
    242 		gen.addAuthorityKeyIdentifierExtension(icert.getSubjectKeyIdentifier());
    243
    244 		if ((typeof(this.policy.pathLenConstraint) != "undefined") && (this.policy.pathLenConstraint > 0)) {
    245 			gen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint - 1);
    246 		} else {
    247 			gen.addBasicConstraintsExtension(false, 0);
    248 		}
    249
    250 		if (this.crldp.length > 0) {
    251 			gen.addCRLDistributionPointURL(this.crldp);
    252 		}
    253
    254 		if (typeof(extensions) == "object") {
    255 			for (var i = 0; i < extensions.length; i++) {
    256 				gen.addExtension(extensions[i].oid, extensions[i].critical, extensions[i].value);
    257 			}
    258 		}
    259
    260 		var cert = gen.generateX509Certificate(prk);
    261
    262 		var id = this.storeCertificateForHolder(certholder, cert, false, undefined, srId);
    263 	}
    264 	finally {
    265 		cp.release();
    266 	}
    267 	return { id: id, cert: cert };
    268 }
    269
    270
    271
    272 /**
    273  * Extension handler method for Sub-CA certificates
    274  *
    275  * @private
    276  */
    277 X509CertificateIssuer.prototype.addExtForSubCA = function(certgen, extvalues) {
    278
    279 	certgen.addKeyUsageExtension(PKIXCommon.keyCertSign | PKIXCommon.cRLSign );
    280 	certgen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint - 1);
    281 }
    282
    283
    284
    285 /**
    286  * Extension handler method for TLS server certificates
    287  *
    288  * @private
    289  */
    290 X509CertificateIssuer.prototype.addExtForTLSServer = function(certgen, extvalues) {
    291
    292 	certgen.addKeyUsageExtension( PKIXCommon.keyAgreement | PKIXCommon.keyEncipherment);
    293 	certgen.addBasicConstraintsExtension(false, 0);
    294
    295 	certgen.addExtendedKeyUsages(["id-csn-369791-tls-server", "id-kp-serverAuth"]);
    296
    297 	var ext = new ASN1("subjectAltName", ASN1.SEQUENCE,
    298 						new ASN1("dNSName", 0x82, new ByteString(extvalues["dNSName"], ASCII))
    299 					);
    300 	certgen.addExtension("id-ce-subjectAltName", false, ext.getBytes());
    301 }
    302
    303
    304
    305 /**
    306  * Extension handler method for TLS client certificates
    307  *
    308  * @private
    309  */
    310 X509CertificateIssuer.prototype.addExtForTLSClient = function(certgen, extvalues) {
    311 	certgen.addKeyUsageExtension( PKIXCommon.digitalSignature);
    312 	certgen.addBasicConstraintsExtension(false, 0);
    313
    314 	// certgen.addExtendedKeyUsages(["id-csn-369791-tls-client", "id-kp-clientAuth"]);
    315 	certgen.addExtendedKeyUsages(["id-kp-clientAuth"]);
    316 }
    317
    318
    319
    320 /**
    321  * Extension handler method for certificates suitable for TLS client authentication and e-Mail signature and encryption
    322  *
    323  * @private
    324  */
    325 X509CertificateIssuer.prototype.addExtForEmailAndTLSClient = function(certgen, extvalues) {
    326
    327 	certgen.addKeyUsageExtension( PKIXCommon.digitalSignature | PKIXCommon.keyEncipherment);
    328 	certgen.addBasicConstraintsExtension(false, 0);
    329
    330 	var ext = new ASN1("subjectAltName", ASN1.SEQUENCE,
    331 						new ASN1("rfc822Name", 0x81, new ByteString(extvalues["email"], ASCII))
    332 					);
    333 	certgen.addExtension("id-ce-subjectAltName", false, ext.getBytes());
    334
    335 	certgen.addExtendedKeyUsages(["id-kp-clientAuth", "id-kp-emailProtection"]);
    336 }
    337
    338
    339
    340 /**
    341  * Issue a CRL
    342  *
    343  * @type CRL
    344  * @return the newly issued CRL
    345  */
    346 X509CertificateIssuer.prototype.issueCRL = function() {
    347 	GPSystem.log(GPSystem.DEBUG, module.id, "issueCRL()");
    348
    349 	var icert = this.getSignerCertificate();
    350 	if (!icert) {
    351 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "No active signer found");
    352 	}
    353
    354 	var c = this.signer.getContent();
    355 	if (c && c.crl) {
    356 		var latestCRL = new CRLGenerator();
    357 		var crlNumber = latestCRL.loadCRLEntries(new ByteString(c.crl, HEX));
    358 		crlNumber++;
    359 	}
    360
    361 	var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true);
    362
    363 	if (!cp) {
    364 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "Signer is offline (Key Domain : " + this.signer.keyDomain + ")");
    365 	}
    366
    367 	try	{
    368 		var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob);
    369
    370 		if (prk == null) {
    371 			throw new GPError(module.id, GPError.INVALID_DATA, 1, "No key with keyId " + this.signer.keyId + " found");
    372 		}
    373
    374 		var x = new CRLGenerator(cp.getCrypto());
    375
    376 		x.reset();
    377 		x.setSignatureAlgorithm(this.policy.signatureAlgorithm);
    378 		var issuer = PKIXCommon.getSubjectAsASN1(icert);
    379 		x.setIssuer(issuer);
    380 		var now = new Date();
    381 		x.setThisUpdate(now);
    382 		x.setNextUpdate(PKIXCommon.addDays(now, this.policy.validityDaysCRL));
    383
    384 		var certdao = this.daof.getCertificateDAO();
    385 		var revocationList = certdao.getRevokedCertificates(this.holder.id, now);
    386
    387 		for each (var cert in revocationList) {
    388 			var serial = new ByteString(cert.serial, HEX);
    389
    390 			var reason = this.mapStatusToReasonCode(cert.status);
    391
    392 			if (cert.revocationDate) {
    393 				var revocationDate = cert.revocationDate;
    394 			} else {
    395 				var revocationDate = now;
    396 				certdao.updateRevocationDate(cert.id, now);
    397 			}
    398
    399 			if (cert.invalidityDate) {
    400 				var invalidityDate = cert.invalidityDate;
    401 			} else {
    402 				var invalidityDate = now;
    403 			}
    404 			var extensions = new ASN1("crlExtensions", ASN1.SEQUENCE);
    405 			extensions.add(x.createInvalidityDateExt(invalidityDate));
    406
    407 			x.revokeCertificate(serial, revocationDate, reason, extensions);
    408 		}
    409
    410 		if (!crlNumber) {
    411 			var crlNumber = 1;
    412 		}
    413 		x.addCRLNumberExtension(crlNumber);
    414
    415 		var crl = x.generateCRL(prk);
    416
    417 		c.crl = crl.getBytes().toString(HEX);
    418 		this.signer.setContent(c);
    419 		var signerDAO = this.daof.getSignerDAO();
    420 		signerDAO.updateContent(this.signer);
    421 	}
    422 	finally {
    423 		cp.release();
    424 	}
    425 	return crl;
    426 }
    427
    428
    429
    430 X509CertificateIssuer.prototype.mapStatusToReasonCode = function(status) {
    431 	switch(status) {
    432 	  case OCSPQuery.REVOKED:
    433 		return null; // "...the reason code CRL entry extension SHOULD be absent instead of using the unspecified (0) reasonCode value." [RFC 5280, 5.3.1]
    434 	  case OCSPQuery.KEYCOMPROMISE:
    435 		return CRLGenerator.keyCompromise;
    436 	  case OCSPQuery.CACOMPROMISE:
    437 		return CRLGenerator.cACompromise;
    438 	  case OCSPQuery.AFFILIATIONCHANGED:
    439 		return CRLGenerator.affiliationChanged;
    440 	  case OCSPQuery.SUPERSEDED:
    441 		return CRLGenerator.superseded;
    442 	  case OCSPQuery.CESSATIONOFOPERATION:
    443 		return CRLGenerator.cessationOfOperation;
    444 	  case OCSPQuery.CERTIFICATEHOLD:
    445 		return CRLGenerator.certificateHold;
    446 	  case OCSPQuery.REMOVEFROMCRL:
    447 		return CRLGenerator.removeFromCRL;
    448 	  case OCSPQuery.PRIVILEGEWITHDRAWN:
    449 		return CRLGenerator.privilegeWithdrawn;
    450 	  case OCSPQuery.AACOMPROMISE:
    451 		return CRLGenerator.aACompromise;
    452 	}
    453
    454  	throw new GPError(module.id, GPError.INVALID_DATA, 1, "Unexpected status: " + status);
    455 }
    456
    457
    458
    459 X509CertificateIssuer.prototype.getDNMask = function(holderId) {
    460 	var signer = this.getSigner();
    461 	var c = signer.getContent();
    462 	return c.dnmask;
    463 }
    464
    465
    466
    467 X509CertificateIssuer.prototype.isOperational = function() {
    468 	GPSystem.log(GPSystem.DEBUG, module.id, "isOperational()");
    469
    470 	var icert = this.getSignerCertificate();
    471 	if (!icert) {
    472 		// No active signer found
    473 		return false;
    474 	}
    475
    476 	var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true);
    477
    478 	if (!cp) {
    479 		// Signer offline
    480 		return false;
    481 	}
    482
    483 	try	{
    484 		var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob);
    485
    486 		if (prk == null) {
    487 			// Key not found
    488 			return false;
    489 		}
    490 	} finally {
    491 		cp.release();
    492 	}
    493
    494 	return true;
    495 }
    496