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 <