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 becomes the current certificate 541 * @param {Boolean} forceUpdate true if certificate should be updated even if one already exists 542 */ 543 CVCertificateStore.prototype.storeCertificate = function(path, cert, makeCurrent, forceUpdate) { 544 GPSystem.log(GPSystem.DEBUG, module.id, "storeCertificate(" + path + ",'" + cert + "'," + makeCurrent + ")"); 545 546 var holderdao = this.daof.getHolderDAO(); 547 548 var holder = holderdao.getHolder(path, this.certtype); 549 550 if (!holder) { 551 GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found, creating new."); 552 holder = holderdao.newHolder(path, this.certtype); 553 } 554 555 var certdao = this.daof.getCertificateDAO(); 556 557 var car = cert.getCAR(); 558 var chr = cert.getCHR(); 559 var dir = Certificate.UP; 560 561 if (car.equals(chr)) { 562 dir = Certificate.SHORT; 563 } 564 565 var certificate = certdao.getCertificateBySerial(holder, chr.toString(), dir); 566 567 if (certificate) { 568 var ecvc = new CVC(certificate.bytes); 569 570 GPSystem.log(GPSystem.INFO, module.id, "storeCertificate() : We already have a certificate for that serial number"); 571 GPSystem.log(GPSystem.INFO, module.id, "Existing: " + ecvc); 572 GPSystem.log(GPSystem.INFO, module.id, "New : " + cert); 573 GPSystem.log(GPSystem.INFO, module.id, "Both are " + (cert.getBytes().equals(certificate.bytes) ? "identical" : "different")); 574 575 if (ecvc.getCAR().equals(ecvc.getCHR()) && (dir == Certificate.UP)) { 576 GPSystem.log(GPSystem.INFO, module.id, "This is an additional link certificate for an existing root certificate"); 577 } else { 578 if (!forceUpdate) { 579 return; 580 } 581 } 582 } 583 584 var template = { 585 keyId: chr.getBytes() 586 }; 587 588 var certificate = certdao.newCertificate(holder, chr.toString(), dir, cert.getBytes(), template); 589 590 if (makeCurrent) { 591 holderdao.updateCurrentCertificate(holder, certificate); 592 } 593 } 594 595 596 597 /** 598 * Return certificate for a given CHR in binary format 599 * 600 * <p>This method returns a self-signed root certificate if the selfsigned 601 * parameter is set. If not set or set to false, then matching link certificate, 602 * if any, is returned rather than the self-signed certificate.</p> 603 * 604 * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM") 605 * @param {PublicKeyReference} chr the public key reference for the certificate 606 * @param {boolean} selfsigned return the self-signed root certificate rather than a link certificate 607 * @returns the certificate or null if not found 608 * @type ByteString 609 */ 610 CVCertificateStore.prototype.getCertificateBinary = function(path, chr, selfsigned) { 611 GPSystem.log(GPSystem.DEBUG, module.id, "getCertificateBinary(" + path + "," + chr + "," + selfsigned + ")"); 612 613 var holderdao = this.daof.getHolderDAO(); 614 615 var holder = holderdao.getHolder(path, this.certtype); 616 617 if (!holder) { 618 GPSystem.log(GPSystem.DEBUG, module.id, "Holder at " + path + " not found"); 619 return null; 620 } 621 622 var certificateDAO = this.daof.getCertificateDAO(); 623 624 var certificate = certificateDAO.getCertificateBySerial(holder, chr.toString(), selfsigned ? Certificate.SHORT : Certificate.UP ); 625 626 if (!certificate) { 627 GPSystem.log(GPSystem.DEBUG, module.id, "Certificate for " + chr.toString() + " not found"); 628 return null; 629 } 630 631 GPSystem.log(GPSystem.DEBUG, module.id, "getCertificateBinary:" + certificate.bytes.bytes(0,10).toString(HEX)); 632 return certificate.bytes; 633 } 634 635 636 637 /** 638 * Return certificate for a given CHR 639 * 640 * <p>This method returns a self-signed root certificate if the selfsigned 641 * parameter is set. If not set or set to false, then matching link certificate, 642 * if any, is returned rather than the self-signed certificate.</p> 643 * 644 * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM") 645 * @param {PublicKeyReference} chr the public key reference for the certificate 646 * @param {boolean} selfsigned return the self-signed root certificate rather than a link certificate 647 * @returns the certificate or null if not found 648 * @type CVC 649 */ 650 CVCertificateStore.prototype.getCertificate = function(path, chr, selfsigned) { 651 var bin = this.getCertificateBinary(path, chr, selfsigned); 652 653 if (bin == null) { 654 return null; 655 } 656 657 var cvc = null; 658 try { 659 cvc = new CVC(bin); 660 } 661 catch (e) { 662 GPSystem.log(GPSystem.ERROR, module.id, e); 663 } 664 return cvc; 665 } 666 667 668 669 /** 670 * Try locating a certificate with the given CHR 671 * 672 * This method tries to find a specific certificate in the store, irrespectively of the holder. 673 * 674 * @param {PublicKeyReference} chr the public key reference for the certificate 675 * @returns the certificate or null if none found or more than one found 676 * @type CVC 677 */ 678 CVCertificateStore.prototype.locateCertificate = function(chr) { 679 GPSystem.log(GPSystem.DEBUG, module.id, "locateCertificate(" + chr + ")"); 680 681 var certificateDAO = this.daof.getCertificateDAO(); 682 683 if (typeof(certificateDAO.getCertificateBySerialAndType) == "undefined") { 684 return null; 685 } 686 687 var certificate = certificateDAO.getCertificateBySerialAndType(chr.toString(), this.certtype); 688 689 if (!certificate) { 690 GPSystem.log(GPSystem.DEBUG, module.id, "Certificate for " + chr.toString() + " not found or not unique"); 691 return null; 692 } 693 694 cvc = new CVC(certificate.bytes); 695 return cvc; 696 } 697 698 699 700 /** 701 * Remove certificate 702 * 703 * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM") 704 * @param {PublicKeyReference} chr the public key reference for this certificate 705 * @param {boolean} selfsigned delete the self-signed root certificate rather than a link certificate 706 * @returns true is deleted 707 * @type boolean 708 */ 709 CVCertificateStore.prototype.deleteCertificate = function(path, chr, selfsigned) { 710 GPSystem.log(GPSystem.DEBUG, module.id, "deleteCertificate(" + path + "," + chr + "," + selfsigned + ")"); 711 712 var holder = this.getHolder(path, false); 713 714 if (!holder) { 715 GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found"); 716 return null; 717 } 718 719 var certificateDAO = this.daof.getCertificateDAO(); 720 721 var certificate = certificateDAO.getCertificateBySerial(holder, chr.toString(), selfsigned ? Certificate.SHORT : Certificate.UP ); 722 723 if (!certificate) { 724 return false; 725 } 726 return certificateDAO.deleteCertificate(certificate); 727 } 728 729 730 731 /** 732 * Return a chain of certificates resembling a path from root to end entity. 733 * 734 * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM") 735 * @param {PublicKeyReference} tochr the public key reference for the certificate at the end of the chain 736 * @param {PublicKeyReference} fromcar the public key reference for the certificate to start with or root if undefined 737 * @returns the list of certificates starting with a self signed root certificate (fromcar undefined) a certificate 738 * issued by fromcar up to an including the certificate referenced by tochr. Return null if fromcar is not found. 739 * @type CVC[] 740 */ 741 CVCertificateStore.prototype.getCertificateChain = function(path, tochr, fromcar) { 742 GPSystem.log(GPSystem.DEBUG, module.id, "getCertificateChain(" + path + "," + tochr + "," + fromcar + ")"); 743 744 var chain = []; 745 var chr = tochr; 746 747 while (true) { 748 var cvc = this.getCertificate(path, chr, false); 749 if (cvc == null) { 750 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate certificate " + chr); 751 } 752 chain.push(cvc); 753 if (typeof(fromcar) == "undefined") { 754 if (cvc.getCAR().equals(cvc.getCHR())) { 755 break; 756 } 757 } else { 758 if (cvc.getCAR().equals(fromcar)) { 759 break; 760 } 761 if (cvc.getCAR().equals(cvc.getCHR())) { 762 return null; // fromcar not found along the chain 763 } 764 } 765 var ofs = path.lastIndexOf("/"); 766 if (ofs > 0) { 767 path = path.substr(0, ofs); 768 } 769 chr = cvc.getCAR(); 770 } 771 772 return chain.reverse(); 773 } 774 775 776 777 /** 778 * List certificates stored for given PKI element sorted by CHR 779 * 780 * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1/UTTERM") 781 * @returns a list of certificates, possibly empty 782 * @type CVC[] 783 */ 784 CVCertificateStore.prototype.listCertificates = function(path) { 785 GPSystem.log(GPSystem.DEBUG, module.id, "listCertificates(" + path + ")"); 786 787 var result = []; 788 789 var holder = this.getHolder(path, true); 790 791 var certificateDAO = this.daof.getCertificateDAO(); 792 793 var certificates = certificateDAO.enumerateCertificates(holder); 794 795 if (!certificates) { 796 GPSystem.log(GPSystem.DEBUG, module.id, "No certificates found"); 797 return result; 798 } 799 800 for (var i = 0; i < certificates.length; i++) { 801 var cvc = new CVC(certificates[i].bytes); 802 result.push(cvc); 803 } 804 805 result.sort(function(a,b) { return a.getCHR().toString() < b.getCHR().toString() ? -1 : (a.getCHR().toString() > b.getCHR().toString() ? 1 : 0) } ); 806 GPSystem.log(GPSystem.DEBUG, module.id, "listCertificates:" + result.length); 807 return result; 808 809 } 810 811 812 813 /** 814 * Returns the domain parameter for a certificate identified by its CHR 815 * 816 * <p>This method traverses the certificate hierachie upwards and follows link certificates 817 * until domain parameter are found.</p> 818 * 819 * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") 820 * @param {PublicKeyReference} chr the CHR of the certificate to start the search with 821 * @return the domain parameter 822 * @type Key 823 */ 824 CVCertificateStore.prototype.getDomainParameter = function(path, chr) { 825 GPSystem.log(GPSystem.DEBUG, module.id, "getDomainParameter(" + path + "," + chr + ")"); 826 827 if (typeof(chr) == "undefined") { // ToDo remove after migration 828 chr = path; 829 var path = "/" + chr.getHolder(); 830 } 831 832 do { 833 var ofs = path.lastIndexOf("/"); 834 if (ofs > 0) { 835 var cvc = this.getCertificate(path, chr); 836 if (cvc == null) { 837 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate certificate " + chr); 838 } 839 chr = cvc.getCAR(); 840 path = path.substr(0, ofs); 841 } 842 } while (ofs > 0); 843 844 do { 845 var cvc = this.getCertificate(path, chr); 846 847 if (cvc == null) { 848 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate certificate " + chr + " in " + path); 849 } 850 851 var p = cvc.getPublicKey(); 852 if (typeof(p.getComponent(Key.ECC_P)) != "undefined") { 853 return p; 854 } 855 chr = cvc.getCAR(); 856 } while (!chr.equals(cvc.getCHR())); 857 858 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate CVCA certificate with domain parameter"); 859 } 860 861 862 863 /** 864 * Returns the default domain parameter for a given PKI 865 * 866 * @param {String} path the PKI path (e.g. "/UTCVCA1/UTDVCA1/UTTERM"). Only the first path element is relevant 867 * @return the domain parameter 868 * @type Key 869 */ 870 CVCertificateStore.prototype.getDefaultDomainParameter = function(path) { 871 GPSystem.log(GPSystem.DEBUG, module.id, "getDefaultDomainParameter(" + path + ")"); 872 873 var pe = path.substr(1).split("/"); 874 chr = this.getCurrentCHR("/" + pe[0]); 875 if (chr == null) { 876 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate current CVCA certificate"); 877 } 878 return this.getDomainParameter(chr); 879 } 880 881 882 883 /** 884 * Returns the default algorithm identifier OID from the most recent link certificate 885 * 886 * @param {String} path the PKI path (e.g. "/UTCVCA1/UTDVCA1/UTTERM"). Only the first path element is relevant 887 * @return the algorithm identifier 888 * @type ByteString 889 */ 890 CVCertificateStore.prototype.getDefaultPublicKeyOID = function(path) { 891 GPSystem.log(GPSystem.DEBUG, module.id, "getDefaultPublicKeyOID(" + path + ")"); 892 893 var pe = path.substr(1).split("/"); 894 chr = this.getCurrentCHR("/" + pe[0]); 895 if (chr == null) { 896 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate current CVCA certificate"); 897 } 898 var cvc = this.getCertificate("/" + pe[0], chr); 899 if (cvc == null) { 900 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate current CVCA certificate"); 901 } 902 903 return cvc.getPublicKeyOID(); 904 } 905 906 907 908 /** 909 * Encode a three character alpha-numeric sequence number 910 * 911 * <p>This function encodes values in the range 0 to 999 as numeric string with leading zeros.</p> 912 * <p>Value in the range 1000 to 34695 (999 + 26 * 36 * 36) are encoded as alphanumeric string.</p> 913 * <p>Value beyond 34696 are truncated</p> 914 * 915 * @param {Number} value integer sequence value 916 * @type String 917 * @return the 3 character string 918 * @private 919 */ 920 CVCertificateStore.encodeBase36 = function(value) { 921 value = value % (1000 + 26 * 36 * 36); 922 var seq; 923 if (value < 1000) { 924 seq = "" + value; 925 } else { 926 value += 11960; 10 * 36 * 36 - 1000 927 seq = ""; 928 while(value > 0) { 929 var c = value % 36; 930 if (c >= 10) { 931 c += 55; 932 } else { 933 c += 48; 934 } 935 seq = String.fromCharCode(c) + seq; 936 value = Math.floor(value / 36); 937 } 938 } 939 seq = "000".substr(0, 3 - seq.length) + seq; 940 return seq; 941 } 942 943 944 945 /** 946 * Create a CHR for the given path and sequence number 947 * 948 * @param {String} path the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") 949 * @param {Number} the sequence number to be translated 950 * @param {String} countryseq the 2 digit country code to include in the sequence number (optional) 951 * @return the CHR 952 * @type PublicKeyReference 953 * @private 954 */ 955 CVCertificateStore.prototype.getCHRForSequenceNumber = function(path, sequence, countryseq) { 956 var pe = path.substr(1).split("/"); 957 var l = pe[pe.length - 1]; 958 959 var str; 960 if (countryseq) { 961 str = countryseq + CVCertificateStore.encodeBase36(sequence); 962 } else { 963 str = "" + sequence; 964 str = "0000".substr(4 - (5 - str.length)).concat(str); 965 } 966 return new PublicKeyReference(l + str); 967 } 968 969 970 971 /** 972 * Determine path for certificate issuer 973 * 974 * For CVCA and DVCA certificates we can determined the path from the CAR. For Terminal 975 * certificates we dont know the full path, as we don't know under which CVCA the DVCA operates 976 * that issued the Terminal certificate. So we use a two-step heuristic which first tries to locate 977 * the DVCA certificate based on the CAR and if that is not unique uses the cvcahint to determine the path 978 * of the issuer 979 */ 980 CVCertificateStore.prototype.getIssuerPathFor = function(cvc, cvcahint) { 981 GPSystem.log(GPSystem.DEBUG, module.id, "getIssuerPathFor('" + cvc + "'," + cvcahint + ")"); 982 983 var car = cvc.getCAR(); 984 var level = cvc.getLevel(); 985 986 if (level != 3) { 987 var path = "/" + car.getHolder(); 988 } else { 989 var cacert = this.locateCertificate(car); 990 if (cacert) { 991 path = "/" + cacert.getCAR().getHolder() + "/" + car.getHolder(); 992 GPSystem.log(GPSystem.DEBUG, module.id, "getIssuerPathFor() using path based on located issuer certificate " + path); 993 } else { 994 path = "/" + CVCertificateStore.nthElementOf(cvcahint, 0) + "/" + car.getHolder(); 995 GPSystem.log(GPSystem.DEBUG, module.id, "getIssuerPathFor() using path based on hint " + path); 996 } 997 } 998 999 return path; 1000 } 1001 1002 1003 1004 /** 1005 * Validate a certificate against a certificate already stored in the certificate store 1006 * 1007 * <p>If the certificate is a terminal certificate, then the first element of the path given 1008 * in cvcahint is used to determine the correct CVCA.</p> 1009 * 1010 * Throws an exception if the certificate can not be validated 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 object containing the path and domain parameter used for validating the certificate. Returns null if no issuer could be found 1016 * @type Object 1017 */ 1018 CVCertificateStore.prototype.validateCertificate = function(crypto, cvc, cvcahint) { 1019 GPSystem.log(GPSystem.DEBUG, module.id, "validateCertificate('" + cvc + "'," + cvcahint + ")"); 1020 1021 var car = cvc.getCAR(); 1022 var level = cvc.getLevel(); 1023 1024 if (car.equals(cvc.getCHR())) { // Self signed 1025 if (level != 1) { 1026 GPSystem.log(GPSystem.ERROR, module.id, "Self-signed certificates only allowed for CVCA: " + cvc); 1027 throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "Self-signed certificates only allowed for CVCA: " + cvc); 1028 } 1029 if (!cvc.verifyWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID())) { 1030 GPSystem.log(GPSystem.ERROR, module.id, "Self-signed certificate failed signature verification. " + cvc); 1031 throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "Self-signed certificate failed signature verification. " + cvc); 1032 } 1033 return; 1034 } 1035 1036 var path = this.getIssuerPathFor(cvc, cvcahint); 1037 var cacert = this.getCertificate(path, car); 1038 1039 if (cacert == null) { 1040 GPSystem.log(GPSystem.ERROR, module.id, "Can't find issuer " + car); 1041 return; 1042 } 1043 1044 var oid = cacert.getPublicKeyOID(); 1045 if (CVC.isECDSA(oid)) { 1046 var dp = this.getDomainParameter(path, car); 1047 } else { 1048 var dp = null; 1049 } 1050 var result = cvc.verifyWith(crypto, cacert.getPublicKey(dp), oid); 1051 if (!result) { 1052 GPSystem.log(GPSystem.ERROR, module.id, "Certificate " + cvc + " failed signature verification with " + cacert); 1053 throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "Certificate " + cvc + " failed signature verification with " + cacert); 1054 } 1055 1056 return { path: path + "/" + cvc.getCHR().getHolder(), dp: dp }; 1057 } 1058 1059 1060 1061 /** 1062 * Insert a single certificates into the certificate store 1063 * 1064 * <p>Before a certificate is imported, the signature is verified.</p> 1065 * <p>If the certificate is a terminal certificate, then the first element of the path given 1066 * in cvcahint is used to determine the correct CVCA.</p> 1067 * 1068 * @param {Crypto} crypto the crypto provider to be used for certificate verification 1069 * @param {CVC} cvc the certificate 1070 * @param {String} cvcahint the PKI path (e.g. "/UTCVCA1/UTDVCA1/UTTERM"). Only the first path element is relevant 1071 * @param {Boolean} forceUpdate true if certificate should be updated even if one already exists 1072 * @returns true, if the certificate was inserted 1073 * @type boolean 1074 */ 1075 CVCertificateStore.prototype.insertCertificate = function(crypto, cvc, cvcahint, forceUpdate) { 1076 GPSystem.log(GPSystem.DEBUG, module.id, "insertCertificate('" + cvc + "'," + cvcahint + ")"); 1077 1078 var car = cvc.getCAR(); 1079 var path = this.getIssuerPathFor(cvc, cvcahint); 1080 1081 var cacert = this.getCertificate(path, car); 1082 if (cacert == null) { 1083 GPSystem.log(GPSystem.ERROR, module.id, "Can't find issuer " + car); 1084 return false; 1085 } 1086 1087 if (CVC.isECDSA(cacert.getPublicKeyOID())) { 1088 var dp = this.getDomainParameter(path, car); 1089 } else { 1090 var dp = null; 1091 } 1092 var result = cvc.verifyWith(crypto, cacert.getPublicKey(dp), cacert.getPublicKeyOID()); 1093 if (!result) { 1094 GPSystem.log(GPSystem.ERROR, module.id, "Certificate " + cvc + " failed signature verification with " + cacert); 1095 return false; 1096 } 1097 1098 var chr = cvc.getCHR(); 1099 var holder = chr.getHolder(); 1100 1101 if (holder == car.getHolder()) { // Link certificate 1102 this.storeCertificate("/" + holder, cvc, true, forceUpdate); 1103 } else { // Subordinate certificate 1104 this.storeCertificate(path + "/" + holder, cvc, true, forceUpdate); 1105 } 1106 1107 return true; 1108 } 1109 1110 1111 1112 /** 1113 * Insert certificates into certificate store 1114 * 1115 * <p>The import into the internal data structure is done in three steps:</p> 1116 * <ol> 1117 * <li>If allowed, all self-signed certificates are imported</li> 1118 * <li>All possible certificate chains are build</li> 1119 * <li>Certificate chains are processed starting with the topmost certificate in the hierachie</li> 1120 * </ol> 1121 * <p>Certificates at the terminal level can only be imported, if the issuing 1122 * DVCA certificate is contained in the list or a hint for the relevant CVCA is 1123 * given in the first element of the path contained in parameter cvcahint.</p> 1124 * <p>Before a certificate is imported, the signature is verified.</p> 1125 * 1126 * @param {Crypto} crypto the crypto provider to be used for certificate verification 1127 * @param {CVC[]} certlist the unordered list of certificates 1128 * @param {Boolean} insertSelfSigned true, if the import of root certificates is allowed 1129 * @param {String} cvcahint the PKI path (e.g. "/UTCVCA1/UTDVCA1/UTTERM"). Only the first path element is relevant 1130 * @param {Boolean} forceUpdate true if certificate should be updated even if one already exists 1131 * @returns the (ideally empty) list of unprocessed certificates. This does not contains certificates 1132 * that fail signature verification. 1133 * @type CVC[] 1134 */ 1135 CVCertificateStore.prototype.insertCertificates = function(crypto, certlist, insertSelfSigned, cvcahint, forceUpdate) { 1136 GPSystem.log(GPSystem.DEBUG, module.id, "insertCertificates('" + certlist + "'," + insertSelfSigned + "," + cvcahint + ")"); 1137 1138 var chrmap = []; 1139 1140 // Iterate certificate list and store self-signed certificates, if allowed 1141 // Generate a map of certificate holder references 1142 var unprocessed = []; 1143 for (var i = 0; i < certlist.length; i++) { 1144 var cvc = certlist[i]; 1145 var chr = cvc.getCHR().toString(); 1146 1147 if (chr == cvc.getCAR().toString()) { // Self signed 1148 var result = cvc.verifyWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID()); 1149 1150 if (result) { 1151 var path = "/" + cvc.getCHR().getHolder(); 1152 if (insertSelfSigned) { // Store self-signed certificates 1153 this.storeCertificate(path, cvc, true, forceUpdate); 1154 } 1155 } else { 1156 GPSystem.log(GPSystem.ERROR, module.id, "Self-signed certificate failed signature verification. " + cvc); 1157 } 1158 } else { 1159 var state = { cvc: cvc, end: true, stored: false }; 1160 unprocessed.push(state); 1161 if (typeof(chrmap[chr]) == "undefined") { 1162 chrmap[chr] = state; 1163 } else { 1164 // Duplicate CHRs for terminals are allowed 1165 chrmap[cvc.getCAR().toString() + "/" + chr] = state; 1166 } 1167 } 1168 } 1169 1170 // Mark certificates that are surely CAs, because an issued certificate is contained in the list 1171 certlist = unprocessed; 1172 for (var i = 0; i < certlist.length; i++) { 1173 var cvc = certlist[i].cvc; 1174 var state = chrmap[cvc.getCAR().toString()]; 1175 if (typeof(state) != "undefined") { 1176 GPSystem.log(GPSystem.DEBUG, module.id, "Mark as CA: " + state.cvc); 1177 state.end = false; 1178 } 1179 } 1180 1181 var unprocessed = []; 1182 for (var i = 0; i < certlist.length; i++) { 1183 var state = certlist[i]; 1184 if (state.end) { // Find all certificates which are at the end of the chain 1185 var list = []; 1186 var lastpathelement = state.cvc.getCHR().getHolder(); 1187 var path = "/" + lastpathelement; 1188 var singlecert = true; 1189 while(true) { // Build a certificate chain and the path for the last certificate 1190 var pathelement = state.cvc.getCAR().getHolder(); 1191 if (pathelement != lastpathelement) { // CVCA Link Certificates don't add to the path 1192 path = "/" + pathelement + path; 1193 } 1194 lastpathelement = pathelement; 1195 1196 if (!state.stored) { // If not already stored, add to the list 1197 list.push(state); 1198 state.stored = true; 1199 } 1200 state = chrmap[state.cvc.getCAR().toString()]; 1201 if (typeof(state) == "undefined") { 1202 break; 1203 } 1204 singlecert = false; 1205 } 1206 if (singlecert && cvcahint) { 1207 GPSystem.log(GPSystem.DEBUG, module.id, "Single certificate might be a terminal certificate, using cvca hint " + cvcahint); 1208 path = cvcahint; 1209 } else { 1210 GPSystem.log(GPSystem.DEBUG, module.id, "Path is " + path); 1211 } 1212 for (var j = list.length - 1; j >= 0; j--) { // Process chain in reverse order 1213 var cvc = list[j].cvc; 1214 if (!this.insertCertificate(crypto, cvc, path, forceUpdate)) { 1215 unprocessed.push(cvc); 1216 } 1217 } 1218 } 1219 } 1220 1221 return unprocessed; 1222 } 1223 // For backward compatibility 1224 CVCertificateStore.prototype.insertCertificates2 = CVCertificateStore.prototype.insertCertificates; 1225