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 25 * Simple CRL generator class 26 */ 27 28 PKIXCommon = require("scsh/x509/PKIXCommon").PKIXCommon; 29 30 31 32 /** 33 * Create a Certificate Revocation List (CRL) generator. 34 * 35 * @class Class implementing a CRL certificate generator 36 * @constructor 37 * 38 * @param {Crypto} crypto the crypto provider to use for signing operations 39 */ 40 function CRLGenerator(crypto) { 41 this.crypto = crypto; 42 this.reset(); 43 } 44 45 exports.CRLGenerator = CRLGenerator; 46 47 48 CRLGenerator.unspecified = 0; 49 CRLGenerator.keyCompromise = 1; 50 CRLGenerator.cACompromise = 2; 51 CRLGenerator.affiliationChanged = 3; 52 CRLGenerator.superseded = 4; 53 CRLGenerator.cessationOfOperation = 5; 54 CRLGenerator.certificateHold = 6; 55 CRLGenerator.removeFromCRL = 8; 56 CRLGenerator.privilegeWithdrawn = 9; 57 CRLGenerator.aACompromise = 10; 58 59 60 61 /** 62 * Resets all internal state variables. 63 * 64 */ 65 CRLGenerator.prototype.reset = function() { 66 this.extensions = []; 67 this.revokedCertificates = []; 68 this.nextUpdate = null; 69 } 70 71 72 73 /** 74 * Sets the isser name. 75 * 76 * <p>The issuer name must be a JavaScript object containing the properties:</p> 77 * <ul> 78 * <li>C - the country</li> 79 * <li>O - the organization</li> 80 * <li>OU - the organization unit</li> 81 * <li>CN - the common name</li> 82 * </ul> 83 * <p>Example:</p> 84 * <pre> 85 * var issuer = { C:"UT", O:"ACME Corporation", CN:"Test-CA" }; 86 * </pre> 87 * @param {Object} issuer the issuer name 88 */ 89 CRLGenerator.prototype.setIssuer = function(issuer) { 90 this.issuer = issuer; 91 } 92 93 94 95 /** 96 * Sets the timestamp for this CRL 97 * 98 * @param {Date} datetime the current date and time 99 */ 100 CRLGenerator.prototype.setThisUpdate = function(datetime) { 101 this.thisUpdate = datetime; 102 } 103 104 105 106 /** 107 * Sets the timestamp for the next update 108 * 109 * @param {Date} datetime the date and time of the next update 110 */ 111 CRLGenerator.prototype.setNextUpdate = function(datetime) { 112 this.nextUpdate = datetime; 113 } 114 115 116 117 /** 118 * Sets the signature algorithm. 119 * 120 * @param {Number} alg the signature algorithm, must be one of Crypto.RSA, Crypto.RSA_SHA256 or Crypto.ECDSA_SHA256 121 */ 122 CRLGenerator.prototype.setSignatureAlgorithm = function(alg) { 123 this.signatureAlgorithm = alg; 124 } 125 126 127 128 /** 129 * Adds an extension to the CRL 130 * 131 * <p>The structure is defined as:</p> 132 * <pre> 133 * Extension ::= SEQUENCE { 134 * extnID OBJECT IDENTIFIER, 135 * critical BOOLEAN DEFAULT FALSE, 136 * extnValue OCTET STRING 137 * -- contains the DER encoding of an ASN.1 value 138 * -- corresponding to the extension type identified 139 * -- by extnID 140 * } 141 *</pre> 142 * @param {String} extnID the extensions object identifier 143 * @param {Boolean} critical the extension is critical 144 * @param {ByteString} the extension value as ByteString 145 */ 146 CRLGenerator.prototype.addExtension = function(extnID, critical, extnValue) { 147 var t = new ASN1("extension", ASN1.SEQUENCE, 148 new ASN1("extnID", ASN1.OBJECT_IDENTIFIER, new ByteString(extnID, OID)) 149 ); 150 151 if (critical) { 152 t.add(new ASN1("critical", ASN1.BOOLEAN, new ByteString("FF", HEX))); 153 } 154 155 t.add(new ASN1("extnValue", ASN1.OCTET_STRING, extnValue)); 156 this.extensions.push(t); 157 158 } 159 160 161 162 /** 163 * Adds the authority public key identifier extension based on the issuers key. 164 * 165 * <p>The key identifier is calculated as SHA-1 hash over the contents of the 166 * issuer public key (Without tag, length and number of unused bits.</p> 167 */ 168 CRLGenerator.prototype.addAuthorityKeyIdentifierExtension = function(publicKey) { 169 if (publicKey.getComponent(Key.MODULUS)) { 170 var spi = PKIXCommon.createRSASubjectPublicKeyInfo(publicKey); 171 } else { 172 var spi = PKIXCommon.createECSubjectPublicKeyInfo(publicKey, this.encodeECDomainParameter); 173 } 174 175 var keyvalue = spi.get(1).value.bytes(1); 176 var hash = this.crypto.digest(Crypto.SHA_1, keyvalue); 177 178 var t = new ASN1(ASN1.SEQUENCE, 179 new ASN1(0x80, hash) 180 ); 181 this.addExtension("id-ce-authorityKeyIdentifier", false, t.getBytes()); 182 } 183 184 185 186 /** 187 * Adds the CRL number extension. 188 * 189 */ 190 CRLGenerator.prototype.addCRLNumberExtension = function(crlnumber) { 191 var t = new ASN1(ASN1.INTEGER, 192 PKIXCommon.convertUnsignedInteger(ByteString.valueOf(crlnumber)) 193 ); 194 this.addExtension("id-ce-cRLNumber", false, t.getBytes()); 195 } 196 197 198 199 /** 200 * Add a revoked certificate to the list. This adds the complete DER encoded structure. 201 * 202 * @param {ASN1} revokedCertificate the information related to the revoked certificate 203 */ 204 CRLGenerator.prototype.addRevokedCertificate = function(revokedCertificate) { 205 this.revokedCertificates.push(revokedCertificate); 206 } 207 208 209 210 CRLGenerator.prototype.createInvalidityDateExt = function(invalidityDate) { 211 var t = new ASN1("extension", ASN1.SEQUENCE, 212 new ASN1("extnID", ASN1.OBJECT_IDENTIFIER, 213 new ByteString("id-ce-invalidityDate", OID))); 214 215 var extnValue = new ASN1("invalidityDate", ASN1.GeneralizedTime, 216 new ByteString(PKIXCommon.dtoUTCFullYear(invalidityDate), ASCII)); 217 218 t.add(new ASN1("extnValue", ASN1.OCTET_STRING, extnValue.getBytes())); 219 220 return t; 221 } 222 223 224 /** 225 * Add a revoked certificate to the list. This adds the complete DER encoded structure. 226 * 227 * @param {ByteString} serial the serial number of the certificate to revoke 228 * @param {Date} timestamp the revocation time, optional, default is now 229 * @param {Number} reason the revocation reason 230 * @param {ASN1} ext the crl entry extensions 231 */ 232 CRLGenerator.prototype.revokeCertificate = function(serial, timestamp, reason, ext) { 233 if (typeof(timestamp) == "undefined") { 234 timestamp = new Date(); 235 } 236 var t = new ASN1("revokedCertificate", ASN1.SEQUENCE, 237 new ASN1("userCertificate", ASN1.INTEGER, PKIXCommon.convertUnsignedInteger(serial)), 238 new ASN1("revocationTime", ASN1.UTCTime, new ByteString(PKIXCommon.dtoUTC(timestamp), ASCII)) 239 ); 240 241 if (typeof(ext) == "undefined") { 242 ext = new ASN1("crlExtensions", ASN1.SEQUENCE); 243 } 244 245 if ((typeof(reason) != "undefined") && (reason != -1)) { 246 ext.add(new ASN1("reason", ASN1.SEQUENCE, 247 new ASN1("extnID", ASN1.OBJECT_IDENTIFIER, new ByteString("id-ce-cRLReasons", OID)), 248 new ASN1("extnValue", ASN1.OCTET_STRING, 249 (new ASN1("cRLReason", ASN1.ENUMERATED, ByteString.valueOf(reason))).getBytes()) 250 ) 251 ); 252 } 253 254 if (ext.length > 0) { 255 t.add(ext); 256 } 257 258 this.revokedCertificates.push(t); 259 } 260 261 262 263 /** 264 * Load list of revoked certificates from an existing CRL 265 * 266 * @param {ByteString} crlbin the DER encoded CRL 267 * @type Number 268 * @return the value of the CRLNumber extension, 0 if extension not defined or -1 if file could not be loaded 269 */ 270 CRLGenerator.prototype.loadCRLEntries = function(crlbin) { 271 var crlnumber = 0; 272 273 try { 274 var crl = new ASN1(crlbin); 275 print(crl); 276 var tbs = crl.get(0); 277 var i = 0; 278 if ((i < tbs.elements) && (tbs.get(i).tag == ASN1.INTEGER)) { // Skip version if present 279 i++; 280 } 281 i += 3; // Skip signature, issuer, thisUpdate 282 if ((i < tbs.elements) && (tbs.get(i).tag == ASN1.UTCTime)) { // nextUpdate if present 283 i++; 284 } 285 if ((i < tbs.elements) && (tbs.get(i).tag == ASN1.SEQUENCE)) { 286 for (var j = 0; j < tbs.get(i).elements; j++) { 287 this.revokedCertificates.push(tbs.get(i).get(j)); 288 } 289 i++; 290 } 291 if ((i < tbs.elements) && (tbs.get(i).tag == 0xA0)) { 292 var l = tbs.get(i).get(0); 293 var oid = new ByteString("id-ce-cRLNumber", OID); 294 for (var j = 0; j < l.elements; j++) { 295 var ext = l.get(j); 296 if (ext.get(0).value.equals(oid)) { 297 var extval = new ASN1(ext.get(1).value); 298 crlnumber = extval.value.toUnsigned(); 299 } 300 } 301 302 } 303 } 304 catch(e) { 305 GPSystem.trace(e); 306 return -1; 307 } 308 return crlnumber; 309 } 310 311 312 313 /** 314 * Gets the issuer name as TLV object 315 * 316 * @return the issuer RDNSequence 317 * @type ASN1 318 */ 319 CRLGenerator.prototype.getIssuer = function() { 320 if (this.issuer instanceof ASN1) { 321 return this.issuer; 322 } else { 323 return PKIXCommon.encodeName(this.issuer); 324 } 325 } 326 327 328 329 /** 330 * Gets the thisUpdate TLV object 331 * 332 * @return the thisUpdate UTC encoded time 333 * @type ASN1 334 */ 335 CRLGenerator.prototype.getThisUpdate = function() { 336 var t = new ASN1("thisUpdate", ASN1.UTCTime, new ByteString(PKIXCommon.dtoUTC(this.thisUpdate), ASCII)); 337 return t; 338 } 339 340 341 342 /** 343 * Gets the nextUpdate TLV object 344 * 345 * @return the nextUpdate UTC encoded time 346 * @type ASN1 347 */ 348 CRLGenerator.prototype.getNextUpdate = function() { 349 var t = new ASN1("nextUpdate", ASN1.UTCTime, new ByteString(PKIXCommon.dtoUTC(this.nextUpdate), ASCII)); 350 return t; 351 } 352 353 354 355 /** 356 * Gets the signature algorithm TLV object 357 * 358 * @return the signature algorithm object 359 * @type ASN1 360 */ 361 CRLGenerator.prototype.getSignatureAlgorithm = function() { 362 return PKIXCommon.encodeSignatureAlgorithm(this.signatureAlgorithm); 363 } 364 365 366 367 /** 368 * Gets revoked certificates 369 * 370 * @return the list of revoked certificates 371 * @type ASN1 372 */ 373 CRLGenerator.prototype.getRevokedCertificates = function() { 374 var t = new ASN1("revokedCertificates", ASN1.SEQUENCE); 375 for (var i = 0; i < this.revokedCertificates.length; i++) { 376 t.add(this.revokedCertificates[i]); 377 } 378 return t; 379 } 380 381 382 383 /** 384 * Gets the CRL extension as TLV object 385 * 386 * @return the CRL extensions 387 * @type ASN1 388 */ 389 CRLGenerator.prototype.getExtensions = function() { 390 var t = new ASN1("extensions", 0xA0); 391 var s = new ASN1("extensions", ASN1.SEQUENCE); 392 393 t.add(s); 394 395 for (var i = 0; i < this.extensions.length; i++) { 396 s.add(this.extensions[i]); 397 } 398 return t; 399 } 400 401 402 403 /** 404 * Gets the part of the CRL that will be signed 405 * 406 * @return the TBSCertificate part 407 * @type ASN1 408 */ 409 CRLGenerator.prototype.getTbsCertificateList = function() { 410 var t = new ASN1("tbsCertList", ASN1.SEQUENCE); 411 t.add(new ASN1("version", ASN1.INTEGER, new ByteString("01", HEX))); 412 t.add(this.getSignatureAlgorithm()); 413 t.add(this.getIssuer()); 414 t.add(this.getThisUpdate()); 415 if (this.nextUpdate != null) { 416 t.add(this.getNextUpdate()); 417 } 418 if (this.revokedCertificates.length > 0) { 419 t.add(this.getRevokedCertificates()); 420 } 421 if (this.extensions.length > 0) { 422 t.add(this.getExtensions()); 423 } 424 return t; 425 } 426 427 428 429 /** 430 * Generates the certificate. 431 * 432 * @return the generated certificate 433 * @type X509 434 */ 435 CRLGenerator.prototype.generateCRL = function(privateKey) { 436 var certlist = new ASN1("certificateList", ASN1.SEQUENCE); 437 438 var tbs = this.getTbsCertificateList(); 439 certlist.add(tbs); 440 certlist.add(this.getSignatureAlgorithm()); 441 442 var signature = this.crypto.sign(privateKey, this.signatureAlgorithm, tbs.getBytes()); 443 signature = (new ByteString("00", HEX)).concat(signature); 444 445 var signatureValue = new ASN1("signatureValue", ASN1.BIT_STRING, signature); 446 certlist.add(signatureValue); 447 448 return certlist; 449 } 450 451 452 453 CRLGenerator.test = function() { 454 455 var crypto = new Crypto(); 456 457 var caPrivateKey = new Key(); 458 caPrivateKey.setType(Key.PRIVATE); 459 460 var caPublicKey = new Key(); 461 caPublicKey.setType(Key.PUBLIC); 462 caPublicKey.setSize(1024); 463 464 crypto.generateKeyPair(Crypto.RSA, caPublicKey, caPrivateKey); 465 466 var x = new CRLGenerator(crypto); 467 468 x.reset(); 469 x.setSignatureAlgorithm(Crypto.RSA); 470 var issuer = [{ C:"UT" },{ O:"ACME Corporation" },{ CN:"Test-CA" }]; 471 x.setIssuer(issuer); 472 var now = new Date(); 473 x.setThisUpdate(now); 474 var nextMonth = new Date(); 475 nextMonth.setMonth((now.getMonth() + 1) % 12); 476 x.setNextUpdate(nextMonth); 477 x.addAuthorityKeyIdentifierExtension(caPublicKey); 478 x.addCRLNumberExtension(11); 479 480 x.revokeCertificate(new ByteString("01", HEX)); 481 x.revokeCertificate(new ByteString("80", HEX), new Date()); 482 x.revokeCertificate(new ByteString("80", HEX), new Date(), CRLGenerator.keyCompromise); 483 x.revokeCertificate(new ByteString("80", HEX), new Date(), CRLGenerator.superseded, new ASN1("crlExtensions", ASN1.SEQUENCE)); 484 485 var extensions = new ASN1("crlExtensions", ASN1.SEQUENCE); 486 extensions.add(x.createInvalidityDateExt(new Date())); 487 x.revokeCertificate(new ByteString("80", HEX), new Date(), CRLGenerator.superseded, extensions); 488 489 var crl = x.generateCRL(caPrivateKey); 490 491 var fn = GPSystem.mapFilename("crl.crl", GPSystem.USR); 492 PKIXCommon.writeFileToDisk(fn, crl.getBytes()); 493 494 print(crl); 495 496 var x = new CRLGenerator(crypto); 497 x.reset(); 498 x.setSignatureAlgorithm(Crypto.RSA); 499 var issuer = [{ C:"UT" },{ O:"ACME Corporation" },{ CN:"Test-CA" }]; 500 x.setIssuer(issuer); 501 var now = new Date(); 502 x.setThisUpdate(now); 503 var nextMonth = new Date(); 504 nextMonth.setMonth((now.getMonth() + 1) % 12); 505 x.setNextUpdate(nextMonth); 506 var crlnumber = x.loadCRLEntries(crl.getBytes()); 507 assert(crlnumber == 11); 508 x.addAuthorityKeyIdentifierExtension(caPublicKey); 509 x.addCRLNumberExtension(crlnumber + 1); 510 511 x.revokeCertificate(crypto.generateRandom(8), new Date(), CRLGenerator.keyCompromise); 512 var crl = x.generateCRL(caPrivateKey); 513 // PKIXCommon.writeFileToDisk(fn, crl.getBytes()); 514 515 print(crl); 516 } 517 518 519