1 // Copyright 2016 Joyent, Inc.
3 module.exports = Certificate;
5 var assert = require('assert-plus');
6 var algs = require('./algs');
7 var crypto = require('crypto');
8 var Fingerprint = require('./fingerprint');
9 var Signature = require('./signature');
10 var errs = require('./errors');
11 var util = require('util');
12 var utils = require('./utils');
13 var Key = require('./key');
14 var PrivateKey = require('./private-key');
15 var Identity = require('./identity');
18 formats['openssh'] = require('./formats/openssh-cert');
19 formats['x509'] = require('./formats/x509');
20 formats['pem'] = require('./formats/x509-pem');
22 var CertificateParseError = errs.CertificateParseError;
23 var InvalidAlgorithmError = errs.InvalidAlgorithmError;
25 function Certificate(opts) {
26 assert.object(opts, 'options');
27 assert.arrayOfObject(opts.subjects, 'options.subjects');
28 utils.assertCompatible(opts.subjects[0], Identity, [1, 0],
30 utils.assertCompatible(opts.subjectKey, Key, [1, 0],
31 'options.subjectKey');
32 utils.assertCompatible(opts.issuer, Identity, [1, 0], 'options.issuer');
33 if (opts.issuerKey !== undefined) {
34 utils.assertCompatible(opts.issuerKey, Key, [1, 0],
37 assert.object(opts.signatures, 'options.signatures');
38 assert.buffer(opts.serial, 'options.serial');
39 assert.date(opts.validFrom, 'options.validFrom');
40 assert.date(opts.validUntil, 'optons.validUntil');
44 this.subjects = opts.subjects;
45 this.issuer = opts.issuer;
46 this.subjectKey = opts.subjectKey;
47 this.issuerKey = opts.issuerKey;
48 this.signatures = opts.signatures;
49 this.serial = opts.serial;
50 this.validFrom = opts.validFrom;
51 this.validUntil = opts.validUntil;
54 Certificate.formats = formats;
56 Certificate.prototype.toBuffer = function (format, options) {
57 if (format === undefined)
59 assert.string(format, 'format');
60 assert.object(formats[format], 'formats[format]');
61 assert.optionalObject(options, 'options');
63 return (formats[format].write(this, options));
66 Certificate.prototype.toString = function (format, options) {
67 if (format === undefined)
69 return (this.toBuffer(format, options).toString());
72 Certificate.prototype.fingerprint = function (algo) {
73 if (algo === undefined)
75 assert.string(algo, 'algorithm');
78 hash: this.hash(algo),
81 return (new Fingerprint(opts));
84 Certificate.prototype.hash = function (algo) {
85 assert.string(algo, 'algorithm');
86 algo = algo.toLowerCase();
87 if (algs.hashAlgs[algo] === undefined)
88 throw (new InvalidAlgorithmError(algo));
90 if (this._hashCache[algo])
91 return (this._hashCache[algo]);
93 var hash = crypto.createHash(algo).
94 update(this.toBuffer('x509')).digest();
95 this._hashCache[algo] = hash;
99 Certificate.prototype.isExpired = function (when) {
100 if (when === undefined)
102 return (!((when.getTime() >= this.validFrom.getTime()) &&
103 (when.getTime() < this.validUntil.getTime())));
106 Certificate.prototype.isSignedBy = function (issuerCert) {
107 utils.assertCompatible(issuerCert, Certificate, [1, 0], 'issuer');
109 if (!this.issuer.equals(issuerCert.subjects[0]))
112 return (this.isSignedByKey(issuerCert.subjectKey));
115 Certificate.prototype.isSignedByKey = function (issuerKey) {
116 utils.assertCompatible(issuerKey, Key, [1, 2], 'issuerKey');
118 if (this.issuerKey !== undefined) {
119 return (this.issuerKey.
120 fingerprint('sha512').matches(issuerKey));
123 var fmt = Object.keys(this.signatures)[0];
124 var valid = formats[fmt].verify(this, issuerKey);
126 this.issuerKey = issuerKey;
130 Certificate.prototype.signWith = function (key) {
131 utils.assertCompatible(key, PrivateKey, [1, 2], 'key');
132 var fmts = Object.keys(formats);
134 for (var i = 0; i < fmts.length; ++i) {
135 if (fmts[i] !== 'pem') {
136 var ret = formats[fmts[i]].sign(this, key);
142 throw (new Error('Failed to sign the certificate for any ' +
143 'available certificate formats'));
147 Certificate.createSelfSigned = function (subjectOrSubjects, key, options) {
149 if (Array.isArray(subjectOrSubjects))
150 subjects = subjectOrSubjects;
152 subjects = [subjectOrSubjects];
154 assert.arrayOfObject(subjects);
155 subjects.forEach(function (subject) {
156 utils.assertCompatible(subject, Identity, [1, 0], 'subject');
159 utils.assertCompatible(key, PrivateKey, [1, 2], 'private key');
161 assert.optionalObject(options, 'options');
162 if (options === undefined)
164 assert.optionalObject(options.validFrom, 'options.validFrom');
165 assert.optionalObject(options.validUntil, 'options.validUntil');
166 var validFrom = options.validFrom;
167 var validUntil = options.validUntil;
168 if (validFrom === undefined)
169 validFrom = new Date();
170 if (validUntil === undefined) {
171 assert.optionalNumber(options.lifetime, 'options.lifetime');
172 var lifetime = options.lifetime;
173 if (lifetime === undefined)
174 lifetime = 10*365*24*3600;
175 validUntil = new Date();
176 validUntil.setTime(validUntil.getTime() + lifetime*1000);
178 assert.optionalBuffer(options.serial, 'options.serial');
179 var serial = options.serial;
180 if (serial === undefined)
181 serial = new Buffer('0000000000000001', 'hex');
183 var cert = new Certificate({
186 subjectKey: key.toPublic(),
187 issuerKey: key.toPublic(),
190 validFrom: validFrom,
191 validUntil: validUntil
199 function (subjectOrSubjects, key, issuer, issuerKey, options) {
201 if (Array.isArray(subjectOrSubjects))
202 subjects = subjectOrSubjects;
204 subjects = [subjectOrSubjects];
206 assert.arrayOfObject(subjects);
207 subjects.forEach(function (subject) {
208 utils.assertCompatible(subject, Identity, [1, 0], 'subject');
211 utils.assertCompatible(key, Key, [1, 0], 'key');
212 if (PrivateKey.isPrivateKey(key))
213 key = key.toPublic();
214 utils.assertCompatible(issuer, Identity, [1, 0], 'issuer');
215 utils.assertCompatible(issuerKey, PrivateKey, [1, 2], 'issuer key');
217 assert.optionalObject(options, 'options');
218 if (options === undefined)
220 assert.optionalObject(options.validFrom, 'options.validFrom');
221 assert.optionalObject(options.validUntil, 'options.validUntil');
222 var validFrom = options.validFrom;
223 var validUntil = options.validUntil;
224 if (validFrom === undefined)
225 validFrom = new Date();
226 if (validUntil === undefined) {
227 assert.optionalNumber(options.lifetime, 'options.lifetime');
228 var lifetime = options.lifetime;
229 if (lifetime === undefined)
230 lifetime = 10*365*24*3600;
231 validUntil = new Date();
232 validUntil.setTime(validUntil.getTime() + lifetime*1000);
234 assert.optionalBuffer(options.serial, 'options.serial');
235 var serial = options.serial;
236 if (serial === undefined)
237 serial = new Buffer('0000000000000001', 'hex');
239 var cert = new Certificate({
243 issuerKey: issuerKey.toPublic(),
246 validFrom: validFrom,
247 validUntil: validUntil
249 cert.signWith(issuerKey);
254 Certificate.parse = function (data, format, options) {
255 if (typeof (data) !== 'string')
256 assert.buffer(data, 'data');
257 if (format === undefined)
259 assert.string(format, 'format');
260 if (typeof (options) === 'string')
261 options = { filename: options };
262 assert.optionalObject(options, 'options');
263 if (options === undefined)
265 assert.optionalString(options.filename, 'options.filename');
266 if (options.filename === undefined)
267 options.filename = '(unnamed)';
269 assert.object(formats[format], 'formats[format]');
272 var k = formats[format].read(data, options);
275 throw (new CertificateParseError(options.filename, format, e));
279 Certificate.isCertificate = function (obj, ver) {
280 return (utils.isCompatible(obj, Certificate, ver));
284 * API versions for Certificate:
285 * [1,0] -- initial ver
287 Certificate.prototype._sshpkApiVersion = [1, 0];
289 Certificate._oldVersionDetect = function (obj) {