signature.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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.parts = opts.parts;
  24. this.part = partLookup;
  25. }
  26. Signature.prototype.toBuffer = function (format) {
  27. if (format === undefined)
  28. format = 'asn1';
  29. assert.string(format, 'format');
  30. var buf;
  31. switch (this.type) {
  32. case 'rsa':
  33. case 'ed25519':
  34. if (format === 'ssh') {
  35. buf = new SSHBuffer({});
  36. buf.writeString('ssh-' + this.type);
  37. buf.writePart(this.part.sig);
  38. return (buf.toBuffer());
  39. } else {
  40. return (this.part.sig.data);
  41. }
  42. case 'dsa':
  43. case 'ecdsa':
  44. var r, s;
  45. if (format === 'asn1') {
  46. var der = new asn1.BerWriter();
  47. der.startSequence();
  48. r = utils.mpNormalize(this.part.r.data);
  49. s = utils.mpNormalize(this.part.s.data);
  50. der.writeBuffer(r, asn1.Ber.Integer);
  51. der.writeBuffer(s, asn1.Ber.Integer);
  52. der.endSequence();
  53. return (der.buffer);
  54. } else if (format === 'ssh' && this.type === 'dsa') {
  55. buf = new SSHBuffer({});
  56. buf.writeString('ssh-dss');
  57. r = this.part.r.data;
  58. if (r.length > 20 && r[0] === 0x00)
  59. r = r.slice(1);
  60. s = this.part.s.data;
  61. if (s.length > 20 && s[0] === 0x00)
  62. s = s.slice(1);
  63. if ((this.hashAlgorithm &&
  64. this.hashAlgorithm !== 'sha1') ||
  65. r.length + s.length !== 40) {
  66. throw (new Error('OpenSSH only supports ' +
  67. 'DSA signatures with SHA1 hash'));
  68. }
  69. buf.writeBuffer(Buffer.concat([r, s]));
  70. return (buf.toBuffer());
  71. } else if (format === 'ssh' && this.type === 'ecdsa') {
  72. var inner = new SSHBuffer({});
  73. r = this.part.r.data;
  74. inner.writeBuffer(r);
  75. inner.writePart(this.part.s);
  76. buf = new SSHBuffer({});
  77. /* XXX: find a more proper way to do this? */
  78. var curve;
  79. if (r[0] === 0x00)
  80. r = r.slice(1);
  81. var sz = r.length * 8;
  82. if (sz === 256)
  83. curve = 'nistp256';
  84. else if (sz === 384)
  85. curve = 'nistp384';
  86. else if (sz === 528)
  87. curve = 'nistp521';
  88. buf.writeString('ecdsa-sha2-' + curve);
  89. buf.writeBuffer(inner.toBuffer());
  90. return (buf.toBuffer());
  91. }
  92. throw (new Error('Invalid signature format'));
  93. default:
  94. throw (new Error('Invalid signature data'));
  95. }
  96. };
  97. Signature.prototype.toString = function (format) {
  98. assert.optionalString(format, 'format');
  99. return (this.toBuffer(format).toString('base64'));
  100. };
  101. Signature.parse = function (data, type, format) {
  102. if (typeof (data) === 'string')
  103. data = new Buffer(data, 'base64');
  104. assert.buffer(data, 'data');
  105. assert.string(format, 'format');
  106. assert.string(type, 'type');
  107. var opts = {};
  108. opts.type = type.toLowerCase();
  109. opts.parts = [];
  110. try {
  111. assert.ok(data.length > 0, 'signature must not be empty');
  112. switch (opts.type) {
  113. case 'rsa':
  114. return (parseOneNum(data, type, format, opts,
  115. 'ssh-rsa'));
  116. case 'ed25519':
  117. return (parseOneNum(data, type, format, opts,
  118. 'ssh-ed25519'));
  119. case 'dsa':
  120. case 'ecdsa':
  121. if (format === 'asn1')
  122. return (parseDSAasn1(data, type, format, opts));
  123. else if (opts.type === 'dsa')
  124. return (parseDSA(data, type, format, opts));
  125. else
  126. return (parseECDSA(data, type, format, opts));
  127. default:
  128. throw (new InvalidAlgorithmError(type));
  129. }
  130. } catch (e) {
  131. if (e instanceof InvalidAlgorithmError)
  132. throw (e);
  133. throw (new SignatureParseError(type, format, e));
  134. }
  135. };
  136. function parseOneNum(data, type, format, opts, headType) {
  137. if (format === 'ssh') {
  138. try {
  139. var buf = new SSHBuffer({buffer: data});
  140. var head = buf.readString();
  141. } catch (e) {
  142. /* fall through */
  143. }
  144. if (head === headType) {
  145. var sig = buf.readPart();
  146. assert.ok(buf.atEnd(), 'extra trailing bytes');
  147. sig.name = 'sig';
  148. opts.parts.push(sig);
  149. return (new Signature(opts));
  150. }
  151. }
  152. opts.parts.push({name: 'sig', data: data});
  153. return (new Signature(opts));
  154. }
  155. function parseDSAasn1(data, type, format, opts) {
  156. var der = new asn1.BerReader(data);
  157. der.readSequence();
  158. var r = der.readString(asn1.Ber.Integer, true);
  159. var s = der.readString(asn1.Ber.Integer, true);
  160. opts.parts.push({name: 'r', data: utils.mpNormalize(r)});
  161. opts.parts.push({name: 's', data: utils.mpNormalize(s)});
  162. return (new Signature(opts));
  163. }
  164. function parseDSA(data, type, format, opts) {
  165. if (data.length != 40) {
  166. var buf = new SSHBuffer({buffer: data});
  167. var d = buf.readBuffer();
  168. if (d.toString('ascii') === 'ssh-dss')
  169. d = buf.readBuffer();
  170. assert.ok(buf.atEnd(), 'extra trailing bytes');
  171. assert.strictEqual(d.length, 40, 'invalid inner length');
  172. data = d;
  173. }
  174. opts.parts.push({name: 'r', data: data.slice(0, 20)});
  175. opts.parts.push({name: 's', data: data.slice(20, 40)});
  176. return (new Signature(opts));
  177. }
  178. function parseECDSA(data, type, format, opts) {
  179. var buf = new SSHBuffer({buffer: data});
  180. var r, s;
  181. var inner = buf.readBuffer();
  182. if (inner.toString('ascii').match(/^ecdsa-/)) {
  183. inner = buf.readBuffer();
  184. assert.ok(buf.atEnd(), 'extra trailing bytes on outer');
  185. buf = new SSHBuffer({buffer: inner});
  186. r = buf.readPart();
  187. } else {
  188. r = {data: inner};
  189. }
  190. s = buf.readPart();
  191. assert.ok(buf.atEnd(), 'extra trailing bytes');
  192. r.name = 'r';
  193. s.name = 's';
  194. opts.parts.push(r);
  195. opts.parts.push(s);
  196. return (new Signature(opts));
  197. }
  198. Signature.isSignature = function (obj, ver) {
  199. return (utils.isCompatible(obj, Signature, ver));
  200. };
  201. /*
  202. * API versions for Signature:
  203. * [1,0] -- initial ver
  204. * [2,0] -- support for rsa in full ssh format, compat with sshpk-agent
  205. * hashAlgorithm property
  206. * [2,1] -- first tagged version
  207. */
  208. Signature.prototype._sshpkApiVersion = [2, 1];
  209. Signature._oldVersionDetect = function (obj) {
  210. assert.func(obj.toBuffer);
  211. if (obj.hasOwnProperty('hashAlgorithm'))
  212. return ([2, 0]);
  213. return ([1, 0]);
  214. };