signature.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. // Copyright 2015 Joyent, Inc.
  2. module.exports = Signature;
  3. var assert = require('assert-plus');
  4. var algs = require('./algs');
  5. var crypto = require('crypto');
  6. var errs = require('./errors');
  7. var utils = require('./utils');
  8. var asn1 = require('asn1');
  9. var SSHBuffer = require('./ssh-buffer');
  10. var InvalidAlgorithmError = errs.InvalidAlgorithmError;
  11. var SignatureParseError = errs.SignatureParseError;
  12. function Signature(opts) {
  13. assert.object(opts, 'options');
  14. assert.arrayOfObject(opts.parts, 'options.parts');
  15. assert.string(opts.type, 'options.type');
  16. var partLookup = {};
  17. for (var i = 0; i < opts.parts.length; ++i) {
  18. var part = opts.parts[i];
  19. partLookup[part.name] = part;
  20. }
  21. this.type = opts.type;
  22. this.hashAlgorithm = opts.hashAlgo;
  23. this.curve = opts.curve;
  24. this.parts = opts.parts;
  25. this.part = partLookup;
  26. }
  27. Signature.prototype.toBuffer = function (format) {
  28. if (format === undefined)
  29. format = 'asn1';
  30. assert.string(format, 'format');
  31. var buf;
  32. var stype = 'ssh-' + this.type;
  33. switch (this.type) {
  34. case 'rsa':
  35. switch (this.hashAlgorithm) {
  36. case 'sha256':
  37. stype = 'rsa-sha2-256';
  38. break;
  39. case 'sha512':
  40. stype = 'rsa-sha2-512';
  41. break;
  42. case 'sha1':
  43. case undefined:
  44. break;
  45. default:
  46. throw (new Error('SSH signature ' +
  47. 'format does not support hash ' +
  48. 'algorithm ' + this.hashAlgorithm));
  49. }
  50. if (format === 'ssh') {
  51. buf = new SSHBuffer({});
  52. buf.writeString(stype);
  53. buf.writePart(this.part.sig);
  54. return (buf.toBuffer());
  55. } else {
  56. return (this.part.sig.data);
  57. }
  58. break;
  59. case 'ed25519':
  60. if (format === 'ssh') {
  61. buf = new SSHBuffer({});
  62. buf.writeString(stype);
  63. buf.writePart(this.part.sig);
  64. return (buf.toBuffer());
  65. } else {
  66. return (this.part.sig.data);
  67. }
  68. break;
  69. case 'dsa':
  70. case 'ecdsa':
  71. var r, s;
  72. if (format === 'asn1') {
  73. var der = new asn1.BerWriter();
  74. der.startSequence();
  75. r = utils.mpNormalize(this.part.r.data);
  76. s = utils.mpNormalize(this.part.s.data);
  77. der.writeBuffer(r, asn1.Ber.Integer);
  78. der.writeBuffer(s, asn1.Ber.Integer);
  79. der.endSequence();
  80. return (der.buffer);
  81. } else if (format === 'ssh' && this.type === 'dsa') {
  82. buf = new SSHBuffer({});
  83. buf.writeString('ssh-dss');
  84. r = this.part.r.data;
  85. if (r.length > 20 && r[0] === 0x00)
  86. r = r.slice(1);
  87. s = this.part.s.data;
  88. if (s.length > 20 && s[0] === 0x00)
  89. s = s.slice(1);
  90. if ((this.hashAlgorithm &&
  91. this.hashAlgorithm !== 'sha1') ||
  92. r.length + s.length !== 40) {
  93. throw (new Error('OpenSSH only supports ' +
  94. 'DSA signatures with SHA1 hash'));
  95. }
  96. buf.writeBuffer(Buffer.concat([r, s]));
  97. return (buf.toBuffer());
  98. } else if (format === 'ssh' && this.type === 'ecdsa') {
  99. var inner = new SSHBuffer({});
  100. r = this.part.r.data;
  101. inner.writeBuffer(r);
  102. inner.writePart(this.part.s);
  103. buf = new SSHBuffer({});
  104. /* XXX: find a more proper way to do this? */
  105. var curve;
  106. if (r[0] === 0x00)
  107. r = r.slice(1);
  108. var sz = r.length * 8;
  109. if (sz === 256)
  110. curve = 'nistp256';
  111. else if (sz === 384)
  112. curve = 'nistp384';
  113. else if (sz === 528)
  114. curve = 'nistp521';
  115. buf.writeString('ecdsa-sha2-' + curve);
  116. buf.writeBuffer(inner.toBuffer());
  117. return (buf.toBuffer());
  118. }
  119. throw (new Error('Invalid signature format'));
  120. default:
  121. throw (new Error('Invalid signature data'));
  122. }
  123. };
  124. Signature.prototype.toString = function (format) {
  125. assert.optionalString(format, 'format');
  126. return (this.toBuffer(format).toString('base64'));
  127. };
  128. Signature.parse = function (data, type, format) {
  129. if (typeof (data) === 'string')
  130. data = new Buffer(data, 'base64');
  131. assert.buffer(data, 'data');
  132. assert.string(format, 'format');
  133. assert.string(type, 'type');
  134. var opts = {};
  135. opts.type = type.toLowerCase();
  136. opts.parts = [];
  137. try {
  138. assert.ok(data.length > 0, 'signature must not be empty');
  139. switch (opts.type) {
  140. case 'rsa':
  141. return (parseOneNum(data, type, format, opts));
  142. case 'ed25519':
  143. return (parseOneNum(data, type, format, opts));
  144. case 'dsa':
  145. case 'ecdsa':
  146. if (format === 'asn1')
  147. return (parseDSAasn1(data, type, format, opts));
  148. else if (opts.type === 'dsa')
  149. return (parseDSA(data, type, format, opts));
  150. else
  151. return (parseECDSA(data, type, format, opts));
  152. default:
  153. throw (new InvalidAlgorithmError(type));
  154. }
  155. } catch (e) {
  156. if (e instanceof InvalidAlgorithmError)
  157. throw (e);
  158. throw (new SignatureParseError(type, format, e));
  159. }
  160. };
  161. function parseOneNum(data, type, format, opts) {
  162. if (format === 'ssh') {
  163. try {
  164. var buf = new SSHBuffer({buffer: data});
  165. var head = buf.readString();
  166. } catch (e) {
  167. /* fall through */
  168. }
  169. if (buf !== undefined) {
  170. var msg = 'SSH signature does not match expected ' +
  171. 'type (expected ' + type + ', got ' + head + ')';
  172. switch (head) {
  173. case 'ssh-rsa':
  174. assert.strictEqual(type, 'rsa', msg);
  175. opts.hashAlgo = 'sha1';
  176. break;
  177. case 'rsa-sha2-256':
  178. assert.strictEqual(type, 'rsa', msg);
  179. opts.hashAlgo = 'sha256';
  180. break;
  181. case 'rsa-sha2-512':
  182. assert.strictEqual(type, 'rsa', msg);
  183. opts.hashAlgo = 'sha512';
  184. break;
  185. case 'ssh-ed25519':
  186. assert.strictEqual(type, 'ed25519', msg);
  187. opts.hashAlgo = 'sha512';
  188. break;
  189. default:
  190. throw (new Error('Unknown SSH signature ' +
  191. 'type: ' + head));
  192. }
  193. var sig = buf.readPart();
  194. assert.ok(buf.atEnd(), 'extra trailing bytes');
  195. sig.name = 'sig';
  196. opts.parts.push(sig);
  197. return (new Signature(opts));
  198. }
  199. }
  200. opts.parts.push({name: 'sig', data: data});
  201. return (new Signature(opts));
  202. }
  203. function parseDSAasn1(data, type, format, opts) {
  204. var der = new asn1.BerReader(data);
  205. der.readSequence();
  206. var r = der.readString(asn1.Ber.Integer, true);
  207. var s = der.readString(asn1.Ber.Integer, true);
  208. opts.parts.push({name: 'r', data: utils.mpNormalize(r)});
  209. opts.parts.push({name: 's', data: utils.mpNormalize(s)});
  210. return (new Signature(opts));
  211. }
  212. function parseDSA(data, type, format, opts) {
  213. if (data.length != 40) {
  214. var buf = new SSHBuffer({buffer: data});
  215. var d = buf.readBuffer();
  216. if (d.toString('ascii') === 'ssh-dss')
  217. d = buf.readBuffer();
  218. assert.ok(buf.atEnd(), 'extra trailing bytes');
  219. assert.strictEqual(d.length, 40, 'invalid inner length');
  220. data = d;
  221. }
  222. opts.parts.push({name: 'r', data: data.slice(0, 20)});
  223. opts.parts.push({name: 's', data: data.slice(20, 40)});
  224. return (new Signature(opts));
  225. }
  226. function parseECDSA(data, type, format, opts) {
  227. var buf = new SSHBuffer({buffer: data});
  228. var r, s;
  229. var inner = buf.readBuffer();
  230. var stype = inner.toString('ascii');
  231. if (stype.slice(0, 6) === 'ecdsa-') {
  232. var parts = stype.split('-');
  233. assert.strictEqual(parts[0], 'ecdsa');
  234. assert.strictEqual(parts[1], 'sha2');
  235. opts.curve = parts[2];
  236. switch (opts.curve) {
  237. case 'nistp256':
  238. opts.hashAlgo = 'sha256';
  239. break;
  240. case 'nistp384':
  241. opts.hashAlgo = 'sha384';
  242. break;
  243. case 'nistp521':
  244. opts.hashAlgo = 'sha512';
  245. break;
  246. default:
  247. throw (new Error('Unsupported ECDSA curve: ' +
  248. opts.curve));
  249. }
  250. inner = buf.readBuffer();
  251. assert.ok(buf.atEnd(), 'extra trailing bytes on outer');
  252. buf = new SSHBuffer({buffer: inner});
  253. r = buf.readPart();
  254. } else {
  255. r = {data: inner};
  256. }
  257. s = buf.readPart();
  258. assert.ok(buf.atEnd(), 'extra trailing bytes');
  259. r.name = 'r';
  260. s.name = 's';
  261. opts.parts.push(r);
  262. opts.parts.push(s);
  263. return (new Signature(opts));
  264. }
  265. Signature.isSignature = function (obj, ver) {
  266. return (utils.isCompatible(obj, Signature, ver));
  267. };
  268. /*
  269. * API versions for Signature:
  270. * [1,0] -- initial ver
  271. * [2,0] -- support for rsa in full ssh format, compat with sshpk-agent
  272. * hashAlgorithm property
  273. * [2,1] -- first tagged version
  274. */
  275. Signature.prototype._sshpkApiVersion = [2, 1];
  276. Signature._oldVersionDetect = function (obj) {
  277. assert.func(obj.toBuffer);
  278. if (obj.hasOwnProperty('hashAlgorithm'))
  279. return ([2, 0]);
  280. return ([1, 0]);
  281. };