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 A Issuer for X.509 Certificates 25 */ 26 27 var X509Signer = require('scsh/x509/X509Signer').X509Signer; 28 29 var PKIXCommon = require("scsh/x509/PKIXCommon").PKIXCommon; 30 var X509CertificateGenerator = require("scsh/x509/X509CertificateGenerator").X509CertificateGenerator; 31 var CRLGenerator = require("scsh/x509/CRLGenerator").CRLGenerator; 32 33 34 35 /** 36 * Create a certification authority that issues X.509 certificates and CRLs 37 * 38 * @class Class implementing a certification authority issuing X.509 certificates and CRLs 39 * @constructor 40 * @param {DAOFactory} daof the factory that can create the required data access objects 41 * @param {CryptoProviderFactory} cpf factory implementing getCryptoProvider() used to get access to crypto providers 42 * @param {Holder} holder the holder object for this signer the database 43 */ 44 function X509CertificateIssuer(daof, cpf, holder) { 45 X509Signer.call(this, daof, cpf, holder); 46 this.crldp = []; 47 } 48 49 X509CertificateIssuer.prototype = Object.create(X509Signer.prototype); 50 X509CertificateIssuer.constructor = X509CertificateIssuer; 51 52 exports.X509CertificateIssuer = X509CertificateIssuer; 53 54 55 56 /** 57 * Create a new certificate issuer 58 * 59 * @param {DAOFactory} daof the factory that can create the required data access objects 60 * @param {String/Number} pathOrHolderId the path of holderIDs (eg. "/UTCVCA/UTDVCA/UTTERM") or the holderId from the database 61 * @param {Number} certtype optional argument, default Holder.X509 62 * @type Number 63 * @return the newly created holder id 64 */ 65 X509CertificateIssuer.createCertificateIssuer = function(daof, pathOrHolderId, certtype, template) { 66 return X509Signer.createSigner(daof, pathOrHolderId, certtype, template); 67 } 68 69 70 71 /** 72 * Add a CRL distribution point to issued certificates 73 * 74 * @param {String} crldp the URL of the distribution point 75 */ 76 X509CertificateIssuer.prototype.addCRLDistributionPoint = function(crldp) { 77 this.crldp.push(crldp); 78 } 79 80 81 82 /** 83 * Create a new randomly generated certificate serial number 84 * 85 * @private 86 * @type ByteString 87 * @return a 8 byte bytestring that resembles an unsigned integer 88 */ 89 X509CertificateIssuer.prototype.newSerialNumber = function() { 90 var crypto = new Crypto(); 91 var serial = crypto.generateRandom(8); 92 93 // Strip first bit to make integer unsigned 94 if (serial.byteAt(0) > 0x7F) { 95 serial = ByteString.valueOf(serial.byteAt(0) & 0x7F).concat(serial.bytes(1)); 96 } 97 return serial; 98 } 99 100 101 102 /** 103 * Issue a self-signed certificate for the given keyId. 104 * 105 * The key must have been previously generated using the newSigner() method 106 * 107 * @param {ByteString} keyId the subject key identifier 108 * @param {Number} srId service request id to be stored with issued certificate 109 */ 110 X509CertificateIssuer.prototype.issueSelfSignedCertificate = function(keyId, srId) { 111 112 if (keyId) { 113 var signerDAO = this.daof.getSignerDAO(); 114 var signer = signerDAO.getSignerByKeyId(this.holder, keyId); 115 } else { 116 var signer = this.signer; 117 } 118 119 if (!signer) { 120 throw new GPError(module.id, GPError.INVALID_DATA, 1, "No signer with keyId " + keyId + " found"); 121 } 122 123 var cp = this.cpf.getCryptoProvider(signer.keyDomain, true); 124 125 if (!cp) { 126 throw new GPError(module.id, GPError.INVALID_DATA, 1, "Signer is offline (Key Domain : " + this.signer.keyDomain + ")"); 127 } 128 129 try { 130 var prk = cp.getPrivateKeyByKeyId(signer.keyId, signer.keyblob); 131 132 if (prk == null) { 133 throw new GPError(module.id, GPError.INVALID_DATA, 1, "No key with keyId " + signer.keyId + " found"); 134 } 135 136 var req = this.getRequest(signer.keyId); 137 138 if (req == null) { 139 throw new GPError(module.id, GPError.INVALID_DATA, 1, "No request with keyId " + signer.keyId + " found"); 140 } 141 142 var pub = req.getPublicKey(); 143 144 var gen = new X509CertificateGenerator(cp.getCrypto()); 145 gen.encodeECDomainParameter = false; 146 147 gen.reset(); 148 gen.setSerialNumber(this.newSerialNumber()); 149 gen.setSignatureAlgorithm(this.policy.signatureAlgorithm); 150 var subject = req.getSubject(); 151 gen.setIssuer(subject); 152 var ced = new Date(); 153 var cxd = PKIXCommon.addDays(ced, this.policy.validityDaysSelfSigned); 154 gen.setNotBefore(ced); 155 gen.setNotAfter(cxd); 156 gen.setSubject(subject); 157 gen.setPublicKey(pub); 158 gen.addSubjectKeyIdentifierExtension(); 159 gen.addKeyUsageExtension(PKIXCommon.keyCertSign | PKIXCommon.cRLSign ); 160 gen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint); 161 162 var cert = gen.generateX509Certificate(prk); 163 164 var id = this.storeCertificate(cert, true, signer.keyId, srId); 165 } 166 finally { 167 cp.release(); 168 } 169 170 return { id: id, cert: cert }; 171 } 172 173 174 175 /** 176 * Issue a new certificate for the given subject and public key 177 * 178 * @param {Number/String/Object} certholder the holder id, path or object 179 * @param {Key} pubkey the public key 180 * @param {Object} subject in ASN1 format or a format accepted by PKIXCommon.encodeName() 181 * @param {Object[]} extensions array of certificate extensions objects with properties oid{String}, critical{boolean} and value{ByteString} 182 * @param {Number} srId service request id to be stored with issued certificate 183 * @type X509 184 * @return the newly generated certificate 185 */ 186 X509CertificateIssuer.prototype.issueCertificate = function(certholder, pubkey, subject, extensions, srId) { 187 assert(pubkey instanceof Key, "Argument pubkey must be instance of Key"); 188 189 if (typeof(certholder) != "object") { 190 var holderdao = this.daof.getHolderDAO(); 191 192 if (typeof(pathOrHolderId) == "string") { 193 certholder = holderdao.getHolder(certholder); 194 } else { 195 certholder = holderdao.getHolderById(certholder); 196 } 197 } 198 199 if (!certholder) { 200 throw new GPError(module.id, GPError.INVALID_DATA, 1, "Holder " + certholder + " not found"); 201 } 202 203 var icert = this.getSignerCertificate(); 204 205 if (!icert) { 206 throw new GPError(module.id, GPError.INVALID_DATA, 1, "No active signer found"); 207 } 208 209 var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true); 210 211 if (!cp) { 212 throw new GPError(module.id, GPError.INVALID_DATA, 1, "Signer is offline (Key Domain : " + this.signer.keyDomain + ")"); 213 } 214 215 try { 216 var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob); 217 218 if (prk == null) { 219 throw new GPError(module.id, GPError.INVALID_DATA, 1, "No key with keyId " + this.signer.keyId + " found"); 220 } 221 222 var issuer = PKIXCommon.getSubjectAsASN1(icert); 223 224 var gen = new X509CertificateGenerator(cp.getCrypto()); 225 gen.encodeECDomainParameter = false; 226 227 gen.reset(); 228 gen.setSerialNumber(this.newSerialNumber()); 229 gen.setSignatureAlgorithm(this.policy.signatureAlgorithm); 230 gen.setIssuer(issuer); 231 var ced = new Date(); 232 var cxd = PKIXCommon.addDays(ced, this.policy.validityDaysCertificates); 233 gen.setNotBefore(ced); 234 gen.setNotAfter(cxd); 235 gen.setSubject(subject); 236 gen.setPublicKey(pubkey); 237 gen.addSubjectKeyIdentifierExtension(); 238 gen.addAuthorityKeyIdentifierExtension(icert.getSubjectKeyIdentifier()); 239 240 if ((typeof(this.policy.pathLenConstraint) != "undefined") && (this.policy.pathLenConstraint > 0)) { 241 gen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint - 1); 242 } else { 243 gen.addBasicConstraintsExtension(false, 0); 244 } 245 246 if (this.crldp.length > 0) { 247 gen.addCRLDistributionPointURL(this.crldp); 248 } 249 250 if (typeof(extensions) == "object") { 251 for (var i = 0; i < extensions.length; i++) { 252 gen.addExtension(extensions[i].oid, extensions[i].critical, extensions[i].value); 253 } 254 } 255 256 var cert = gen.generateX509Certificate(prk); 257 258 var id = this.storeCertificateForHolder(certholder, cert, false, undefined, srId); 259 } 260 finally { 261 cp.release(); 262 } 263 return { id: id, cert: cert }; 264 } 265 266 267 268 /** 269 * Extension handler method for Sub-CA certificates 270 * 271 * @private 272 */ 273 X509CertificateIssuer.prototype.addExtForSubCA = function(certgen, extvalues) { 274 275 certgen.addKeyUsageExtension(PKIXCommon.keyCertSign | PKIXCommon.cRLSign ); 276 certgen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint - 1); 277 } 278 279 280 281 /** 282 * Extension handler method for TLS server certificates 283 * 284 * @private 285 */ 286 X509CertificateIssuer.prototype.addExtForTLSServer = function(certgen, extvalues) { 287 288 certgen.addKeyUsageExtension( PKIXCommon.keyAgreement | PKIXCommon.keyEncipherment); 289 certgen.addBasicConstraintsExtension(false, 0); 290 291 certgen.addExtendedKeyUsages(["id-csn-369791-tls-server", "id-kp-serverAuth"]); 292 293 var ext = new ASN1("subjectAltName", ASN1.SEQUENCE, 294 new ASN1("dNSName", 0x82, new ByteString(extvalues["dNSName"], ASCII)) 295 ); 296 certgen.addExtension("id-ce-subjectAltName", false, ext.getBytes()); 297 } 298 299 300 301 /** 302 * Extension handler method for TLS client certificates 303 * 304 * @private 305 */ 306 X509CertificateIssuer.prototype.addExtForTLSClient = function(certgen, extvalues) { 307 certgen.addKeyUsageExtension( PKIXCommon.digitalSignature); 308 certgen.addBasicConstraintsExtension(false, 0); 309 310 // certgen.addExtendedKeyUsages(["id-csn-369791-tls-client", "id-kp-clientAuth"]); 311 certgen.addExtendedKeyUsages(["id-kp-clientAuth"]); 312 } 313 314 315 316 /** 317 * Extension handler method for certificates suitable for TLS client authentication and e-Mail signature and encryption 318 * 319 * @private 320 */ 321 X509CertificateIssuer.prototype.addExtForEmailAndTLSClient = function(certgen, extvalues) { 322 323 certgen.addKeyUsageExtension( PKIXCommon.digitalSignature | PKIXCommon.keyEncipherment); 324 certgen.addBasicConstraintsExtension(false, 0); 325 326 var ext = new ASN1("subjectAltName", ASN1.SEQUENCE, 327 new ASN1("rfc822Name", 0x81, new ByteString(extvalues["email"], ASCII)) 328 ); 329 certgen.addExtension("id-ce-subjectAltName", false, ext.getBytes()); 330 331 certgen.addExtendedKeyUsages(["id-kp-clientAuth", "id-kp-emailProtection"]); 332 } 333 334 335 336 /** 337 * Issue a CRL 338 * 339 * @type CRL 340 * @return the newly issued CRL 341 */ 342 X509CertificateIssuer.prototype.issueCRL = function() { 343 GPSystem.log(GPSystem.DEBUG, module.id, "issueCRL()"); 344 345 var icert = this.getSignerCertificate(); 346 if (!icert) { 347 throw new GPError(module.id, GPError.INVALID_DATA, 1, "No active signer found"); 348 } 349 350 var c = this.signer.getContent(); 351 if (c && c.crl) { 352 var latestCRL = new CRLGenerator(); 353 var crlNumber = latestCRL.loadCRLEntries(new ByteString(c.crl, HEX)); 354 crlNumber++; 355 } 356 357 var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true); 358 359 if (!cp) { 360 throw new GPError(module.id, GPError.INVALID_DATA, 1, "Signer is offline (Key Domain : " + this.signer.keyDomain + ")"); 361 } 362 363 try { 364 var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob); 365 366 if (prk == null) { 367 throw new GPError(module.id, GPError.INVALID_DATA, 1, "No key with keyId " + this.signer.keyId + " found"); 368 } 369 370 var x = new CRLGenerator(cp.getCrypto()); 371 372 x.reset(); 373 x.setSignatureAlgorithm(this.policy.signatureAlgorithm); 374 var issuer = PKIXCommon.getSubjectAsASN1(icert); 375 x.setIssuer(issuer); 376 var now = new Date(); 377 x.setThisUpdate(now); 378 x.setNextUpdate(PKIXCommon.addDays(now, this.policy.validityDaysCRL)); 379 380 var certdao = this.daof.getCertificateDAO(); 381 var revocationList = certdao.getRevokedCertificates(this.holder.id, now); 382 383 for each (var cert in revocationList) { 384 var serial = new ByteString(cert.serial, HEX); 385 386 var reason = this.mapStatusToReasonCode(cert.status); 387 388 if (cert.revocationDate) { 389 var revocationDate = cert.revocationDate; 390 } else { 391 var revocationDate = now; 392 certdao.updateRevocationDate(cert.id, now); 393 } 394 395 if (cert.invalidityDate) { 396 var invalidityDate = cert.invalidityDate; 397 } else { 398 var invalidityDate = now; 399 } 400 var extensions = new ASN1("crlExtensions", ASN1.SEQUENCE); 401 extensions.add(x.createInvalidityDateExt(invalidityDate)); 402 403 x.revokeCertificate(serial, revocationDate, reason, extensions); 404 } 405 406 if (!crlNumber) { 407 var crlNumber = 1; 408 } 409 x.addCRLNumberExtension(crlNumber); 410 411 var crl = x.generateCRL(prk); 412 413 c.crl = crl.getBytes().toString(HEX); 414 this.signer.setContent(c); 415 var signerDAO = this.daof.getSignerDAO(); 416 signerDAO.updateContent(this.signer); 417 } 418 finally { 419 cp.release(); 420 } 421 return crl; 422 } 423 424 425 426 X509CertificateIssuer.prototype.mapStatusToReasonCode = function(status) { 427 switch(status) { 428 case OCSPQuery.REVOKED: 429 return null; // "...the reason code CRL entry extension SHOULD be absent instead of using the unspecified (0) reasonCode value." [RFC 5280, 5.3.1] 430 case OCSPQuery.KEYCOMPROMISE: 431 return CRLGenerator.keyCompromise; 432 case OCSPQuery.CACOMPROMISE: 433 return CRLGenerator.cACompromise; 434 case OCSPQuery.AFFILIATIONCHANGED: 435 return CRLGenerator.affiliationChanged; 436 case OCSPQuery.SUPERSEDED: 437 return CRLGenerator.superseded; 438 case OCSPQuery.CESSATIONOFOPERATION: 439 return CRLGenerator.cessationOfOperation; 440 case OCSPQuery.CERTIFICATEHOLD: 441 return CRLGenerator.certificateHold; 442 case OCSPQuery.REMOVEFROMCRL: 443 return CRLGenerator.removeFromCRL; 444 case OCSPQuery.PRIVILEGEWITHDRAWN: 445 return CRLGenerator.privilegeWithdrawn; 446 case OCSPQuery.AACOMPROMISE: 447 return CRLGenerator.aACompromise; 448 } 449 450 throw new GPError(module.id, GPError.INVALID_DATA, 1, "Unexpected status: " + status); 451 } 452 453 454 455 X509CertificateIssuer.prototype.getDNMask = function(holderId) { 456 var signer = this.getSigner(); 457 var c = signer.getContent(); 458 return c.dnmask; 459 } 460 461 462 463 X509CertificateIssuer.prototype.isOperational = function() { 464 GPSystem.log(GPSystem.DEBUG, module.id, "isOperational()"); 465 466 var icert = this.getSignerCertificate(); 467 if (!icert) { 468 // No active signer found 469 return false; 470 } 471 472 var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true); 473 474 if (!cp) { 475 // Signer offline 476 return false; 477 } 478 479 try { 480 var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob); 481 482 if (prk == null) { 483 // Key not found 484 return false; 485 } 486 } finally { 487 cp.release(); 488 } 489 490 return true; 491 } 492