util.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. // Copyright 2012 Joyent, Inc. All rights reserved.
  2. var assert = require('assert-plus');
  3. var crypto = require('crypto');
  4. var asn1 = require('asn1');
  5. var ctype = require('ctype');
  6. ///--- Helpers
  7. function readNext(buffer, offset) {
  8. var len = ctype.ruint32(buffer, 'big', offset);
  9. offset += 4;
  10. var newOffset = offset + len;
  11. return {
  12. data: buffer.slice(offset, newOffset),
  13. offset: newOffset
  14. };
  15. }
  16. function writeInt(writer, buffer) {
  17. writer.writeByte(0x02); // ASN1.Integer
  18. writer.writeLength(buffer.length);
  19. for (var i = 0; i < buffer.length; i++)
  20. writer.writeByte(buffer[i]);
  21. return writer;
  22. }
  23. function rsaToPEM(key) {
  24. var buffer;
  25. var der;
  26. var exponent;
  27. var i;
  28. var modulus;
  29. var newKey = '';
  30. var offset = 0;
  31. var type;
  32. var tmp;
  33. try {
  34. buffer = new Buffer(key.split(' ')[1], 'base64');
  35. tmp = readNext(buffer, offset);
  36. type = tmp.data.toString();
  37. offset = tmp.offset;
  38. if (type !== 'ssh-rsa')
  39. throw new Error('Invalid ssh key type: ' + type);
  40. tmp = readNext(buffer, offset);
  41. exponent = tmp.data;
  42. offset = tmp.offset;
  43. tmp = readNext(buffer, offset);
  44. modulus = tmp.data;
  45. } catch (e) {
  46. throw new Error('Invalid ssh key: ' + key);
  47. }
  48. // DER is a subset of BER
  49. der = new asn1.BerWriter();
  50. der.startSequence();
  51. der.startSequence();
  52. der.writeOID('1.2.840.113549.1.1.1');
  53. der.writeNull();
  54. der.endSequence();
  55. der.startSequence(0x03); // bit string
  56. der.writeByte(0x00);
  57. // Actual key
  58. der.startSequence();
  59. writeInt(der, modulus);
  60. writeInt(der, exponent);
  61. der.endSequence();
  62. // bit string
  63. der.endSequence();
  64. der.endSequence();
  65. tmp = der.buffer.toString('base64');
  66. for (i = 0; i < tmp.length; i++) {
  67. if ((i % 64) === 0)
  68. newKey += '\n';
  69. newKey += tmp.charAt(i);
  70. }
  71. if (!/\\n$/.test(newKey))
  72. newKey += '\n';
  73. return '-----BEGIN PUBLIC KEY-----' + newKey + '-----END PUBLIC KEY-----\n';
  74. }
  75. function dsaToPEM(key) {
  76. var buffer;
  77. var offset = 0;
  78. var tmp;
  79. var der;
  80. var newKey = '';
  81. var type;
  82. var p;
  83. var q;
  84. var g;
  85. var y;
  86. try {
  87. buffer = new Buffer(key.split(' ')[1], 'base64');
  88. tmp = readNext(buffer, offset);
  89. type = tmp.data.toString();
  90. offset = tmp.offset;
  91. /* JSSTYLED */
  92. if (!/^ssh-ds[as].*/.test(type))
  93. throw new Error('Invalid ssh key type: ' + type);
  94. tmp = readNext(buffer, offset);
  95. p = tmp.data;
  96. offset = tmp.offset;
  97. tmp = readNext(buffer, offset);
  98. q = tmp.data;
  99. offset = tmp.offset;
  100. tmp = readNext(buffer, offset);
  101. g = tmp.data;
  102. offset = tmp.offset;
  103. tmp = readNext(buffer, offset);
  104. y = tmp.data;
  105. } catch (e) {
  106. console.log(e.stack);
  107. throw new Error('Invalid ssh key: ' + key);
  108. }
  109. // DER is a subset of BER
  110. der = new asn1.BerWriter();
  111. der.startSequence();
  112. der.startSequence();
  113. der.writeOID('1.2.840.10040.4.1');
  114. der.startSequence();
  115. writeInt(der, p);
  116. writeInt(der, q);
  117. writeInt(der, g);
  118. der.endSequence();
  119. der.endSequence();
  120. der.startSequence(0x03); // bit string
  121. der.writeByte(0x00);
  122. writeInt(der, y);
  123. der.endSequence();
  124. der.endSequence();
  125. tmp = der.buffer.toString('base64');
  126. for (var i = 0; i < tmp.length; i++) {
  127. if ((i % 64) === 0)
  128. newKey += '\n';
  129. newKey += tmp.charAt(i);
  130. }
  131. if (!/\\n$/.test(newKey))
  132. newKey += '\n';
  133. return '-----BEGIN PUBLIC KEY-----' + newKey + '-----END PUBLIC KEY-----\n';
  134. }
  135. ///--- API
  136. module.exports = {
  137. /**
  138. * Converts an OpenSSH public key (rsa only) to a PKCS#8 PEM file.
  139. *
  140. * The intent of this module is to interoperate with OpenSSL only,
  141. * specifically the node crypto module's `verify` method.
  142. *
  143. * @param {String} key an OpenSSH public key.
  144. * @return {String} PEM encoded form of the RSA public key.
  145. * @throws {TypeError} on bad input.
  146. * @throws {Error} on invalid ssh key formatted data.
  147. */
  148. sshKeyToPEM: function sshKeyToPEM(key) {
  149. assert.string(key, 'ssh_key');
  150. /* JSSTYLED */
  151. if (/^ssh-rsa.*/.test(key))
  152. return rsaToPEM(key);
  153. /* JSSTYLED */
  154. if (/^ssh-ds[as].*/.test(key))
  155. return dsaToPEM(key);
  156. throw new Error('Only RSA and DSA public keys are allowed');
  157. },
  158. /**
  159. * Generates an OpenSSH fingerprint from an ssh public key.
  160. *
  161. * @param {String} key an OpenSSH public key.
  162. * @return {String} key fingerprint.
  163. * @throws {TypeError} on bad input.
  164. * @throws {Error} if what you passed doesn't look like an ssh public key.
  165. */
  166. fingerprint: function fingerprint(key) {
  167. assert.string(key, 'ssh_key');
  168. var pieces = key.split(' ');
  169. if (!pieces || !pieces.length || pieces.length < 2)
  170. throw new Error('invalid ssh key');
  171. var data = new Buffer(pieces[1], 'base64');
  172. var hash = crypto.createHash('md5');
  173. hash.update(data);
  174. var digest = hash.digest('hex');
  175. var fp = '';
  176. for (var i = 0; i < digest.length; i++) {
  177. if (i && i % 2 === 0)
  178. fp += ':';
  179. fp += digest[i];
  180. }
  181. return fp;
  182. },
  183. /**
  184. * Converts a PKGCS#8 PEM file to an OpenSSH public key (rsa)
  185. *
  186. * The reverse of the above function.
  187. */
  188. pemToRsaSSHKey: function pemToRsaSSHKey(pem, comment) {
  189. assert.equal('string', typeof pem, 'typeof pem');
  190. // chop off the BEGIN PUBLIC KEY and END PUBLIC KEY portion
  191. var cleaned = pem.split('\n').slice(1, -2).join('');
  192. var buf = new Buffer(cleaned, 'base64');
  193. var der = new asn1.BerReader(buf);
  194. der.readSequence();
  195. der.readSequence();
  196. var oid = der.readOID();
  197. assert.equal(oid, '1.2.840.113549.1.1.1', 'pem not in RSA format');
  198. // Null -- XXX this probably isn't good practice
  199. der.readByte();
  200. der.readByte();
  201. // bit string sequence
  202. der.readSequence(0x03);
  203. der.readByte();
  204. der.readSequence();
  205. // modulus
  206. assert.equal(der.peek(), asn1.Ber.Integer, 'modulus not an integer');
  207. der._offset = der.readLength(der.offset + 1);
  208. var modulus = der._buf.slice(der.offset, der.offset + der.length);
  209. der._offset += der.length;
  210. // exponent
  211. assert.equal(der.peek(), asn1.Ber.Integer, 'exponent not an integer');
  212. der._offset = der.readLength(der.offset + 1);
  213. var exponent = der._buf.slice(der.offset, der.offset + der.length);
  214. der._offset += der.length;
  215. // now, make the key
  216. var type = new Buffer('ssh-rsa');
  217. var buffer = new Buffer(4 + type.length + 4 + modulus.length + 4 + exponent.length);
  218. var i = 0;
  219. buffer.writeUInt32BE(type.length, i); i += 4;
  220. type.copy(buffer, i); i += type.length;
  221. buffer.writeUInt32BE(exponent.length, i); i += 4;
  222. exponent.copy(buffer, i); i += exponent.length;
  223. buffer.writeUInt32BE(modulus.length, i); i += 4;
  224. modulus.copy(buffer, i); i += modulus.length;
  225. var s = type.toString() + ' ' + buffer.toString('base64') + ' ' + (comment || '');
  226. return s;
  227. }
  228. };