trouble-in-terror-town/node_modules/sshpk/lib/certificate.js
Mikolaj 2bbacbea09 did some more work on networking and removed EOS in favor of LRM
did some more work on networking and removed EOS in favor of Light Reflective Mirror
2022-05-31 15:04:31 +02:00

410 lines
11 KiB
JavaScript

// 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]);
};