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 if (typeof(this.policy.restrictPublicKey) != "undefined") { 145 pub.algorithmIdentifier = PKIXCommon.restrictedPublicKeyAlgorithmIdentifier(this.policy.restrictPublicKey); 146 } 147 148 var gen = new X509CertificateGenerator(cp.getCrypto()); 149 gen.encodeECDomainParameter = false; 150 151 gen.reset(); 152 gen.setSerialNumber(this.newSerialNumber()); 153 gen.setSignatureAlgorithm(this.policy.signatureAlgorithm); 154 var subject = req.getSubject(); 155 gen.setIssuer(subject); 156 var ced = new Date(); 157 var cxd = PKIXCommon.addDays(ced, this.policy.validityDaysSelfSigned); 158 gen.setNotBefore(ced); 159 gen.setNotAfter(cxd); 160 gen.setSubject(subject); 161 gen.setPublicKey(pub); 162 gen.addSubjectKeyIdentifierExtension(); 163 gen.addKeyUsageExtension(PKIXCommon.keyCertSign | PKIXCommon.cRLSign ); 164 gen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint); 165 166 var cert = gen.generateX509Certificate(prk); 167 168 var id = this.storeCertificate(cert, true, signer.keyId, srId); 169 } 170 finally { 171 cp.release(); 172 } 173 174 return { id: id, cert: cert }; 175 } 176 177 178 179 /** 180 * Issue a new certificate for the given subject and public key 181 * 182 * @param {Number/String/Object} certholder the holder id, path or object 183 * @param {Key} pubkey the public key 184 * @param {Object} subject in ASN1 format or a format accepted by PKIXCommon.encodeName() 185 * @param {Object[]} extensions array of certificate extensions objects with properties oid{String}, critical{boolean} and value{ByteString} 186 * @param {Number} srId service request id to be stored with issued certificate 187 * @type X509 188 * @return the newly generated certificate 189 */ 190 X509CertificateIssuer.prototype.issueCertificate = function(certholder, pubkey, subject, extensions, srId) { 191 assert(pubkey instanceof Key, "Argument pubkey must be instance of Key"); 192 193 if (typeof(certholder) != "object") { 194 var holderdao = this.daof.getHolderDAO(); 195 196 if (typeof(pathOrHolderId) == "string") { 197 certholder = holderdao.getHolder(certholder); 198 } else { 199 certholder = holderdao.getHolderById(certholder); 200 } 201 } 202 203 if (!certholder) { 204 throw new GPError(module.id, GPError.INVALID_DATA, 1, "Holder " + certholder + " not found"); 205 } 206 207 var icert = this.getSignerCertificate(); 208 209 if (!icert) { 210 throw new GPError(module.id, GPError.INVALID_DATA, 1, "No active signer found"); 211 } 212 213 var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true); 214 215 if (!cp) { 216 throw new GPError(module.id, GPError.INVALID_DATA, 1, "Signer is offline (Key Domain : " + this.signer.keyDomain + ")"); 217 } 218 219 try { 220 var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob); 221 222 if (prk == null) { 223 throw new GPError(module.id, GPError.INVALID_DATA, 1, "No key with keyId " + this.signer.keyId + " found"); 224 } 225 226 var issuer = PKIXCommon.getSubjectAsASN1(icert); 227 228 var gen = new X509CertificateGenerator(cp.getCrypto()); 229 gen.encodeECDomainParameter = false; 230 231 gen.reset(); 232 gen.setSerialNumber(this.newSerialNumber()); 233 gen.setSignatureAlgorithm(this.policy.signatureAlgorithm); 234 gen.setIssuer(issuer); 235 var ced = new Date(); 236 var cxd = PKIXCommon.addDays(ced, this.policy.validityDaysCertificates); 237 gen.setNotBefore(ced); 238 gen.setNotAfter(cxd); 239 gen.setSubject(subject); 240 gen.setPublicKey(pubkey); 241 gen.addSubjectKeyIdentifierExtension(); 242 gen.addAuthorityKeyIdentifierExtension(icert.getSubjectKeyIdentifier()); 243 244 if ((typeof(this.policy.pathLenConstraint) != "undefined") && (this.policy.pathLenConstraint > 0)) { 245 gen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint - 1); 246 } else { 247 gen.addBasicConstraintsExtension(false, 0); 248 } 249 250 if (this.crldp.length > 0) { 251 gen.addCRLDistributionPointURL(this.crldp); 252 } 253 254 if (typeof(extensions) == "object") { 255 for (var i = 0; i < extensions.length; i++) { 256 gen.addExtension(extensions[i].oid, extensions[i].critical, extensions[i].value); 257 } 258 } 259 260 var cert = gen.generateX509Certificate(prk); 261 262 var id = this.storeCertificateForHolder(certholder, cert, false, undefined, srId); 263 } 264 finally { 265 cp.release(); 266 } 267 return { id: id, cert: cert }; 268 } 269 270 271 272 /** 273 * Extension handler method for Sub-CA certificates 274 * 275 * @private 276 */ 277 X509CertificateIssuer.prototype.addExtForSubCA = function(certgen, extvalues) { 278 279 certgen.addKeyUsageExtension(PKIXCommon.keyCertSign | PKIXCommon.cRLSign ); 280 certgen.addBasicConstraintsExtension(true, this.policy.pathLenConstraint - 1); 281 } 282 283 284 285 /** 286 * Extension handler method for TLS server certificates 287 * 288 * @private 289 */ 290 X509CertificateIssuer.prototype.addExtForTLSServer = function(certgen, extvalues) { 291 292 certgen.addKeyUsageExtension( PKIXCommon.keyAgreement | PKIXCommon.keyEncipherment); 293 certgen.addBasicConstraintsExtension(false, 0); 294 295 certgen.addExtendedKeyUsages(["id-csn-369791-tls-server", "id-kp-serverAuth"]); 296 297 var ext = new ASN1("subjectAltName", ASN1.SEQUENCE, 298 new ASN1("dNSName", 0x82, new ByteString(extvalues["dNSName"], ASCII)) 299 ); 300 certgen.addExtension("id-ce-subjectAltName", false, ext.getBytes()); 301 } 302 303 304 305 /** 306 * Extension handler method for TLS client certificates 307 * 308 * @private 309 */ 310 X509CertificateIssuer.prototype.addExtForTLSClient = function(certgen, extvalues) { 311 certgen.addKeyUsageExtension( PKIXCommon.digitalSignature); 312 certgen.addBasicConstraintsExtension(false, 0); 313 314 // certgen.addExtendedKeyUsages(["id-csn-369791-tls-client", "id-kp-clientAuth"]); 315 certgen.addExtendedKeyUsages(["id-kp-clientAuth"]); 316 } 317 318 319 320 /** 321 * Extension handler method for certificates suitable for TLS client authentication and e-Mail signature and encryption 322 * 323 * @private 324 */ 325 X509CertificateIssuer.prototype.addExtForEmailAndTLSClient = function(certgen, extvalues) { 326 327 certgen.addKeyUsageExtension( PKIXCommon.digitalSignature | PKIXCommon.keyEncipherment); 328 certgen.addBasicConstraintsExtension(false, 0); 329 330 var ext = new ASN1("subjectAltName", ASN1.SEQUENCE, 331 new ASN1("rfc822Name", 0x81, new ByteString(extvalues["email"], ASCII)) 332 ); 333 certgen.addExtension("id-ce-subjectAltName", false, ext.getBytes()); 334 335 certgen.addExtendedKeyUsages(["id-kp-clientAuth", "id-kp-emailProtection"]); 336 } 337 338 339 340 /** 341 * Issue a CRL 342 * 343 * @type CRL 344 * @return the newly issued CRL 345 */ 346 X509CertificateIssuer.prototype.issueCRL = function() { 347 GPSystem.log(GPSystem.DEBUG, module.id, "issueCRL()"); 348 349 var icert = this.getSignerCertificate(); 350 if (!icert) { 351 throw new GPError(module.id, GPError.INVALID_DATA, 1, "No active signer found"); 352 } 353 354 var c = this.signer.getContent(); 355 if (c && c.crl) { 356 var latestCRL = new CRLGenerator(); 357 var crlNumber = latestCRL.loadCRLEntries(new ByteString(c.crl, HEX)); 358 crlNumber++; 359 } 360 361 var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true); 362 363 if (!cp) { 364 throw new GPError(module.id, GPError.INVALID_DATA, 1, "Signer is offline (Key Domain : " + this.signer.keyDomain + ")"); 365 } 366 367 try { 368 var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob); 369 370 if (prk == null) { 371 throw new GPError(module.id, GPError.INVALID_DATA, 1, "No key with keyId " + this.signer.keyId + " found"); 372 } 373 374 var x = new CRLGenerator(cp.getCrypto()); 375 376 x.reset(); 377 x.setSignatureAlgorithm(this.policy.signatureAlgorithm); 378 var issuer = PKIXCommon.getSubjectAsASN1(icert); 379 x.setIssuer(issuer); 380 var now = new Date(); 381 x.setThisUpdate(now); 382 x.setNextUpdate(PKIXCommon.addDays(now, this.policy.validityDaysCRL)); 383 384 var certdao = this.daof.getCertificateDAO(); 385 var revocationList = certdao.getRevokedCertificates(this.holder.id, now); 386 387 for each (var cert in revocationList) { 388 var serial = new ByteString(cert.serial, HEX); 389 390 var reason = this.mapStatusToReasonCode(cert.status); 391 392 if (cert.revocationDate) { 393 var revocationDate = cert.revocationDate; 394 } else { 395 var revocationDate = now; 396 certdao.updateRevocationDate(cert.id, now); 397 } 398 399 if (cert.invalidityDate) { 400 var invalidityDate = cert.invalidityDate; 401 } else { 402 var invalidityDate = now; 403 } 404 var extensions = new ASN1("crlExtensions", ASN1.SEQUENCE); 405 extensions.add(x.createInvalidityDateExt(invalidityDate)); 406 407 x.revokeCertificate(serial, revocationDate, reason, extensions); 408 } 409 410 if (!crlNumber) { 411 var crlNumber = 1; 412 } 413 x.addCRLNumberExtension(crlNumber); 414 415 var crl = x.generateCRL(prk); 416 417 c.crl = crl.getBytes().toString(HEX); 418 this.signer.setContent(c); 419 var signerDAO = this.daof.getSignerDAO(); 420 signerDAO.updateContent(this.signer); 421 } 422 finally { 423 cp.release(); 424 } 425 return crl; 426 } 427 428 429 430 X509CertificateIssuer.prototype.mapStatusToReasonCode = function(status) { 431 switch(status) { 432 case OCSPQuery.REVOKED: 433 return null; // "...the reason code CRL entry extension SHOULD be absent instead of using the unspecified (0) reasonCode value." [RFC 5280, 5.3.1] 434 case OCSPQuery.KEYCOMPROMISE: 435 return CRLGenerator.keyCompromise; 436 case OCSPQuery.CACOMPROMISE: 437 return CRLGenerator.cACompromise; 438 case OCSPQuery.AFFILIATIONCHANGED: 439 return CRLGenerator.affiliationChanged; 440 case OCSPQuery.SUPERSEDED: 441 return CRLGenerator.superseded; 442 case OCSPQuery.CESSATIONOFOPERATION: 443 return CRLGenerator.cessationOfOperation; 444 case OCSPQuery.CERTIFICATEHOLD: 445 return CRLGenerator.certificateHold; 446 case OCSPQuery.REMOVEFROMCRL: 447 return CRLGenerator.removeFromCRL; 448 case OCSPQuery.PRIVILEGEWITHDRAWN: 449 return CRLGenerator.privilegeWithdrawn; 450 case OCSPQuery.AACOMPROMISE: 451 return CRLGenerator.aACompromise; 452 } 453 454 throw new GPError(module.id, GPError.INVALID_DATA, 1, "Unexpected status: " + status); 455 } 456 457 458 459 X509CertificateIssuer.prototype.getDNMask = function(holderId) { 460 var signer = this.getSigner(); 461 var c = signer.getContent(); 462 return c.dnmask; 463 } 464 465 466 467 X509CertificateIssuer.prototype.isOperational = function() { 468 GPSystem.log(GPSystem.DEBUG, module.id, "isOperational()"); 469 470 var icert = this.getSignerCertificate(); 471 if (!icert) { 472 // No active signer found 473 return false; 474 } 475 476 var cp = this.cpf.getCryptoProvider(this.signer.keyDomain, true); 477 478 if (!cp) { 479 // Signer offline 480 return false; 481 } 482 483 try { 484 var prk = cp.getPrivateKeyByKeyId(this.signer.keyId, this.signer.keyblob); 485 486 if (prk == null) { 487 // Key not found 488 return false; 489 } 490 } finally { 491 cp.release(); 492 } 493 494 return true; 495 } 496