You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							410 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							410 lines
						
					
					
						
							11 KiB
						
					
					
				
								// 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]);
							 | 
						|
								};
							 | 
						|
								
							 |