certificate.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  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. this._hashCache = {};
  37. this.subjects = opts.subjects;
  38. this.issuer = opts.issuer;
  39. this.subjectKey = opts.subjectKey;
  40. this.issuerKey = opts.issuerKey;
  41. this.signatures = opts.signatures;
  42. this.serial = opts.serial;
  43. this.validFrom = opts.validFrom;
  44. this.validUntil = opts.validUntil;
  45. }
  46. Certificate.formats = formats;
  47. Certificate.prototype.toBuffer = function (format, options) {
  48. if (format === undefined)
  49. format = 'x509';
  50. assert.string(format, 'format');
  51. assert.object(formats[format], 'formats[format]');
  52. assert.optionalObject(options, 'options');
  53. return (formats[format].write(this, options));
  54. };
  55. Certificate.prototype.toString = function (format, options) {
  56. if (format === undefined)
  57. format = 'pem';
  58. return (this.toBuffer(format, options).toString());
  59. };
  60. Certificate.prototype.fingerprint = function (algo) {
  61. if (algo === undefined)
  62. algo = 'sha256';
  63. assert.string(algo, 'algorithm');
  64. var opts = {
  65. type: 'certificate',
  66. hash: this.hash(algo),
  67. algorithm: algo
  68. };
  69. return (new Fingerprint(opts));
  70. };
  71. Certificate.prototype.hash = function (algo) {
  72. assert.string(algo, 'algorithm');
  73. algo = algo.toLowerCase();
  74. if (algs.hashAlgs[algo] === undefined)
  75. throw (new InvalidAlgorithmError(algo));
  76. if (this._hashCache[algo])
  77. return (this._hashCache[algo]);
  78. var hash = crypto.createHash(algo).
  79. update(this.toBuffer('x509')).digest();
  80. this._hashCache[algo] = hash;
  81. return (hash);
  82. };
  83. Certificate.prototype.isExpired = function (when) {
  84. if (when === undefined)
  85. when = new Date();
  86. return (!((when.getTime() >= this.validFrom.getTime()) &&
  87. (when.getTime() < this.validUntil.getTime())));
  88. };
  89. Certificate.prototype.isSignedBy = function (issuerCert) {
  90. utils.assertCompatible(issuerCert, Certificate, [1, 0], 'issuer');
  91. if (!this.issuer.equals(issuerCert.subjects[0]))
  92. return (false);
  93. return (this.isSignedByKey(issuerCert.subjectKey));
  94. };
  95. Certificate.prototype.isSignedByKey = function (issuerKey) {
  96. utils.assertCompatible(issuerKey, Key, [1, 2], 'issuerKey');
  97. if (this.issuerKey !== undefined) {
  98. return (this.issuerKey.
  99. fingerprint('sha512').matches(issuerKey));
  100. }
  101. var fmt = Object.keys(this.signatures)[0];
  102. var valid = formats[fmt].verify(this, issuerKey);
  103. if (valid)
  104. this.issuerKey = issuerKey;
  105. return (valid);
  106. };
  107. Certificate.prototype.signWith = function (key) {
  108. utils.assertCompatible(key, PrivateKey, [1, 2], 'key');
  109. var fmts = Object.keys(formats);
  110. var didOne = false;
  111. for (var i = 0; i < fmts.length; ++i) {
  112. if (fmts[i] !== 'pem') {
  113. var ret = formats[fmts[i]].sign(this, key);
  114. if (ret === true)
  115. didOne = true;
  116. }
  117. }
  118. if (!didOne) {
  119. throw (new Error('Failed to sign the certificate for any ' +
  120. 'available certificate formats'));
  121. }
  122. };
  123. Certificate.createSelfSigned = function (subjectOrSubjects, key, options) {
  124. var subjects;
  125. if (Array.isArray(subjectOrSubjects))
  126. subjects = subjectOrSubjects;
  127. else
  128. subjects = [subjectOrSubjects];
  129. assert.arrayOfObject(subjects);
  130. subjects.forEach(function (subject) {
  131. utils.assertCompatible(subject, Identity, [1, 0], 'subject');
  132. });
  133. utils.assertCompatible(key, PrivateKey, [1, 2], 'private key');
  134. assert.optionalObject(options, 'options');
  135. if (options === undefined)
  136. options = {};
  137. assert.optionalObject(options.validFrom, 'options.validFrom');
  138. assert.optionalObject(options.validUntil, 'options.validUntil');
  139. var validFrom = options.validFrom;
  140. var validUntil = options.validUntil;
  141. if (validFrom === undefined)
  142. validFrom = new Date();
  143. if (validUntil === undefined) {
  144. assert.optionalNumber(options.lifetime, 'options.lifetime');
  145. var lifetime = options.lifetime;
  146. if (lifetime === undefined)
  147. lifetime = 10*365*24*3600;
  148. validUntil = new Date();
  149. validUntil.setTime(validUntil.getTime() + lifetime*1000);
  150. }
  151. assert.optionalBuffer(options.serial, 'options.serial');
  152. var serial = options.serial;
  153. if (serial === undefined)
  154. serial = new Buffer('0000000000000001', 'hex');
  155. var cert = new Certificate({
  156. subjects: subjects,
  157. issuer: subjects[0],
  158. subjectKey: key.toPublic(),
  159. issuerKey: key.toPublic(),
  160. signatures: {},
  161. serial: serial,
  162. validFrom: validFrom,
  163. validUntil: validUntil
  164. });
  165. cert.signWith(key);
  166. return (cert);
  167. };
  168. Certificate.create =
  169. function (subjectOrSubjects, key, issuer, issuerKey, options) {
  170. var subjects;
  171. if (Array.isArray(subjectOrSubjects))
  172. subjects = subjectOrSubjects;
  173. else
  174. subjects = [subjectOrSubjects];
  175. assert.arrayOfObject(subjects);
  176. subjects.forEach(function (subject) {
  177. utils.assertCompatible(subject, Identity, [1, 0], 'subject');
  178. });
  179. utils.assertCompatible(key, Key, [1, 0], 'key');
  180. if (PrivateKey.isPrivateKey(key))
  181. key = key.toPublic();
  182. utils.assertCompatible(issuer, Identity, [1, 0], 'issuer');
  183. utils.assertCompatible(issuerKey, PrivateKey, [1, 2], 'issuer key');
  184. assert.optionalObject(options, 'options');
  185. if (options === undefined)
  186. options = {};
  187. assert.optionalObject(options.validFrom, 'options.validFrom');
  188. assert.optionalObject(options.validUntil, 'options.validUntil');
  189. var validFrom = options.validFrom;
  190. var validUntil = options.validUntil;
  191. if (validFrom === undefined)
  192. validFrom = new Date();
  193. if (validUntil === undefined) {
  194. assert.optionalNumber(options.lifetime, 'options.lifetime');
  195. var lifetime = options.lifetime;
  196. if (lifetime === undefined)
  197. lifetime = 10*365*24*3600;
  198. validUntil = new Date();
  199. validUntil.setTime(validUntil.getTime() + lifetime*1000);
  200. }
  201. assert.optionalBuffer(options.serial, 'options.serial');
  202. var serial = options.serial;
  203. if (serial === undefined)
  204. serial = new Buffer('0000000000000001', 'hex');
  205. var cert = new Certificate({
  206. subjects: subjects,
  207. issuer: issuer,
  208. subjectKey: key,
  209. issuerKey: issuerKey.toPublic(),
  210. signatures: {},
  211. serial: serial,
  212. validFrom: validFrom,
  213. validUntil: validUntil
  214. });
  215. cert.signWith(issuerKey);
  216. return (cert);
  217. };
  218. Certificate.parse = function (data, format, options) {
  219. if (typeof (data) !== 'string')
  220. assert.buffer(data, 'data');
  221. if (format === undefined)
  222. format = 'auto';
  223. assert.string(format, 'format');
  224. if (typeof (options) === 'string')
  225. options = { filename: options };
  226. assert.optionalObject(options, 'options');
  227. if (options === undefined)
  228. options = {};
  229. assert.optionalString(options.filename, 'options.filename');
  230. if (options.filename === undefined)
  231. options.filename = '(unnamed)';
  232. assert.object(formats[format], 'formats[format]');
  233. try {
  234. var k = formats[format].read(data, options);
  235. return (k);
  236. } catch (e) {
  237. throw (new CertificateParseError(options.filename, format, e));
  238. }
  239. };
  240. Certificate.isCertificate = function (obj, ver) {
  241. return (utils.isCompatible(obj, Certificate, ver));
  242. };
  243. /*
  244. * API versions for Certificate:
  245. * [1,0] -- initial ver
  246. */
  247. Certificate.prototype._sshpkApiVersion = [1, 0];
  248. Certificate._oldVersionDetect = function (obj) {
  249. return ([1, 0]);
  250. };