1 /**
  2  *  ---------
  3  * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
  4  * |#       #|  
  5  * |#       #|  Copyright (c) 1999-2010 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 Simple CVC-CA
 25  */
 26 
 27 
 28 if (typeof(__ScriptingServer) == "undefined") {
 29 	load("cvcertstore.js");
 30 	load("EAC2CVRequestGenerator.js");
 31 	load("EAC2CVCertificateGenerator.js");
 32 }
 33 
 34 
 35 
 36 /**
 37  * Creates a new CVC-CA instance
 38  *
 39  * @class Class supporting a certification authority that can issue CVC certificates
 40  * for the EAC protocol.
 41  *
 42  * @constructor
 43  * @param {Crypto} crypto the crypto provider to use
 44  * @param {CVCertificateStore} certstore the certificate store to use
 45  * @param {String} path the path of holderIDs (eg. "/UTCVCA/UTDVCA/UTTERM")
 46  */
 47 function CVCCA(crypto, certstore, holderId, parentId, path) {
 48 	this.crypto = crypto;
 49 	this.certstore = certstore;
 50 	
 51 	if (typeof(path) == "undefined") {	// ToDo: Remove after migration
 52 		this.holderId = holderId;
 53 		this.parentId = parentId;
 54 	
 55 		if (this.isRootCA()) {		// CVCA
 56 			this.path = "/" + holderId;
 57 		} else {					// DVCA
 58 			this.path = "/" + parentId + "/" + holderId;
 59 		}
 60 	} else {
 61 		this.path = path;
 62 		var pe = path.substr(1).split("/");
 63 		var l = pe.length;
 64 		assert(l >= 1);
 65 		this.holderId = pe[l - 1];
 66 		if (l > 1) {
 67 			this.parentId = pe[l - 2];
 68 		} else {
 69 			this.parentId = this.holderId;
 70 		}
 71 	}
 72 	this.keyspec = new Key();
 73 	this.keyspec.setComponent(Key.ECC_CURVE_OID, new ByteString("brainpoolP256r1", OID));
 74 	this.taAlgorithmIdentifier = new ByteString("id-TA-ECDSA-SHA-256", OID);
 75 	this.countryseq = null;
 76 }
 77 
 78 
 79 
 80 CVCCA.prototype.getCrypto = function() {
 81 	if (this.crypto) {
 82 		return this.crypto;
 83 	}
 84 	return this.certstore.getCrypto()
 85 }
 86 
 87 
 88 
 89 /**
 90  * Returns true if this is a root CA
 91  *
 92  * @returns true if this is a root CA
 93  * @type boolean
 94  */
 95 CVCCA.prototype.isRootCA = function() {
 96 	return this.holderId == this.parentId;
 97 }
 98 
 99 
