      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 var 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.
    152  *
    153  * @param {Number} alg the signature algorithm.
    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 	if (ts >= "500101000000Z") {
    369 		ts = "20" + ts;
    370 		t.add(new ASN1("notBefore", ASN1.GeneralizedTime, new ByteString(ts, ASCII)));
    371 	} else {
    372 		t.add(new ASN1("notBefore", ASN1.UTCTime, new ByteString(ts, ASCII)));
    373 	}
    374
    375 	var ts = this.notAfter;
    376 	if (typeof(ts) != "string") {
    377 		ts = PKIXCommon.dtoUTC(this.notAfter);
    378 	}
    379 	if (ts >= "500101000000Z") {
    380 		ts = "20" + ts;
    381 		t.add(new ASN1("notAfter", ASN1.GeneralizedTime, new ByteString(ts, ASCII)));
    382 	} else {
    383 		t.add(new ASN1("notAfter", ASN1.UTCTime, new ByteString(ts, ASCII)));
    384 	}
    385 	return t;
    386 }
    387
    388
    389
    390 /**
    391  * Gets the subject name as TLV object
    392  *
    393  * @return the issuer RDNSequence
    394  * @type ASN1
    395  */
    396 X509CertificateGenerator.prototype.getSubject = function() {
    397 	if (this.subject instanceof ASN1) {
    398 		return this.subject;
    399 	} else {
    400 		return PKIXCommon.encodeName(this.subject);
    401 	}
    402 }
    403
    404
    405
    406 /**
    407  * Gets the subject's public key as TLV object
    408  *
    409  * @return the subject's public key info
    410  * @type ASN1
    411  */
    412 X509CertificateGenerator.prototype.getSubjectPublicKeyInfo = function() {
    413 	if (this.publicKey.getComponent(Key.MODULUS)) {
    414 		return PKIXCommon.createRSASubjectPublicKeyInfo(this.publicKey);
    415 	} else {
    416 		return PKIXCommon.createECSubjectPublicKeyInfo(this.publicKey, this.encodeECDomainParameter);
    417 	}
    418 }
    419
    420
    421
    422 /**
    423  * Gets the certificate extension as TLV object
    424  *
    425  * @return the certificate extensions
    426  * @type ASN1
    427  */
    428 X509CertificateGenerator.prototype.getExtensions = function() {
    429 	var t = new ASN1("extensions", 0xA3);
    430 	var s = new ASN1("extensions", ASN1.SEQUENCE);
    431
    432 	t.add(s);
    433
    434 	for (var i = 0; i < this.extensions.length; i++) {
    435 		s.add(this.extensions[i]);
    436 	}
    437 	return t;
    438 }
    439
    440
    441
    442 /**
    443  * Gets the part of the certificate that will be signed
    444  *
    445  * @return the TBSCertificate part
    446  * @type ASN1
    447  */
    448 X509CertificateGenerator.prototype.getTbsCertificate = function() {
    449 	var t = new ASN1("tbsCertificate", ASN1.SEQUENCE);
    450 	t.add(new ASN1("version", 0xA0,
    451 			new ASN1("version", ASN1.INTEGER, new ByteString("02", HEX))));
    452 	t.add(new ASN1("serialNumber", ASN1.INTEGER, PKIXCommon.convertUnsignedInteger(this.serialNumber)));
    453 	t.add(this.getSignatureAlgorithm());
    454 	t.add(this.getIssuer());
    455 	t.add(this.getValidity());
    456 	t.add(this.getSubject());
    457 	t.add(this.getSubjectPublicKeyInfo());
    458 	t.add(this.getExtensions());
    459 	return t;
    460 }
    461
    462
    463
    464 /**
    465  * Gets the signature algorithm TLV object
    466  *
    467  * @return the signature algorithm object
    468  * @type ASN1
    469  */
    470 X509CertificateGenerator.prototype.getSignatureAlgorithm = function() {
    471 	return PKIXCommon.encodeSignatureAlgorithm(this.signatureAlgorithm);
    472 }
    473
    474
    475
    476 /**
    477  * Generates the certificate.
    478  *
    479  * @return the generated certificate
    480  * @type X509
    481  */
    482 X509CertificateGenerator.prototype.generateX509Certificate = function(privateKey) {
    483 	var certificate = new ASN1("certificate", ASN1.SEQUENCE);
    484
    485 	var tbs = this.getTbsCertificate();
    486 	certificate.add(tbs);
    487 	certificate.add(this.getSignatureAlgorithm());
    488
    489 	var signature = this.crypto.sign(privateKey, this.signatureAlgorithm, tbs.getBytes());
    490 	signature = (new ByteString("00", HEX)).concat(signature);
    491
    492 	var signatureValue = new ASN1("signatureValue", ASN1.BIT_STRING, signature);
    493 	certificate.add(signatureValue);
    494
    495 //	print(certificate);
    496 	return new X509(certificate.getBytes());
    497 }
    498
    499
    500
    501 X509CertificateGenerator.RSATest = function() {
    502
    503 	var crypto = new Crypto();
    504
    505 	var caPrivateKey = new Key();
    506 	caPrivateKey.setType(Key.PRIVATE);
    507
    508 	var caPublicKey = new Key();
    509 	caPublicKey.setType(Key.PUBLIC);
    510 	caPublicKey.setSize(1024);
    511
    512 	crypto.generateKeyPair(Crypto.RSA, caPublicKey, caPrivateKey);
    513
    514 //	var caPrivKey = new Key("profiles/kp_rsa_private.xml");
    515
    516 	var x = new X509CertificateGenerator(crypto);
    517
    518 	x.reset();
    519 	x.setSerialNumber(new ByteString("01", HEX));
    520 	x.setSignatureAlgorithm(Crypto.RSA);
    521 	var issuer = { C:"UT", O:"ACME Corporation", CN:"Test-CA" };
    522 	x.setIssuer(issuer);
    523 	x.setNotBefore("060825120000Z");
    524 	x.setNotAfter("160825120000Z");
    525 	var subject = { C:"UT", O:"Utopia CA", OU:"ACME Corporation", CN:"Joe Doe" };
    526 	x.setSubject(subject);
    527
    528 	x.setPublicKey(caPublicKey);
    529
    530 	x.addKeyUsageExtension(	PKIXCommon.digitalSignature |
    531 							PKIXCommon.keyCertSign |
    532 							PKIXCommon.cRLSign );
    533
    534 	x.addBasicConstraintsExtension(true, 0);
    535 	x.addSubjectKeyIdentifierExtension();
    536 	x.addAuthorityKeyIdentifierExtension(caPublicKey);
    537
    538 	var cert = x.generateX509Certificate(caPrivateKey);
    539 	var fn = GPSystem.mapFilename("cert_rsa.cer", GPSystem.USR);
    540 	PKIXCommon.writeFileToDisk(fn, cert.getBytes());
    541
    542 	cert.verifyWith(cert);
    543
    544 	print(cert);
    545 }
    546
    547
    548
    549 X509CertificateGenerator.ECCTest = function() {
    550
    551 	var crypto = new Crypto();
    552
    553 	var caPrivateKey = new Key();
    554 	caPrivateKey.setType(Key.PRIVATE);
    555
    556 	var caPublicKey = new Key();
    557 	caPublicKey.setType(Key.PUBLIC);
    558 	caPublicKey.setComponent(Key.ECC_CURVE_OID, new ByteString("brainpoolP256r1", OID));
    559
    560 	crypto.generateKeyPair(Crypto.EC, caPublicKey, caPrivateKey);
    561
    562 	var x = new X509CertificateGenerator(crypto);
    563
    564 	x.reset();
    565 	x.setSerialNumber(new ByteString("01", HEX));
    566 	x.setSignatureAlgorithm(Crypto.ECDSA_SHA256);
    567 	var issuer = { C:"UT", O:"ACME Corporation", CN:"Test-CA" };
    568 	x.setIssuer(issuer);
    569 	var t = new Date();
    570 	x.setNotBefore(t);
    571 	x.setNotAfter(PKIXCommon.addDays(t, 180));
    572 	var subject = { C:"UT", O:"Utopia CA", OU:"ACME Corporation", CN:"Joe Doe" };
    573 	x.setSubject(subject);
    574
    575 	x.setPublicKey(caPublicKey);
    576
    577 	x.addKeyUsageExtension(	PKIXCommon.digitalSignature |
    578 							PKIXCommon.keyCertSign |
    579 							PKIXCommon.cRLSign );
    580
    581 	x.addBasicConstraintsExtension(true, 0);
    582 	x.addSubjectKeyIdentifierExtension();
    583 	x.addAuthorityKeyIdentifierExtension(caPublicKey);
    584
    585 	var cert = x.generateX509Certificate(caPrivateKey);
    586
    587 	var fn = GPSystem.mapFilename("cert_ecc.cer", GPSystem.USR);
    588 	PKIXCommon.writeFileToDisk(fn, cert.getBytes());
    589
    590 	cert.verifyWith(cert);
    591
    592 	print(cert);
    593 	print(new ASN1(cert.getBytes()));
    594 }
    595