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 Support for a card verifiable certificates store according to EAC 1.1/2.0
 25  */
 26  
 27 
 28 
 29 
 30 if (typeof(__ScriptingServer) == "undefined") {
 31 	load("tools/pkcs8.js");
 32 	load("cvc.js");
 33 }
 34 
 35 
 36 /**
 37  * Create an object to access a certificate store.
 38  *
 39  * @class Class that abstracts a certificate and key store for a EAC PKI.
 40  *
 41  * @constructor
 42  * @param {String} path the root of the certificate store
 43  */
 44 function CVCertificateStore(path) {
 45 	this.path = path;
 46 }
 47 
 48 
 49 
 50 /**
 51  * Loads a binary file from disk
 52  *
 53  * @param {String} filename the fully qualified file name
 54  * @return the binary content
 55  * @type ByteString
 56  */
 57 CVCertificateStore.loadBinaryFile = function(filename) {
 58 	// Open stream
 59 	var f = new java.io.FileInputStream(filename);
 60 	
 61 	// Determine file size
 62 	var flen = f.available();
 63 
 64 	// Allocate native byte array
 65 	var bs = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, flen);
 66 	
 67 	// Read into byte array
 68 	var len = f.read(bs);
 69 
 70 	f.close();
 71 	
 72 	// Allocate JavaScript ByteBuffer from native/wrapped byte array
 73 	var bb = new ByteBuffer(bs);
 74 	
 75 	// Convert to JavaScript ByteString
 76 	var data = bb.toByteString();
 77 
 78 	return data;
 79 }
 80 
 81 
 82 
 83 /**
 84  * Saves a binary file to disk
 85  *
 86  * @param {String} filename the fully qualified file name
 87  * @param {ByteString} data the binary content
 88  */
 89 CVCertificateStore.saveBinaryFile = function(filename, data) {
 90 	// Open stream
 91 	var f = new java.io.FileOutputStream(filename);
 92 	f.write(data);
 93 	f.close();
 94 }
 95 
 96 
 97 
 98 /**
 99  * Loads a XML file from disk
100  *
101  * @param {String} filename the fully qualified file name
102  * @return the XML content
103  * @type XML
104  */
105 CVCertificateStore.loadXMLFile = function(filename) {
106 	// Open stream
107 	var f = new java.io.FileReader(filename);
108 	var bfr = new java.io.BufferedReader(f);
109 
110 	// Skip processing instructions
111 	var result;
112 	do	{
113 		result = bfr.readLine();
114 	} while ((result != null) && (result.substr(0, 2) == "<?"));
115 	
116 	if (result == null) {
117 		bfr.close();
118 		f.close();
119 		return null;
120 	}
121 	
122 	var line;
123 	while ((line = bfr.readLine()) != null) {
124 		result += line + "\n";
125 	}
126 	bfr.close();
127 	f.close();
128 	default xml namespace = "";
129 	return new XML(result);
130 }
131 
132 
133 
134 /**
135  * Saves XML to disk
136  *
137  * @param {String} filename the fully qualified file name
138  * @param {XML} data the XML content
139  */
140 CVCertificateStore.saveXMLFile = function(filename, xml) {
141 	var fw = new java.io.FileWriter(filename);
142 	fw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
143 	fw.write(xml.toXMLString());
144 	fw.close();
145 }
146 
147 
148 
149 /**
150  * Strip the last element of the path, effectively defining the parent within the path
151  *
152  * @param {String} path the path to strip the last element from
153  * @returns the parent path or null for the root
154  * @type String
155  */
156 CVCertificateStore.parentPathOf = function(path) {
157 	var ofs = path.lastIndexOf("/");
158 	if (ofs <= 0) {
159 		return null;
160 	}
161 	return path.substr(0, ofs);
162 }
163 
164 
165 
166 /**
167  * Return the n-element of the path
168  *
169  * @param {String} path the path to return the last element from
170  * @returns the last path element or null for the root
171  * @type String
172  */
173 CVCertificateStore.nthElementOf = function(path, n) {
174 	var pe = path.substr(1).split("/");
175 	if (typeof(n) == "undefined") {
176 		return pe[pe.length - 1];
177 	}
178 	return pe[n];
179 }
180 
181 
182 
183 /**
184  * Check path for legal encodings
185  */
186 CVCertificateStore.checkPath = function(path) {
187 	if ((path.indexOf("/..") >= 0) ||
188 		(path.indexOf("../") >= 0) ||
189 		(path.indexOf("\\..") >= 0) ||
190 		(path.indexOf("..\\") >= 0) ||
191 		(path.indexOf("\0") >= 0) ||
192 		(path.indexOf("~") >= 0)) {
193 		throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Path \"" + path + "\" contains illegal characters");
194 	}
195 }
196 
197 
198 
199 /**
200  * Return a suitable crypto object. This may be overwritten by derived classes
201  *
202  * @type Crypto
203  * @return the Crypto object
204  */
205 CVCertificateStore.prototype.getCrypto = function() {
206 	if (this.crypto == undefined) {
207 		this.crypto = new Crypto();
208 	}
209 	return this.crypto;
210 }
211 
212 
213 
214 /**
215  * Map to absolute path on file system
216  * @param {String} path the relative path
217  * @type String
218  * @return the absolute path on the file system
219  */
220 CVCertificateStore.prototype.mapPath = function(path) {
221 	CVCertificateStore.checkPath(path);
222 	return this.path + path;
223 }
224 
225 
226 
227 /**
228  * Returns the current terminal certificate for a given CVCA reference.
229  *
230  * @param {PublicKeyReference} cvcaref the public key reference (CHR) of the root CA.
231  * @return the current terminal CVC
232  * @type CVC
233  */
234 CVCertificateStore.prototype.getTerminalCertificateFor = function(cvcaref) {
235 	var fn = this.mapPath("/" + cvcaref.getHolder() + "/terminal/current.cvcert");
236 	
237 	var bin = CVCertificateStore.loadBinaryFile(fn);
238 	
239 	var cvc = new CVC(bin);
240 	
241 	return cvc;
242 }
243 
244 
245 
246 /**
247  * Returns the document verifier certificate for a given CVCA and DV reference.
248  *
249  * @param {PublicKeyReference} cvcaref the public key reference (CHR) of the CVCA.
250  * @param {PublicKeyReference} dvcaref the public key reference (CHR) of the DV.
251  * @return the document verifier CVC
252  * @type CVC
253  */
254 CVCertificateStore.prototype.getDVCACertificateFor = function(cvcaref, dvcaref) {
255 	var fn = this.mapPath("/" + cvcaref.getHolder() + "/" + dvcaref.getHolder() + "/" + dvcaref.toString() + ".cvcert");
256 	
257 	var bin = CVCertificateStore.loadBinaryFile(fn);
258 	
259 	var cvc = new CVC(bin);
260 	
261 	return cvc;
262 }
263 
264 
265 
266 /**
267  * Returns the country verifying certification authority's certificate for a given CVCA reference.
268  *
269  * @param {PublicKeyReference} cvcaref the public key reference (CHR) of the CVCA.
270  * @param {PublicKeyReference} dvcaref the public key reference (CHR) of the DV.
271  * @return the country verifying certification authority's certificate
272  * @type CVC
273  */
274 CVCertificateStore.prototype.getCVCACertificateFor = function(cvcaref) {
275 	var fn = this.mapPath("/" + cvcaref.getHolder() + "/" + cvcaref.toString() + ".cvcert");
276 	
277 	var f = new java.io.File(fn);
278 	if (!f.exists()) {
279 		fn = this.path + "/" + cvcaref.getHolder() + "/" + cvcaref.toString() + ".selfsigned.cvcert";
280 	}
281 	
282 	var bin = CVCertificateStore.loadBinaryFile(fn);
283 	
284 	var cvc = new CVC(bin);
285 	
286 	return cvc;
287 }
288 
289 
290 
291 /**
292  * Returns a certificate chain for the current terminal certificate up to, but not including the 
293  * the CVCA certificated referenced.
294  *
295  * @param {PublicKeyReference} cvcaref the public key reference (CHR) of the CVCA.
296  * @return the list of certificates starting with optional CVCA link certificates, the DV certificate and
297  *         the terminal certificate
298  * @type CVC[]
299  */
300 CVCertificateStore.prototype.getCertificateChainFor = function(cvcaref) {
301 	var chain = new Array();
302 	
303 	var termcert = this.getTerminalCertificateFor(cvcaref);
304 	chain.push(termcert);
305 	
306 	var dvcaref = termcert.getCAR();
307 	
308 	var dvcacert = this.getDVCACertificateFor(cvcaref, dvcaref);
309 	chain.push(dvcacert);
310 	
311 	var ref = dvcacert.getCAR();
312 	
313 	while (ref.toString() != cvcaref.toString()) {
314 		var cvcacert = this.getCVCACertificateFor(cvcaref);
315 		chain.push(cvcacert);
316 		
317 		var ref = cvcacert.getCAR();
318 	}
319 
320 	return(chain.reverse());
321 }
322 
323 
324 
325 /**
326  * Return the current terminal key for a PKI identified by the CVCA reference
327  *
328  * @param {PublicKeyReference} cvcaref the public key reference (CHR) of the CVCA.
329  * @return the private key
330  * @type Key
331  */
332 CVCertificateStore.prototype.getTerminalKeyFor = function(cvcaref) {
333 	var fn = this.mapPath("/" + cvcaref.getHolder() + "/terminal/current.pkcs8");
334 	
335 	var bin = CVCertificateStore.loadBinaryFile(fn);
336 
337 	return PKCS8.decodeKeyFromPKCS8Format(bin);
338 }
339 
340 
341 
342 /**
343  * Store a private key in the certificate store
344  *
345  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
346  * @param {PublicKeyReference} chr the public key reference for this key
347  * @param {Key} prk the private key
348  */
349 CVCertificateStore.prototype.storePrivateKey = function(path, chr, prk) {
350 	default xml namespace = "";
351 	var cfg = this.loadConfig(path);
352 	if (cfg == null) {
353 		cfg = this.getDefaultConfig(path);
354 		this.saveConfig(path, cfg);
355 	}
356 	
357 	var p8 = PKCS8.encodeKeyUsingPKCS8Format(prk);
358 	var fn = this.mapPath(path + "/" + chr.toString() + ".pkcs8");
359 	GPSystem.trace("Saving private key to " + fn);
360 	CVCertificateStore.saveBinaryFile(fn, p8);
361 }
362 
363 
364 
365 /**
366  * Get a private key in the certificate store
367  *
368  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
369  * @param {PublicKeyReference} chr the public key reference for this key
370  * @returns the private key or null if not found
371  * @type Key
372  */
373 CVCertificateStore.prototype.getPrivateKey = function(path, chr) {
374 	var fn = this.mapPath(path + "/" + chr.toString() + ".pkcs8");
375 
376 	try	{
377 		var bin = CVCertificateStore.loadBinaryFile(fn);
378 	}
379 	catch(e) {
380 //		GPSystem.trace(e);
381 		return null;
382 	}
383 
384 	return PKCS8.decodeKeyFromPKCS8Format(bin);
385 }
386 
387 
388 
389 /**
390  * Remove private key
391  *
392  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
393  * @param {PublicKeyReference} chr the public key reference for this key
394  * @returns true is deleted
395  * @type boolean
396  */
397 CVCertificateStore.prototype.deletePrivateKey = function(path, chr) {
398 	var fn = this.mapPath(path + "/" + chr.toString() + ".pkcs8");
399 	var f = new java.io.File(fn);
400 	return f["delete"]();		// delete is a reserved keyword
401 }
402 
403 
404 
405 /**
406  * Store a certificate request in the certificate store
407  *
408  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
409  * @param {CVC} req the request
410  */
411 CVCertificateStore.prototype.storeRequest = function(path, req) {
412 	var chr = req.getCHR();
413 	var fn = this.mapPath(path + "/" + chr.toString() + ".cvreq");
414 	GPSystem.trace("Saving request to " + fn);
415 	CVCertificateStore.saveBinaryFile(fn, req.getBytes());
416 }
417 
418 
419 
420 /**
421  * Return request for given CHR
422  *
423  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
424  * @param {PublicKeyReference} chr the public key reference for the certificate
425  * @type CVC
426  * @return the request or null
427  */
428 CVCertificateStore.prototype.getRequest = function(path, chr) {
429 	var fn = this.mapPath(path + "/" + chr.toString() + ".cvreq");
430 	var bin = null;
431 	
432 	try	{
433 		bin = CVCertificateStore.loadBinaryFile(fn);
434 	}
435 	catch (e) {
436 //		GPSystem.trace(e);
437 		return null;
438 	}
439 	return new CVC(bin);
440 }
441 
442 
443 
444 /**
445  * Remove request
446  *
447  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
448  * @param {PublicKeyReference} chr the public key reference for this request
449  * @returns true is deleted
450  * @type boolean
451  */
452 CVCertificateStore.prototype.deleteRequest = function(path, chr) {
453 	var fn = this.mapPath(path + "/" + chr.toString() + ".cvreq");
454 	var f = new java.io.File(fn);
455 	return f["delete"]();		// delete is a reserved keyword
456 }
457 
458 
459 
460 /**
461  * Store a certificate in the certificate store
462  *
463  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
464  * @param {CVC} cert the certificate
465  * @param {Boolean} makeCurrent true if this certificate become the current certificate
466  */
467 CVCertificateStore.prototype.storeCertificate = function(path, cert, makeCurrent) {
468 	default xml namespace = "";
469 	var car = cert.getCAR();
470 	var chr = cert.getCHR();
471 	if (car.equals(chr)) {
472 		var fn = this.mapPath(path + "/" + chr.toString() + ".selfsigned.cvcert");
473 	} else {
474 		var fn = this.mapPath(path + "/" + chr.toString() + ".cvcert");
475 	}
476 
477 	var f = new java.io.File(fn);
478 	if (f.exists()) {
479 		return;
480 	}
481 	
482 	var cfg = this.loadConfig(path);
483 	if (cfg == null) {
484 		cfg = this.getDefaultConfig(path);
485 		this.saveConfig(path, cfg);
486 	}
487 
488 	GPSystem.trace("Saving certificate to " + fn);
489 	CVCertificateStore.saveBinaryFile(fn, cert.getBytes());
490 
491 	if (makeCurrent) {
492 		var cfg = this.loadConfig(path);
493 		cfg.sequence.currentCHR = chr.toString();
494 		this.saveConfig(path, cfg);
495 	}
496 }
497 
498 
499 
500 /**
501  * Remove certificate
502  *
503  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
504  * @param {PublicKeyReference} chr the public key reference for this certificate
505  * @param {boolean} selfsigned delete the self-signed root certificate rather than a link certificate
506  * @returns true is deleted
507  * @type boolean
508  */
509 CVCertificateStore.prototype.deleteCertificate = function(path, chr, selfsigned) {
510 	if (selfsigned) {
511 		var fn = this.mapPath(path + "/" + chr.toString() + ".selfsigned.cvcert");
512 	} else {
513 		var fn = this.mapPath(path + "/" + chr.toString() + ".cvcert");
514 	}
515 	var f = new java.io.File(fn);
516 	return f["delete"]();		// delete is a reserved keyword
517 }
518 
519 
520 
521 /**
522  * Return certificate for a given CHR in binary format
523  *
524  * <p>This method returns a self-signed root certificate if the selfsigned
525  *    parameter is set. If not set or set to false, then matching link certificate,
526  *    if any, is returned rather than the self-signed certificate.</p>
527  *
528  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
529  * @param {PublicKeyReference} chr the public key reference for the certificate
530  * @param {boolean} selfsigned return the self-signed root certificate rather than a link certificate
531  * @returns the certificate or null if not found
532  * @type ByteString
533  */
534 CVCertificateStore.prototype.getCertificateBinary = function(path, chr, selfsigned) {
535 	if (selfsigned) {
536 		var fn = this.mapPath(path + "/" + chr.toString() + ".selfsigned.cvcert");
537 	} else {
538 		var fn = this.mapPath(path + "/" + chr.toString() + ".cvcert");
539 
540 		var f = new java.io.File(fn);
541 		if (!f.exists()) {
542 			var fn = this.mapPath(path + "/" + chr.toString() + ".selfsigned.cvcert");
543 		}
544 	}
545 	
546 	var bin = null;
547 	try	{
548 		bin = CVCertificateStore.loadBinaryFile(fn);
549 	}
550 	catch (e) {
551 //		GPSystem.trace(e);
552 	}
553 	return bin;
554 }
555 
556 
557 
558 /**
559  * Return certificate for a given CHR
560  *
561  * <p>This method returns a self-signed root certificate if the selfsigned
562  *    parameter is set. If not set or set to false, then matching link certificate,
563  *    if any, is returned rather than the self-signed certificate.</p>
564  *
565  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
566  * @param {PublicKeyReference} chr the public key reference for the certificate
567  * @param {boolean} selfsigned return the self-signed root certificate rather than a link certificate
568  * @returns the certificate or null if not found
569  * @type CVC
570  */
571 CVCertificateStore.prototype.getCertificate = function(path, chr, selfsigned) {
572 	var bin = this.getCertificateBinary(path, chr, selfsigned);
573 	
574 	if (bin == null) {
575 		return null;
576 	}
577 	
578 	var cvc = null;
579 	try	{
580 		cvc = new CVC(bin);
581 	}
582 	catch (e) {
583 		GPSystem.trace(e);
584 	}
585 	return cvc;
586 }
587 
588 
589 
590 /**
591  * Return a chain of certificates resembling a path from root to end entity.
592  *
593  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
594  * @param {PublicKeyReference} tochr the public key reference for the certificate at the end of the chain
595  * @param {PublicKeyReference} fromcar the public key reference for the certificate to start with or root if undefined
596  * @returns the list of certificates starting with a self signed root certificate (fromcar undefined) a certificate
597  *          issued by fromcar up to an including the certificate referenced by tochr. Return null if fromcar is not found.
598  * @type CVC[]
599  */
600 CVCertificateStore.prototype.getCertificateChain = function(path, tochr, fromcar) {
601 	var chain = [];
602 	var chr = tochr;
603 	
604 	while (true) {
605 		var cvc = this.getCertificate(path, chr, false);
606 		if (cvc == null) {
607 			throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate certificate " + chr);
608 		}
609 		chain.push(cvc);
610 		if (typeof(fromcar) == "undefined") {
611 			if (cvc.getCAR().equals(cvc.getCHR())) {
612 				break;
613 			}
614 		} else {
615 			if (cvc.getCAR().equals(fromcar)) {
616 				break;
617 			}
618 			if (cvc.getCAR().equals(cvc.getCHR())) {
619 				return null;	// fromcar not found along the chain
620 			}
621 		}
622 		var ofs = path.lastIndexOf("/");
623 		if (ofs > 0) {
624 			path = path.substr(0, ofs);
625 		}
626 		chr = cvc.getCAR();
627 	}
628 	
629 	return chain.reverse();
630 }
631 
632 
633 
634 /**
635  * List certificates stored for given PKI element sorted by CHR
636  *
637  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM")
638  * @returns a list of certificates, possibly empty
639  * @type CVC[]
640  */
641 CVCertificateStore.prototype.listCertificates = function(path) {
642 	var result = [];
643 
644 	var fn = this.mapPath(path);
645 	var f = new java.io.File(fn);
646 	if (!f.exists()) {
647 		return result;
648 	}
649 	var files = f.list();
650 	
651 	for (var i = 0; i < files.length; i++) {
652 		var s = new String(files[i]);
653 		var n = s.match(/\.(cvcert|CVCERT)$/);
654 		if (n) {
655 			var bin = CVCertificateStore.loadBinaryFile(fn + "/" + s);
656 			var cvc = new CVC(bin);
657 			result.push(cvc);
658 		}
659 	}
660 	result.sort(function(a,b) { return a.getCHR().toString() < b.getCHR().toString() ? -1 : (a.getCHR().toString() > b.getCHR().toString() ? 1 : 0) } );
661 	return result;
662 }
663 
664 
665 
666 /**
667  * List certificate holders for a given PKI element
668  *
669  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1")
670  * @returns a list of holder ids, possibly empty
671  * @type String[]
672  */
673 CVCertificateStore.prototype.listHolders = function(path) {
674 	var result = [];
675 
676 	var fn = this.mapPath(path);
677 	var f = new java.io.File(fn);
678 	if (!f.exists()) {
679 		return result;
680 	}
681 	var files = f.list();
682 	
683 	for (var i = 0; i < files.length; i++) {
684 		var s = new String(files[i]);
685 		var fd = new java.io.File(f, s);
686 		if (fd.isDirectory()) {
687 			result.push(s);
688 		}
689 	}
690 	return result;
691 }
692 
693 
694 
695 /**
696  * Returns the domain parameter for a certificate identified by its CHR
697  *
698  * <p>This method traverses the certificate hierachie upwards and follows link certificates
699  *    until domain parameter are found.</p>
700  * 
701  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1")
702  * @param {PublicKeyReference} chr the CHR of the certificate to start the search with
703  * @return the domain parameter
704  * @type Key
705  */
706 CVCertificateStore.prototype.getDomainParameter = function(path, chr) {
707 	if (typeof(chr) == "undefined") {	// ToDo remove after migration
708 		chr = path;
709 		var path = "/" + chr.getHolder();
710 	}
711 	
712 	do	{
713 		var ofs = path.lastIndexOf("/");
714 		if (ofs > 0) {
715 			var cvc = this.getCertificate(path, chr);
716 			if (cvc == null) {
717 				throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate certificate " + chr);
718 			}
719 			chr = cvc.getCAR();
720 			path = path.substr(0, ofs);
721 		}
722 	} while (ofs > 0);
723 	
724 	do {
725 		var cvc = this.getCertificate(path, chr);
726 		
727 		if (cvc == null) {
728 			throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate certificate " + chr + " in " + path);
729 		}
730 		
731 		var p = cvc.getPublicKey();
732 		if (typeof(p.getComponent(Key.ECC_P)) != "undefined") {
733 			return p;
734 		}
735 		chr = cvc.getCAR();
736 	} while (!chr.equals(cvc.getCHR()));
737 
738 	throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate CVCA certificate with domain parameter");
739 }
740 
741 
742 
743 /**
744  * Returns the default domain parameter for a given PKI
745  * 
746  * @param {String} path the PKI path (e.g. "/UTCVCA1/UTDVCA1/UTTERM"). Only the first path element is relevant
747  * @return the domain parameter
748  * @type Key
749  */
750 CVCertificateStore.prototype.getDefaultDomainParameter = function(path) {
751 	var pe = path.substr(1).split("/");
752 	chr = this.getCurrentCHR("/" + pe[0]);
753 	if (chr == null) {
754 		throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate current CVCA certificate");
755 	}
756 	return this.getDomainParameter(chr);
757 }
758 
759 
760 
761 /**
762  * Returns the default algorithm identifier OID from the most recent link certificate
763  * 
764  * @param {String} path the PKI path (e.g. "/UTCVCA1/UTDVCA1/UTTERM"). Only the first path element is relevant
765  * @return the algorithm identifier
766  * @type ByteString
767  */
768 CVCertificateStore.prototype.getDefaultPublicKeyOID = function(path) {
769 	var pe = path.substr(1).split("/");
770 	chr = this.getCurrentCHR("/" + pe[0]);
771 	if (chr == null) {
772 		throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate current CVCA certificate");
773 	}
774 	var cvc = this.getCertificate("/" + pe[0], chr);
775 	if (cvc == null) {
776 		throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "Could not locate current CVCA certificate");
777 	}
778 	
779 	return cvc.getPublicKeyOID();
780 }
781 
782 
783 
784 /**
785  * Return the current CHR for which a valid certificate exists
786  *
787  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1")
788  * @returns the current CHR for which a certificate exists or null if none exists
789  * @type PublicKeyReference
790  */
791 CVCertificateStore.prototype.getCurrentCHR = function(path) {
792 	default xml namespace = "";
793 	var cfg = this.loadConfig(path);
794 	if (cfg == null) {
795 		return null;
796 	}
797 
798 	if (cfg.sequence.currentCHR.toString()) {
799 		return new PublicKeyReference(cfg.sequence.currentCHR.toString());
800 	}
801 	
802 	return null;
803 }
804 
805 
806 
807 /**
808  * Return the next CHR
809  *
810  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1")
811  * @param {String} countryseq the 2 digit country code to include in the sequence number (optional)
812  * @returns the next CHR based on the sequence counter maintained in the configuration file
813  * @type PublicKeyReference
814  */
815 CVCertificateStore.prototype.getNextCHR = function(path, countryseq) {
816 	default xml namespace = "";
817 	var cfg = this.loadConfig(path);
818 	if (cfg == null) {
819 		cfg = this.getDefaultConfig();
820 	}
821 	var seq = parseInt(cfg.sequence.current);
822 	seq += 1;
823 	cfg.sequence.current = seq;
824 	this.saveConfig(path, cfg);
825 
826 	return this.getCHRForSequenceNumber(path, seq, countryseq);
827 }
828 
829 
830 
831 /**
832  * Encode a three character alpha-numeric sequence number
833  *
834  * <p>This function encodes values in the range 0 to 999 as numeric string with leading zeros.</p>
835  * <p>Value in the range 1000 to 34695 (999 + 26 * 36 * 36) are encoded as alphanumeric string.</p>
836  * <p>Value beyond 34696 are truncated</p>
837  *
838  * @param {Number} value integer sequence value
839  * @type String
840  * @return the 3 character string
841  */
842 CVCertificateStore.encodeBase36 = function(value) {
843 	value = value % (1000 + 26 * 36 * 36);
844 	var seq;
845 	if (value < 1000) {
846 		seq = "" + value;
847 	} else {
848 		value += 11960;			10 * 36 * 36 - 1000
849 		seq = "";
850 		while(value > 0) {
851 			var c = value % 36;
852 			if (c >= 10) {
853 				c += 55;
854 			} else {
855 				c += 48;
856 			}
857 			seq = String.fromCharCode(c) + seq;
858 			value = Math.floor(value / 36);
859 		}
860 	}
861 	seq = "000".substr(0, 3 - seq.length) + seq;
862 	return seq;
863 }
864 
865 
866 
867 /**
868  * Create a CHR for the given path and sequence number
869  *
870  * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1")
871  * @param {Number} the sequence number to be translated
872  * @param {String} countryseq the 2 digit country code to include in the sequence number (optional)
873  * @return the CHR
874  * @type PublicKeyReference
875  */
876 CVCertificateStore.prototype.getCHRForSequenceNumber = function(path, sequence, countryseq) {
877 	var pe = path.substr(1).split("/");
878 	var l = pe[pe.length - 1];
879 
880 	var str;
881 	if (countryseq) {
882 		str = countryseq + CVCertificateStore.encodeBase36(sequence);
883 	} else {
884 		str = "" + sequence;
885 		str = "0000".substr(4 - (5 - str.length)).concat(str);
886 	}
887 	return new PublicKeyReference(l + str);
888 }
889 
890 
891 
892 /**
893  * Insert certificates into certificate store
894  *
895  * <p>The import into the internal data structure is done in three steps:</p>
896  * <ol>
897  *  <li>If allowed, all self-signed certificates are imported</li>
898  *  <li>All certificates issued by root CAs are imported</li>
899  *  <li>All other certificates issued by subordinate CAs are imported</li>
900  * </ol>
901  * <p>Certificates at the terminal level can only be imported, if the issuing
902  *    DVCA certificate is contained in the list. Even if a DVCA certificate
903  *    is already stored, the import of such a certificate will be skipped if the
904  *    DVCA certificate is not part of the imported list.</p>
905  * <p>Before a certificate is imported, the signature is verified.</p>
906  *
907  * @param {Crypto} crypto the crypto provider to be used for certificate verification
908  * @param {CVC[]} certlist the unordered list of certificates
909  * @param {Boolean} insertSelfSigned true, if the import of root certificates is allowed
910  * @returns the (ideally empty) list of unprocessed certificates. This does not contains certificates
911  *          that fail signature verification.
912  * @type CVC[]
913  */
914 CVCertificateStore.prototype.insertCertificates = function(crypto, certlist, insertSelfSigned) {
915 
916 	var chrmap = [];
917 	
918 	var unprocessed = [];
919 	for (var i = 0; i < certlist.length; i++) {
920 		var cvc = certlist[i];
921 		var chr = cvc.getCHR().toString();
922 		
923 		if (chr == cvc.getCAR().toString()) { // Self signed
924 			var result = cvc.verifyWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID());
925 
926 			if (result) {
927 				var path = "/" + cvc.getCHR().getHolder();
928 				if (insertSelfSigned) {		// Store self-signed certificates
929 					this.storeCertificate(path, cvc, true);
930 				}
931 			} else {
932 				GPSystem.trace("Self-signed certificate failed signature verification. " + cvc);
933 			}
934 		} else {
935 			unprocessed.push(cvc);
936 			chrmap[chr] = cvc;
937 		}
938 	}
939 	certlist = unprocessed;
940 	
941 	var unprocessed = [];		// Collect unprocessed certificates
942 	var capath = [];			// Map of CA names to CA paths
943 	for (var i = 0; i < certlist.length; i++) {	// Process all certificates issued by root
944 		var cvc = certlist[i];
945 		var car = cvc.getCAR();
946 		var cacert = this.getCertificate("/" + car.getHolder(), car);
947 		if (cacert != null) {	// Issued by a root CA
948 			var dp = this.getDomainParameter("/" + car.getHolder(), car);
949 			var result = cvc.verifyWith(crypto, cacert.getPublicKey(dp), cacert.getPublicKeyOID());
950 			if (result) {
951 				var chr = cvc.getCHR();
952 				var holder = chr.getHolder();
953 				
954 				if (holder == car.getHolder()) {	// Link certificate
955 					this.storeCertificate("/" + holder, cvc, true);
956 				} else {							// Subordinate certificate
957 					var path = "/" + car.getHolder() + "/" + holder;
958 					this.storeCertificate(path, cvc, true);
959 					capath[holder] = path;			// Store in list of processed DVCA
960 				}
961 			} else {
962 				GPSystem.trace("Certificate " + cvc + " failed signature verification with " + cacert);
963 			}
964 		} else {
965 			unprocessed.push(cvc);
966 		}
967 	}
968 	certlist = unprocessed;
969 	
970 	var unprocessed = [];		// Collect unprocessed certificates
971 	for (var i = 0; i < certlist.length; i++) {		// Process remaining certificates
972 		var cvc = certlist[i];
973 		var car = cvc.getCAR();
974 		
975 		var path = capath[car.getHolder()];			// Try to locate DVCA processed in previous step
976 		if (path) {
977 			var cacert = this.getCertificate(path, car);
978 			var cacertcar = cacert.getCAR();
979 			if (cacert != null) {
980 				// Determine root certificate to obtain domain parameter
981 				var dp = this.getDomainParameter(path, cacertcar);
982 				var result = cvc.verifyWith(crypto, cacert.getPublicKey(dp), cacert.getPublicKeyOID());
983 				if (result) {
984 					var chr = cvc.getCHR();
985 					var holder = chr.getHolder();
986 					
987 					this.storeCertificate(path + "/" + holder, cvc, true);
988 				} else {
989 					GPSystem.trace("Certificate " + cvc + " failed signature verification with " + cacvc);
990 				}
991 			} else {
992 				GPSystem.trace("Could not find certificate " + car.toString());
993 				unprocessed.push(cvc);
994 			}
995 		} else {
996 			GPSystem.trace("Could not locate CA " + car.toString());
997 			unprocessed.push(cvc);
998 		}
999 	}
1000 	return unprocessed;
1001 }
1002 
1003 
1004 
1005 /**
1006  * Insert a single certificates into the certificate store
1007  *
1008  * <p>Before a certificate is imported, the signature is verified.</p>
1009  * <p>If the certificate is a terminal certificate, then the first element of the path given
1010  *    in cvcahint is used to determine the correct CVCA.</p>
1011  *
1012  * @param {Crypto} crypto the crypto provider to be used for certificate verification
1013  * @param {CVC} cvc the certificate
1014  * @param {String} cvcahint the PKI path (e.g. "/UTCVCA1/UTDVCA1/UTTERM"). Only the first path element is relevant
1015  * @returns true, if the certificate was inserted
1016  * @type boolean
1017  */
1018 CVCertificateStore.prototype.insertCertificate = function(crypto, cvc, cvcahint) {
1019 
1020 	var car = cvc.getCAR();
1021 	var path = "/" + car.getHolder();
1022 	var cacert = this.getCertificate(path, car);
1023 	if (cacert == null) {
1024 		var path = "/" + CVCertificateStore.nthElementOf(cvcahint, 0) + "/" + car.getHolder();
1025 //		print("Using hint " + path);
1026 		var cacert = this.getCertificate(path, car);
1027 		if (cacert == null) {
1028 			return false;
1029 		}
1030 	}
1031 	
1032 	if (CVC.isECDSA(cacert.getPublicKeyOID())) {
1033 		var dp = this.getDomainParameter(path, car);
1034 	} else {
1035 		var dp = null;
1036 	}
1037 	var result = cvc.verifyWith(crypto, cacert.getPublicKey(dp), cacert.getPublicKeyOID());
1038 	if (!result) {
1039 		GPSystem.trace("Certificate " + cvc + " failed signature verification with " + cacert);
1040 		return false;
1041 	}
1042 
1043 	var chr = cvc.getCHR();
1044 	var holder = chr.getHolder();
1045 
1046 	if (holder == car.getHolder()) {	// Link certificate
1047 		this.storeCertificate("/" + holder, cvc, true);
1048 	} else {							// Subordinate certificate
1049 		this.storeCertificate(path + "/" + holder, cvc, true);
1050 	}
1051 
1052 	return true;
1053 }
1054 
1055 
1056 
1057 /**
1058  * Insert certificates into certificate store
1059  *
1060  * <p>The import into the internal data structure is done in three steps:</p>
1061  * <ol>
1062  *  <li>If allowed, all self-signed certificates are imported</li>
1063  *  <li>All possible certificate chains are build</li>
1064  *  <li>Certificate chains are processed starting with the topmost certificate in the hierachie</li>
1065  * </ol>
1066  * <p>Certificates at the terminal level can only be imported, if the issuing
1067  *    DVCA certificate is contained in the list or a hint for the relevant CVCA is
1068  *    given in the first element of the path contained in parameter cvcahint.</p>
1069  * <p>Before a certificate is imported, the signature is verified.</p>
1070  *
1071  * @param {Crypto} crypto the crypto provider to be used for certificate verification
1072  * @param {CVC[]} certlist the unordered list of certificates
1073  * @param {Boolean} insertSelfSigned true, if the import of root certificates is allowed
1074  * @param {String} cvcahint the PKI path (e.g. "/UTCVCA1/UTDVCA1/UTTERM"). Only the first path element is relevant
1075  * @returns the (ideally empty) list of unprocessed certificates. This does not contains certificates
1076  *          that fail signature verification.
1077  * @type CVC[]
1078  */
1079 CVCertificateStore.prototype.insertCertificates2 = function(crypto, certlist, insertSelfSigned, cvcahint) {
1080 
1081 	var chrmap = [];
1082 	
1083 	// Iterate certificate list and store self-signed certificates, if allowed
1084 	// Generate a map of certificate holder references
1085 	var unprocessed = [];
1086 	for (var i = 0; i < certlist.length; i++) {
1087 		var cvc = certlist[i];
1088 		var chr = cvc.getCHR().toString();
1089 		
1090 		if (chr == cvc.getCAR().toString()) { // Self signed
1091 			var result = cvc.verifyWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID());
1092 
1093 			if (result) {
1094 				var path = "/" + cvc.getCHR().getHolder();
1095 				if (insertSelfSigned) {		// Store self-signed certificates
1096 					this.storeCertificate(path, cvc, true);
1097 				}
1098 			} else {
1099 				GPSystem.trace("Self-signed certificate failed signature verification. " + cvc);
1100 			}
1101 		} else {
1102 			var state = { cvc: cvc, end: true, stored: false };
1103 			unprocessed.push(state);
1104 			if (typeof(chrmap[chr]) == "undefined") {
1105 				chrmap[chr] = state;
1106 			} else {
1107 				// Duplicate CHRs for terminals are allowed
1108 				chrmap[cvc.getCAR().toString() + "/" + chr] = state;
1109 			}
1110 		}
1111 	}
1112 	
1113 	// Mark certificates that are surely CAs, because an issued certificate is contained in the list
1114 	certlist = unprocessed;
1115 	for (var i = 0; i < certlist.length; i++) {
1116 		var cvc = certlist[i].cvc;
1117 		var state = chrmap[cvc.getCAR().toString()];
1118 		if (typeof(state) != "undefined") {
1119 //			print("Mark as CA: " + state.cvc);
1120 			state.end = false;
1121 		}
1122 	}
1123 	
1124 	var unprocessed = [];
1125 	for (var i = 0; i < certlist.length; i++) {
1126 		var state = certlist[i];
1127 		if (state.end) {		// Find all certificates which are at the end of the chain
1128 			var list = [];
1129 			var lastpathelement = state.cvc.getCHR().getHolder();
1130 			var path = "/" + lastpathelement;
1131 			var singlecert = true;
1132 			while(true)	{		// Build a certificate chain and the path for the last certificate
1133 				var pathelement = state.cvc.getCAR().getHolder();
1134 				if (pathelement != lastpathelement) {		// CVCA Link Certificates don't add to the path
1135 					path = "/" + pathelement + path;
1136 				}
1137 				lastpathelement = pathelement;
1138 
1139 				if (!state.stored) {			// If not already stored, add to the list
1140 					list.push(state);
1141 					state.stored = true;
1142 				}
1143 				state = chrmap[state.cvc.getCAR().toString()];
1144 				if (typeof(state) == "undefined") {
1145 					break;
1146 				}
1147 				singlecert = false;
1148 			}
1149 			if (singlecert && cvcahint) {
1150 //				print("Single certificate might be a terminal certificate, using cvca hint");
1151 				path = cvcahint;
1152 			} else {
1153 //				print(path);
1154 			}
1155 			for (var j = list.length - 1; j >= 0; j--) {	// Process chain in reverse order
1156 				var cvc = list[j].cvc;
1157 				if (!this.insertCertificate(crypto, cvc, path)) {
1158 					unprocessed.push(cvc);
1159 				}
1160 			}
1161 		}
1162 	}
1163 
1164 	return unprocessed;
1165 }
1166 
1167 
1168 
1169 /**
1170  * Load configuration
1171  *
1172  * @param {String} path the relative path of the PKI element (e.g. "UTCVCA1/UTDVCA1")
1173  * @return the configuration object or null if none defined
1174  * @type XML
1175  */
1176 CVCertificateStore.prototype.loadConfig = function(path) {
1177 	var fn = this.mapPath(path + "/config.xml");
1178 	var cfgxml = null;
1179 	
1180 	try	{
1181 		var cfgxml = CVCertificateStore.loadXMLFile(fn);
1182 	}
1183 	catch(e) {
1184 //		GPSystem.trace(e);
1185 	}
1186 	return cfgxml;
1187 }
1188 
1189 
1190 
1191 /**
1192  * Save configuration
1193  *
1194  * <p>This method will create the necessary path and save the configuration to config.xml</p>
1195  
1196  * @param {String} path the relative path of the PKI element (e.g. "UTCVCA1/UTDVCA1")
1197  * @param {XML} cfg the configuration object
1198  */
1199 CVCertificateStore.prototype.saveConfig = function(path, cfg) {
1200 
1201 	if (arguments.length != 2) {
1202 		throw new GPError("CVCertificateStore", GPError.INVALID_ARGUMENTS, 0, "path and cfg argument required");
1203 	}
1204 	
1205 	var fn = this.mapPath(path);
1206 	var f = new java.io.File(fn);
1207 	if (!f.exists()) {
1208 		f.mkdirs();
1209 	}
1210 	
1211 	var fn = this.mapPath(path + "/config.xml");
1212 	CVCertificateStore.saveXMLFile(fn, cfg);
1213 }
1214 
1215 
1216 
1217 /**
1218  * Create a default configuration
1219  *
1220  * @returns a suitable default configuration object
1221  * @type XML
1222  */
1223 CVCertificateStore.prototype.getDefaultConfig = function() {
1224 	default xml namespace = "";
1225 	var defaultCfg = 
1226 		<CAConfig>
1227 			<sequence>
1228 				<current>0</current>
1229 			</sequence>
1230 		<