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