| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 | // Copyright 2016 Joyent, Inc.module.exports = Certificate;var assert = require('assert-plus');var Buffer = require('safer-buffer').Buffer;var algs = require('./algs');var crypto = require('crypto');var Fingerprint = require('./fingerprint');var Signature = require('./signature');var errs = require('./errors');var util = require('util');var utils = require('./utils');var Key = require('./key');var PrivateKey = require('./private-key');var Identity = require('./identity');var formats = {};formats['openssh'] = require('./formats/openssh-cert');formats['x509'] = require('./formats/x509');formats['pem'] = require('./formats/x509-pem');var CertificateParseError = errs.CertificateParseError;var InvalidAlgorithmError = errs.InvalidAlgorithmError;function Certificate(opts) {	assert.object(opts, 'options');	assert.arrayOfObject(opts.subjects, 'options.subjects');	utils.assertCompatible(opts.subjects[0], Identity, [1, 0],	    'options.subjects');	utils.assertCompatible(opts.subjectKey, Key, [1, 0],	    'options.subjectKey');	utils.assertCompatible(opts.issuer, Identity, [1, 0], 'options.issuer');	if (opts.issuerKey !== undefined) {		utils.assertCompatible(opts.issuerKey, Key, [1, 0],		    'options.issuerKey');	}	assert.object(opts.signatures, 'options.signatures');	assert.buffer(opts.serial, 'options.serial');	assert.date(opts.validFrom, 'options.validFrom');	assert.date(opts.validUntil, 'optons.validUntil');	assert.optionalArrayOfString(opts.purposes, 'options.purposes');	this._hashCache = {};	this.subjects = opts.subjects;	this.issuer = opts.issuer;	this.subjectKey = opts.subjectKey;	this.issuerKey = opts.issuerKey;	this.signatures = opts.signatures;	this.serial = opts.serial;	this.validFrom = opts.validFrom;	this.validUntil = opts.validUntil;	this.purposes = opts.purposes;}Certificate.formats = formats;Certificate.prototype.toBuffer = function (format, options) {	if (format === undefined)		format = 'x509';	assert.string(format, 'format');	assert.object(formats[format], 'formats[format]');	assert.optionalObject(options, 'options');	return (formats[format].write(this, options));};Certificate.prototype.toString = function (format, options) {	if (format === undefined)		format = 'pem';	return (this.toBuffer(format, options).toString());};Certificate.prototype.fingerprint = function (algo) {	if (algo === undefined)		algo = 'sha256';	assert.string(algo, 'algorithm');	var opts = {		type: 'certificate',		hash: this.hash(algo),		algorithm: algo	};	return (new Fingerprint(opts));};Certificate.prototype.hash = function (algo) {	assert.string(algo, 'algorithm');	algo = algo.toLowerCase();	if (algs.hashAlgs[algo] === undefined)		throw (new InvalidAlgorithmError(algo));	if (this._hashCache[algo])		return (this._hashCache[algo]);	var hash = crypto.createHash(algo).	    update(this.toBuffer('x509')).digest();	this._hashCache[algo] = hash;	return (hash);};Certificate.prototype.isExpired = function (when) {	if (when === undefined)		when = new Date();	return (!((when.getTime() >= this.validFrom.getTime()) &&		(when.getTime() < this.validUntil.getTime())));};Certificate.prototype.isSignedBy = function (issuerCert) {	utils.assertCompatible(issuerCert, Certificate, [1, 0], 'issuer');	if (!this.issuer.equals(issuerCert.subjects[0]))		return (false);	if (this.issuer.purposes && this.issuer.purposes.length > 0 &&	    this.issuer.purposes.indexOf('ca') === -1) {		return (false);	}	return (this.isSignedByKey(issuerCert.subjectKey));};Certificate.prototype.getExtension = function (keyOrOid) {	assert.string(keyOrOid, 'keyOrOid');	var ext = this.getExtensions().filter(function (maybeExt) {		if (maybeExt.format === 'x509')			return (maybeExt.oid === keyOrOid);		if (maybeExt.format === 'openssh')			return (maybeExt.name === keyOrOid);		return (false);	})[0];	return (ext);};Certificate.prototype.getExtensions = function () {	var exts = [];	var x509 = this.signatures.x509;	if (x509 && x509.extras && x509.extras.exts) {		x509.extras.exts.forEach(function (ext) {			ext.format = 'x509';			exts.push(ext);		});	}	var openssh = this.signatures.openssh;	if (openssh && openssh.exts) {		openssh.exts.forEach(function (ext) {			ext.format = 'openssh';			exts.push(ext);		});	}	return (exts);};Certificate.prototype.isSignedByKey = function (issuerKey) {	utils.assertCompatible(issuerKey, Key, [1, 2], 'issuerKey');	if (this.issuerKey !== undefined) {		return (this.issuerKey.		    fingerprint('sha512').matches(issuerKey));	}	var fmt = Object.keys(this.signatures)[0];	var valid = formats[fmt].verify(this, issuerKey);	if (valid)		this.issuerKey = issuerKey;	return (valid);};Certificate.prototype.signWith = function (key) {	utils.assertCompatible(key, PrivateKey, [1, 2], 'key');	var fmts = Object.keys(formats);	var didOne = false;	for (var i = 0; i < fmts.length; ++i) {		if (fmts[i] !== 'pem') {			var ret = formats[fmts[i]].sign(this, key);			if (ret === true)				didOne = true;		}	}	if (!didOne) {		throw (new Error('Failed to sign the certificate for any ' +		    'available certificate formats'));	}};Certificate.createSelfSigned = function (subjectOrSubjects, key, options) {	var subjects;	if (Array.isArray(subjectOrSubjects))		subjects = subjectOrSubjects;	else		subjects = [subjectOrSubjects];	assert.arrayOfObject(subjects);	subjects.forEach(function (subject) {		utils.assertCompatible(subject, Identity, [1, 0], 'subject');	});	utils.assertCompatible(key, PrivateKey, [1, 2], 'private key');	assert.optionalObject(options, 'options');	if (options === undefined)		options = {};	assert.optionalObject(options.validFrom, 'options.validFrom');	assert.optionalObject(options.validUntil, 'options.validUntil');	var validFrom = options.validFrom;	var validUntil = options.validUntil;	if (validFrom === undefined)		validFrom = new Date();	if (validUntil === undefined) {		assert.optionalNumber(options.lifetime, 'options.lifetime');		var lifetime = options.lifetime;		if (lifetime === undefined)			lifetime = 10*365*24*3600;		validUntil = new Date();		validUntil.setTime(validUntil.getTime() + lifetime*1000);	}	assert.optionalBuffer(options.serial, 'options.serial');	var serial = options.serial;	if (serial === undefined)		serial = Buffer.from('0000000000000001', 'hex');	var purposes = options.purposes;	if (purposes === undefined)		purposes = [];	if (purposes.indexOf('signature') === -1)		purposes.push('signature');	/* Self-signed certs are always CAs. */	if (purposes.indexOf('ca') === -1)		purposes.push('ca');	if (purposes.indexOf('crl') === -1)		purposes.push('crl');	/*	 * If we weren't explicitly given any other purposes, do the sensible	 * thing and add some basic ones depending on the subject type.	 */	if (purposes.length <= 3) {		var hostSubjects = subjects.filter(function (subject) {			return (subject.type === 'host');		});		var userSubjects = subjects.filter(function (subject) {			return (subject.type === 'user');		});		if (hostSubjects.length > 0) {			if (purposes.indexOf('serverAuth') === -1)				purposes.push('serverAuth');		}		if (userSubjects.length > 0) {			if (purposes.indexOf('clientAuth') === -1)				purposes.push('clientAuth');		}		if (userSubjects.length > 0 || hostSubjects.length > 0) {			if (purposes.indexOf('keyAgreement') === -1)				purposes.push('keyAgreement');			if (key.type === 'rsa' &&			    purposes.indexOf('encryption') === -1)				purposes.push('encryption');		}	}	var cert = new Certificate({		subjects: subjects,		issuer: subjects[0],		subjectKey: key.toPublic(),		issuerKey: key.toPublic(),		signatures: {},		serial: serial,		validFrom: validFrom,		validUntil: validUntil,		purposes: purposes	});	cert.signWith(key);	return (cert);};Certificate.create =    function (subjectOrSubjects, key, issuer, issuerKey, options) {	var subjects;	if (Array.isArray(subjectOrSubjects))		subjects = subjectOrSubjects;	else		subjects = [subjectOrSubjects];	assert.arrayOfObject(subjects);	subjects.forEach(function (subject) {		utils.assertCompatible(subject, Identity, [1, 0], 'subject');	});	utils.assertCompatible(key, Key, [1, 0], 'key');	if (PrivateKey.isPrivateKey(key))		key = key.toPublic();	utils.assertCompatible(issuer, Identity, [1, 0], 'issuer');	utils.assertCompatible(issuerKey, PrivateKey, [1, 2], 'issuer key');	assert.optionalObject(options, 'options');	if (options === undefined)		options = {};	assert.optionalObject(options.validFrom, 'options.validFrom');	assert.optionalObject(options.validUntil, 'options.validUntil');	var validFrom = options.validFrom;	var validUntil = options.validUntil;	if (validFrom === undefined)		validFrom = new Date();	if (validUntil === undefined) {		assert.optionalNumber(options.lifetime, 'options.lifetime');		var lifetime = options.lifetime;		if (lifetime === undefined)			lifetime = 10*365*24*3600;		validUntil = new Date();		validUntil.setTime(validUntil.getTime() + lifetime*1000);	}	assert.optionalBuffer(options.serial, 'options.serial');	var serial = options.serial;	if (serial === undefined)		serial = Buffer.from('0000000000000001', 'hex');	var purposes = options.purposes;	if (purposes === undefined)		purposes = [];	if (purposes.indexOf('signature') === -1)		purposes.push('signature');	if (options.ca === true) {		if (purposes.indexOf('ca') === -1)			purposes.push('ca');		if (purposes.indexOf('crl') === -1)			purposes.push('crl');	}	var hostSubjects = subjects.filter(function (subject) {		return (subject.type === 'host');	});	var userSubjects = subjects.filter(function (subject) {		return (subject.type === 'user');	});	if (hostSubjects.length > 0) {		if (purposes.indexOf('serverAuth') === -1)			purposes.push('serverAuth');	}	if (userSubjects.length > 0) {		if (purposes.indexOf('clientAuth') === -1)			purposes.push('clientAuth');	}	if (userSubjects.length > 0 || hostSubjects.length > 0) {		if (purposes.indexOf('keyAgreement') === -1)			purposes.push('keyAgreement');		if (key.type === 'rsa' &&		    purposes.indexOf('encryption') === -1)			purposes.push('encryption');	}	var cert = new Certificate({		subjects: subjects,		issuer: issuer,		subjectKey: key,		issuerKey: issuerKey.toPublic(),		signatures: {},		serial: serial,		validFrom: validFrom,		validUntil: validUntil,		purposes: purposes	});	cert.signWith(issuerKey);	return (cert);};Certificate.parse = function (data, format, options) {	if (typeof (data) !== 'string')		assert.buffer(data, 'data');	if (format === undefined)		format = 'auto';	assert.string(format, 'format');	if (typeof (options) === 'string')		options = { filename: options };	assert.optionalObject(options, 'options');	if (options === undefined)		options = {};	assert.optionalString(options.filename, 'options.filename');	if (options.filename === undefined)		options.filename = '(unnamed)';	assert.object(formats[format], 'formats[format]');	try {		var k = formats[format].read(data, options);		return (k);	} catch (e) {		throw (new CertificateParseError(options.filename, format, e));	}};Certificate.isCertificate = function (obj, ver) {	return (utils.isCompatible(obj, Certificate, ver));};/* * API versions for Certificate: * [1,0] -- initial ver * [1,1] -- openssh format now unpacks extensions */Certificate.prototype._sshpkApiVersion = [1, 1];Certificate._oldVersionDetect = function (obj) {	return ([1, 0]);};
 |