| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Enc = require('./encoding.js'); | 
					
						
							| 
									
										
										
										
											2018-12-09 23:08:08 -07:00
										 |  |  | var PEM = require('./pem.js'); | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  | var SSH = module.exports; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SSH.pack = function (opts) { | 
					
						
							| 
									
										
										
										
											2018-12-09 23:08:08 -07:00
										 |  |  |   if (opts.jwk.d && !opts.public) { | 
					
						
							|  |  |  |     return SSH._packPrivate(opts); | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     delete opts.jwk.d; | 
					
						
							|  |  |  |     return SSH._packPublic(opts); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // https://tools.ietf.org/html/rfc4253#section-6.6
 | 
					
						
							|  |  |  | SSH._packPublic = function (opts) { | 
					
						
							|  |  |  |   var els = SSH._packKey(opts); | 
					
						
							|  |  |  |   var hex = SSH._packElements(els); | 
					
						
							|  |  |  |   var typ = Enc.hexToBin(els[0]); | 
					
						
							|  |  |  |   var parts = [ typ, Enc.hexToBase64(hex) ]; | 
					
						
							|  |  |  |   if (opts.comment) { parts.push(opts.comment); } | 
					
						
							|  |  |  |   return parts.join(' '); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SSH._packPrivate = function (opts) { | 
					
						
							|  |  |  |   var pubjwk = JSON.parse(JSON.stringify(opts.jwk)); | 
					
						
							|  |  |  |   delete pubjwk.d; | 
					
						
							|  |  |  |   var pubels = SSH._packKey({ jwk: pubjwk }); | 
					
						
							|  |  |  |   var pubhex = SSH._packElements(pubels); | 
					
						
							|  |  |  |   var els = SSH._packKey(opts); | 
					
						
							|  |  |  |   var hex = SSH._packElements(els); | 
					
						
							|  |  |  |   var privlen = hex.length/2; | 
					
						
							|  |  |  |   var padlen = (privlen % 8) && (8 - (privlen % 8)) || 0; // blocksize is 8 (no cipher)
 | 
					
						
							|  |  |  |   var bin = "openssh-key-v1" + String.fromCharCode(0) | 
					
						
							|  |  |  |     + Enc.hexToBin( | 
					
						
							|  |  |  |         SSH._packElements([ | 
					
						
							|  |  |  |           Enc.binToHex("none")  // ciphername
 | 
					
						
							|  |  |  |         , Enc.binToHex("none")  // kdfname
 | 
					
						
							|  |  |  |         , ""                    // empty kdf
 | 
					
						
							|  |  |  |         ]) | 
					
						
							|  |  |  |       + SSH._numToUint32Hex(1)  // number of keys (always 1)
 | 
					
						
							|  |  |  |       + SSH._numToUint32Hex(pubhex.length/2)  // pubkey length
 | 
					
						
							|  |  |  |       + pubhex | 
					
						
							|  |  |  |       + SSH._numToUint32Hex(8 + privlen + padlen) // privkey length
 | 
					
						
							|  |  |  |       + '62636a7362636a73'            // 64-bit dummy checksum ("bcjs", "bcjs")
 | 
					
						
							|  |  |  |       + hex                           // (only cihpered keys use real checksums)
 | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |   var pad = ''; | 
					
						
							|  |  |  |   var i; | 
					
						
							|  |  |  |   for (i = 1; i <= padlen; i += 1) { | 
					
						
							|  |  |  |     pad += '0' + i; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return PEM.packBlock({ | 
					
						
							|  |  |  |     type: "OPENSSH PRIVATE KEY" | 
					
						
							|  |  |  |   , bytes: Enc.binToBuf(bin + Enc.hexToBin(pad)) | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SSH._packKey = function (opts) { | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  |   var jwk = opts.jwk; | 
					
						
							|  |  |  |   var els = []; | 
					
						
							|  |  |  |   var len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if ("RSA" === jwk.kty) { | 
					
						
							| 
									
										
										
										
											2018-12-09 23:08:08 -07:00
										 |  |  |     els.push(Enc.binToHex('ssh-rsa')); | 
					
						
							|  |  |  |     if (jwk.d) { | 
					
						
							| 
									
										
										
										
											2018-12-09 23:31:13 -07:00
										 |  |  |       // unswap n and e for private key format
 | 
					
						
							| 
									
										
										
										
											2018-12-10 23:23:17 -07:00
										 |  |  |       els.push(SSH._padBigInt(Enc.base64ToHex(jwk.n))); | 
					
						
							|  |  |  |       els.push(SSH._padBigInt(Enc.base64ToHex(jwk.e))); | 
					
						
							|  |  |  |       els.push(SSH._padBigInt(Enc.base64ToHex(jwk.d))); | 
					
						
							|  |  |  |       els.push(SSH._padBigInt(Enc.base64ToHex(jwk.qi))); | 
					
						
							|  |  |  |       els.push(SSH._padBigInt(Enc.base64ToHex(jwk.p))); | 
					
						
							|  |  |  |       els.push(SSH._padBigInt(Enc.base64ToHex(jwk.q))); | 
					
						
							| 
									
										
										
										
											2018-12-09 23:08:08 -07:00
										 |  |  |       els.push(Enc.binToHex(opts.comment || '')); | 
					
						
							| 
									
										
										
										
											2018-12-09 23:31:13 -07:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       // swap n and e for public key format
 | 
					
						
							| 
									
										
										
										
											2018-12-10 23:23:17 -07:00
										 |  |  |       els.push(SSH._padBigInt(Enc.base64ToHex(jwk.e))); | 
					
						
							|  |  |  |       els.push(SSH._padBigInt(Enc.base64ToHex(jwk.n))); | 
					
						
							| 
									
										
										
										
											2018-12-09 23:08:08 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-12-09 23:31:13 -07:00
										 |  |  |     return els; | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if ("P-256" === jwk.crv) { | 
					
						
							| 
									
										
										
										
											2018-12-09 23:08:08 -07:00
										 |  |  |     els.push(Enc.binToHex('ecdsa-sha2-nistp256')); | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  |     els.push(Enc.binToHex('nistp256')); | 
					
						
							|  |  |  |     len = 32; | 
					
						
							|  |  |  |   } else if ("P-384" === jwk.crv) { | 
					
						
							| 
									
										
										
										
											2018-12-09 23:08:08 -07:00
										 |  |  |     els.push(Enc.binToHex('ecdsa-sha2-nistp384')); | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  |     els.push(Enc.binToHex('nistp384')); | 
					
						
							|  |  |  |     len = 48; | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     throw new Error("unknown key type " + (jwk.crv || jwk.kty)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   els.push('04' | 
					
						
							| 
									
										
										
										
											2018-12-10 23:23:17 -07:00
										 |  |  |     + SSH._padBytes(Enc.base64ToHex(jwk.x), len) | 
					
						
							|  |  |  |     + SSH._padBytes(Enc.base64ToHex(jwk.y), len) | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  |   ); | 
					
						
							| 
									
										
										
										
											2018-12-09 23:08:08 -07:00
										 |  |  |   if (jwk.d) { | 
					
						
							| 
									
										
										
										
											2018-12-10 23:23:17 -07:00
										 |  |  |     // I was able to empirically confirm that the leading 00 is expected for
 | 
					
						
							|  |  |  |     // ambiguous BigInt negatives (0x80 set), and that the length can dip down
 | 
					
						
							|  |  |  |     // to 31 bytes when the leading byte is 0x00. I suspect that if I had tried
 | 
					
						
							|  |  |  |     // 65k iterations that I'd have seen at least one 30 byte number
 | 
					
						
							|  |  |  |     els.push(SSH._padBigInt(Enc.base64ToHex(jwk.d))); | 
					
						
							|  |  |  |     //console.warn('els:', els[els.length - 1]);
 | 
					
						
							| 
									
										
										
										
											2018-12-09 23:08:08 -07:00
										 |  |  |     els.push(Enc.binToHex(opts.comment || '')); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return els; | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-09 23:08:08 -07:00
										 |  |  | SSH._packElements = function(els) { | 
					
						
							|  |  |  |   return els.map(function (hex) { | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  |     return SSH._numToUint32Hex(hex.length/2) + hex; | 
					
						
							|  |  |  |   }).join(''); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SSH._numToUint32Hex = function (num) { | 
					
						
							|  |  |  |   var hex = num.toString(16); | 
					
						
							|  |  |  |   while (hex.length < 8) { | 
					
						
							|  |  |  |     hex = '0' + hex; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return hex; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-10 23:23:17 -07:00
										 |  |  | SSH._padBigInt = function (hex) { | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  |   // BigInt is negative if the high order bit 0x80 is set,
 | 
					
						
							|  |  |  |   // so ASN1, SSH, and many other formats pad with '0x00'
 | 
					
						
							|  |  |  |   // to signifiy a positive number.
 | 
					
						
							|  |  |  |   var i = parseInt(hex.slice(0, 2), 16); | 
					
						
							| 
									
										
										
										
											2018-12-10 23:23:17 -07:00
										 |  |  |   //console.warn('l', hex.length/2, 'i', i);
 | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  |   if (0x80 & i) { | 
					
						
							| 
									
										
										
										
											2018-12-10 23:23:17 -07:00
										 |  |  |     //console.warn('0x80 true');
 | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  |     return '00' + hex; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return hex; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-10 23:23:17 -07:00
										 |  |  | SSH._padBytes = function (hex, len) { | 
					
						
							| 
									
										
										
										
											2018-12-02 00:50:49 -07:00
										 |  |  |   while (hex.length < len * 2) { | 
					
						
							|  |  |  |     hex = '00' + hex; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return hex; | 
					
						
							|  |  |  | }; |