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