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