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 Certificate = require('scsh/pki-db/Certificate').Certificate;
 28 
 29 PKIXCommon = require('scsh/x509/PKIXCommon').PKIXCommon;
 30 Holder = require('scsh/pki-db/Holder').Holder;
 31 PKCS8 = require('scsh/pkcs/PKCS8').PKCS8;
 32 PKCS10 = require('scsh/pkcs/PKCS10').PKCS10;
 33 
 34 
 35 
 36 /**
 37  * Create an object to access a X509 certificate store.
 38  *
 39  * @class Class that abstracts a certificate, request and key store for a X509 PKI.
 40  *
 41  * @constructor
 42  * @param {DAOFactory} DAOFactory the factory that can create data access objects for persistent information
 43  */
 44 function X509CertificateStore(daof) {
 45 	assert(daof, "Parameter doaf can't be empty");
 46 
 47 	this.daof = daof;
 48 	this.certtype = Holder.X509;
 49 }
 50 
 51 exports.X509CertificateStore = X509CertificateStore;
 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  */
423 X509CertificateStore.prototype.storeCertificate = function(pathOrHolderId, cert, makeCurrent) {
424 	GPSystem.log(GPSystem.DEBUG, module.id, "storeCertificate(" + pathOrHolderId + ",'" + cert + "'," + makeCurrent + ")");
425 
426 	var holderdao = this.daof.getHolderDAO();
427 
428 	if (typeof(pathOrHolderId) == "number") {
429 		var holder = holderdao.getHolderById(pathOrHolderId, this.certtype);
430 		if (holder == null) {
431 			throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate holder for id " + pathOrHolderId);
432 		}
433 	} else {
434 		var holder = holderdao.getHolder(pathOrHolderId, this.certtype);
435 
436 		if (!holder) {
437 			GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found, creating new.");
438 			holder = holderdao.newHolder(pathOrHolderId, Holder.X509);
439 		}
440 	}
441 
442 	var certdao = this.daof.getCertificateDAO();
443 
444 	var issuer = cert.getIssuerDNString();
445 	var subject = cert.getSubjectDNString();
446 	var autid = cert.getAuthorityKeyIdentifier();
447 	var subid = cert.getSubjectKeyIdentifier();
448 	var serial = cert.getSerialNumberString();
449 
450 	if ((autid && autid.equals(subid)) || issuer.equals(subject)) {
451 		dir = Certificate.SHORT;
452 	} else {
453 		dir = Certificate.UP;
454 	}
455 
456 	var certificate = certdao.getCertificateBySerial(holder, serial, dir);
457 
458 	if (certificate) {
459 		GPSystem.log(GPSystem.INFO, module.id, "storeCertificate() : We already have a different certificate for that serial number");
460 		GPSystem.log(GPSystem.INFO, module.id, "Existing: " + (new X509(certificate.bytes)));
461 		GPSystem.log(GPSystem.INFO, module.id, "New     : " + cert);
462 
463 		return;
464 	}
465 
466 	var template = {
467 		keyId: PKIXCommon.determineKeyIdentifier(cert.getPublicKey())
468 	};
469 
470 	var certificate = certdao.newCertificate(holder, serial, dir, cert.getBytes(), template);
471 
472 	if (makeCurrent) {
473 		holderdao.updateCurrentCertificate(holder, certificate);
474 	}
475 }
476 
477 
478 
479 /**
480  * Get current key id
481  *
482  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
483  * @type ByteString
484  * @return the current key id or null if none defined
485  */
486 X509CertificateStore.prototype.getCurrentKeyIdAndCertificate = function(pathOrHolderId) {
487 	GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentKeyId(" + pathOrHolderId + ")");
488 
489 	var holder = this.getHolder(pathOrHolderId, false);
490 
491 	var certdao = this.daof.getCertificateDAO();
492 
493 	var certificate = certdao.getCurrentCertificate(holder);
494 
495 	if (!certificate) {
496 		return null;
497 	}
498 
499 	return { keyId: certificate.keyId, certificate: new X509(certificate.bytes) };
500 }
501 
502 
503 
504 /**
505  * Get current certificate for given path or holderId
506  *
507  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
508  * @type X509
509  * @return the current certificate or null if none defined
510  */
511 X509CertificateStore.prototype.getCurrentCertificate = function(pathOrHolderId) {
512 	GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentCertificate(" + pathOrHolderId + ")");
513 
514 	var holder = this.getHolder(pathOrHolderId, false);
515 
516 	var certdao = this.daof.getCertificateDAO();
517 
518 	var certificate = certdao.getCurrentCertificate(holder);
519 
520 	if (!certificate) {
521 		return null;
522 	}
523 
524 	return new X509(certificate.bytes);
525 }
526 
527 
528 
529 /**
530  * Get current certificate for given path or holderId
531  *
532  * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database
533  * @type X509
534  * @return the current certificate or null if none defined
535  */
536 X509CertificateStore.prototype.getCurrentCertificateAndSigner = function(pathOrHolderId) {
537 	GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentCertificateAndSigner(" + pathOrHolderId + ")");
538 
539 	var holder = this.getHolder(pathOrHolderId, false);
540 
541 	var certdao = this.daof.getCertificateDAO();
542 
543 	var certificate = certdao.getCurrentCertificate(holder);
544 
545 	if (!certificate) {
546 		return null;
547 	}
548 
549 	var cert = new X509(certificate.bytes);
550 
551 	var signerDAO = this.daof.getSignerDAO();
552 
553 	var signer = signerDAO.getSignerByKeyId(holder, certificate.keyId);
554 
555 	if (!signer) {
556 		return null;
557 	}
558 
559 	var prk = PKCS8.decodeKeyFromPKCS8Format(signer.keyblob);
560 
561 	return { signerPrK: prk, signerCert: cert };
562 }
563