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 		var gen = new X509CertificateGenerator(cp.getCrypto());
145 		gen.encodeECDomainParameter = false;
146 
147 		gen.reset();
148 		gen.setSerialNumber(this.newSerialNumber());
149 		gen.setSignatureAlgorithm(this.policy.signatureAlgorithm);
150 		var subject = req.getSubject();
151 		gen.setIssuer(subject);
152 		var ced = new Date();
153 		var cxd = PKIXCommon.addDays(ced, this.policy.validityDaysSelfSigned);
154 		gen.setNotBefore(ced);
155 		gen.setNotAfter(cxd);
156 		gen.setSubject(subject);
157 		gen.setPublicKey(pub);
158 		gen.addSubjectKeyIdentifierExtension();
159 		gen.addKeyUsageExtension(PKIXCommon.keyCertSign | PKIXCommon.cRLSign );
160 		gen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint);
161 
162 		var cert = gen.generateX509Certificate(prk);
163 
164 		var id = this.storeCertificate(cert, true, signer.keyId, srId);
165 	}
166 	finally {
167 		cp.release();
168 	}
169 
170 	return { id: id, cert: cert };
171 }
172 
173 
174 
175 /**
176  * Issue a new certificate for the given subject and public key
177  *
178  * @param {Number/String/Object} certholder the holder id, path or object
179  * @param {Key} pubkey the public key
180  * @param {Object} subject in ASN1 format or a format accepted by PKIXCommon.encodeName()
181  * @param {Object[]} extensions array of certificate extensions objects with properties oid{String}, critical{boolean} and value{ByteString}
182  * @param {Number} srId service request id to be stored with issued certificate
183  * @type X509
184  * @return the newly generated certificate
185  */
186 X509CertificateIssuer.prototype.issueCertificate = function(certholder, pubkey, subject, extensions, srId) {
187 	assert(pubkey instanceof Key, "Argument pubkey must be instance of Key");
188 
189 	if (typeof(certholder) != "object") {
190 		var holderdao = this.daof.getHolderDAO();
191 
192 		if (typeof(pathOrHolderId) == "string") {
193 			certholder = holderdao.getHolder(certholder);
194 		} else {
195 			certholder = holderdao.getHolderById(certholder);
196 		}
197 	}
198 
199 	if (!certholder) {
200 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "Holder " + certholder + " not found");
201 	}
202 
203 	var icert = this.getSignerCertificate();
204 
205 	if (!icert) {
206 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "No active signer found");
207 	}
208 
209 	var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true);
210 
211 	if (!cp) {
212 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "Signer is offline (Key Domain : " + this.signer.keyDomain + ")");
213 	}
214 
215 	try	{
216 		var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob);
217 
218 		if (prk == null) {
219 			throw new GPError(module.id, GPError.INVALID_DATA, 1, "No key with keyId " + this.signer.keyId + " found");
220 		}
221 
222 		var issuer = PKIXCommon.getSubjectAsASN1(icert);
223 
224 		var gen = new X509CertificateGenerator(cp.getCrypto());
225 		gen.encodeECDomainParameter = false;
226 
227 		gen.reset();
228 		gen.setSerialNumber(this.newSerialNumber());
229 		gen.setSignatureAlgorithm(this.policy.signatureAlgorithm);
230 		gen.setIssuer(issuer);
231 		var ced = new Date();
232 		var cxd = PKIXCommon.addDays(ced, this.policy.validityDaysCertificates);
233 		gen.setNotBefore(ced);
234 		gen.setNotAfter(cxd);
235 		gen.setSubject(subject);
236 		gen.setPublicKey(pubkey);
237 		gen.addSubjectKeyIdentifierExtension();
238 		gen.addAuthorityKeyIdentifierExtension(icert.getSubjectKeyIdentifier());
239 
240 		if ((typeof(this.policy.pathLenConstraint) != "undefined") && (this.policy.pathLenConstraint > 0)) {
241 			gen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint - 1);
242 		} else {
243 			gen.addBasicConstraintsExtension(false, 0);
244 		}
245 
246 		if (this.crldp.length > 0) {
247 			gen.addCRLDistributionPointURL(this.crldp);
248 		}
249 
250 		if (typeof(extensions) == "object") {
251 			for (var i = 0; i < extensions.length; i++) {
252 				gen.addExtension(extensions[i].oid, extensions[i].critical, extensions[i].value);
253 			}
254 		}
255 
256 		var cert = gen.generateX509Certificate(prk);
257 
258 		var id = this.storeCertificateForHolder(certholder, cert, false, undefined, srId);
259 	}
260 	finally {
261 		cp.release();
262 	}
263 	return { id: id, cert: cert };
264 }
265 
266 
267 
268 /**
269  * Extension handler method for Sub-CA certificates
270  *
271  * @private
272  */
273 X509CertificateIssuer.prototype.addExtForSubCA = function(certgen, extvalues) {
274 
275 	certgen.addKeyUsageExtension(PKIXCommon.keyCertSign | PKIXCommon.cRLSign );
276 	certgen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint - 1);
277 }
278 
279 
280 
281 /**
282  * Extension handler method for TLS server certificates
283  *
284  * @private
285  */
286 X509CertificateIssuer.prototype.addExtForTLSServer = function(certgen, extvalues) {
287 
288 	certgen.addKeyUsageExtension( PKIXCommon.keyAgreement | PKIXCommon.keyEncipherment);
289 	certgen.addBasicConstraintsExtension(false, 0);
290 
291 	certgen.addExtendedKeyUsages(["id-csn-369791-tls-server", "id-kp-serverAuth"]);
292 
293 	var ext = new ASN1("subjectAltName", ASN1.SEQUENCE,
294 						new ASN1("dNSName", 0x82, new ByteString(extvalues["dNSName"], ASCII))
295 					);
296 	certgen.addExtension("id-ce-subjectAltName", false, ext.getBytes());
297 }
298 
299 
300 
301 /**
302  * Extension handler method for TLS client certificates
303  *
304  * @private
305  */
306 X509CertificateIssuer.prototype.addExtForTLSClient = function(certgen, extvalues) {
307 	certgen.addKeyUsageExtension( PKIXCommon.digitalSignature);
308 	certgen.addBasicConstraintsExtension(false, 0);
309 
310 	// certgen.addExtendedKeyUsages(["id-csn-369791-tls-client", "id-kp-clientAuth"]);
311 	certgen.addExtendedKeyUsages(["id-kp-clientAuth"]);
312 }
313 
314 
315 
316 /**
317  * Extension handler method for certificates suitable for TLS client authentication and e-Mail signature and encryption
318  *
319  * @private
320  */
321 X509CertificateIssuer.prototype.addExtForEmailAndTLSClient = function(certgen, extvalues) {
322 
323 	certgen.addKeyUsageExtension( PKIXCommon.digitalSignature | PKIXCommon.keyEncipherment);
324 	certgen.addBasicConstraintsExtension(false, 0);
325 
326 	var ext = new ASN1("subjectAltName", ASN1.SEQUENCE,
327 						new ASN1("rfc822Name", 0x81, new ByteString(extvalues["email"], ASCII))
328 					);
329 	certgen.addExtension("id-ce-subjectAltName", false, ext.getBytes());
330 
331 	certgen.addExtendedKeyUsages(["id-kp-clientAuth", "id-kp-emailProtection"]);
332 }
333 
334 
335 
336 /**
337  * Issue a CRL
338  *
339  * @type CRL
340  * @return the newly issued CRL
341  */
342 X509CertificateIssuer.prototype.issueCRL = function() {
343 	GPSystem.log(GPSystem.DEBUG, module.id, "issueCRL()");
344 
345 	var icert = this.getSignerCertificate();
346 	if (!icert) {
347 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "No active signer found");
348 	}
349 
350 	var c = this.signer.getContent();
351 	if (c && c.crl) {
352 		var latestCRL = new CRLGenerator();
353 		var crlNumber = latestCRL.loadCRLEntries(new ByteString(c.crl, HEX));
354 		crlNumber++;
355 	}
356 
357 	var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true);
358 
359 	if (!cp) {
360 		throw new GPError(module.id, GPError.INVALID_DATA, 1, "Signer is offline (Key Domain : " + this.signer.keyDomain + ")");
361 	}
362 
363 	try	{
364 		var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob);
365 
366 		if (prk == null) {
367 			throw new GPError(module.id, GPError.INVALID_DATA, 1, "No key with keyId " + this.signer.keyId + " found");
368 		}
369 
370 		var x = new CRLGenerator(cp.getCrypto());
371 
372 		x.reset();
373 		x.setSignatureAlgorithm(this.policy.signatureAlgorithm);
374 		var issuer = PKIXCommon.getSubjectAsASN1(icert);
375 		x.setIssuer(issuer);
376 		var now = new Date();
377 		x.setThisUpdate(now);
378 		x.setNextUpdate(PKIXCommon.addDays(now, this.policy.validityDaysCRL));
379 
380 		var certdao = this.daof.getCertificateDAO();
381 		var revocationList = certdao.getRevokedCertificates(this.holder.id, now);
382 
383 		for each (var cert in revocationList) {
384 			var serial = new ByteString(cert.serial, HEX);
385 
386 			var reason = this.mapStatusToReasonCode(cert.status);
387 
388 			if (cert.revocationDate) {
389 				var revocationDate = cert.revocationDate;
390 			} else {
391 				var revocationDate = now;
392 				certdao.updateRevocationDate(cert.id, now);
393 			}
394 
395 			if (cert.invalidityDate) {
396 				var invalidityDate = cert.invalidityDate;
397 			} else {
398 				var invalidityDate = now;
399 			}
400 			var extensions = new ASN1("crlExtensions", ASN1.SEQUENCE);
401 			extensions.add(x.createInvalidityDateExt(invalidityDate));
402 
403 			x.revokeCertificate(serial, revocationDate, reason, extensions);
404 		}
405 
406 		if (!crlNumber) {
407 			var crlNumber = 1;
408 		}
409 		x.addCRLNumberExtension(crlNumber);
410 
411 		var crl = x.generateCRL(prk);
412 
413 		c.crl = crl.getBytes().toString(HEX);
414 		this.signer.setContent(c);
415 		var signerDAO = this.daof.getSignerDAO();
416 		signerDAO.updateContent(this.signer);
417 	}
418 	finally {
419 		cp.release();
420 	}
421 	return crl;
422 }
423 
424 
425 
426 X509CertificateIssuer.prototype.mapStatusToReasonCode = function(status) {
427 	switch(status) {
428 	  case OCSPQuery.REVOKED:
429 		return null; // "...the reason code CRL entry extension SHOULD be absent instead of using the unspecified (0) reasonCode value." [RFC 5280, 5.3.1]
430 	  case OCSPQuery.KEYCOMPROMISE:
431 		return CRLGenerator.keyCompromise;
432 	  case OCSPQuery.CACOMPROMISE:
433 		return CRLGenerator.cACompromise;
434 	  case OCSPQuery.AFFILIATIONCHANGED:
435 		return CRLGenerator.affiliationChanged;
436 	  case OCSPQuery.SUPERSEDED:
437 		return CRLGenerator.superseded;
438 	  case OCSPQuery.CESSATIONOFOPERATION:
439 		return CRLGenerator.cessationOfOperation;
440 	  case OCSPQuery.CERTIFICATEHOLD:
441 		return CRLGenerator.certificateHold;
442 	  case OCSPQuery.REMOVEFROMCRL:
443 		return CRLGenerator.removeFromCRL;
444 	  case OCSPQuery.PRIVILEGEWITHDRAWN:
445 		return CRLGenerator.privilegeWithdrawn;
446 	  case OCSPQuery.AACOMPROMISE:
447 		return CRLGenerator.aACompromise;
448 	}
449 
450  	throw new GPError(module.id, GPError.INVALID_DATA, 1, "Unexpected status: " + status);
451 }
452 
453 
454 
455 X509CertificateIssuer.prototype.getDNMask = function(holderId) {
456 	var signer = this.getSigner();
457 	var c = signer.getContent();
458 	return c.dnmask;
459 }
460 
461 
462 
463 X509CertificateIssuer.prototype.isOperational = function() {
464 	GPSystem.log(GPSystem.DEBUG, module.id, "isOperational()");
465 
466 	var icert = this.getSignerCertificate();
467 	if (!icert) {
468 		// No active signer found
469 		return false;
470 	}
471 
472 	var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true);
473 
474 	if (!cp) {
475 		// Signer offline
476 		return false;
477 	}
478 
479 	try	{
480 		var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob);
481 
482 		if (prk == null) {
483 			// Key not found
484 			return false;
485 		}
486 	} finally {
487 		cp.release();
488 	}
489 
490 	return true;
491 }
492