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 * @type Certificate 423 * @return the Certificate entry from the database or null 424 */ 425 X509CertificateStore.prototype.storeCertificate = function(pathOrHolderId, cert, makeCurrent, srId) { 426 GPSystem.log(GPSystem.DEBUG, module.id, "storeCertificate(" + pathOrHolderId + ",'" + cert + "'," + makeCurrent + ")"); 427 428 var holderdao = this.daof.getHolderDAO(); 429 430 if (typeof(pathOrHolderId) == "number") { 431 var holder = holderdao.getHolderById(pathOrHolderId, this.certtype); 432 if (holder == null) { 433 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate holder for id " + pathOrHolderId); 434 } 435 } else { 436 var holder = holderdao.getHolder(pathOrHolderId, this.certtype); 437 438 if (!holder) { 439 GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found, creating new."); 440 holder = holderdao.newHolder(pathOrHolderId, Holder.X509); 441 } 442 } 443 444 var certdao = this.daof.getCertificateDAO(); 445 446 var issuer = cert.getIssuerDNString(); 447 var subject = cert.getSubjectDNString(); 448 var autid = cert.getAuthorityKeyIdentifier(); 449 var subid = cert.getSubjectKeyIdentifier(); 450 var serial = cert.getSerialNumber().toString(HEX); 451 452 if ((autid && autid.equals(subid)) || issuer.equals(subject)) { 453 dir = Certificate.SHORT; 454 } else { 455 dir = Certificate.UP; 456 } 457 458 var certificate = certdao.getCertificateBySerial(holder, serial, dir); 459 460 if (certificate) { 461 GPSystem.log(GPSystem.INFO, module.id, "storeCertificate() : We already have a different certificate for that serial number"); 462 GPSystem.log(GPSystem.INFO, module.id, "Existing: " + (new X509(certificate.bytes))); 463 GPSystem.log(GPSystem.INFO, module.id, "New : " + cert); 464 465 return certificate; 466 } 467 468 var template = { 469 keyId: PKIXCommon.determineKeyIdentifier(cert.getPublicKey()), 470 expiry: new LongDate(cert.getNotAfter()) 471 }; 472 473 if (srId) { 474 template.serviceRequestId = srId; 475 } 476 477 var certificate = certdao.newCertificate(holder, serial, dir, cert.getBytes(), template); 478 479 if (makeCurrent || !holder.certId) { 480 holderdao.updateCurrentCertificate(holder, certificate); 481 } 482 483 return certificate; 484 } 485 486 487 488 /** 489 * Import a certificate that can be validated by one of certificates in the database. 490 * 491 * @param {X509} cert the certificate 492 * @param {Boolean} makeCurrent true if this certificate becomes the current certificate 493 * @type Certificate 494 * @return the Certificate entry from the database or null 495 */ 496 X509CertificateStore.prototype.importCertificate = function(cert, makeCurrent) { 497 GPSystem.log(GPSystem.DEBUG, module.id, "importCertificate(" + cert + ")"); 498 499 var issuer = cert.getIssuerDNString(); 500 var subject = cert.getSubjectDNString(); 501 if (issuer.equals(subject)) { 502 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "importCertificate can not import self-signed certificates"); 503 } 504 505 var certdao = this.daof.getCertificateDAO(); 506 507 var autid = cert.getAuthorityKeyIdentifier(); 508 var subid = cert.getSubjectKeyIdentifier(); 509 510 var certdto = certdao.getCertificateByKeyId(autid); 511 512 if (certdto == null) { 513 GPSystem.log(GPSystem.WARN, module.id, "No certificate with AuthorityKeyIdentifier " + autid.toString(HEX) + " found"); 514 return null; 515 } 516 517 var ca = new X509(certdto.bytes); 518 cert.verifyWith(ca); 519 520 var parentId = certdto.holderId; 521 var name = PKIXCommon.makeName(PKIXCommon.parseDN(subject)); 522 523 var holderdao = this.daof.getHolderDAO(); 524 var holder = holderdao.getHolderByTypeAndName(parentId, name, Holder.X509); 525 526 if (!holder) { 527 GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found, creating new."); 528 holder = holderdao.newHolderForParent(parentId, Holder.X509, { name: name } ); 529 } 530 531 var holderId = holder.id; 532 533 return this.storeCertificate(holderId, cert, makeCurrent); 534 } 535 536 537 538 /** 539 * Get current key id 540 * 541 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 542 * @type ByteString 543 * @return the current key id or null if none defined 544 */ 545 X509CertificateStore.prototype.getCurrentKeyIdAndCertificate = function(pathOrHolderId) { 546 GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentKeyId(" + pathOrHolderId + ")"); 547 548 var holder = this.getHolder(pathOrHolderId, false); 549 550 var certdao = this.daof.getCertificateDAO(); 551 552 var certificate = certdao.getCurrentCertificate(holder); 553 554 if (!certificate) { 555 return null; 556 } 557 558 return { keyId: certificate.keyId, certificate: new X509(certificate.bytes) }; 559 } 560 561 562 563 /** 564 * Get current certificate for given path or holderId 565 * 566 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 567 * @type X509 568 * @return the current certificate or null if none defined 569 */ 570 X509CertificateStore.prototype.getCurrentCertificate = function(pathOrHolderId) { 571 GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentCertificate(" + pathOrHolderId + ")"); 572 573 var holder = this.getHolder(pathOrHolderId, false); 574 575 var certdao = this.daof.getCertificateDAO(); 576 577 var certificate = certdao.getCurrentCertificate(holder); 578 579 if (!certificate) { 580 return null; 581 } 582 583 return new X509(certificate.bytes); 584 } 585 586 587 588 /** 589 * Get current certificate for given path or holderId 590 * 591 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 592 * @type X509 593 * @return the current certificate or null if none defined 594 */ 595 X509CertificateStore.prototype.getCurrentCertificateAndSigner = function(pathOrHolderId) { 596 GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentCertificateAndSigner(" + pathOrHolderId + ")"); 597 598 var holder = this.getHolder(pathOrHolderId, false); 599 600 var certdao = this.daof.getCertificateDAO(); 601 602 var certificate = certdao.getCurrentCertificate(holder); 603 604 if (!certificate) { 605 return null; 606 } 607 608 var cert = new X509(certificate.bytes); 609 610 var signerDAO = this.daof.getSignerDAO(); 611 612 var signer = signerDAO.getSignerByKeyId(holder, certificate.keyId); 613 614 if (!signer) { 615 return null; 616 } 617 618 var prk = PKCS8.decodeKeyFromPKCS8Format(signer.keyblob); 619 620 return { signerPrK: prk, signerCert: cert }; 621 } 622