agent.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. var Socket = require('net').Socket;
  2. var EventEmitter = require('events').EventEmitter;
  3. var inherits = require('util').inherits;
  4. var path = require('path');
  5. var fs = require('fs');
  6. var cp = require('child_process');
  7. var readUInt32BE = require('./buffer-helpers').readUInt32BE;
  8. var writeUInt32BE = require('./buffer-helpers').writeUInt32BE;
  9. var writeUInt32LE = require('./buffer-helpers').writeUInt32LE;
  10. var REQUEST_IDENTITIES = 11;
  11. var IDENTITIES_ANSWER = 12;
  12. var SIGN_REQUEST = 13;
  13. var SIGN_RESPONSE = 14;
  14. var FAILURE = 5;
  15. var RE_CYGWIN_SOCK = /^\!<socket >(\d+) s ([A-Z0-9]{8}\-[A-Z0-9]{8}\-[A-Z0-9]{8}\-[A-Z0-9]{8})/;
  16. // Format of `//./pipe/ANYTHING`, with forward slashes and backward slashes being interchangeable
  17. var WINDOWS_PIPE_REGEX = /^[/\\][/\\]\.[/\\]pipe[/\\].+/;
  18. module.exports = function(sockPath, key, keyType, data, cb) {
  19. var sock;
  20. var error;
  21. var sig;
  22. var datalen;
  23. var keylen = 0;
  24. var isSigning = Buffer.isBuffer(key);
  25. var type;
  26. var count = 0;
  27. var siglen = 0;
  28. var nkeys = 0;
  29. var keys;
  30. var comlen = 0;
  31. var comment = false;
  32. var accept;
  33. var reject;
  34. if (typeof key === 'function' && typeof keyType === 'function') {
  35. // agent forwarding
  36. accept = key;
  37. reject = keyType;
  38. } else if (isSigning) {
  39. keylen = key.length;
  40. datalen = data.length;
  41. } else {
  42. cb = key;
  43. key = undefined;
  44. }
  45. function onconnect() {
  46. var buf;
  47. if (isSigning) {
  48. /*
  49. byte SSH2_AGENTC_SIGN_REQUEST
  50. string key_blob
  51. string data
  52. uint32 flags
  53. */
  54. var p = 9;
  55. buf = Buffer.allocUnsafe(4 + 1 + 4 + keylen + 4 + datalen + 4);
  56. writeUInt32BE(buf, buf.length - 4, 0);
  57. buf[4] = SIGN_REQUEST;
  58. writeUInt32BE(buf, keylen, 5);
  59. key.copy(buf, p);
  60. writeUInt32BE(buf, datalen, p += keylen);
  61. data.copy(buf, p += 4);
  62. writeUInt32BE(buf, 0, p += datalen);
  63. sock.write(buf);
  64. } else {
  65. /*
  66. byte SSH2_AGENTC_REQUEST_IDENTITIES
  67. */
  68. sock.write(Buffer.from([0, 0, 0, 1, REQUEST_IDENTITIES]));
  69. }
  70. }
  71. function ondata(chunk) {
  72. for (var i = 0, len = chunk.length; i < len; ++i) {
  73. if (type === undefined) {
  74. // skip over packet length
  75. if (++count === 5) {
  76. type = chunk[i];
  77. count = 0;
  78. }
  79. } else if (type === SIGN_RESPONSE) {
  80. /*
  81. byte SSH2_AGENT_SIGN_RESPONSE
  82. string signature_blob
  83. */
  84. if (!sig) {
  85. siglen <<= 8;
  86. siglen += chunk[i];
  87. if (++count === 4) {
  88. sig = Buffer.allocUnsafe(siglen);
  89. count = 0;
  90. }
  91. } else {
  92. sig[count] = chunk[i];
  93. if (++count === siglen) {
  94. sock.removeAllListeners('data');
  95. return sock.destroy();
  96. }
  97. }
  98. } else if (type === IDENTITIES_ANSWER) {
  99. /*
  100. byte SSH2_AGENT_IDENTITIES_ANSWER
  101. uint32 num_keys
  102. Followed by zero or more consecutive keys, encoded as:
  103. string public key blob
  104. string public key comment
  105. */
  106. if (keys === undefined) {
  107. nkeys <<= 8;
  108. nkeys += chunk[i];
  109. if (++count === 4) {
  110. keys = new Array(nkeys);
  111. count = 0;
  112. if (nkeys === 0) {
  113. sock.removeAllListeners('data');
  114. return sock.destroy();
  115. }
  116. }
  117. } else {
  118. if (!key) {
  119. keylen <<= 8;
  120. keylen += chunk[i];
  121. if (++count === 4) {
  122. key = Buffer.allocUnsafe(keylen);
  123. count = 0;
  124. }
  125. } else if (comment === false) {
  126. key[count] = chunk[i];
  127. if (++count === keylen) {
  128. keys[nkeys - 1] = key;
  129. keylen = 0;
  130. count = 0;
  131. comment = true;
  132. if (--nkeys === 0) {
  133. key = undefined;
  134. sock.removeAllListeners('data');
  135. return sock.destroy();
  136. }
  137. }
  138. } else if (comment === true) {
  139. comlen <<= 8;
  140. comlen += chunk[i];
  141. if (++count === 4) {
  142. count = 0;
  143. if (comlen > 0)
  144. comment = comlen;
  145. else {
  146. key = undefined;
  147. comment = false;
  148. }
  149. comlen = 0;
  150. }
  151. } else {
  152. // skip comments
  153. if (++count === comment) {
  154. comment = false;
  155. count = 0;
  156. key = undefined;
  157. }
  158. }
  159. }
  160. } else if (type === FAILURE) {
  161. if (isSigning)
  162. error = new Error('Agent unable to sign data');
  163. else
  164. error = new Error('Unable to retrieve list of keys from agent');
  165. sock.removeAllListeners('data');
  166. return sock.destroy();
  167. }
  168. }
  169. }
  170. function onerror(err) {
  171. error = err;
  172. }
  173. function onclose() {
  174. if (error)
  175. cb(error);
  176. else if ((isSigning && !sig) || (!isSigning && !keys))
  177. cb(new Error('Unexpected disconnection from agent'));
  178. else if (isSigning && sig)
  179. cb(undefined, sig);
  180. else if (!isSigning && keys)
  181. cb(undefined, keys);
  182. }
  183. if (process.platform === 'win32' && !WINDOWS_PIPE_REGEX.test(sockPath)) {
  184. if (sockPath === 'pageant') {
  185. // Pageant (PuTTY authentication agent)
  186. sock = new PageantSock();
  187. } else {
  188. // cygwin ssh-agent instance
  189. var triedCygpath = false;
  190. fs.readFile(sockPath, function readCygsocket(err, data) {
  191. if (err) {
  192. if (triedCygpath)
  193. return cb(new Error('Invalid cygwin unix socket path'));
  194. // try using `cygpath` to convert a possible *nix-style path to the
  195. // real Windows path before giving up ...
  196. cp.exec('cygpath -w "' + sockPath + '"',
  197. function(err, stdout, stderr) {
  198. if (err || stdout.length === 0)
  199. return cb(new Error('Invalid cygwin unix socket path'));
  200. triedCygpath = true;
  201. sockPath = stdout.toString().replace(/[\r\n]/g, '');
  202. fs.readFile(sockPath, readCygsocket);
  203. });
  204. return;
  205. }
  206. var m;
  207. if (m = RE_CYGWIN_SOCK.exec(data.toString('ascii'))) {
  208. var port;
  209. var secret;
  210. var secretbuf;
  211. var state;
  212. var bc = 0;
  213. var isRetrying = false;
  214. var inbuf = [];
  215. var credsbuf = Buffer.allocUnsafe(12);
  216. var i;
  217. var j;
  218. // use 0 for pid, uid, and gid to ensure we get an error and also
  219. // a valid uid and gid from cygwin so that we don't have to figure it
  220. // out ourselves
  221. credsbuf.fill(0);
  222. // parse cygwin unix socket file contents
  223. port = parseInt(m[1], 10);
  224. secret = m[2].replace(/\-/g, '');
  225. secretbuf = Buffer.allocUnsafe(16);
  226. for (i = 0, j = 0; j < 32; ++i,j+=2)
  227. secretbuf[i] = parseInt(secret.substring(j, j + 2), 16);
  228. // convert to host order (always LE for Windows)
  229. for (i = 0; i < 16; i += 4)
  230. writeUInt32LE(secretbuf, readUInt32BE(secretbuf, i), i);
  231. function _onconnect() {
  232. bc = 0;
  233. state = 'secret';
  234. sock.write(secretbuf);
  235. }
  236. function _ondata(data) {
  237. bc += data.length;
  238. if (state === 'secret') {
  239. // the secret we sent is echoed back to us by cygwin, not sure of
  240. // the reason for that, but we ignore it nonetheless ...
  241. if (bc === 16) {
  242. bc = 0;
  243. state = 'creds';
  244. sock.write(credsbuf);
  245. }
  246. } else if (state === 'creds') {
  247. // if this is the first attempt, make sure to gather the valid
  248. // uid and gid for our next attempt
  249. if (!isRetrying)
  250. inbuf.push(data);
  251. if (bc === 12) {
  252. sock.removeListener('connect', _onconnect);
  253. sock.removeListener('data', _ondata);
  254. sock.removeListener('close', _onclose);
  255. if (isRetrying) {
  256. addSockListeners();
  257. sock.emit('connect');
  258. } else {
  259. isRetrying = true;
  260. credsbuf = Buffer.concat(inbuf);
  261. writeUInt32LE(credsbuf, process.pid, 0);
  262. sock.destroy();
  263. tryConnect();
  264. }
  265. }
  266. }
  267. }
  268. function _onclose() {
  269. cb(new Error('Problem negotiating cygwin unix socket security'));
  270. }
  271. function tryConnect() {
  272. sock = new Socket();
  273. sock.once('connect', _onconnect);
  274. sock.on('data', _ondata);
  275. sock.once('close', _onclose);
  276. sock.connect(port);
  277. }
  278. tryConnect();
  279. } else
  280. cb(new Error('Malformed cygwin unix socket file'));
  281. });
  282. return;
  283. }
  284. } else
  285. sock = new Socket();
  286. function addSockListeners() {
  287. if (!accept && !reject) {
  288. sock.once('connect', onconnect);
  289. sock.on('data', ondata);
  290. sock.once('error', onerror);
  291. sock.once('close', onclose);
  292. } else {
  293. var chan;
  294. sock.once('connect', function() {
  295. chan = accept();
  296. var isDone = false;
  297. function onDone() {
  298. if (isDone)
  299. return;
  300. sock.destroy();
  301. isDone = true;
  302. }
  303. chan.once('end', onDone)
  304. .once('close', onDone)
  305. .on('data', function(data) {
  306. sock.write(data);
  307. });
  308. sock.on('data', function(data) {
  309. chan.write(data);
  310. });
  311. });
  312. sock.once('close', function() {
  313. if (!chan)
  314. reject();
  315. });
  316. }
  317. }
  318. addSockListeners();
  319. sock.connect(sockPath);
  320. };
  321. // win32 only ------------------------------------------------------------------
  322. if (process.platform === 'win32') {
  323. var RET_ERR_BADARGS = 10;
  324. var RET_ERR_UNAVAILABLE = 11;
  325. var RET_ERR_NOMAP = 12;
  326. var RET_ERR_BINSTDIN = 13;
  327. var RET_ERR_BINSTDOUT = 14;
  328. var RET_ERR_BADLEN = 15;
  329. var ERROR = {};
  330. var EXEPATH = path.resolve(__dirname, '..', 'util/pagent.exe');
  331. ERROR[RET_ERR_BADARGS] = new Error('Invalid pagent.exe arguments');
  332. ERROR[RET_ERR_UNAVAILABLE] = new Error('Pageant is not running');
  333. ERROR[RET_ERR_NOMAP] = new Error('pagent.exe could not create an mmap');
  334. ERROR[RET_ERR_BINSTDIN] = new Error('pagent.exe could not set mode for stdin');
  335. ERROR[RET_ERR_BINSTDOUT] = new Error('pagent.exe could not set mode for stdout');
  336. ERROR[RET_ERR_BADLEN] = new Error('pagent.exe did not get expected input payload');
  337. function PageantSock() {
  338. this.proc = undefined;
  339. this.buffer = null;
  340. }
  341. inherits(PageantSock, EventEmitter);
  342. PageantSock.prototype.write = function(buf) {
  343. if (this.buffer === null)
  344. this.buffer = buf;
  345. else {
  346. this.buffer = Buffer.concat([this.buffer, buf],
  347. this.buffer.length + buf.length);
  348. }
  349. // Wait for at least all length bytes
  350. if (this.buffer.length < 4)
  351. return;
  352. var len = readUInt32BE(this.buffer, 0);
  353. // Make sure we have a full message before querying pageant
  354. if ((this.buffer.length - 4) < len)
  355. return;
  356. buf = this.buffer.slice(0, 4 + len);
  357. if (this.buffer.length > (4 + len))
  358. this.buffer = this.buffer.slice(4 + len);
  359. else
  360. this.buffer = null;
  361. var self = this;
  362. var proc;
  363. var hadError = false;
  364. proc = this.proc = cp.spawn(EXEPATH, [ buf.length ]);
  365. proc.stdout.on('data', function(data) {
  366. self.emit('data', data);
  367. });
  368. proc.once('error', function(err) {
  369. if (!hadError) {
  370. hadError = true;
  371. self.emit('error', err);
  372. }
  373. });
  374. proc.once('close', function(code) {
  375. self.proc = undefined;
  376. if (ERROR[code] && !hadError) {
  377. hadError = true;
  378. self.emit('error', ERROR[code]);
  379. }
  380. self.emit('close', hadError);
  381. });
  382. proc.stdin.end(buf);
  383. };
  384. PageantSock.prototype.end = PageantSock.prototype.destroy = function() {
  385. this.buffer = null;
  386. if (this.proc) {
  387. this.proc.kill();
  388. this.proc = undefined;
  389. }
  390. };
  391. PageantSock.prototype.connect = function() {
  392. this.emit('connect');
  393. };
  394. }