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 Generic X509 certificate, request and key store 25 */ 26 27 var PKIXCommon = require('scsh/x509/PKIXCommon').PKIXCommon; 28 var Certificate = require('scsh/pki-db/Certificate').Certificate; 29 var Holder = require('scsh/pki-db/Holder').Holder; 30 var LongDate = require('scsh/pki-db/LongDate').LongDate; 31 var PKCS8 = require('scsh/pkcs/PKCS8').PKCS8; 32 var PKCS10 = require('scsh/pkcs/PKCS10').PKCS10; 33 34 35 /** 36 * Create an object to access a X509 certificate store. 37 * 38 * @class Class that abstracts a certificate, request and key store for a X509 PKI. 39 * 40 * @constructor 41 * @param {DAOFactory} DAOFactory the factory that can create data access objects for persistent information 42 */ 43 function X509CertificateStore(daof) { 44 assert(daof, "Parameter doaf can't be empty"); 45 46 this.daof = daof; 47 this.certtype = Holder.X509; 48 } 49 50 exports.X509CertificateStore = X509CertificateStore; 51 52 53 54 /** 55 * Strip the last element of the path, effectively defining the parent within the path 56 * 57 * @param {String} path the path to strip the last element from 58 * @returns the parent path or null for the root 59 * @type String 60 */ 61 X509CertificateStore.parentPathOf = function(path) { 62 assert(typeof(path) == "string", "Argument path must be string"); 63 var ofs = path.lastIndexOf("/"); 64 if (ofs <= 0) { 65 return null; 66 } 67 return path.substr(0, ofs); 68 } 69 70 71 72 /** 73 * Return the n-element of the path 74 * 75 * @param {String} path the path to return the last element from 76 * @returns the last path element or null for the root 77 * @type String 78 */ 79 X509CertificateStore.nthElementOf = function(path, n) { 80 assert(typeof(path) == "string", "Argument path must be string"); 81 var pe = path.substr(1).split("/"); 82 if (typeof(n) == "undefined") { 83 return pe[pe.length - 1]; 84 } 85 return pe[n]; 86 } 87 88 89 90 /** 91 * Return a suitable crypto object. This may be overwritten by derived classes 92 * 93 * @type Crypto 94 * @return the Crypto object 95 */ 96 X509CertificateStore.prototype.getCrypto = function() { 97 if (this.crypto == undefined) { 98 this.crypto = new Crypto(); 99 } 100 return this.crypto; 101 } 102 103 104 105 /** 106 * Check if holder exists for path or holderId 107 * 108 * @param {String/Number} pathOrHolderId 109 * @type Holder 110 * @return true if holder exists 111 * @private 112 */ 113 X509CertificateStore.prototype.hasHolder = function(pathOrHolderId) { 114 var holderdao = this.daof.getHolderDAO(); 115 116 if (typeof(pathOrHolderId) == "number") { 117 var holder = holderdao.getHolderById(pathOrHolderId, this.certtype); 118 } else { 119 var holder = holderdao.getHolder(pathOrHolderId, this.certtype); 120 } 121 return holder != null; 122 } 123 124 125 126 /** 127 * List certificate holders for a given PKI element 128 * 129 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 130 * @returns a list of holder ids, possibly empty 131 * @type String[] 132 */ 133 X509CertificateStore.prototype.listHolders = function(pathOrHolderId) { 134 GPSystem.log(GPSystem.DEBUG, module.id, "listHolders(" + pathOrHolderId + ")"); 135 136 var holderdao = this.daof.getHolderDAO(); 137 138 var result = holderdao.getHolderList(pathOrHolderId, this.certtype); 139 return result; 140 } 141 142 143 144 /** 145 * Get existing holder object for given path or holderId 146 * 147 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 148 * @param {Boolean} create create if holder doesn't exist 149 * @type Holder 150 * @return the holder object 151 * @private 152 */ 153 X509CertificateStore.prototype.getHolder = function(pathOrHolderId, create) { 154 var holderdao = this.daof.getHolderDAO(); 155 156 if (typeof(pathOrHolderId) == "number") { 157 var holder = holderdao.getHolderById(pathOrHolderId, this.certtype); 158 } else { 159 var holder = holderdao.getHolder(pathOrHolderId, this.certtype); 160 } 161 162 if (!holder) { 163 if (!create) { 164 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate holder for " + pathOrHolderId); 165 } 166 GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found, creating new."); 167 if (typeof(pathOrHolderId) == "number") { 168 holder = holderdao.newHolderWithParent(0, this.certtype); 169 } else { 170 holder = holderdao.newHolder(pathOrHolderId, this.certtype); 171 } 172 } 173 174 return holder; 175 } 176 177 178 179 /** 180 * Create new signer based on key pair generated externally 181 * 182 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 183 * @param {String} name the unique name of the signer in the holder context 184 * @param {Key} puk the public key 185 * @param {ByteString} keyblob the wrapped private key 186 * @type ByteString 187 * @return the subject key identifier 188 */ 189 X509CertificateStore.prototype.newSigner = function(pathOrHolderId, name, puk, keyblob) { 190 GPSystem.log(GPSystem.DEBUG, module.id, "newSigner(" + pathOrHolderId + "," + name + ")"); 191 192 var holder = this.getHolder(pathOrHolderId, false); 193 194 var keyId = PKIXCommon.determineKeyIdentifier(puk); 195 196 var template = {}; 197 198 if (keyblob instanceof ByteString) { 199 template.keyblob = keyblob; 200 } 201 202 var signerDAO = this.daof.getSignerDAO(); 203 signerDAO.newSigner(holder, name, keyId, template); 204 return keyId; 205 } 206 207 208 209 /** 210 * Get the signer identified by the keyId 211 * 212 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 213 * @param {ByteString} keyId the key identifier 214 * @type Signer 215 * @return this Signer object 216 */ 217 X509CertificateStore.prototype.getSigner = function(pathOrHolderId, keyId) { 218 GPSystem.log(GPSystem.DEBUG, module.id, "getSigner(" + pathOrHolderId + "," + keyId + ")"); 219 220 var holder = this.getHolder(pathOrHolderId, false); 221 222 var signerDAO = this.daof.getSignerDAO(); 223 224 var signer = signerDAO.getSignerByKeyId(holder, keyId); 225 226 if (!signer) { 227 return null; 228 } 229 230 return signer; 231 } 232 233 234 235 /** 236 * Generate key pair 237 * 238 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 239 * @param {String} name the unique name of the signer in the holder context 240 * @param {Number} algo the key generation algorithm (Crypto.EC or Crypto.RSA) 241 * @param {Key} prk the private key template 242 * @param {Key} puk the public key template 243 * @type ByteString 244 * @return the subject key identifier 245 */ 246 X509CertificateStore.prototype.generateKeyPair = function(pathOrHolderId, name, algo, prk, puk) { 247 GPSystem.log(GPSystem.DEBUG, module.id, "generateKeyPair(" + pathOrHolderId + "," + name + ")"); 248 249 var holder = this.getHolder(pathOrHolderId, false); 250 251 var crypto = this.getCrypto(); 252 253 // Generate key pair 254 crypto.generateKeyPair(algo, puk, prk); 255 256 var keyId = PKIXCommon.determineKeyIdentifier(puk); 257 258 var template = { 259 keyblob: PKCS8.encodeKeyUsingPKCS8Format(prk) 260 }; 261 262 var signerDAO = this.daof.getSignerDAO(); 263 signerDAO.newSigner(holder, name, keyId, template); 264 return keyId; 265 } 266 267 268 269 /** 270 * Get a private key in the certificate store 271 * 272 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 273 * @param {String} name the unique name of the signer in the holder context 274 * @returns the private key or null if not found 275 * @type Key 276 */ 277 X509CertificateStore.prototype.getPrivateKeyByName = function(pathOrHolderId, name) { 278 GPSystem.log(GPSystem.DEBUG, module.id, "getPrivateKey(" + pathOrHolderId + "," + name + ")"); 279 280 var holder = this.getHolder(pathOrHolderId, false); 281 282 var signerDAO = this.daof.getSignerDAO(); 283 var signer = signerDAO.getSignerByName(holder, name); 284 285 if (!signer) { 286 return signer; 287 } 288 289 return PKCS8.decodeKeyFromPKCS8Format(signer.keyblob); 290 } 291 292 293 294 /** 295 * Get a private key in the certificate store 296 * 297 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 298 * @param {ByteString} keyId the unique key identifier 299 * @returns the private key or null if not found 300 * @type Key 301 */ 302 X509CertificateStore.prototype.getPrivateKeyByKeyId = function(pathOrHolderId, keyId) { 303 GPSystem.log(GPSystem.DEBUG, module.id, "getPrivateKeyByKeyId(" + pathOrHolderId + "," + keyId + ")"); 304 305 var holder = this.getHolder(pathOrHolderId, false); 306 307 var signerDAO = this.daof.getSignerDAO(); 308 var signer = signerDAO.getSignerByKeyId(holder, keyId); 309 310 if (!signer) { 311 return signer; 312 } 313 314 return PKCS8.decodeKeyFromPKCS8Format(signer.keyblob); 315 } 316 317 318 319 /** 320 * Remove private key 321 * 322 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 323 * @param {String} name the unique name of the signer in the holder context 324 * @returns true is deleted 325 * @type boolean 326 */ 327 X509CertificateStore.prototype.deletePrivateKey = function(pathOrHolderId, name) { 328 GPSystem.log(GPSystem.DEBUG, module.id, "deleteRequest(" + pathOrHolderId + "," + name + ")"); 329 330 var holder = this.getHolder(pathOrHolderId, true); 331 332 var signerDAO = this.daof.getSignerDAO(); 333 var signer = signerDAO.getSignerByName(holder, name.toString()); 334 335 if (!signer) { 336 return false; 337 } 338 339 return signerDAO.deleteSigner(signer); 340 } 341 342 343 344 /** 345 * Remove request 346 * 347 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 348 * @param {ByteString} keyId the unique keyId of the request in the holder context 349 * @returns true is deleted 350 * @type boolean 351 */ 352 X509CertificateStore.prototype.deleteRequest = function(pathOrHolderId, keyId) { 353 GPSystem.log(GPSystem.DEBUG, module.id, "deleteRequest(" + pathOrHolderId + "," + keyId + ")"); 354 355 var holder = this.getHolder(pathOrHolderId, true); 356 357 var requestDAO = this.daof.getRequestDAO(); 358 var request = requestDAO.getRequestByKeyId(holder, keyId); 359 360 if (!request) { 361 return false; 362 } 363 364 return requestDAO.deleteRequest(request); 365 } 366 367 368 369 /** 370 * Store a certificate request in the certificate store 371 * 372 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 373 * @param {PKCS10} req the request 374 * @type ByteString 375 * @return the request's keyId 376 */ 377 X509CertificateStore.prototype.storeRequest = function(pathOrHolderId, req) { 378 GPSystem.log(GPSystem.DEBUG, module.id, "storeRequest(" + pathOrHolderId + "," + req + ")"); 379 380 var holder = this.getHolder(pathOrHolderId, true); 381 382 var requestDAO = this.daof.getRequestDAO(); 383 384 var puk = req.getPublicKey(); 385 var keyId = PKIXCommon.determineKeyIdentifier(puk); 386 387 requestDAO.newRequest(holder, keyId, req.getBytes()); 388 389 return keyId; 390 } 391 392 393 394 /** 395 * Return request for given keyId 396 * 397 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 398 * @param {ByteString} keyId the unique keyId of the request in the holder context 399 * @type PKCS10 400 * @return the request or null 401 */ 402 X509CertificateStore.prototype.getRequest = function(pathOrHolderId, keyId) { 403 GPSystem.log(GPSystem.DEBUG, module.id, "getRequest(" + pathOrHolderId + "," + keyId + ")"); 404 405 var holder = this.getHolder(pathOrHolderId, true); 406 407 var requestDAO = this.daof.getRequestDAO(); 408 var request = requestDAO.getRequestByKeyId(holder, keyId); 409 410 var req = new PKCS10(request.bytes); 411 return req; 412 } 413 414 415 416 /** 417 * Store a certificate in the certificate store 418 * 419 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 420 * @param {X509} cert the certificate 421 * @param {Boolean} makeCurrent true if this certificate becomes the current certificate 422 * @param {Number} srId service request id to be stored with issued certificate (optional) 423 * @type Certificate 424 * @return the Certificate entry from the database or null 425 */ 426 X509CertificateStore.prototype.storeCertificate = function(pathOrHolderId, cert, makeCurrent, srId) { 427 GPSystem.log(GPSystem.DEBUG, module.id, "storeCertificate(" + pathOrHolderId + ",'" + cert + "'," + makeCurrent + ")"); 428 429 var holderdao = this.daof.getHolderDAO(); 430 431 if (typeof(pathOrHolderId) == "number") { 432 var holder = holderdao.getHolderById(pathOrHolderId, this.certtype); 433 if (holder == null) { 434 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate holder for id " + pathOrHolderId); 435 } 436 } else { 437 var holder = holderdao.getHolder(pathOrHolderId, this.certtype); 438 439 if (!holder) { 440 GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found, creating new."); 441 holder = holderdao.newHolder(pathOrHolderId, Holder.X509); 442 } 443 } 444 445 var certdao = this.daof.getCertificateDAO(); 446 447 var issuer = cert.getIssuerDNString(); 448 var subject = cert.getSubjectDNString(); 449 var autid = cert.getAuthorityKeyIdentifier(); 450 var subid = cert.getSubjectKeyIdentifier(); 451 var serial = cert.getSerialNumber().toString(HEX); 452 453 if ((autid && autid.equals(subid)) || issuer.equals(subject)) { 454 dir = Certificate.SHORT; 455 } else { 456 dir = Certificate.UP; 457 } 458 459 var certificate = certdao.getCertificateBySerial(holder, serial, dir); 460 461 if (certificate) { 462 GPSystem.log(GPSystem.INFO, module.id, "storeCertificate() : We already have a different certificate for that serial number"); 463 GPSystem.log(GPSystem.INFO, module.id, "Existing: " + (new X509(certificate.bytes))); 464 GPSystem.log(GPSystem.INFO, module.id, "New : " + cert); 465 466 return certificate; 467 } 468 469 var template = { 470 keyId: PKIXCommon.determineKeyIdentifier(cert.getPublicKey()), 471 expiry: new LongDate(cert.getNotAfter()) 472 }; 473 474 if (srId) { 475 template.serviceRequestId = srId; 476 } 477 478 var certificate = certdao.newCertificate(holder, serial, dir, cert.getBytes(), template); 479 480 if (makeCurrent || !holder.certId) { 481 holderdao.updateCurrentCertificate(holder, certificate); 482 } 483 484 return certificate; 485 } 486 487 488 489 /** 490 * Import a certificate that can be validated by one of certificates in the database. 491 * 492 * @param {X509} cert the certificate 493 * @param {Boolean} makeCurrent true if this certificate becomes the current certificate 494 * @param {Number} srId service request id to be stored with issued certificate (optional) 495 * @type Certificate 496 * @return the Certificate entry from the database or null 497 */ 498 X509CertificateStore.prototype.importCertificate = function(cert, makeCurrent, srId) { 499 GPSystem.log(GPSystem.DEBUG, module.id, "importCertificate(" + cert + ")"); 500 501 var issuer = cert.getIssuerDNString(); 502 var subject = cert.getSubjectDNString(); 503 if (issuer.equals(subject)) { 504 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "importCertificate can not import self-signed certificates"); 505 } 506 507 var certdao = this.daof.getCertificateDAO(); 508 509 var autid = cert.getAuthorityKeyIdentifier(); 510 var subid = cert.getSubjectKeyIdentifier(); 511 512 var certdto = certdao.getCertificateByKeyId(autid); 513 514 if (certdto == null) { 515 GPSystem.log(GPSystem.WARN, module.id, "No certificate with AuthorityKeyIdentifier " + autid.toString(HEX) + " found"); 516 return null; 517 } 518 519 var ca = new X509(certdto.bytes); 520 cert.verifyWith(ca); 521 522 var parentId = certdto.holderId; 523 var name = PKIXCommon.makeName(PKIXCommon.parseDN(subject)); 524 525 var holderdao = this.daof.getHolderDAO(); 526 var holder = holderdao.getHolderByTypeAndName(parentId, name, Holder.X509); 527 528 if (!holder) { 529 GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found, creating new."); 530 holder = holderdao.newHolderForParent(parentId, Holder.X509, { name: name } ); 531 } 532 533 var holderId = holder.id; 534 535 return this.storeCertificate(holderId, cert, makeCurrent, srId); 536 } 537 538 539 540 /** 541 * Get current key id 542 * 543 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 544 * @type ByteString 545 * @return the current key id or null if none defined 546 */ 547 X509CertificateStore.prototype.getCurrentKeyIdAndCertificate = function(pathOrHolderId) { 548 GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentKeyId(" + pathOrHolderId + ")"); 549 550 var holder = this.getHolder(pathOrHolderId, false); 551 552 var certdao = this.daof.getCertificateDAO(); 553 554 var certificate = certdao.getCurrentCertificate(holder); 555 556 if (!certificate) { 557 return null; 558 } 559 560 return { keyId: certificate.keyId, certificate: new X509(certificate.bytes) }; 561 } 562 563 564 565 /** 566 * Get current certificate for given path or holderId 567 * 568 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 569 * @type X509 570 * @return the current certificate or null if none defined 571 */ 572 X509CertificateStore.prototype.getCurrentCertificate = function(pathOrHolderId) { 573 GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentCertificate(" + pathOrHolderId + ")"); 574 575 var holder = this.getHolder(pathOrHolderId, false); 576 577 var certdao = this.daof.getCertificateDAO(); 578 579 var certificate = certdao.getCurrentCertificate(holder); 580 581 if (!certificate) { 582 return null; 583 } 584 585 return new X509(certificate.bytes); 586 } 587 588 589 590 /** 591 * Get certificate chain for given path or holderId 592 * 593 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 594 * @type X509[] 595 * @return the certificate chain, starting with the current certificate of the holder and ending with the self-signed trust anchor 596 */ 597 X509CertificateStore.prototype.getCertificateChain = function(pathOrHolderId) { 598 GPSystem.log(GPSystem.DEBUG, module.id, "getCertificateChain(" + pathOrHolderId + ")"); 599 600 var chain = []; 601 var certdao = this.daof.getCertificateDAO(); 602 603 do { 604 var holder = this.getHolder(pathOrHolderId, false); 605 606 var certificate = certdao.getCurrentCertificate(holder); 607 608 if (!certificate) { 609 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "No current certificate found for " + holder); 610 } 611 612 chain.push(new X509(certificate.bytes)); 613 pathOrHolderId = holder.parentId; 614 } while (pathOrHolderId != undefined); 615 616 return chain; 617 } 618 619 620 621 /** 622 * Get current certificate for given path or holderId 623 * 624 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 625 * @type X509 626 * @return the current certificate or null if none defined 627 */ 628 X509CertificateStore.prototype.getCurrentCertificateAndSigner = function(pathOrHolderId) { 629 GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentCertificateAndSigner(" + pathOrHolderId + ")"); 630 631 var holder = this.getHolder(pathOrHolderId, false); 632 633 var certdao = this.daof.getCertificateDAO(); 634 635 var certificate = certdao.getCurrentCertificate(holder); 636 637 if (!certificate) { 638 return null; 639 } 640 641 var cert = new X509(certificate.bytes); 642 643 var signerDAO = this.daof.getSignerDAO(); 644 645 var signer = signerDAO.getSignerByKeyId(holder, certificate.keyId); 646 647 if (!signer) { 648 return null; 649 } 650 651 var prk = PKCS8.decodeKeyFromPKCS8Format(signer.keyblob); 652 653 return { signerPrK: prk, signerCert: cert }; 654 } 655