Initial commit
[yaffs-website] / node_modules / sshpk / lib / certificate.js
1 // Copyright 2016 Joyent, Inc.
2
3 module.exports = Certificate;
4
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');
16
17 var formats = {};
18 formats['openssh'] = require('./formats/openssh-cert');
19 formats['x509'] = require('./formats/x509');
20 formats['pem'] = require('./formats/x509-pem');
21
22 var CertificateParseError = errs.CertificateParseError;
23 var InvalidAlgorithmError = errs.InvalidAlgorithmError;
24
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],
29             'options.subjects');
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],
35                     'options.issuerKey');
36         }
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');
41
42         assert.optionalArrayOfString(opts.purposes, 'options.purposes');
43
44         this._hashCache = {};
45
46         this.subjects = opts.subjects;
47         this.issuer = opts.issuer;
48         this.subjectKey = opts.subjectKey;
49         this.issuerKey = opts.issuerKey;
50         this.signatures = opts.signatures;
51         this.serial = opts.serial;
52         this.validFrom = opts.validFrom;
53         this.validUntil = opts.validUntil;
54         this.purposes = opts.purposes;
55 }
56
57 Certificate.formats = formats;
58
59 Certificate.prototype.toBuffer = function (format, options) {
60         if (format === undefined)
61                 format = 'x509';
62         assert.string(format, 'format');
63         assert.object(formats[format], 'formats[format]');
64         assert.optionalObject(options, 'options');
65
66         return (formats[format].write(this, options));
67 };
68
69 Certificate.prototype.toString = function (format, options) {
70         if (format === undefined)
71                 format = 'pem';
72         return (this.toBuffer(format, options).toString());
73 };
74
75 Certificate.prototype.fingerprint = function (algo) {
76         if (algo === undefined)
77                 algo = 'sha256';
78         assert.string(algo, 'algorithm');
79         var opts = {
80                 type: 'certificate',
81                 hash: this.hash(algo),
82                 algorithm: algo
83         };
84         return (new Fingerprint(opts));
85 };
86
87 Certificate.prototype.hash = function (algo) {
88         assert.string(algo, 'algorithm');
89         algo = algo.toLowerCase();
90         if (algs.hashAlgs[algo] === undefined)
91                 throw (new InvalidAlgorithmError(algo));
92
93         if (this._hashCache[algo])
94                 return (this._hashCache[algo]);
95
96         var hash = crypto.createHash(algo).
97             update(this.toBuffer('x509')).digest();
98         this._hashCache[algo] = hash;
99         return (hash);
100 };
101
102 Certificate.prototype.isExpired = function (when) {
103         if (when === undefined)
104                 when = new Date();
105         return (!((when.getTime() >= this.validFrom.getTime()) &&
106                 (when.getTime() < this.validUntil.getTime())));
107 };
108
109 Certificate.prototype.isSignedBy = function (issuerCert) {
110         utils.assertCompatible(issuerCert, Certificate, [1, 0], 'issuer');
111
112         if (!this.issuer.equals(issuerCert.subjects[0]))
113                 return (false);
114         if (this.issuer.purposes && this.issuer.purposes.length > 0 &&
115             this.issuer.purposes.indexOf('ca') === -1) {
116                 return (false);
117         }
118
119         return (this.isSignedByKey(issuerCert.subjectKey));
120 };
121
122 Certificate.prototype.isSignedByKey = function (issuerKey) {
123         utils.assertCompatible(issuerKey, Key, [1, 2], 'issuerKey');
124
125         if (this.issuerKey !== undefined) {
126                 return (this.issuerKey.
127                     fingerprint('sha512').matches(issuerKey));
128         }
129
130         var fmt = Object.keys(this.signatures)[0];
131         var valid = formats[fmt].verify(this, issuerKey);
132         if (valid)
133                 this.issuerKey = issuerKey;
134         return (valid);
135 };
136
137 Certificate.prototype.signWith = function (key) {
138         utils.assertCompatible(key, PrivateKey, [1, 2], 'key');
139         var fmts = Object.keys(formats);
140         var didOne = false;
141         for (var i = 0; i < fmts.length; ++i) {
142                 if (fmts[i] !== 'pem') {
143                         var ret = formats[fmts[i]].sign(this, key);
144                         if (ret === true)
145                                 didOne = true;
146                 }
147         }
148         if (!didOne) {
149                 throw (new Error('Failed to sign the certificate for any ' +
150                     'available certificate formats'));
151         }
152 };
153
154 Certificate.createSelfSigned = function (subjectOrSubjects, key, options) {
155         var subjects;
156         if (Array.isArray(subjectOrSubjects))
157                 subjects = subjectOrSubjects;
158         else
159                 subjects = [subjectOrSubjects];
160
161         assert.arrayOfObject(subjects);
162         subjects.forEach(function (subject) {
163                 utils.assertCompatible(subject, Identity, [1, 0], 'subject');
164         });
165
166         utils.assertCompatible(key, PrivateKey, [1, 2], 'private key');
167
168         assert.optionalObject(options, 'options');
169         if (options === undefined)
170                 options = {};
171         assert.optionalObject(options.validFrom, 'options.validFrom');
172         assert.optionalObject(options.validUntil, 'options.validUntil');
173         var validFrom = options.validFrom;
174         var validUntil = options.validUntil;
175         if (validFrom === undefined)
176                 validFrom = new Date();
177         if (validUntil === undefined) {
178                 assert.optionalNumber(options.lifetime, 'options.lifetime');
179                 var lifetime = options.lifetime;
180                 if (lifetime === undefined)
181                         lifetime = 10*365*24*3600;
182                 validUntil = new Date();
183                 validUntil.setTime(validUntil.getTime() + lifetime*1000);
184         }
185         assert.optionalBuffer(options.serial, 'options.serial');
186         var serial = options.serial;
187         if (serial === undefined)
188                 serial = new Buffer('0000000000000001', 'hex');
189
190         var purposes = options.purposes;
191         if (purposes === undefined)
192                 purposes = [];
193
194         if (purposes.indexOf('signature') === -1)
195                 purposes.push('signature');
196
197         /* Self-signed certs are always CAs. */
198         if (purposes.indexOf('ca') === -1)
199                 purposes.push('ca');
200         if (purposes.indexOf('crl') === -1)
201                 purposes.push('crl');
202
203         /*
204          * If we weren't explicitly given any other purposes, do the sensible
205          * thing and add some basic ones depending on the subject type.
206          */
207         if (purposes.length <= 3) {
208                 var hostSubjects = subjects.filter(function (subject) {
209                         return (subject.type === 'host');
210                 });
211                 var userSubjects = subjects.filter(function (subject) {
212                         return (subject.type === 'user');
213                 });
214                 if (hostSubjects.length > 0) {
215                         if (purposes.indexOf('serverAuth') === -1)
216                                 purposes.push('serverAuth');
217                 }
218                 if (userSubjects.length > 0) {
219                         if (purposes.indexOf('clientAuth') === -1)
220                                 purposes.push('clientAuth');
221                 }
222                 if (userSubjects.length > 0 || hostSubjects.length > 0) {
223                         if (purposes.indexOf('keyAgreement') === -1)
224                                 purposes.push('keyAgreement');
225                         if (key.type === 'rsa' &&
226                             purposes.indexOf('encryption') === -1)
227                                 purposes.push('encryption');
228                 }
229         }
230
231         var cert = new Certificate({
232                 subjects: subjects,
233                 issuer: subjects[0],
234                 subjectKey: key.toPublic(),
235                 issuerKey: key.toPublic(),
236                 signatures: {},
237                 serial: serial,
238                 validFrom: validFrom,
239                 validUntil: validUntil,
240                 purposes: purposes
241         });
242         cert.signWith(key);
243
244         return (cert);
245 };
246
247 Certificate.create =
248     function (subjectOrSubjects, key, issuer, issuerKey, options) {
249         var subjects;
250         if (Array.isArray(subjectOrSubjects))
251                 subjects = subjectOrSubjects;
252         else
253                 subjects = [subjectOrSubjects];
254
255         assert.arrayOfObject(subjects);
256         subjects.forEach(function (subject) {
257                 utils.assertCompatible(subject, Identity, [1, 0], 'subject');
258         });
259
260         utils.assertCompatible(key, Key, [1, 0], 'key');
261         if (PrivateKey.isPrivateKey(key))
262                 key = key.toPublic();
263         utils.assertCompatible(issuer, Identity, [1, 0], 'issuer');
264         utils.assertCompatible(issuerKey, PrivateKey, [1, 2], 'issuer key');
265
266         assert.optionalObject(options, 'options');
267         if (options === undefined)
268                 options = {};
269         assert.optionalObject(options.validFrom, 'options.validFrom');
270         assert.optionalObject(options.validUntil, 'options.validUntil');
271         var validFrom = options.validFrom;
272         var validUntil = options.validUntil;
273         if (validFrom === undefined)
274                 validFrom = new Date();
275         if (validUntil === undefined) {
276                 assert.optionalNumber(options.lifetime, 'options.lifetime');
277                 var lifetime = options.lifetime;
278                 if (lifetime === undefined)
279                         lifetime = 10*365*24*3600;
280                 validUntil = new Date();
281                 validUntil.setTime(validUntil.getTime() + lifetime*1000);
282         }
283         assert.optionalBuffer(options.serial, 'options.serial');
284         var serial = options.serial;
285         if (serial === undefined)
286                 serial = new Buffer('0000000000000001', 'hex');
287
288         var purposes = options.purposes;
289         if (purposes === undefined)
290                 purposes = [];
291
292         if (purposes.indexOf('signature') === -1)
293                 purposes.push('signature');
294
295         if (options.ca === true) {
296                 if (purposes.indexOf('ca') === -1)
297                         purposes.push('ca');
298                 if (purposes.indexOf('crl') === -1)
299                         purposes.push('crl');
300         }
301
302         var hostSubjects = subjects.filter(function (subject) {
303                 return (subject.type === 'host');
304         });
305         var userSubjects = subjects.filter(function (subject) {
306                 return (subject.type === 'user');
307         });
308         if (hostSubjects.length > 0) {
309                 if (purposes.indexOf('serverAuth') === -1)
310                         purposes.push('serverAuth');
311         }
312         if (userSubjects.length > 0) {
313                 if (purposes.indexOf('clientAuth') === -1)
314                         purposes.push('clientAuth');
315         }
316         if (userSubjects.length > 0 || hostSubjects.length > 0) {
317                 if (purposes.indexOf('keyAgreement') === -1)
318                         purposes.push('keyAgreement');
319                 if (key.type === 'rsa' &&
320                     purposes.indexOf('encryption') === -1)
321                         purposes.push('encryption');
322         }
323
324         var cert = new Certificate({
325                 subjects: subjects,
326                 issuer: issuer,
327                 subjectKey: key,
328                 issuerKey: issuerKey.toPublic(),
329                 signatures: {},
330                 serial: serial,
331                 validFrom: validFrom,
332                 validUntil: validUntil,
333                 purposes: purposes
334         });
335         cert.signWith(issuerKey);
336
337         return (cert);
338 };
339
340 Certificate.parse = function (data, format, options) {
341         if (typeof (data) !== 'string')
342                 assert.buffer(data, 'data');
343         if (format === undefined)
344                 format = 'auto';
345         assert.string(format, 'format');
346         if (typeof (options) === 'string')
347                 options = { filename: options };
348         assert.optionalObject(options, 'options');
349         if (options === undefined)
350                 options = {};
351         assert.optionalString(options.filename, 'options.filename');
352         if (options.filename === undefined)
353                 options.filename = '(unnamed)';
354
355         assert.object(formats[format], 'formats[format]');
356
357         try {
358                 var k = formats[format].read(data, options);
359                 return (k);
360         } catch (e) {
361                 throw (new CertificateParseError(options.filename, format, e));
362         }
363 };
364
365 Certificate.isCertificate = function (obj, ver) {
366         return (utils.isCompatible(obj, Certificate, ver));
367 };
368
369 /*
370  * API versions for Certificate:
371  * [1,0] -- initial ver
372  */
373 Certificate.prototype._sshpkApiVersion = [1, 0];
374
375 Certificate._oldVersionDetect = function (obj) {
376         return ([1, 0]);
377 };