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