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 Generic X509 certificate, request and key store
 25  */
 26 
 27 var PKIXCommon = require('scsh/x509/PKIXCommon').PKIXCommon;
 28 var Certificate = require('scsh/pki-db/Certificate').Certificate;
 29 var Holder = require('scsh/pki-db/Holder').Holder;
 30 var LongDate = require('scsh/pki-db/LongDate').LongDate;
 31 var PKCS8 = require('scsh/pkcs/PKCS8').PKCS8;
 32 var PKCS10 = require('scsh/pkcs/PKCS10').PKCS10;
 33 
 34 
 35 /**
 36  * Create an object to access a X509 certificate store.
 37  *
 38  * @class Class that abstracts a certificate, request and key store for a X509 PKI.
 39  *
 40  * @constructor
 41  * @param {DAOFactory} DAOFactory the factory that can create data access objects for persistent information
 42  */
 43 function X509CertificateStore(daof) {
 44 	assert(daof, "Parameter doaf can't be empty");
 45 
 46 	this.daof = daof;
 47 	this.certtype = Holder.X509;
 48 }
 49 
 50 exports.X509CertificateStore = X509CertificateStore;
 51 
 52 
 53 
 54 /**
 55  * Strip the last element of the path, effectively defining the parent within the path
 56  *
 57  * @param {String} path the path to strip the last element from
 58  * @returns the parent path or null for the root
 59  * @type String
 60  */
 61 X509CertificateStore.parentPathOf = function(path) {
 62 	assert(typeof(path) == "string", "Argument path must be string");
 63 	var ofs = path.lastIndexOf("/");
 64 	if (ofs <= 0) {
 65 		return null;
 66 	}
 67 	return path.substr(0, ofs);
 68 }
 69 
 70 
 71 
 72 /**
 73  * Return the n-element of the path
 74  *
 75  * @param {String} path the path to return the last element from
 76  * @returns the last path element or null for the root
 77  * @type String
 78  */
 79 X509CertificateStore.nthElementOf = function(path, n) {
 80 	assert(typeof(path) == "string", "Argument path must be string");
 81 	var pe = path.substr(1).split("/");
 82 	if (typeof(n) == "undefined") {
 83 		return pe[pe.length - 1];
 84 	}
 85 	return pe[n];
 86 }
 87 
 88 
 89 
 90 /**
 91  * Return a suitable crypto object. This may be overwritten by derived classes
 92  *
 93  * @type Crypto
 94  * @return the Crypto object
 95  */
 96 X509CertificateStore.prototype.getCrypto = function() {
 97 	if (this.crypto == undefined) {
 98 		this.crypto = new Crypto();
 99 	}
100 	return this.crypto;
101 }
102 
103 
104 
105 /**
106  * Check if holder exists for path or holderId
107  *
108  * @param {String/Number} pathOrHolderId
109  * @type Holder
110  * @return true if holder exists
111  * @private
112  */
113 X509CertificateStore.prototype.hasHolder = function(pathOrHolderId) {
114 	var holderdao = this.daof.getHolderDAO();
115 
116 	if (typeof(pathOrHolderId) == "number") {
117 		var holder = holderdao.getHolderById(pathOrHolderId, this.certtype);
118 	} else {
119 		var holder = holderdao.getHolder(pathOrHolderId, this.certtype);
120 	}
121 	return holder != null;
122 }
123 
124 
125 
126 /**
127  * List certificate holders for a given PKI element
128  *
129  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
130  * @returns a list of holder ids, possibly empty
131  * @type String[]
132  */
133 X509CertificateStore.prototype.listHolders = function(pathOrHolderId) {
134 	GPSystem.log(GPSystem.DEBUG, module.id, "listHolders(" + pathOrHolderId + ")");
135 
136 	var holderdao = this.daof.getHolderDAO();
137 
138 	var result = holderdao.getHolderList(pathOrHolderId, this.certtype);
139 	return result;
140 }
141 
142 
143 
144 /**
145  * Get existing holder object for given path or holderId
146  *
147  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
148  * @param {Boolean} create create if holder doesn't exist
149  * @type Holder
150  * @return the holder object
151  * @private
152  */
153 X509CertificateStore.prototype.getHolder = function(pathOrHolderId, create) {
154 	var holderdao = this.daof.getHolderDAO();
155 
156 	if (typeof(pathOrHolderId) == "number") {
157 		var holder = holderdao.getHolderById(pathOrHolderId, this.certtype);
158 	} else {
159 		var holder = holderdao.getHolder(pathOrHolderId, this.certtype);
160 	}
161 
162 	if (!holder) {
163 		if (!create) {
164 			throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate holder for " + pathOrHolderId);
165 		}
166 		GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found, creating new.");
167 		if (typeof(pathOrHolderId) == "number") {
168 			holder = holderdao.newHolderWithParent(0, this.certtype);
169 		} else {
170 			holder = holderdao.newHolder(pathOrHolderId, this.certtype);
171 		}
172 	}
173 
174 	return holder;
175 }
176 
177 
178 
179 /**
180  * Create new signer based on key pair generated externally
181  *
182  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
183  * @param {String} name the unique name of the signer in the holder context
184  * @param {Key} puk the public key
185  * @param {ByteString} keyblob the wrapped private key
186  * @type ByteString
187  * @return the subject key identifier
188  */
189 X509CertificateStore.prototype.newSigner = function(pathOrHolderId, name, puk, keyblob) {
190 	GPSystem.log(GPSystem.DEBUG, module.id, "newSigner(" + pathOrHolderId + "," + name + ")");
191 
192 	var holder = this.getHolder(pathOrHolderId, false);
193 
194 	var keyId = PKIXCommon.determineKeyIdentifier(puk);
195 
196 	var template = {};
197 
198 	if (keyblob instanceof ByteString)  {
199 		template.keyblob = keyblob;
200 	}
201 
202 	var signerDAO = this.daof.getSignerDAO();
203 	signerDAO.newSigner(holder, name, keyId, template);
204 	return keyId;
205 }
206 
207 
208 
209 /**
210  * Get the signer identified by the keyId
211  *
212  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
213  * @param {ByteString} keyId the key identifier
214  * @type Signer
215  * @return this Signer object
216  */
217 X509CertificateStore.prototype.getSigner = function(pathOrHolderId, keyId) {
218 	GPSystem.log(GPSystem.DEBUG, module.id, "getSigner(" + pathOrHolderId + "," + keyId + ")");
219 
220 	var holder = this.getHolder(pathOrHolderId, false);
221 
222 	var signerDAO = this.daof.getSignerDAO();
223 
224 	var signer = signerDAO.getSignerByKeyId(holder, keyId);
225 
226 	if (!signer) {
227 		return null;
228 	}
229 
230 	return signer;
231 }
232 
233 
234 
235 /**
236  * Generate key pair
237  *
238  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
239  * @param {String} name the unique name of the signer in the holder context
240  * @param {Number} algo the key generation algorithm (Crypto.EC or Crypto.RSA)
241  * @param {Key} prk the private key template
242  * @param {Key} puk the public key template
243  * @type ByteString
244  * @return the subject key identifier
245  */
246 X509CertificateStore.prototype.generateKeyPair = function(pathOrHolderId, name, algo, prk, puk) {
247 	GPSystem.log(GPSystem.DEBUG, module.id, "generateKeyPair(" + pathOrHolderId + "," + name + ")");
248 
249 	var holder = this.getHolder(pathOrHolderId, false);
250 
251 	var crypto = this.getCrypto();
252 
253 	// Generate key pair
254 	crypto.generateKeyPair(algo, puk, prk);
255 
256 	var keyId = PKIXCommon.determineKeyIdentifier(puk);
257 
258 	var template = {
259 		keyblob: PKCS8.encodeKeyUsingPKCS8Format(prk)
260 	};
261 
262 	var signerDAO = this.daof.getSignerDAO();
263 	signerDAO.newSigner(holder, name, keyId, template);
264 	return keyId;
265 }
266 
267 
268 
269 /**
270  * Get a private key in the certificate store
271  *
272  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
273  * @param {String} name the unique name of the signer in the holder context
274  * @returns the private key or null if not found
275  * @type Key
276  */
277 X509CertificateStore.prototype.getPrivateKeyByName = function(pathOrHolderId, name) {
278 	GPSystem.log(GPSystem.DEBUG, module.id, "getPrivateKey(" + pathOrHolderId + "," + name + ")");
279 
280 	var holder = this.getHolder(pathOrHolderId, false);
281 
282 	var signerDAO = this.daof.getSignerDAO();
283 	var signer = signerDAO.getSignerByName(holder, name);
284 
285 	if (!signer) {
286 		return signer;
287 	}
288 
289 	return PKCS8.decodeKeyFromPKCS8Format(signer.keyblob);
290 }
291 
292 
293 
294 /**
295  * Get a private key in the certificate store
296  *
297  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
298  * @param {ByteString} keyId the unique key identifier
299  * @returns the private key or null if not found
300  * @type Key
301  */
302 X509CertificateStore.prototype.getPrivateKeyByKeyId = function(pathOrHolderId, keyId) {
303 	GPSystem.log(GPSystem.DEBUG, module.id, "getPrivateKeyByKeyId(" + pathOrHolderId + "," + keyId + ")");
304 
305 	var holder = this.getHolder(pathOrHolderId, false);
306 
307 	var signerDAO = this.daof.getSignerDAO();
308 	var signer = signerDAO.getSignerByKeyId(holder, keyId);
309 
310 	if (!signer) {
311 		return signer;
312 	}
313 
314 	return PKCS8.decodeKeyFromPKCS8Format(signer.keyblob);
315 }
316 
317 
318 
319 /**
320  * Remove private key
321  *
322  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
323  * @param {String} name the unique name of the signer in the holder context
324  * @returns true is deleted
325  * @type boolean
326  */
327 X509CertificateStore.prototype.deletePrivateKey = function(pathOrHolderId, name) {
328 	GPSystem.log(GPSystem.DEBUG, module.id, "deleteRequest(" + pathOrHolderId + "," + name + ")");
329 
330 	var holder = this.getHolder(pathOrHolderId, true);
331 
332 	var signerDAO = this.daof.getSignerDAO();
333 	var signer = signerDAO.getSignerByName(holder, name.toString());
334 
335 	if (!signer) {
336 		return false;
337 	}
338 
339 	return signerDAO.deleteSigner(signer);
340 }
341 
342 
343 
344 /**
345  * Remove request
346  *
347  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
348  * @param {ByteString} keyId the unique keyId of the request in the holder context
349  * @returns true is deleted
350  * @type boolean
351  */
352 X509CertificateStore.prototype.deleteRequest = function(pathOrHolderId, keyId) {
353 	GPSystem.log(GPSystem.DEBUG, module.id, "deleteRequest(" + pathOrHolderId + "," + keyId + ")");
354 
355 	var holder = this.getHolder(pathOrHolderId, true);
356 
357 	var requestDAO = this.daof.getRequestDAO();
358 	var request = requestDAO.getRequestByKeyId(holder, keyId);
359 
360 	if (!request) {
361 		return false;
362 	}
363 
364 	return requestDAO.deleteRequest(request);
365 }
366 
367 
368 
369 /**
370  * Store a certificate request in the certificate store
371  *
372  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
373  * @param {PKCS10} req the request
374  * @type ByteString
375  * @return the request's keyId
376  */
377 X509CertificateStore.prototype.storeRequest = function(pathOrHolderId, req) {
378 	GPSystem.log(GPSystem.DEBUG, module.id, "storeRequest(" + pathOrHolderId + "," + req + ")");
379 
380 	var holder = this.getHolder(pathOrHolderId, true);
381 
382 	var requestDAO = this.daof.getRequestDAO();
383 
384 	var puk = req.getPublicKey();
385 	var keyId = PKIXCommon.determineKeyIdentifier(puk);
386 
387 	requestDAO.newRequest(holder, keyId, req.getBytes());
388 
389 	return keyId;
390 }
391 
392 
393 
394 /**
395  * Return request for given keyId
396  *
397  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
398  * @param {ByteString} keyId the unique keyId of the request in the holder context
399  * @type PKCS10
400  * @return the request or null
401  */
402 X509CertificateStore.prototype.getRequest = function(pathOrHolderId, keyId) {
403 	GPSystem.log(GPSystem.DEBUG, module.id, "getRequest(" + pathOrHolderId + "," + keyId + ")");
404 
405 	var holder = this.getHolder(pathOrHolderId, true);
406 
407 	var requestDAO = this.daof.getRequestDAO();
408 	var request = requestDAO.getRequestByKeyId(holder, keyId);
409 
410 	var req = new PKCS10(request.bytes);
411 	return req;
412 }
413 
414 
415 
416 /**
417  * Store a certificate in the certificate store
418  *
419  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
420  * @param {X509} cert the certificate
421  * @param {Boolean} makeCurrent true if this certificate becomes the current certificate
422  * @param {Number} srId service request id to be stored with issued certificate (optional)
423  * @type Certificate
424  * @return the Certificate entry from the database or null
425  */
426 X509CertificateStore.prototype.storeCertificate = function(pathOrHolderId, cert, makeCurrent, srId) {
427 	GPSystem.log(GPSystem.DEBUG, module.id, "storeCertificate(" + pathOrHolderId + ",'" + cert + "'," + makeCurrent + ")");
428 
429 	var holderdao = this.daof.getHolderDAO();
430 
431 	if (typeof(pathOrHolderId) == "number") {
432 		var holder = holderdao.getHolderById(pathOrHolderId, this.certtype);
433 		if (holder == null) {
434 			throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate holder for id " + pathOrHolderId);
435 		}
436 	} else {
437 		var holder = holderdao.getHolder(pathOrHolderId, this.certtype);
438 
439 		if (!holder) {
440 			GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found, creating new.");
441 			holder = holderdao.newHolder(pathOrHolderId, Holder.X509);
442 		}
443 	}
444 
445 	var certdao = this.daof.getCertificateDAO();
446 
447 	var issuer = cert.getIssuerDNString();
448 	var subject = cert.getSubjectDNString();
449 	var autid = cert.getAuthorityKeyIdentifier();
450 	var subid = cert.getSubjectKeyIdentifier();
451 	var serial = cert.getSerialNumber().toString(HEX);
452 
453 	if ((autid && autid.equals(subid)) || issuer.equals(subject)) {
454 		dir = Certificate.SHORT;
455 	} else {
456 		dir = Certificate.UP;
457 	}
458 
459 	var certificate = certdao.getCertificateBySerial(holder, serial, dir);
460 
461 	if (certificate) {
462 		GPSystem.log(GPSystem.INFO, module.id, "storeCertificate() : We already have a different certificate for that serial number");
463 		GPSystem.log(GPSystem.INFO, module.id, "Existing: " + (new X509(certificate.bytes)));
464 		GPSystem.log(GPSystem.INFO, module.id, "New     : " + cert);
465 
466 		return certificate;
467 	}
468 
469 	var template = {
470 		keyId: PKIXCommon.determineKeyIdentifier(cert.getPublicKey()),
471 		expiry: new LongDate(cert.getNotAfter())
472 	};
473 
474 	if (srId) {
475 		template.serviceRequestId = srId;
476 	}
477 
478 	var certificate = certdao.newCertificate(holder, serial, dir, cert.getBytes(), template);
479 
480 	if (makeCurrent || !holder.certId) {
481 		holderdao.updateCurrentCertificate(holder, certificate);
482 	}
483 
484 	return certificate;
485 }
486 
487 
488 
489 /**
490  * Import a certificate that can be validated by one of certificates in the database.
491  *
492  * @param {X509} cert the certificate
493  * @param {Boolean} makeCurrent true if this certificate becomes the current certificate
494  * @param {Number} srId service request id to be stored with issued certificate (optional)
495  * @type Certificate
496  * @return the Certificate entry from the database or null
497  */
498 X509CertificateStore.prototype.importCertificate = function(cert, makeCurrent, srId) {
499 	GPSystem.log(GPSystem.DEBUG, module.id, "importCertificate(" + cert + ")");
500 
501 	var issuer = cert.getIssuerDNString();
502 	var subject = cert.getSubjectDNString();
503 	if (issuer.equals(subject)) {
504 		throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "importCertificate can not import self-signed certificates");
505 	}
506 
507 	var certdao = this.daof.getCertificateDAO();
508 
509 	var autid = cert.getAuthorityKeyIdentifier();
510 	var subid = cert.getSubjectKeyIdentifier();
511 
512 	var certdto = certdao.getCertificateByKeyId(autid);
513 
514 	if (certdto == null) {
515 		GPSystem.log(GPSystem.WARN, module.id, "No certificate with AuthorityKeyIdentifier " + autid.toString(HEX) + " found");
516 		return null;
517 	}
518 
519 	var ca = new X509(certdto.bytes);
520 	cert.verifyWith(ca);
521 
522 	var parentId = certdto.holderId;
523 	var name = PKIXCommon.makeName(PKIXCommon.parseDN(subject));
524 
525 	var holderdao = this.daof.getHolderDAO();
526 	var holder = holderdao.getHolderByTypeAndName(parentId, name, Holder.X509);
527 
528 	if (!holder) {
529 		GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found, creating new.");
530 		holder = holderdao.newHolderForParent(parentId, Holder.X509, { name: name } );
531 	}
532 
533 	var holderId = holder.id;
534 
535 	return this.storeCertificate(holderId, cert, makeCurrent, srId);
536 }
537 
538 
539 
540 /**
541  * Get current key id
542  *
543  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
544  * @type ByteString
545  * @return the current key id or null if none defined
546  */
547 X509CertificateStore.prototype.getCurrentKeyIdAndCertificate = function(pathOrHolderId) {
548 	GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentKeyId(" + pathOrHolderId + ")");
549 
550 	var holder = this.getHolder(pathOrHolderId, false);
551 
552 	var certdao = this.daof.getCertificateDAO();
553 
554 	var certificate = certdao.getCurrentCertificate(holder);
555 
556 	if (!certificate) {
557 		return null;
558 	}
559 
560 	return { keyId: certificate.keyId, certificate: new X509(certificate.bytes) };
561 }
562 
563 
564 
565 /**
566  * Get current certificate for given path or holderId
567  *
568  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
569  * @type X509
570  * @return the current certificate or null if none defined
571  */
572 X509CertificateStore.prototype.getCurrentCertificate = function(pathOrHolderId) {
573 	GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentCertificate(" + pathOrHolderId + ")");
574 
575 	var holder = this.getHolder(pathOrHolderId, false);
576 
577 	var certdao = this.daof.getCertificateDAO();
578 
579 	var certificate = certdao.getCurrentCertificate(holder);
580 
581 	if (!certificate) {
582 		return null;
583 	}
584 
585 	return new X509(certificate.bytes);
586 }
587 
588 
589 
590 /**
591  * Get certificate chain for given path or holderId
592  *
593  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
594  * @type X509[]
595  * @return the certificate chain, starting with the current certificate of the holder and ending with the self-signed trust anchor
596  */
597 X509CertificateStore.prototype.getCertificateChain = function(pathOrHolderId) {
598 	GPSystem.log(GPSystem.DEBUG, module.id, "getCertificateChain(" + pathOrHolderId + ")");
599 
600 	var chain = [];
601 	var certdao = this.daof.getCertificateDAO();
602 
603 	do	{
604 		var holder = this.getHolder(pathOrHolderId, false);
605 
606 		var certificate = certdao.getCurrentCertificate(holder);
607 
608 		if (!certificate) {
609 			throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "No current certificate found for " + holder);
610 		}
611 
612 		chain.push(new X509(certificate.bytes));
613 		pathOrHolderId = holder.parentId;
614 	} while (pathOrHolderId != undefined);
615 
616 	return chain;
617 }
618 
619 
620 
621 /**
622  * Get current certificate for given path or holderId
623  *
624  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
625  * @type X509
626  * @return the current certificate or null if none defined
627  */
628 X509CertificateStore.prototype.getCurrentCertificateAndSigner = function(pathOrHolderId) {
629 	GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentCertificateAndSigner(" + pathOrHolderId + ")");
630 
631 	var holder = this.getHolder(pathOrHolderId, false);
632 
633 	var certdao = this.daof.getCertificateDAO();
634 
635 	var certificate = certdao.getCurrentCertificate(holder);
636 
637 	if (!certificate) {
638 		return null;
639 	}
640 
641 	var cert = new X509(certificate.bytes);
642 
643 	var signerDAO = this.daof.getSignerDAO();
644 
645 	var signer = signerDAO.getSignerByKeyId(holder, certificate.keyId);
646 
647 	if (!signer) {
648 		return null;
649 	}
650 
651 	var prk = PKCS8.decodeKeyFromPKCS8Format(signer.keyblob);
652 
653 	return { signerPrK: prk, signerCert: cert };
654 }
655