certificate.js 11 KB

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