certificate.js 10 KB

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