You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

410 lines
11 KiB

  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.getExtension = function (keyOrOid) {
  103. assert.string(keyOrOid, 'keyOrOid');
  104. var ext = this.getExtensions().filter(function (maybeExt) {
  105. if (maybeExt.format === 'x509')
  106. return (maybeExt.oid === keyOrOid);
  107. if (maybeExt.format === 'openssh')
  108. return (maybeExt.name === keyOrOid);
  109. return (false);
  110. })[0];
  111. return (ext);
  112. };
  113. Certificate.prototype.getExtensions = function () {
  114. var exts = [];
  115. var x509 = this.signatures.x509;
  116. if (x509 && x509.extras && x509.extras.exts) {
  117. x509.extras.exts.forEach(function (ext) {
  118. ext.format = 'x509';
  119. exts.push(ext);
  120. });
  121. }
  122. var openssh = this.signatures.openssh;
  123. if (openssh && openssh.exts) {
  124. openssh.exts.forEach(function (ext) {
  125. ext.format = 'openssh';
  126. exts.push(ext);
  127. });
  128. }
  129. return (exts);
  130. };
  131. Certificate.prototype.isSignedByKey = function (issuerKey) {
  132. utils.assertCompatible(issuerKey, Key, [1, 2], 'issuerKey');
  133. if (this.issuerKey !== undefined) {
  134. return (this.issuerKey.
  135. fingerprint('sha512').matches(issuerKey));
  136. }
  137. var fmt = Object.keys(this.signatures)[0];
  138. var valid = formats[fmt].verify(this, issuerKey);
  139. if (valid)
  140. this.issuerKey = issuerKey;
  141. return (valid);
  142. };
  143. Certificate.prototype.signWith = function (key) {
  144. utils.assertCompatible(key, PrivateKey, [1, 2], 'key');
  145. var fmts = Object.keys(formats);
  146. var didOne = false;
  147. for (var i = 0; i < fmts.length; ++i) {
  148. if (fmts[i] !== 'pem') {
  149. var ret = formats[fmts[i]].sign(this, key);
  150. if (ret === true)
  151. didOne = true;
  152. }
  153. }
  154. if (!didOne) {
  155. throw (new Error('Failed to sign the certificate for any ' +
  156. 'available certificate formats'));
  157. }
  158. };
  159. Certificate.createSelfSigned = function (subjectOrSubjects, key, options) {
  160. var subjects;
  161. if (Array.isArray(subjectOrSubjects))
  162. subjects = subjectOrSubjects;
  163. else
  164. subjects = [subjectOrSubjects];
  165. assert.arrayOfObject(subjects);
  166. subjects.forEach(function (subject) {
  167. utils.assertCompatible(subject, Identity, [1, 0], 'subject');
  168. });
  169. utils.assertCompatible(key, PrivateKey, [1, 2], 'private key');
  170. assert.optionalObject(options, 'options');
  171. if (options === undefined)
  172. options = {};
  173. assert.optionalObject(options.validFrom, 'options.validFrom');
  174. assert.optionalObject(options.validUntil, 'options.validUntil');
  175. var validFrom = options.validFrom;
  176. var validUntil = options.validUntil;
  177. if (validFrom === undefined)
  178. validFrom = new Date();
  179. if (validUntil === undefined) {
  180. assert.optionalNumber(options.lifetime, 'options.lifetime');
  181. var lifetime = options.lifetime;
  182. if (lifetime === undefined)
  183. lifetime = 10*365*24*3600;
  184. validUntil = new Date();
  185. validUntil.setTime(validUntil.getTime() + lifetime*1000);
  186. }
  187. assert.optionalBuffer(options.serial, 'options.serial');
  188. var serial = options.serial;
  189. if (serial === undefined)
  190. serial = Buffer.from('0000000000000001', 'hex');
  191. var purposes = options.purposes;
  192. if (purposes === undefined)
  193. purposes = [];
  194. if (purposes.indexOf('signature') === -1)
  195. purposes.push('signature');
  196. /* Self-signed certs are always CAs. */
  197. if (purposes.indexOf('ca') === -1)
  198. purposes.push('ca');
  199. if (purposes.indexOf('crl') === -1)
  200. purposes.push('crl');
  201. /*
  202. * If we weren't explicitly given any other purposes, do the sensible
  203. * thing and add some basic ones depending on the subject type.
  204. */
  205. if (purposes.length <= 3) {
  206. var hostSubjects = subjects.filter(function (subject) {
  207. return (subject.type === 'host');
  208. });
  209. var userSubjects = subjects.filter(function (subject) {
  210. return (subject.type === 'user');
  211. });
  212. if (hostSubjects.length > 0) {
  213. if (purposes.indexOf('serverAuth') === -1)
  214. purposes.push('serverAuth');
  215. }
  216. if (userSubjects.length > 0) {
  217. if (purposes.indexOf('clientAuth') === -1)
  218. purposes.push('clientAuth');
  219. }
  220. if (userSubjects.length > 0 || hostSubjects.length > 0) {
  221. if (purposes.indexOf('keyAgreement') === -1)
  222. purposes.push('keyAgreement');
  223. if (key.type === 'rsa' &&
  224. purposes.indexOf('encryption') === -1)
  225. purposes.push('encryption');
  226. }
  227. }
  228. var cert = new Certificate({
  229. subjects: subjects,
  230. issuer: subjects[0],
  231. subjectKey: key.toPublic(),
  232. issuerKey: key.toPublic(),
  233. signatures: {},
  234. serial: serial,
  235. validFrom: validFrom,
  236. validUntil: validUntil,
  237. purposes: purposes
  238. });
  239. cert.signWith(key);
  240. return (cert);
  241. };
  242. Certificate.create =
  243. function (subjectOrSubjects, key, issuer, issuerKey, options) {
  244. var subjects;
  245. if (Array.isArray(subjectOrSubjects))
  246. subjects = subjectOrSubjects;
  247. else
  248. subjects = [subjectOrSubjects];
  249. assert.arrayOfObject(subjects);
  250. subjects.forEach(function (subject) {
  251. utils.assertCompatible(subject, Identity, [1, 0], 'subject');
  252. });
  253. utils.assertCompatible(key, Key, [1, 0], 'key');
  254. if (PrivateKey.isPrivateKey(key))
  255. key = key.toPublic();
  256. utils.assertCompatible(issuer, Identity, [1, 0], 'issuer');
  257. utils.assertCompatible(issuerKey, PrivateKey, [1, 2], 'issuer key');
  258. assert.optionalObject(options, 'options');
  259. if (options === undefined)
  260. options = {};
  261. assert.optionalObject(options.validFrom, 'options.validFrom');
  262. assert.optionalObject(options.validUntil, 'options.validUntil');
  263. var validFrom = options.validFrom;
  264. var validUntil = options.validUntil;
  265. if (validFrom === undefined)
  266. validFrom = new Date();
  267. if (validUntil === undefined) {
  268. assert.optionalNumber(options.lifetime, 'options.lifetime');
  269. var lifetime = options.lifetime;
  270. if (lifetime === undefined)
  271. lifetime = 10*365*24*3600;
  272. validUntil = new Date();
  273. validUntil.setTime(validUntil.getTime() + lifetime*1000);
  274. }
  275. assert.optionalBuffer(options.serial, 'options.serial');
  276. var serial = options.serial;
  277. if (serial === undefined)
  278. serial = Buffer.from('0000000000000001', 'hex');
  279. var purposes = options.purposes;
  280. if (purposes === undefined)
  281. purposes = [];
  282. if (purposes.indexOf('signature') === -1)
  283. purposes.push('signature');
  284. if (options.ca === true) {
  285. if (purposes.indexOf('ca') === -1)
  286. purposes.push('ca');
  287. if (purposes.indexOf('crl') === -1)
  288. purposes.push('crl');
  289. }
  290. var hostSubjects = subjects.filter(function (subject) {
  291. return (subject.type === 'host');
  292. });
  293. var userSubjects = subjects.filter(function (subject) {
  294. return (subject.type === 'user');
  295. });
  296. if (hostSubjects.length > 0) {
  297. if (purposes.indexOf('serverAuth') === -1)
  298. purposes.push('serverAuth');
  299. }
  300. if (userSubjects.length > 0) {
  301. if (purposes.indexOf('clientAuth') === -1)
  302. purposes.push('clientAuth');
  303. }
  304. if (userSubjects.length > 0 || hostSubjects.length > 0) {
  305. if (purposes.indexOf('keyAgreement') === -1)
  306. purposes.push('keyAgreement');
  307. if (key.type === 'rsa' &&
  308. purposes.indexOf('encryption') === -1)
  309. purposes.push('encryption');
  310. }
  311. var cert = new Certificate({
  312. subjects: subjects,
  313. issuer: issuer,
  314. subjectKey: key,
  315. issuerKey: issuerKey.toPublic(),
  316. signatures: {},
  317. serial: serial,
  318. validFrom: validFrom,
  319. validUntil: validUntil,
  320. purposes: purposes
  321. });
  322. cert.signWith(issuerKey);
  323. return (cert);
  324. };
  325. Certificate.parse = function (data, format, options) {
  326. if (typeof (data) !== 'string')
  327. assert.buffer(data, 'data');
  328. if (format === undefined)
  329. format = 'auto';
  330. assert.string(format, 'format');
  331. if (typeof (options) === 'string')
  332. options = { filename: options };
  333. assert.optionalObject(options, 'options');
  334. if (options === undefined)
  335. options = {};
  336. assert.optionalString(options.filename, 'options.filename');
  337. if (options.filename === undefined)
  338. options.filename = '(unnamed)';
  339. assert.object(formats[format], 'formats[format]');
  340. try {
  341. var k = formats[format].read(data, options);
  342. return (k);
  343. } catch (e) {
  344. throw (new CertificateParseError(options.filename, format, e));
  345. }
  346. };
  347. Certificate.isCertificate = function (obj, ver) {
  348. return (utils.isCompatible(obj, Certificate, ver));
  349. };
  350. /*
  351. * API versions for Certificate:
  352. * [1,0] -- initial ver
  353. * [1,1] -- openssh format now unpacks extensions
  354. */
  355. Certificate.prototype._sshpkApiVersion = [1, 1];
  356. Certificate._oldVersionDetect = function (obj) {
  357. return ([1, 0]);
  358. };