| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var SSH = module.exports; | 
					
						
							|  |  |  | var Enc = require('./encoding.js'); | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  | var PEM = require('./pem.js'); | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  | SSH.parse = function (opts) { | 
					
						
							| 
									
										
										
										
											2018-12-09 01:43:41 -07:00
										 |  |  |   var pub = opts.pem || opts.pub || opts; | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  |   var ssh = SSH.parseBlock(pub); | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |   if ('OPENSSH PRIVATE KEY' === ssh.type) { | 
					
						
							|  |  |  |     ssh = SSH.parsePrivateElements(ssh); | 
					
						
							| 
									
										
										
										
											2018-12-09 21:03:38 -07:00
										 |  |  |     if (7 === ssh.elements.length) { | 
					
						
							|  |  |  |       // RSA Private Keys have the `e` and `n` swapped (which is actually more normal)
 | 
					
						
							|  |  |  |       // but we have to reswap them to make them consistent with the public key format
 | 
					
						
							|  |  |  |       ssh.elements.splice(1, 0, ssh.elements.splice(2 ,1)[0]); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |     if (opts.public) { | 
					
						
							|  |  |  |       ssh.elements = ssh.elements.slice(0, 3); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     ssh.elements = SSH.parseElements(ssh.bytes); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  |   //delete ssh.bytes;
 | 
					
						
							|  |  |  |   return SSH.parsePublicKey(ssh); | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  | /*global Promise*/ | 
					
						
							|  |  |  | SSH.fingerprint = function (opts) { | 
					
						
							|  |  |  |   var ssh; | 
					
						
							|  |  |  |   if (opts.bytes) { | 
					
						
							|  |  |  |     ssh = opts; | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  |   } else { | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  |     ssh = SSH.parseBlock(opts.pub); | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  |   // for browser compat
 | 
					
						
							|  |  |  |   return Promise.resolve().then(function () { | 
					
						
							|  |  |  |     return 'SHA256:' + require('crypto').createHash('sha256') | 
					
						
							|  |  |  |       .update(ssh.bytes).digest('base64').replace(/=+$/g, ''); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  | SSH.parseBlock = function (ssh) { | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |   if (/^-----BEGIN OPENSSH PRIVATE KEY-----/.test(ssh)) { | 
					
						
							|  |  |  |     return PEM.parseBlock(ssh); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  |   ssh = ssh.split(/\s+/g); | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  |   return { | 
					
						
							|  |  |  |     type: ssh[0] | 
					
						
							|  |  |  |   , bytes: Enc.base64ToBuf(ssh[1]) | 
					
						
							|  |  |  |   , comment: ssh[2] | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  | SSH.parsePrivateElements = function (ssh) { | 
					
						
							|  |  |  |   // https://coolaj86.com/articles/the-openssh-private-key-format/
 | 
					
						
							| 
									
										
										
										
											2018-12-01 22:05:03 -07:00
										 |  |  |   var buf = ssh.bytes; | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  |   var fulllen = buf.byteLength || buf.length; | 
					
						
							|  |  |  |   var offset = (buf.byteOffset || 0); | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |   var dv = new DataView(buf.buffer.slice(offset, offset + fulllen)); | 
					
						
							|  |  |  |   var index = 0; | 
					
						
							|  |  |  |   var padlen = 0; | 
					
						
							|  |  |  |   var len; | 
					
						
							| 
									
										
										
										
											2018-12-09 01:43:41 -07:00
										 |  |  |   var pub; | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // The last byte will be either
 | 
					
						
							|  |  |  |   //   * a non-printable pad character
 | 
					
						
							|  |  |  |   //   * a printable comment character
 | 
					
						
							|  |  |  |   function lastByteIsPad() { | 
					
						
							|  |  |  |     var n = ssh.bytes[(ssh.bytes.bytesLength || ssh.bytes.length) - 1]; | 
					
						
							|  |  |  |     return n >= 0x01 && n <= 0x07; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   while (lastByteIsPad()) { | 
					
						
							|  |  |  |     padlen += 1; | 
					
						
							|  |  |  |     len = (ssh.bytes.bytesLength || ssh.bytes.length); | 
					
						
							|  |  |  |     ssh.bytes = ssh.bytes.slice(0, len - 1); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   //  o  p  e  n  s  s  h  -  k  e  y  -  v  1  NULL
 | 
					
						
							|  |  |  |   // 6f 70 65 6e 73 73 68 2d 6b 65 79 2d 76 31 00
 | 
					
						
							|  |  |  |   // 15 characters
 | 
					
						
							|  |  |  |   // 4-byte len, "none" (encryption)
 | 
					
						
							|  |  |  |   // 4-byte len, "none" (kdfname)
 | 
					
						
							|  |  |  |   if ('none' !== Enc.bufToBin(ssh.bytes.slice(15 + 8 + 4, 15 + 8 + 8))) { | 
					
						
							|  |  |  |     throw new Error("Key is either encrypted (not yet supported), corrupt, or not openssh-key-v1"); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (padlen >= 8) { | 
					
						
							|  |  |  |     throw new Error("Padding length should be between 0 and 7, not '" + padlen + "'." | 
					
						
							|  |  |  |       + " Probably not an ssh private key."); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   // 4-byte len, nil (kdf)
 | 
					
						
							|  |  |  |   // 4-byte number of keys
 | 
					
						
							|  |  |  |   index += 15 + 8 + 8 + 4 + 4; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // length of public key
 | 
					
						
							|  |  |  |   len = dv.getUint32(index, false); | 
					
						
							|  |  |  |   // throw away public key (it's in the private key)
 | 
					
						
							|  |  |  |   index += 4 + len; | 
					
						
							| 
									
										
										
										
											2018-12-09 01:43:41 -07:00
										 |  |  |   pub = ssh.bytes.slice(index - len, index); | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // length of dummy checksum + private key + padding
 | 
					
						
							|  |  |  |   len = dv.getUint32(index, false) - padlen; | 
					
						
							|  |  |  |   // throw away dummy checksum
 | 
					
						
							|  |  |  |   index += 4 + 8; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ssh.elements = SSH.parseElements(ssh.bytes.slice(index, index + (len - 8))); | 
					
						
							|  |  |  |   index += Array.prototype.reduce.call(ssh.elements, function (el, sum) { | 
					
						
							|  |  |  |     // 32-bit len + element len
 | 
					
						
							|  |  |  |     return 4 + (el.byteLength || el.length) + sum; | 
					
						
							|  |  |  |   }, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // comment will exist, even if it's an empty string
 | 
					
						
							|  |  |  |   ssh.comment = Enc.bufToBin(ssh.elements.pop()); | 
					
						
							| 
									
										
										
										
											2018-12-09 01:43:41 -07:00
										 |  |  |   ssh.bytes = pub; | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |   return ssh; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | SSH.parseElements = function (buf) { | 
					
						
							|  |  |  |   var fulllen = buf.byteLength || buf.length; | 
					
						
							|  |  |  |   // Note: node has weird offsets
 | 
					
						
							|  |  |  |   var offset = (buf.byteOffset || 0); | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  |   var i = 0; | 
					
						
							|  |  |  |   var index = 0; | 
					
						
							|  |  |  |   // using dataview to be browser-compatible (I do want _some_ code reuse)
 | 
					
						
							|  |  |  |   var dv = new DataView(buf.buffer.slice(offset, offset + fulllen)); | 
					
						
							|  |  |  |   var els = []; | 
					
						
							|  |  |  |   var el; | 
					
						
							|  |  |  |   var len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while (index < fulllen) { | 
					
						
							|  |  |  |     i += 1; | 
					
						
							|  |  |  |     if (i > 15) { throw new Error("15+ elements, probably not a public ssh key"); } | 
					
						
							|  |  |  |     len = dv.getUint32(index, false); | 
					
						
							|  |  |  |     index += 4; | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |     if (0 === len) { continue; } | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  |     el = buf.slice(index, index + len); | 
					
						
							|  |  |  |     // remove BigUInt '00' prefix
 | 
					
						
							|  |  |  |     if (0x00 === el[0]) { | 
					
						
							|  |  |  |       el = el.slice(1); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     els.push(el); | 
					
						
							|  |  |  |     index += len; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (fulllen !== index) { | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  |     throw new Error("invalid ssh public key length \n" + els.map(function (b) { | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  |       return Enc.bufToHex(b); | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  |     }).join('\n')); | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |   return els; | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  | SSH.parsePublicKey = function (ssh) { | 
					
						
							|  |  |  |   var els = ssh.elements; | 
					
						
							|  |  |  |   var typ = Enc.bufToBin(els[0]); | 
					
						
							|  |  |  |   var len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // RSA keys are all the same
 | 
					
						
							|  |  |  |   if (SSH.types.rsa === typ) { | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |     if (3 === els.length) { | 
					
						
							|  |  |  |       ssh.jwk = { | 
					
						
							|  |  |  |         kty: 'RSA' | 
					
						
							|  |  |  |       , e: Enc.bufToUrlBase64(els[1]) | 
					
						
							| 
									
										
										
										
											2018-12-09 21:03:38 -07:00
										 |  |  |       , n: Enc.bufToUrlBase64(els[2]) | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |       }; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       console.log('len:', els.length); | 
					
						
							|  |  |  |       ssh.jwk = { | 
					
						
							|  |  |  |         kty: 'RSA' | 
					
						
							|  |  |  |       , e: Enc.bufToUrlBase64(els[1]) | 
					
						
							| 
									
										
										
										
											2018-12-09 21:03:38 -07:00
										 |  |  |       , n: Enc.bufToUrlBase64(els[2]) | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |       , d: Enc.bufToUrlBase64(els[3]) | 
					
						
							|  |  |  |       , p: Enc.bufToUrlBase64(els[5]) | 
					
						
							|  |  |  |       , q: Enc.bufToUrlBase64(els[6]) | 
					
						
							|  |  |  |       //, dp: Enc.bufToUrlBase64(els[x])
 | 
					
						
							|  |  |  |       //, dq: Enc.bufToUrlBase64(els[x])
 | 
					
						
							|  |  |  |       , qi: Enc.bufToUrlBase64(els[4]) | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  |     return ssh; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // EC keys are each different
 | 
					
						
							|  |  |  |   if (SSH.types.p256 === typ) { | 
					
						
							|  |  |  |     len = 32; | 
					
						
							|  |  |  |     ssh.jwk = { kty: 'EC', crv: 'P-256' }; | 
					
						
							|  |  |  |   } else if (SSH.types.p384 === typ) { | 
					
						
							|  |  |  |     len = 48; | 
					
						
							|  |  |  |     ssh.jwk = { kty: 'EC', crv: 'P-384' }; | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     throw new Error("Unsupported ssh public key type: " | 
					
						
							|  |  |  |       + Enc.bufToBin(els[0])); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // els[1] is just a repeat of a subset of els[0]
 | 
					
						
							|  |  |  |   var x = els[2].slice(1, 1 + len); | 
					
						
							|  |  |  |   var y = els[2].slice(1 + len, 1 + len + len); | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |   var d; | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // I don't think EC keys use 0x00 padding, but just in case
 | 
					
						
							|  |  |  |   while (0x00 === x[0]) { x = x.slice(1); } | 
					
						
							|  |  |  |   while (0x00 === y[0]) { y = y.slice(1); } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |   if (els[3]) { | 
					
						
							| 
									
										
										
										
											2018-12-09 01:57:29 -07:00
										 |  |  |     d = els[3]; | 
					
						
							|  |  |  |     while (0x00 === d[0]) { d = d.slice(1); } | 
					
						
							|  |  |  |     ssh.jwk.d = Enc.bufToUrlBase64(d); | 
					
						
							| 
									
										
										
										
											2018-12-09 01:28:03 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-12-02 22:07:51 -07:00
										 |  |  |   ssh.jwk.x = Enc.bufToUrlBase64(x); | 
					
						
							|  |  |  |   ssh.jwk.y = Enc.bufToUrlBase64(y); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ssh; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-01 20:32:22 -07:00
										 |  |  | SSH.types = { | 
					
						
							|  |  |  |   // 19 '00000013'
 | 
					
						
							|  |  |  |   // e c d s a - s h a 2 - n i s t p 2 5 6
 | 
					
						
							|  |  |  |   // 65636473612d736861322d6e69737470323536
 | 
					
						
							|  |  |  |   // 6e69737470323536
 | 
					
						
							|  |  |  |   p256: 'ecdsa-sha2-nistp256' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // 19 '00000013'
 | 
					
						
							|  |  |  |   // e c d s a - s h a 2 - n i s t p 3 8 4
 | 
					
						
							|  |  |  |   // 65636473612d736861322d6e69737470333834
 | 
					
						
							|  |  |  |   // 6e69737470323536
 | 
					
						
							|  |  |  | , p384: 'ecdsa-sha2-nistp384' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // 7 '00000007'
 | 
					
						
							|  |  |  |   // s s h - r s a
 | 
					
						
							|  |  |  |   // 7373682d727361
 | 
					
						
							|  |  |  | , rsa: 'ssh-rsa' | 
					
						
							|  |  |  | }; |