100 
101 /**
102  * Returns true if this CA is operational.
103  *
104  * @returns true if this CA is operational
105  * @type boolean
106  */
107 CVCCA.prototype.isOperational = function() {
108 	var currentchr = this.certstore.getCurrentCHR(this.path);
109 	if (currentchr == null) {
110 		return false;
111 	}
112 	var cvc = this.certstore.getCertificate(this.path, currentchr);
113 	return !cvc.isExpired();
114 }
115 
116 
117 
118 /**
119  * Sets the key specification for generating requests
120  *
121  * @param {Key} keyparam a key object containing key parameters (e.g. EC Curve)
122  * @param {ByteString} algorithm the terminal authentication algorithm object identifier
123  */
124 CVCCA.prototype.setKeySpec = function(keyparam, algorithm) {
125 	this.keyspec = keyparam;
126 	this.taAlgorithmIdentifier = algorithm;
127 }
128 
129 
130 
131 /**
132  * Set flags that controls the removal of the previous key if the certificate for the new key is imported
133  *
134  * @param {boolean} removePreviousKey true to remove, false to keep
135  */
136 CVCCA.prototype.setRemovePreviousKey = function(removePreviousKey) {
137 	this.removePreviousKey = removePreviousKey;
138 }
139 
140 
141 
142 /**
143  * Set country code to be included in sequence number of public key reference
144  *
145  * @param {String} countryseq the two character country code
146  */
147 CVCCA.prototype.setCountryCodeForSequence = function(countryseq) {
148 	this.countryseq = countryseq;
149 }
150 
151 
152 
153 /**
154  * Generate a certificate request
155  *
156  * @param {PublicKeyReference} car the CA at which this request is addressed
157  * @param {boolean} forceInitial force an initial request, even if a current certificate is available
158  * @param {boolean} signinitial sign with initial key (sequence = 00000)
159  * @return the certificate request
160  * @type CVC
161  */
162 CVCCA.prototype.generateRequest = function(car, forceinitial, signinitial) {
163 	if (this.certstore.sc != undefined) {
164 		return this.generateRequestHSM(car, forceinitial, signinitial);
165 	} else {
166 		return this.generateRequestPKCS8(car, forceinitial, signinitial);
167 	}
168 }
169 
170 
171 
172 /**
173  * Generate a certificate request using a PKCS#8 based private key
174  *
175  * @param {PublicKeyReference} car the CA at which this request is addressed
176  * @param {boolean} forceInitial force an initial request, even if a current certificate is available
177  * @param {boolean} signinitial sign with initial key (sequence = 00000)
178  * @return the certificate request
179  * @type CVC
180  */
181 CVCCA.prototype.generateRequestPKCS8 = function(car, forceinitial, signinitial) {
182 
183 	// Obtain key parameter
184 
185 	if (typeof(this.keyspec.getComponent(Key.ECC_P)) != "undefined") {
186 		var prk = new Key(this.keyspec);
187 		prk.setType(Key.PRIVATE);
188 		var keyalg = Crypto.EC;
189 	} else {
190 		var prk = new Key();
191 		prk.setType(Key.PRIVATE);
192 		var keyalg = Crypto.RSA;
193 	}
194 	var puk = new Key(this.keyspec);
195 	puk.setType(Key.PUBLIC);
196 
197 	// Determine CHR
198 	var currentchr = this.certstore.getCurrentCHR(this.path);
199 	var nextchr = this.certstore.getNextCHR(this.path, this.countryseq);
200 	
201 	// Generate key pair
202 	this.getCrypto().generateKeyPair(keyalg, puk, prk);
203 
204 	// Save private key
205 	this.certstore.storePrivateKey(this.path, nextchr, prk);
206 	
207 	// Generate certificate request
208 	var reqGenerator = new EAC2CVRequestGenerator(this.getCrypto());
209 
210 	// Set CPI
211 	reqGenerator.setProfileIdentifier(0x00);
212 
213 	// Set public key for request
214 	reqGenerator.setPublicKey(puk);
215 
216 	// Set oid of algorithm
217 	reqGenerator.setTAAlgorithmIdentifier(this.taAlgorithmIdentifier);
218 
219 	// Set CHR for the request
220 	reqGenerator.setCHR(nextchr);
221 
222 	if ((typeof(car) != "undefined") && (car != null)) {
223 		reqGenerator.setCAR(car);
224 	}
225 	
226 	if ((currentchr != null) && !forceinitial) {
227 		var previousprk = this.certstore.getPrivateKey(this.path, currentchr);
228 		var previouscvc = this.certstore.getCertificate(this.path, currentchr);
229 		var req = reqGenerator.generateAuthenticatedCVRequest(prk, previousprk, currentchr, previouscvc.getPublicKeyOID());
230 	} else {
231 		// Generate the request
232 		if (signinitial) {
233 			var initialchr = new PublicKeyReference(nextchr.getHolder() + "00000");
234 			var firstprk = this.certstore.getPrivateKey(this.path, initialchr);
235 			var req = reqGenerator.generateAuthenticatedCVRequest(prk, firstprk, initialchr);
236 		} else {
237 			var req = reqGenerator.generateCVRequest(prk);
238 		}
239 	}
240 	
241 	req = new CVC(req);
242 	
243 	this.certstore.storeRequest(this.path, req);
244 	
245 	return req;
246 }
247 
248 
249 
250 /**
251  * Generate a certificate request using a SmartCard-HSM based private key
252  *
253  * @param {PublicKeyReference} car the CA at which this request is addressed
254  * @param {boolean} forceInitial force an initial request, even if a current certificate is available
255  * @param {boolean} signinitial sign with initial key (sequence = 00000)
256  * @return the certificate request
257  * @type CVC
258  */
259 CVCCA.prototype.generateRequestHSM = function(car, forceinitial, signinitial) {
260 
261 	var req = this.certstore.generateRequest(this.path, car, forceinitial, signinitial, this.keyspec, this.taAlgorithmIdentifier);
262 	this.certstore.storeRequest(this.path, req);
263 	
264 	return req;
265 }
266 
267 
268 
269 /**
270  * Counter-sign a request
271  *
272  * @param {CVC} req the initial request
273  * @return the certificate request
274  * @type CVC
275  */
276 CVCCA.prototype.counterSignRequest = function(request) {
277 	assert(!request.isAuthenticatedRequest());
278 	
279 	var car = this.certstore.getCurrentCHR(this.path);
280 	assert(car != null);
281 	
282 	var cacvc = this.certstore.getCertificate(this.path, car);
283 	assert(cacvc != null);
284 	
285 	var signingTAAlgorithmIdentifier = cacvc.getPublicKeyOID();
286 	var prk = this.certstore.getPrivateKey(this.path, car);
287 
288 	var req = EAC2CVRequestGenerator.signAuthenticatedCVRequest(this.getCrypto(), request.getASN1(), prk, car, signingTAAlgorithmIdentifier);
289 	return new CVC(req);
290 }
291 
292 
293 
294 /**
295  * Generate an initial certificate request
296  *
297  * @param {PublicKeyReference} car the CA at which this request is addressed
298  * @return the certificate request
299  * @type CVC
300  */
301 CVCCA.prototype.generateInitialRequest = function(car) {
302 	return this.generateRequest(car, true, false);
303 }
304 
305 
306 
307 /**
308  * Generate a signed initial certificate request
309  *
310  * @param {PublicKeyReference} car the CA at which this request is addressed
311  * @return the certificate request
312  * @type CVC
313  */
314 CVCCA.prototype.generateSignedInitialRequest = function(car) {
315 	return this.generateRequest(car, true, true);
316 }
317 
318 
319 
320 /**
321  * Generate certificate for certificate request
322  *
323  * <p>Certificate contents is defined through the policy object:</p>
324  * <pre>
325  *  	var policy = { certificateValidityDays: 2,
326  * 				   chatRoleOID: new ByteString("id-IS", OID),
327  * 				   chatRights: new ByteString("E3", HEX),
328  * 				   includeDomainParameter: true,
329  * 				   extensions: []
330  * 				 };
331  * </pre>
332  *
333  * @param {CVC} req the certificate request
334  * @param {Object} policy the object with policy settings
335  * @returns the certificate
336  * @type CVC
337  */
338 CVCCA.prototype.generateCertificate = function(req, policy) {
339 	var car = this.certstore.getCurrentCHR(this.path);
340 	var maxExpDate = null;
341 	var signingTAAlgorithmIdentifier = req.getPublicKeyOID();
342 	
343 	if (car == null) {				// No CA certificate found
344 		if (this.isRootCA()) {
345 			car = req.getCHR();		// Generate a self-signed root certificate
346 		} else {
347 			throw new GPError("CVCCA", GPError.INVALID_DATA, 0, "No current certificate found");
348 		}
349 	} else {
350 		var cacvc = this.certstore.getCertificate(this.path, car);
351 		var signingTAAlgorithmIdentifier = cacvc.getPublicKeyOID();
352 		if (policy.shellModelForExpirationDate) {
353 			maxExpDate = cacvc.getCXD();
354 		}
355 	}
356 
357 	var generator = new EAC2CVCertificateGenerator(this.getCrypto());
358 	generator.setCAR(car);
359 	generator.setCHR(req.getCHR());
360 	var effDate = new Date();
361 	if (policy.ced) {
362 		effDate = policy.ced;
363 	}
364 	effDate.setHours(12, 0, 0, 0);
365 	var expDate = new Date((policy.certificateValidityDays - 1) * (1000 * 60 * 60 * 24) + effDate.getTime());
366 	expDate.setHours(12, 0, 0, 0);
367 
368 	if (maxExpDate != null) {
369 		if (effDate.getTime() > maxExpDate.getTime()) {
370 			throw new GPError("CVCCA", GPError.INVALID_DATA, 0, "CA certificate is expired");
371 		}
372 		// Expiration date of issued certificate must not exceed expiration date of issuing CA
373 		if (expDate.getTime() > maxExpDate.getTime()) {
374 			expDate = maxExpDate;
375 		}
376 	}
377 	
378 	generator.setEffectiveDate(effDate);
379 	generator.setExpiryDate(expDate);
380 	generator.setChatOID(policy.chatRoleOID);
381 	generator.setChatAuthorizationLevel(policy.chatRights);
382 	generator.setPublicKey(req.getPublicKey());
383 	generator.setProfileIdentifier(0x00);
384 	generator.setTAAlgorithmIdentifier(req.getPublicKeyOID());
385 	generator.setIncludeDomainParameters(policy.includeDomainParameter);
386 	generator.setExtensions(policy.extensions);
387 	var prk = this.certstore.getPrivateKey(this.path, car);
388 	var cvc = generator.generateCVCertificate(prk, signingTAAlgorithmIdentifier);
389 	
390 	return cvc;
391 }
392 
393 
394 
395 /**
396  * Store issued certificate
397  *
398  * @param {CVC} cert a newly issued certificate
399  */
400 CVCCA.prototype.storeCertificate = function(cert) {
401 	var chrHolder = cert.getCHR().getHolder();
402 	this.certstore.storeCertificate(this.path + "/" + chrHolder, cert, false);
403 }
404 
405 
406 
407 /**
408  * Import a certificate into the certificate store and make it the current certificate
409  *
410  * @param {CVC} cert the certificate
411  */
412 CVCCA.prototype.importCertificate = function(cert) {
413 	var chr = cert.getCHR();
414 	var prk = this.certstore.getPrivateKey(this.path, chr);
415 	if (prk == null) {
416 		throw new GPError("CVCCA", GPError.INVALID_DATA, 0, "Invalid certificate, not matching private key");
417 	}
418 	var c = this.certstore.getCertificate(this.path, cert.getCHR());
419 	if (c != null) {
420 		GPSystem.trace("### Certificate " + c + " already stored");
421 	}
422 	if (this.isRootCA() && !this.isOperational()) {
423 		this.certstore.storeCertificate(this.path, cert, (c == null));
424 	} else {
425 		if (!this.certstore.insertCertificate(this.getCrypto(), cert, this.path)) {
426 			throw new GPError("CVCCA", GPError.CRYPTO_FAILED, 0, "Could not validate certificate");
427 		}
428 	}
429 }
430 
431 
432 
433 /**
434  * Import a list of certificates into the certificate store
435  *
436  * @param {CVC[]} certs the list of certificates
437  */
438 CVCCA.prototype.importCertificates = function(certs) {
439 	var list = this.certstore.insertCertificates2(this.getCrypto(), certs, true, this.path);
440 	
441 	// Process my own certificates. Should be one at maximum, matching a request
442 	for (var i = 0; i < certs.length; i++) {
443 		var cert = certs[i];
444 		var chr = cert.getCHR();
445 		
446 		if (this.holderId == chr.getHolder()) {
447 			var prk = this.certstore.getPrivateKey(this.path, chr);
448 			if (prk == null) {
449 				GPSystem.trace("We do not have a key for " + cert.toString() + " - ignored...");
450 			} else {
451 
452 				if (this.removePreviousKey) {
453 					var req = this.certstore.getRequest(this.path, chr);
454 					var previous = req.getOuterCAR();
455 					if ((previous != null) && (previous.getSequenceNo() != "00000")) {
456 						this.certstore.deleteCertificate(this.path, previous, false);
457 						this.certstore.deleteRequest(this.path, previous);
458 						this.certstore.deletePrivateKey(this.path, previous);
459 					}
460 				}
461 			}
462 		}
463 	}
464 	
465 	return list;
466 }
467 
468 
469 
470 /**
471  * Returns a list of relevant certificates.
472  *
473  * <p>If the CA is the root CA, then all self-signed and link certificates are returned.</p>
474  * <p>If the CA is a DVCA, then all certificates of the associated root and the current
475  *    DVCA certificate is returned.</p>
476  *
477  * @param {PublicKeyReference} fromCAR the optional starting point for the list if not a root CA
478  */
479 CVCCA.prototype.getCertificateList = function(fromCAR) {
480 	var list;
481 	
482 	if (this.isRootCA()) {
483 		list = this.certstore.listCertificates(this.path);
484 	} else {
485 		var path = this.path;
486 		
487 		while(true) {
488 			var chr = this.certstore.getCurrentCHR(path);
489 			if (chr == null) {
490 				var ofs = path.lastIndexOf("/");
491 				if (ofs == 0) {
492 					list = [];
493 				} else {
494 					path = path.substr(0, ofs);
495 					continue;
496 				}
497 			} else {
498 				list = this.certstore.getCertificateChain(path, chr, fromCAR);
499 			}
500 			break;
501 		}
502 	}
503 	
504 	return list;
505 }
506 
507 
508 
509 /**
510  * Return certificate issued by this CA
511  *
512  * @param {PublicKeyReference} chr the certificate holder reference
513  * @returns the certificate or null if not found
514  * @type CVC
515  */
516 CVCCA.prototype.getIssuedCertificate = function(chr) {
517 	var path = this.path + "/" + chr.getHolder();
518 	
519 	var cvc = this.certstore.getCertificate(path, chr);
520 	if (cvc == null) {
521 		GPSystem.trace("No certificate found for " + chr);
522 		return null;
523 	}
524 	
525 	return cvc;
526 }
527 
528 
529 
530 /**
531  * Return authentic public key with domain parameter for a given CHR subordinate to the CA
532  *
533  * @param {PublicKeyReference} chr the certificate holder reference
534  * @returns the public key or null
535  * @type Key
536  */
537 CVCCA.prototype.getAuthenticPublicKey = function(chr) {
538 	var cvc = this.getIssuedCertificate(chr);
539 	
540 	if (cvc == null) {
541 		return null;
542 	}
543 	
544 	if (this.isRootCA()) {
545 		var dp = this.certstore.getDomainParameter(cvc.getCAR());
546 	} else {
547 		var dvcacvc = this.certstore.getCertificate(this.path, cvc.getCAR());
548 		if (dvcacvc == null) {
549 			GPSystem.trace("No certificate found for " + cvc.getCAR());
550 			return null;
551 		}
552 		var dp = this.certstore.getDomainParameter(dvcacvc.getCAR());
553 	}
554 	
555 	return(cvc.getPublicKey(dp));
556 }
557 
558 
559 
560 CVCCA.testPath = GPSystem.mapFilename("testca", GPSystem.CWD);
561 
562 CVCCA.test = function() {
563 	
564 	var crypto = new Crypto();
565 	
566 	var ss = new CVCertificateStore(CVCCA.testPath + "/cvca");
567 	var cvca = new CVCCA(crypto, ss, null, null, "/UTCVCA");
568 	
569 	// Create a new request
570 	var req = cvca.generateRequest(null, false);
571 	print("Request: " + req);
572 	print(req.getASN1());
573 	
574 	assert(req.verifyWith(crypto, req.getPublicKey()));
575 	
576 	// Create self-signed or link certificate based on request
577 	var policy = { certificateValidityDays: 2,
578 				   chatRoleOID: new ByteString("id-IS", OID),
579 				   chatRights: new ByteString("E3", HEX),
580 				   includeDomainParameter: true,
581 				   extensions: []
582 				 };
583 	var cert = cvca.generateCertificate(req, policy);
584 	print("Certificate: " + cert);
585 	print(cert.getASN1());
586 
587 	// Import certificate into store, making it the most current certificate
588 	cvca.storeCertificate(cert);
589 	
590 	// Generate additional self-signed root certificate
591 	// This must be done after the link certificate has been imported
592 	var policy = { certificateValidityDays: 2,
593 				   chatRoleOID: new ByteString("id-IS", OID),
594 				   chatRights: new ByteString("E3", HEX),
595 				   includeDomainParameter: true,
596 				   extensions: []
597 				 };
598 	var cert = cvca.generateCertificate(req, policy);
599 	print("Certificate: " + cert);
600 	print(cert.getASN1());
601 
602 	// Import certificate into store, making it the most current certificate
603 	cvca.storeCertificate(cert);
604 
605 	var ss = new CVCertificateStore(CVCCA.testPath + "/dvca");
606 	var dvca = new CVCCA(crypto, ss, null, null, "/UTCVCA/UTDVCA");
607 
608 	var certlist = cvca.getCertificateList();
609 	var list = dvca.importCertificates(certlist);
610 
611 	if (list.length > 0) {
612 		print("Warning: Could not import the following certificates");
613 		for (var i = 0; i < list.length; i++) {
614 			print(list[i]);
615 		}
616 	}
617 
618 	// Create a new request
619 	var req = dvca.generateRequest(null, false);
620 	print("Request: " + req);
621 	print(req.getASN1());
622 	
623 	// Sign this request with root CA
624 	// This must be done after the link certificate has been imported
625 	var policy = { certificateValidityDays: 2,
626 				   chatRoleOID: new ByteString("id-IS", OID),
627 				   chatRights: new ByteString("A3", HEX),
628 				   includeDomainParameter: false,
629 				   extensions: []
630 				 };
631 	var cert = cvca.generateCertificate(req, policy);
632 	print("Certificate: " + cert);
633 	print(cert.getASN1());
634 
635 	cvca.storeCertificate(cert);
636 	dvca.importCertificate(cert);
637 
638 
639 	var ss = new CVCertificateStore(CVCCA.testPath + "/term");
640 	var term = new CVCCA(crypto, ss, null, null, "/UTCVCA/UTDVCA/UTTERM");
641 
642 	var certlist = dvca.getCertificateList();
643 	print("Certificate list: ");
644 	print(certlist);
645 	var list = term.importCertificates(certlist);
646 
647 	if (list.length > 0) {
648 		print("Warning: Could not import the following certificates");
649 		for (var i = 0; i < list.length; i++) {
650 			print(list[i]);
651 		}
652 	}
653 
654 	// Create a new request
655 	var req = term.generateRequest(null, false);
656 	print("Request: " + req);
657 	print(req.getASN1());
658 	
659 	// Sign this request with DVCA
660 	// This must be done after the link certificate has been imported
661 	var policy = { certificateValidityDays: 2,
662 				   chatRoleOID: new ByteString("id-IS", OID),
663 				   chatRights: new ByteString("23", HEX),
664 				   includeDomainParameter: false,
665 				   extensions: []
666 				 };
667 	var cert = dvca.generateCertificate(req, policy);
668 	print("Certificate: " + cert);
669 	print(cert.getASN1());
670 
671 	dvca.storeCertificate(cert);
672 	term.importCertificate(cert);
673 }
674 
675 
676 // CVCCA.test();
677