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 Certificate = require('scsh/pki-db/Certificate').Certificate; 28 29 PKIXCommon = require('scsh/x509/PKIXCommon').PKIXCommon; 30 Holder = require('scsh/pki-db/Holder').Holder; 31 PKCS8 = require('scsh/pkcs/PKCS8').PKCS8; 32 PKCS10 = require('scsh/pkcs/PKCS10').PKCS10; 33 34 35 36 /** 37 * Create an object to access a X509 certificate store. 38 * 39 * @class Class that abstracts a certificate, request and key store for a X509 PKI. 40 * 41 * @constructor 42 * @param {DAOFactory} DAOFactory the factory that can create data access objects for persistent information 43 */ 44 function X509CertificateStore(daof) { 45 assert(daof, "Parameter doaf can't be empty"); 46 47 this.daof = daof; 48 this.certtype = Holder.X509; 49 } 50 51 exports.X509CertificateStore = X509CertificateStore; 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 */ 423 X509CertificateStore.prototype.storeCertificate = function(pathOrHolderId, cert, makeCurrent) { 424 GPSystem.log(GPSystem.DEBUG, module.id, "storeCertificate(" + pathOrHolderId + ",'" + cert + "'," + makeCurrent + ")"); 425 426 var holderdao = this.daof.getHolderDAO(); 427 428 if (typeof(pathOrHolderId) == "number") { 429 var holder = holderdao.getHolderById(pathOrHolderId, this.certtype); 430 if (holder == null) { 431 throw new GPError(module.id, GPError.INVALID_ARGUMENTS, 0, "Could not locate holder for id " + pathOrHolderId); 432 } 433 } else { 434 var holder = holderdao.getHolder(pathOrHolderId, this.certtype); 435 436 if (!holder) { 437 GPSystem.log(GPSystem.DEBUG, module.id, "Holder not found, creating new."); 438 holder = holderdao.newHolder(pathOrHolderId, Holder.X509); 439 } 440 } 441 442 var certdao = this.daof.getCertificateDAO(); 443 444 var issuer = cert.getIssuerDNString(); 445 var subject = cert.getSubjectDNString(); 446 var autid = cert.getAuthorityKeyIdentifier(); 447 var subid = cert.getSubjectKeyIdentifier(); 448 var serial = cert.getSerialNumberString(); 449 450 if ((autid && autid.equals(subid)) || issuer.equals(subject)) { 451 dir = Certificate.SHORT; 452 } else { 453 dir = Certificate.UP; 454 } 455 456 var certificate = certdao.getCertificateBySerial(holder, serial, dir); 457 458 if (certificate) { 459 GPSystem.log(GPSystem.INFO, module.id, "storeCertificate() : We already have a different certificate for that serial number"); 460 GPSystem.log(GPSystem.INFO, module.id, "Existing: " + (new X509(certificate.bytes))); 461 GPSystem.log(GPSystem.INFO, module.id, "New : " + cert); 462 463 return; 464 } 465 466 var template = { 467 keyId: PKIXCommon.determineKeyIdentifier(cert.getPublicKey()) 468 }; 469 470 var certificate = certdao.newCertificate(holder, serial, dir, cert.getBytes(), template); 471 472 if (makeCurrent) { 473 holderdao.updateCurrentCertificate(holder, certificate); 474 } 475 } 476 477 478 479 /** 480 * Get current key id 481 * 482 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 483 * @type ByteString 484 * @return the current key id or null if none defined 485 */ 486 X509CertificateStore.prototype.getCurrentKeyIdAndCertificate = function(pathOrHolderId) { 487 GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentKeyId(" + pathOrHolderId + ")"); 488 489 var holder = this.getHolder(pathOrHolderId, false); 490 491 var certdao = this.daof.getCertificateDAO(); 492 493 var certificate = certdao.getCurrentCertificate(holder); 494 495 if (!certificate) { 496 return null; 497 } 498 499 return { keyId: certificate.keyId, certificate: new X509(certificate.bytes) }; 500 } 501 502 503 504 /** 505 * Get current certificate for given path or holderId 506 * 507 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 508 * @type X509 509 * @return the current certificate or null if none defined 510 */ 511 X509CertificateStore.prototype.getCurrentCertificate = function(pathOrHolderId) { 512 GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentCertificate(" + pathOrHolderId + ")"); 513 514 var holder = this.getHolder(pathOrHolderId, false); 515 516 var certdao = this.daof.getCertificateDAO(); 517 518 var certificate = certdao.getCurrentCertificate(holder); 519 520 if (!certificate) { 521 return null; 522 } 523 524 return new X509(certificate.bytes); 525 } 526 527 528 529 /** 530 * Get current certificate for given path or holderId 531 * 532 * @param {String/Number} pathOrHolderId the relative path of the PKI element (e.g. "/UTCVCA1/UTDVCA1") or the holderId from the database 533 * @type X509 534 * @return the current certificate or null if none defined 535 */ 536 X509CertificateStore.prototype.getCurrentCertificateAndSigner = function(pathOrHolderId) { 537 GPSystem.log(GPSystem.DEBUG, module.id, "getCurrentCertificateAndSigner(" + pathOrHolderId + ")"); 538 539 var holder = this.getHolder(pathOrHolderId, false); 540 541 var certdao = this.daof.getCertificateDAO(); 542 543 var certificate = certdao.getCurrentCertificate(holder); 544 545 if (!certificate) { 546 return null; 547 } 548 549 var cert = new X509(certificate.bytes); 550 551 var signerDAO = this.daof.getSignerDAO(); 552 553 var signer = signerDAO.getSignerByKeyId(holder, certificate.keyId); 554 555 if (!signer) { 556 return null; 557 } 558 559 var prk = PKCS8.decodeKeyFromPKCS8Format(signer.keyblob); 560 561 return { signerPrK: prk, signerCert: cert }; 562 } 563