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 X509 certificate generator class following RFC5280 25 */ 26 27 PKIXCommon = require("scsh/x509/PKIXCommon").PKIXCommon; 28 29 30 31 /** 32 * Create a X.509 certificate generator. 33 * 34 * @class Class implementing a X.509 certificate generator 35 * @constructor 36 * 37 * @param {Crypto} crypto the crypto provider to use for signing operations 38 */ 39 function X509CertificateGenerator(crypto) { 40 this.crypto = crypto; 41 this.encodeECDomainParameter = true; 42 this.reset(); 43 } 44 45 exports.X509CertificateGenerator = X509CertificateGenerator; 46 47 48 49 /** 50 * Resets all internal state variables. 51 * 52 */ 53 X509CertificateGenerator.prototype.reset = function() { 54 this.extensions = new Array(); 55 56 } 57 58 59 60 /** 61 * Sets the serial number. 62 * 63 * @param {ByteString} serialNumber the serial number for the certificate 64 */ 65 X509CertificateGenerator.prototype.setSerialNumber = function(serialNumber) { 66 this.serialNumber = serialNumber; 67 } 68 69 70 71 /** 72 * Sets the isser name. 73 * 74 * <p>The issuer name must be a JavaScript object containing the properties:</p> 75 * <ul> 76 * <li>C - the country</li> 77 * <li>O - the organization</li> 78 * <li>OU - the organization unit</li> 79 * <li>CN - the common name</li> 80 * </ul> 81 * <p>Example:</p> 82 * <pre> 83 * var issuer = { C:"UT", O:"ACME Corporation", CN:"Test-CA" }; 84 * </pre> 85 * @param {Object} issuer the issuer name 86 */ 87 X509CertificateGenerator.prototype.setIssuer = function(issuer) { 88 this.issuer = issuer; 89 } 90 91 92 93 /** 94 * Sets the effective date for the certificate. 95 * 96 * @param {String or Date} date the date in format YYMMDDHHMMSSZ 97 */ 98 X509CertificateGenerator.prototype.setNotBefore = function(date) { 99 this.notBefore = date; 100 } 101 102 103 104 /** 105 * Sets the expiration date for the certificate. 106 * 107 * @param {String or Date} date the date in format YYMMDDHHMMSSZ 108 */ 109 X509CertificateGenerator.prototype.setNotAfter = function(date) { 110 this.notAfter = date; 111 } 112 113 114 115 /** 116 * Sets the subject name. 117 * 118 * <p>The subject name must be a JavaScript object containing the properties:</p> 119 * <ul> 120 * <li>C - the country</li> 121 * <li>O - the organization</li> 122 * <li>OU - the organization unit</li> 123 * <li>CN - the common name</li> 124 * </ul> 125 * <p>Example:</p> 126 * <pre> 127 * var subject = { C:"UT", O:"ACME Corporation", CN:"Joe Doe" }; 128 * </pre> 129 * @param {Object} subject the subject name 130 */ 131 X509CertificateGenerator.prototype.setSubject = function(subject) { 132 this.subject = subject; 133 } 134 135 136 137 /** 138 * Sets the subjects public key 139 * 140 * <p>The methods accepts ECC and RSA Public Keys.</p> 141 * 142 * @param {Key} publicKey the subjects public key 143 */ 144 X509CertificateGenerator.prototype.setPublicKey = function(publicKey) { 145 this.publicKey = publicKey; 146 } 147 148 149 150 /** 151 * Sets the signature algorithm. Currently only Crypto.RSA is supported 152 * 153 * @param {Number} alg the signature algorithm, only Crypto.RSA supported 154 */ 155 X509CertificateGenerator.prototype.setSignatureAlgorithm = function(alg) { 156 this.signatureAlgorithm = alg; 157 } 158 159 160 161 /** 162 * Adds an extension to the certificate 163 * 164 * <p>The structure is defined as:</p> 165 * <pre> 166 * Extension ::= SEQUENCE { 167 * extnID OBJECT IDENTIFIER, 168 * critical BOOLEAN DEFAULT FALSE, 169 * extnValue OCTET STRING 170 * -- contains the DER encoding of an ASN.1 value 171 * -- corresponding to the extension type identified 172 * -- by extnID 173 * } 174 *</pre> 175 * @param {String} extnID the extensions object identifier 176 * @param {Boolean} critical the extension is critical 177 * @param {ByteString} the extension value as ByteString 178 */ 179 X509CertificateGenerator.prototype.addExtension = function(extnID, critical, extnValue) { 180 var t = new ASN1("extension", ASN1.SEQUENCE, 181 new ASN1("extnID", ASN1.OBJECT_IDENTIFIER, new ByteString(extnID, OID)) 182 ); 183 184 if (critical) { 185 t.add(new ASN1("critical", ASN1.BOOLEAN, new ByteString("FF", HEX))); 186 } 187 188 t.add(new ASN1("extnValue", ASN1.OCTET_STRING, extnValue)); 189 this.extensions.push(t); 190 191 } 192 193 194 195 /** 196 * Adds the key usage extension. 197 * 198 * <p>The following flags are defined:</p> 199 * <pre> 200 * PKIXCommon.digitalSignature = 0x0080; 201 * PKIXCommon.nonRepudiation = 0x0040; 202 * PKIXCommon.keyEncipherment = 0x0020; 203 * PKIXCommon.dataEncipherment = 0x0010; 204 * PKIXCommon.keyAgreement = 0x0008; 205 * PKIXCommon.keyCertSign = 0x0004; 206 * PKIXCommon.cRLSign = 0x0002; 207 * PKIXCommon.encipherOnly = 0x0001; 208 * PKIXCommon.decipherOnly = 0x8000; 209 * </pre> 210 * @param {Number} the key usage flags as combination of the flags defined above. 211 */ 212 X509CertificateGenerator.prototype.addKeyUsageExtension = function(flags) { 213 var t = new ASN1(ASN1.BIT_STRING, PKIXCommon.bitstringForInteger(flags)); 214 this.addExtension("2.5.29.15", true, t.getBytes()); 215 } 216 217 // Deprecated: Use PKIXCommon. instead 218 X509CertificateGenerator.digitalSignature = 0x0080; 219 X509CertificateGenerator.nonRepudiation = 0x0040; 220 X509CertificateGenerator.keyEncipherment = 0x0020; 221 X509CertificateGenerator.dataEncipherment = 0x0010; 222 X509CertificateGenerator.keyAgreement = 0x0008; 223 X509CertificateGenerator.keyCertSign = 0x0004; 224 X509CertificateGenerator.cRLSign = 0x0002; 225 X509CertificateGenerator.encipherOnly = 0x0001; 226 X509CertificateGenerator.decipherOnly = 0x8000; 227 228 229 230 /** 231 * Adds the BasicConstraints extension. 232 * 233 * @param {Boolean} cA the certificate belongs to a CA 234 * @param {Number} pathLenConstraint the maximum number of subordinate CA certificates 235 */ 236 X509CertificateGenerator.prototype.addBasicConstraintsExtension = function(cA, pathLenConstraint) { 237 var t = new ASN1("BasicConstraints",ASN1.SEQUENCE); 238 if (cA) { 239 t.add(new ASN1("cA", ASN1.BOOLEAN, new ByteString("FF", HEX))); 240 } 241 if (pathLenConstraint >= 0) { 242 var bb = new ByteBuffer(); 243 bb.append(pathLenConstraint); 244 t.add(new ASN1("pathLenConstraint", ASN1.INTEGER, bb.toByteString())); 245 } 246 this.addExtension("2.5.29.19", true, t.getBytes()); 247 } 248 249 250 251 /** 252 * Adds the subject public key identifier extension based on the certificates subject key. 253 * 254 * <p>The key identifier is calculated as SHA-1 hash over the contents of the 255 * subject public key (Without tag, length and number of unused bits.</p> 256 */ 257 X509CertificateGenerator.prototype.addSubjectKeyIdentifierExtension = function() { 258 var spi = this.getSubjectPublicKeyInfo(); 259 var keyvalue = spi.get(1).value.bytes(1); 260 var crypto = new Crypto(); 261 var hash = crypto.digest(Crypto.SHA_1, keyvalue); 262 263 var t = new ASN1(ASN1.OCTET_STRING, hash); 264 this.addExtension("2.5.29.14", false, t.getBytes()); 265 } 266 267 268 269 /** 270 * Adds the authority public key identifier extension based on the issuers key. 271 * 272 * <p>The key identifier is calculated as SHA-1 hash over the contents of the 273 * issuer public key (Without tag, length and number of unused bits.</p> 274 * 275 * @param {Key/ByteString} publicKeyOrId the authority subject key or authority key identifier 276 */ 277 X509CertificateGenerator.prototype.addAuthorityKeyIdentifierExtension = function(publicKeyOrId) { 278 if (publicKeyOrId instanceof Key) { 279 if (publicKeyOrId.getComponent(Key.MODULUS)) { 280 var spi = PKIXCommon.createRSASubjectPublicKeyInfo(publicKeyOrId); 281 } else { 282 var spi = PKIXCommon.createECSubjectPublicKeyInfo(publicKeyOrId, this.encodeECDomainParameter); 283 } 284 285 var keyvalue = spi.get(1).value.bytes(1); 286 var crypto = new Crypto(); 287 var hash = crypto.digest(Crypto.SHA_1, keyvalue); 288 } else { 289 var hash = publicKeyOrId; 290 } 291 292 var t = new ASN1(ASN1.SEQUENCE, 293 new ASN1(0x80, hash) 294 ); 295 this.addExtension("2.5.29.35", false, t.getBytes()); 296 } 297 298 299 300 /** 301 * Adds the CRL distribution point URLs. 302 * 303 * @param {String[]} url a list of URLs 304 */ 305 X509CertificateGenerator.prototype.addCRLDistributionPointURL = function(url) { 306 var t = new ASN1("cRLDistributionPoints", ASN1.SEQUENCE); 307 308 for (var i = 0; i < url.length; i++) { 309 t.add( new ASN1("cRLDistributionPoint", ASN1.SEQUENCE, 310 new ASN1("distributionPoint", 0xA0, 311 new ASN1("fullName", 0xA0, 312 new ASN1("uniformResourceIdentifier", 0x86, new ByteString(url[i], ASCII)) 313 ) 314 ) 315 )); 316 } 317 this.addExtension("id-ce-cRLDistributionPoints", false, t.getBytes()); 318 } 319 320 321 322 /** 323 * Adds the extended key usage extension 324 * 325 * @param {String[]} oids the list of object identifier names 326 * @param {Boolean} critical the extension is critical 327 */ 328 X509CertificateGenerator.prototype.addExtendedKeyUsages = function(oids, critical) { 329 var t = new ASN1("extKeyUsages", ASN1.SEQUENCE); 330 331 for (var i = 0; i < oids.length; i++) { 332 t.add( new ASN1("keyPurposeId", ASN1.OBJECT_IDENTIFIER, new ByteString(oids[i], OID) )); 333 } 334 this.addExtension("id-ce-extKeyUsage", critical, t.getBytes()); 335 } 336 337 338 339 /** 340 * Gets the issuer name as TLV object 341 * 342 * @return the issuer RDNSequence 343 * @type ASN1 344 */ 345 X509CertificateGenerator.prototype.getIssuer = function() { 346 if (this.issuer instanceof ASN1) { 347 return this.issuer; 348 } else { 349 return PKIXCommon.encodeName(this.issuer); 350 } 351 } 352 353 354 355 /** 356 * Gets the certificate validity as TLV object 357 * 358 * @return the certificates validity 359 * @type ASN1 360 */ 361 X509CertificateGenerator.prototype.getValidity = function() { 362 var t = new ASN1("validity", ASN1.SEQUENCE); 363 364 var ts = this.notBefore; 365 if (typeof(ts) != "string") { 366 ts = PKIXCommon.dtoUTC(this.notBefore); 367 } 368 t.add(new ASN1("notBefore", ASN1.UTCTime, new ByteString(ts, ASCII))); 369 370 var ts = this.notAfter; 371 if (typeof(ts) != "string") { 372 ts = PKIXCommon.dtoUTC(this.notAfter); 373 } 374 t.add(new ASN1("notAfter", ASN1.UTCTime, new ByteString(ts, ASCII))); 375 return t; 376 } 377 378 379 380 /** 381 * Gets the subject name as TLV object 382 * 383 * @return the issuer RDNSequence 384 * @type ASN1 385 */ 386 X509CertificateGenerator.prototype.getSubject = function() { 387 if (this.subject instanceof ASN1) { 388 return this.subject; 389 } else { 390 return PKIXCommon.encodeName(this.subject); 391 } 392 } 393 394 395 396 /** 397 * Gets the subject's public key as TLV object 398 * 399 * @return the subject's public key info 400 * @type ASN1 401 */ 402 X509CertificateGenerator.prototype.getSubjectPublicKeyInfo = function() { 403 if (this.publicKey.getComponent(Key.MODULUS)) { 404 return PKIXCommon.createRSASubjectPublicKeyInfo(this.publicKey); 405 } else { 406 return PKIXCommon.createECSubjectPublicKeyInfo(this.publicKey, this.encodeECDomainParameter); 407 } 408 } 409 410 411 412 /** 413 * Gets the certificate extension as TLV object 414 * 415 * @return the certificate extensions 416 * @type ASN1 417 */ 418 X509CertificateGenerator.prototype.getExtensions = function() { 419 var t = new ASN1("extensions", 0xA3); 420 var s = new ASN1("extensions", ASN1.SEQUENCE); 421 422 t.add(s); 423 424 for (var i = 0; i < this.extensions.length; i++) { 425 s.add(this.extensions[i]); 426 } 427 return t; 428 } 429 430 431 432 /** 433 * Gets the part of the certificate that will be signed 434 * 435 * @return the TBSCertificate part 436 * @type ASN1 437 */ 438 X509CertificateGenerator.prototype.getTbsCertificate = function() { 439 var t = new ASN1("tbsCertificate", ASN1.SEQUENCE); 440 t.add(new ASN1("version", 0xA0, 441 new ASN1("version", ASN1.INTEGER, new ByteString("02", HEX)))); 442 t.add(new ASN1("serialNumber", ASN1.INTEGER, PKIXCommon.convertUnsignedInteger(this.serialNumber))); 443 t.add(this.getSignatureAlgorithm()); 444 t.add(this.getIssuer()); 445 t.add(this.getValidity()); 446 t.add(this.getSubject()); 447 t.add(this.getSubjectPublicKeyInfo()); 448 t.add(this.getExtensions()); 449 return t; 450 } 451 452 453 454 /** 455 * Gets the signature algorithm TLV object 456 * 457 * @return the signature algorithm object 458 * @type ASN1 459 */ 460 X509CertificateGenerator.prototype.getSignatureAlgorithm = function() { 461 return PKIXCommon.encodeSignatureAlgorithm(this.signatureAlgorithm); 462 } 463 464 465 466 /** 467 * Generates the certificate. 468 * 469 * @return the generated certificate 470 * @type X509 471 */ 472 X509CertificateGenerator.prototype.generateX509Certificate = function(privateKey) { 473 var certificate = new ASN1("certificate", ASN1.SEQUENCE); 474 475 var tbs = this.getTbsCertificate(); 476 certificate.add(tbs); 477 certificate.add(this.getSignatureAlgorithm()); 478 479 var signature = this.crypto.sign(privateKey, this.signatureAlgorithm, tbs.getBytes()); 480 signature = (new ByteString("00", HEX)).concat(signature); 481 482 var signatureValue = new ASN1("signatureValue", ASN1.BIT_STRING, signature); 483 certificate.add(signatureValue); 484 485 // print(certificate); 486 return new X509(certificate.getBytes()); 487 } 488 489 490 491 X509CertificateGenerator.RSATest = function() { 492 493 var crypto = new Crypto(); 494 495 var caPrivateKey = new Key(); 496 caPrivateKey.setType(Key.PRIVATE); 497 498 var caPublicKey = new Key(); 499 caPublicKey.setType(Key.PUBLIC); 500 caPublicKey.setSize(1024); 501 502 crypto.generateKeyPair(Crypto.RSA, caPublicKey, caPrivateKey); 503 504 // var caPrivKey = new Key("profiles/kp_rsa_private.xml"); 505 506 var x = new X509CertificateGenerator(crypto); 507 508 x.reset(); 509 x.setSerialNumber(new ByteString("01", HEX)); 510 x.setSignatureAlgorithm(Crypto.RSA); 511 var issuer = { C:"UT", O:"ACME Corporation", CN:"Test-CA" }; 512 x.setIssuer(issuer); 513 x.setNotBefore("060825120000Z"); 514 x.setNotAfter("160825120000Z"); 515 var subject = { C:"UT", O:"Utopia CA", OU:"ACME Corporation", CN:"Joe Doe" }; 516 x.setSubject(subject); 517 518 x.setPublicKey(caPublicKey); 519 520 x.addKeyUsageExtension( PKIXCommon.digitalSignature | 521 PKIXCommon.keyCertSign | 522 PKIXCommon.cRLSign ); 523 524 x.addBasicConstraintsExtension(true, 0); 525 x.addSubjectKeyIdentifierExtension(); 526 x.addAuthorityKeyIdentifierExtension(caPublicKey); 527 528 var cert = x.generateX509Certificate(caPrivateKey); 529 var fn = GPSystem.mapFilename("cert_rsa.cer", GPSystem.USR); 530 PKIXCommon.writeFileToDisk(fn, cert.getBytes()); 531 532 cert.verifyWith(cert); 533 534 print(cert); 535 } 536 537 538 539 X509CertificateGenerator.ECCTest = function() { 540 541 var crypto = new Crypto(); 542 543 var caPrivateKey = new Key(); 544 caPrivateKey.setType(Key.PRIVATE); 545 546 var caPublicKey = new Key(); 547 caPublicKey.setType(Key.PUBLIC); 548 caPublicKey.setComponent(Key.ECC_CURVE_OID, new ByteString("brainpoolP256r1", OID)); 549 550 crypto.generateKeyPair(Crypto.EC, caPublicKey, caPrivateKey); 551 552 var x = new X509CertificateGenerator(crypto); 553 554 x.reset(); 555 x.setSerialNumber(new ByteString("01", HEX)); 556 x.setSignatureAlgorithm(Crypto.ECDSA_SHA256); 557 var issuer = { C:"UT", O:"ACME Corporation", CN:"Test-CA" }; 558 x.setIssuer(issuer); 559 var t = new Date(); 560 x.setNotBefore(t); 561 x.setNotAfter(PKIXCommon.addDays(t, 180)); 562 var subject = { C:"UT", O:"Utopia CA", OU:"ACME Corporation", CN:"Joe Doe" }; 563 x.setSubject(subject); 564 565 x.setPublicKey(caPublicKey); 566 567 x.addKeyUsageExtension( PKIXCommon.digitalSignature | 568 PKIXCommon.keyCertSign | 569 PKIXCommon.cRLSign ); 570 571 x.addBasicConstraintsExtension(true, 0); 572 x.addSubjectKeyIdentifierExtension(); 573 x.addAuthorityKeyIdentifierExtension(caPublicKey); 574 575 var cert = x.generateX509Certificate(caPrivateKey); 576 577 var fn = GPSystem.mapFilename("cert_ecc.cer", GPSystem.USR); 578 PKIXCommon.writeFileToDisk(fn, cert.getBytes()); 579 580 cert.verifyWith(cert); 581 582 print(cert); 583 print(new ASN1(cert.getBytes())); 584 } 585