twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

certificate.js (11600B)


      1 // Copyright 2016 Joyent, Inc.
      2 
      3 module.exports = Certificate;
      4 
      5 var assert = require('assert-plus');
      6 var Buffer = require('safer-buffer').Buffer;
      7 var algs = require('./algs');
      8 var crypto = require('crypto');
      9 var Fingerprint = require('./fingerprint');
     10 var Signature = require('./signature');
     11 var errs = require('./errors');
     12 var util = require('util');
     13 var utils = require('./utils');
     14 var Key = require('./key');
     15 var PrivateKey = require('./private-key');
     16 var Identity = require('./identity');
     17 
     18 var formats = {};
     19 formats['openssh'] = require('./formats/openssh-cert');
     20 formats['x509'] = require('./formats/x509');
     21 formats['pem'] = require('./formats/x509-pem');
     22 
     23 var CertificateParseError = errs.CertificateParseError;
     24 var InvalidAlgorithmError = errs.InvalidAlgorithmError;
     25 
     26 function Certificate(opts) {
     27 	assert.object(opts, 'options');
     28 	assert.arrayOfObject(opts.subjects, 'options.subjects');
     29 	utils.assertCompatible(opts.subjects[0], Identity, [1, 0],
     30 	    'options.subjects');
     31 	utils.assertCompatible(opts.subjectKey, Key, [1, 0],
     32 	    'options.subjectKey');
     33 	utils.assertCompatible(opts.issuer, Identity, [1, 0], 'options.issuer');
     34 	if (opts.issuerKey !== undefined) {
     35 		utils.assertCompatible(opts.issuerKey, Key, [1, 0],
     36 		    'options.issuerKey');
     37 	}
     38 	assert.object(opts.signatures, 'options.signatures');
     39 	assert.buffer(opts.serial, 'options.serial');
     40 	assert.date(opts.validFrom, 'options.validFrom');
     41 	assert.date(opts.validUntil, 'optons.validUntil');
     42 
     43 	assert.optionalArrayOfString(opts.purposes, 'options.purposes');
     44 
     45 	this._hashCache = {};
     46 
     47 	this.subjects = opts.subjects;
     48 	this.issuer = opts.issuer;
     49 	this.subjectKey = opts.subjectKey;
     50 	this.issuerKey = opts.issuerKey;
     51 	this.signatures = opts.signatures;
     52 	this.serial = opts.serial;
     53 	this.validFrom = opts.validFrom;
     54 	this.validUntil = opts.validUntil;
     55 	this.purposes = opts.purposes;
     56 }
     57 
     58 Certificate.formats = formats;
     59 
     60 Certificate.prototype.toBuffer = function (format, options) {
     61 	if (format === undefined)
     62 		format = 'x509';
     63 	assert.string(format, 'format');
     64 	assert.object(formats[format], 'formats[format]');
     65 	assert.optionalObject(options, 'options');
     66 
     67 	return (formats[format].write(this, options));
     68 };
     69 
     70 Certificate.prototype.toString = function (format, options) {
     71 	if (format === undefined)
     72 		format = 'pem';
     73 	return (this.toBuffer(format, options).toString());
     74 };
     75 
     76 Certificate.prototype.fingerprint = function (algo) {
     77 	if (algo === undefined)
     78 		algo = 'sha256';
     79 	assert.string(algo, 'algorithm');
     80 	var opts = {
     81 		type: 'certificate',
     82 		hash: this.hash(algo),
     83 		algorithm: algo
     84 	};
     85 	return (new Fingerprint(opts));
     86 };
     87 
     88 Certificate.prototype.hash = function (algo) {
     89 	assert.string(algo, 'algorithm');
     90 	algo = algo.toLowerCase();
     91 	if (algs.hashAlgs[algo] === undefined)
     92 		throw (new InvalidAlgorithmError(algo));
     93 
     94 	if (this._hashCache[algo])
     95 		return (this._hashCache[algo]);
     96 
     97 	var hash = crypto.createHash(algo).
     98 	    update(this.toBuffer('x509')).digest();
     99 	this._hashCache[algo] = hash;
    100 	return (hash);
    101 };
    102 
    103 Certificate.prototype.isExpired = function (when) {
    104 	if (when === undefined)
    105 		when = new Date();
    106 	return (!((when.getTime() >= this.validFrom.getTime()) &&
    107 		(when.getTime() < this.validUntil.getTime())));
    108 };
    109 
    110 Certificate.prototype.isSignedBy = function (issuerCert) {
    111 	utils.assertCompatible(issuerCert, Certificate, [1, 0], 'issuer');
    112 
    113 	if (!this.issuer.equals(issuerCert.subjects[0]))
    114 		return (false);
    115 	if (this.issuer.purposes && this.issuer.purposes.length > 0 &&
    116 	    this.issuer.purposes.indexOf('ca') === -1) {
    117 		return (false);
    118 	}
    119 
    120 	return (this.isSignedByKey(issuerCert.subjectKey));
    121 };
    122 
    123 Certificate.prototype.getExtension = function (keyOrOid) {
    124 	assert.string(keyOrOid, 'keyOrOid');
    125 	var ext = this.getExtensions().filter(function (maybeExt) {
    126 		if (maybeExt.format === 'x509')
    127 			return (maybeExt.oid === keyOrOid);
    128 		if (maybeExt.format === 'openssh')
    129 			return (maybeExt.name === keyOrOid);
    130 		return (false);
    131 	})[0];
    132 	return (ext);
    133 };
    134 
    135 Certificate.prototype.getExtensions = function () {
    136 	var exts = [];
    137 	var x509 = this.signatures.x509;
    138 	if (x509 && x509.extras && x509.extras.exts) {
    139 		x509.extras.exts.forEach(function (ext) {
    140 			ext.format = 'x509';
    141 			exts.push(ext);
    142 		});
    143 	}
    144 	var openssh = this.signatures.openssh;
    145 	if (openssh && openssh.exts) {
    146 		openssh.exts.forEach(function (ext) {
    147 			ext.format = 'openssh';
    148 			exts.push(ext);
    149 		});
    150 	}
    151 	return (exts);
    152 };
    153 
    154 Certificate.prototype.isSignedByKey = function (issuerKey) {
    155 	utils.assertCompatible(issuerKey, Key, [1, 2], 'issuerKey');
    156 
    157 	if (this.issuerKey !== undefined) {
    158 		return (this.issuerKey.
    159 		    fingerprint('sha512').matches(issuerKey));
    160 	}
    161 
    162 	var fmt = Object.keys(this.signatures)[0];
    163 	var valid = formats[fmt].verify(this, issuerKey);
    164 	if (valid)
    165 		this.issuerKey = issuerKey;
    166 	return (valid);
    167 };
    168 
    169 Certificate.prototype.signWith = function (key) {
    170 	utils.assertCompatible(key, PrivateKey, [1, 2], 'key');
    171 	var fmts = Object.keys(formats);
    172 	var didOne = false;
    173 	for (var i = 0; i < fmts.length; ++i) {
    174 		if (fmts[i] !== 'pem') {
    175 			var ret = formats[fmts[i]].sign(this, key);
    176 			if (ret === true)
    177 				didOne = true;
    178 		}
    179 	}
    180 	if (!didOne) {
    181 		throw (new Error('Failed to sign the certificate for any ' +
    182 		    'available certificate formats'));
    183 	}
    184 };
    185 
    186 Certificate.createSelfSigned = function (subjectOrSubjects, key, options) {
    187 	var subjects;
    188 	if (Array.isArray(subjectOrSubjects))
    189 		subjects = subjectOrSubjects;
    190 	else
    191 		subjects = [subjectOrSubjects];
    192 
    193 	assert.arrayOfObject(subjects);
    194 	subjects.forEach(function (subject) {
    195 		utils.assertCompatible(subject, Identity, [1, 0], 'subject');
    196 	});
    197 
    198 	utils.assertCompatible(key, PrivateKey, [1, 2], 'private key');
    199 
    200 	assert.optionalObject(options, 'options');
    201 	if (options === undefined)
    202 		options = {};
    203 	assert.optionalObject(options.validFrom, 'options.validFrom');
    204 	assert.optionalObject(options.validUntil, 'options.validUntil');
    205 	var validFrom = options.validFrom;
    206 	var validUntil = options.validUntil;
    207 	if (validFrom === undefined)
    208 		validFrom = new Date();
    209 	if (validUntil === undefined) {
    210 		assert.optionalNumber(options.lifetime, 'options.lifetime');
    211 		var lifetime = options.lifetime;
    212 		if (lifetime === undefined)
    213 			lifetime = 10*365*24*3600;
    214 		validUntil = new Date();
    215 		validUntil.setTime(validUntil.getTime() + lifetime*1000);
    216 	}
    217 	assert.optionalBuffer(options.serial, 'options.serial');
    218 	var serial = options.serial;
    219 	if (serial === undefined)
    220 		serial = Buffer.from('0000000000000001', 'hex');
    221 
    222 	var purposes = options.purposes;
    223 	if (purposes === undefined)
    224 		purposes = [];
    225 
    226 	if (purposes.indexOf('signature') === -1)
    227 		purposes.push('signature');
    228 
    229 	/* Self-signed certs are always CAs. */
    230 	if (purposes.indexOf('ca') === -1)
    231 		purposes.push('ca');
    232 	if (purposes.indexOf('crl') === -1)
    233 		purposes.push('crl');
    234 
    235 	/*
    236 	 * If we weren't explicitly given any other purposes, do the sensible
    237 	 * thing and add some basic ones depending on the subject type.
    238 	 */
    239 	if (purposes.length <= 3) {
    240 		var hostSubjects = subjects.filter(function (subject) {
    241 			return (subject.type === 'host');
    242 		});
    243 		var userSubjects = subjects.filter(function (subject) {
    244 			return (subject.type === 'user');
    245 		});
    246 		if (hostSubjects.length > 0) {
    247 			if (purposes.indexOf('serverAuth') === -1)
    248 				purposes.push('serverAuth');
    249 		}
    250 		if (userSubjects.length > 0) {
    251 			if (purposes.indexOf('clientAuth') === -1)
    252 				purposes.push('clientAuth');
    253 		}
    254 		if (userSubjects.length > 0 || hostSubjects.length > 0) {
    255 			if (purposes.indexOf('keyAgreement') === -1)
    256 				purposes.push('keyAgreement');
    257 			if (key.type === 'rsa' &&
    258 			    purposes.indexOf('encryption') === -1)
    259 				purposes.push('encryption');
    260 		}
    261 	}
    262 
    263 	var cert = new Certificate({
    264 		subjects: subjects,
    265 		issuer: subjects[0],
    266 		subjectKey: key.toPublic(),
    267 		issuerKey: key.toPublic(),
    268 		signatures: {},
    269 		serial: serial,
    270 		validFrom: validFrom,
    271 		validUntil: validUntil,
    272 		purposes: purposes
    273 	});
    274 	cert.signWith(key);
    275 
    276 	return (cert);
    277 };
    278 
    279 Certificate.create =
    280     function (subjectOrSubjects, key, issuer, issuerKey, options) {
    281 	var subjects;
    282 	if (Array.isArray(subjectOrSubjects))
    283 		subjects = subjectOrSubjects;
    284 	else
    285 		subjects = [subjectOrSubjects];
    286 
    287 	assert.arrayOfObject(subjects);
    288 	subjects.forEach(function (subject) {
    289 		utils.assertCompatible(subject, Identity, [1, 0], 'subject');
    290 	});
    291 
    292 	utils.assertCompatible(key, Key, [1, 0], 'key');
    293 	if (PrivateKey.isPrivateKey(key))
    294 		key = key.toPublic();
    295 	utils.assertCompatible(issuer, Identity, [1, 0], 'issuer');
    296 	utils.assertCompatible(issuerKey, PrivateKey, [1, 2], 'issuer key');
    297 
    298 	assert.optionalObject(options, 'options');
    299 	if (options === undefined)
    300 		options = {};
    301 	assert.optionalObject(options.validFrom, 'options.validFrom');
    302 	assert.optionalObject(options.validUntil, 'options.validUntil');
    303 	var validFrom = options.validFrom;
    304 	var validUntil = options.validUntil;
    305 	if (validFrom === undefined)
    306 		validFrom = new Date();
    307 	if (validUntil === undefined) {
    308 		assert.optionalNumber(options.lifetime, 'options.lifetime');
    309 		var lifetime = options.lifetime;
    310 		if (lifetime === undefined)
    311 			lifetime = 10*365*24*3600;
    312 		validUntil = new Date();
    313 		validUntil.setTime(validUntil.getTime() + lifetime*1000);
    314 	}
    315 	assert.optionalBuffer(options.serial, 'options.serial');
    316 	var serial = options.serial;
    317 	if (serial === undefined)
    318 		serial = Buffer.from('0000000000000001', 'hex');
    319 
    320 	var purposes = options.purposes;
    321 	if (purposes === undefined)
    322 		purposes = [];
    323 
    324 	if (purposes.indexOf('signature') === -1)
    325 		purposes.push('signature');
    326 
    327 	if (options.ca === true) {
    328 		if (purposes.indexOf('ca') === -1)
    329 			purposes.push('ca');
    330 		if (purposes.indexOf('crl') === -1)
    331 			purposes.push('crl');
    332 	}
    333 
    334 	var hostSubjects = subjects.filter(function (subject) {
    335 		return (subject.type === 'host');
    336 	});
    337 	var userSubjects = subjects.filter(function (subject) {
    338 		return (subject.type === 'user');
    339 	});
    340 	if (hostSubjects.length > 0) {
    341 		if (purposes.indexOf('serverAuth') === -1)
    342 			purposes.push('serverAuth');
    343 	}
    344 	if (userSubjects.length > 0) {
    345 		if (purposes.indexOf('clientAuth') === -1)
    346 			purposes.push('clientAuth');
    347 	}
    348 	if (userSubjects.length > 0 || hostSubjects.length > 0) {
    349 		if (purposes.indexOf('keyAgreement') === -1)
    350 			purposes.push('keyAgreement');
    351 		if (key.type === 'rsa' &&
    352 		    purposes.indexOf('encryption') === -1)
    353 			purposes.push('encryption');
    354 	}
    355 
    356 	var cert = new Certificate({
    357 		subjects: subjects,
    358 		issuer: issuer,
    359 		subjectKey: key,
    360 		issuerKey: issuerKey.toPublic(),
    361 		signatures: {},
    362 		serial: serial,
    363 		validFrom: validFrom,
    364 		validUntil: validUntil,
    365 		purposes: purposes
    366 	});
    367 	cert.signWith(issuerKey);
    368 
    369 	return (cert);
    370 };
    371 
    372 Certificate.parse = function (data, format, options) {
    373 	if (typeof (data) !== 'string')
    374 		assert.buffer(data, 'data');
    375 	if (format === undefined)
    376 		format = 'auto';
    377 	assert.string(format, 'format');
    378 	if (typeof (options) === 'string')
    379 		options = { filename: options };
    380 	assert.optionalObject(options, 'options');
    381 	if (options === undefined)
    382 		options = {};
    383 	assert.optionalString(options.filename, 'options.filename');
    384 	if (options.filename === undefined)
    385 		options.filename = '(unnamed)';
    386 
    387 	assert.object(formats[format], 'formats[format]');
    388 
    389 	try {
    390 		var k = formats[format].read(data, options);
    391 		return (k);
    392 	} catch (e) {
    393 		throw (new CertificateParseError(options.filename, format, e));
    394 	}
    395 };
    396 
    397 Certificate.isCertificate = function (obj, ver) {
    398 	return (utils.isCompatible(obj, Certificate, ver));
    399 };
    400 
    401 /*
    402  * API versions for Certificate:
    403  * [1,0] -- initial ver
    404  * [1,1] -- openssh format now unpacks extensions
    405  */
    406 Certificate.prototype._sshpkApiVersion = [1, 1];
    407 
    408 Certificate._oldVersionDetect = function (obj) {
    409 	return ([1, 0]);
    410 };