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