Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save michele-tn/83156455e528fcee4f84301c699912b4 to your computer and use it in GitHub Desktop.
Save michele-tn/83156455e528fcee4f84301c699912b4 to your computer and use it in GitHub Desktop.

πŸš€ Automated Reverse SSH Port Forwarding

πŸ” A Windows Service for Secure SSH Tunneling & Multi-Port Forwarding

πŸ›  Features

βœ… Automated Reverse Port Forwarding – Supports 22, 3389, 5800, 5900
βœ… Windows Service Mode – Prevents accidental termination
βœ… Auto-Reconnect – If disconnected, reconnects automatically
βœ… Encrypted Passphrase – Loads AES-encrypted passphrase from file
βœ… Embedded Private Key Security – Uses Renci.SshNet for seamless authentication


πŸ“¦ Installation

πŸ”Ή 1️⃣ Install Dependencies

dotnet add package Renci.SshNet

πŸ”Ή 2️⃣ Configure SSH Settings

  • Replace remote.server.com and user in the source code.
  • Embed id_rsa as a resource (Build Action: Embedded Resource).
  • Use the encryption tool (see below) to generate encrypted_passphrase.bin.

πŸ”Ή 3️⃣ Compile and Install as a Windows Service

sc create SshTunnelService binPath= "C:\path\to\program.exe" start= auto

πŸ” Encrypting Your Passphrase

Use this small utility to encrypt your private key's passphrase securely:

πŸ” PassphraseEncryptor

A simple Windows Forms application in C# that allows you to encrypt a passphrase using AES encryption and save it to a .enc file.


πŸš€ Features

  • Input a passphrase
  • Encrypt using AES (symmetric encryption)
  • Save encrypted data to file

πŸ› οΈ Requirements

  • .NET Framework 4.7.2 or higher
  • Visual Studio or any C# IDE that supports Windows Forms

🧩 Project Structure

PassphraseEncryptor/
β”‚
β”œβ”€β”€ Program.cs
β”œβ”€β”€ Form1.cs
└── Form1.Designer.cs

πŸ“„ Code Files

Program.cs

using System;
using System.Windows.Forms;

namespace PassphraseEncryptor
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new PassphraseEncryptorForm());
        }
    }
}

Form1.cs

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;

namespace PassphraseEncryptor
{
    public partial class PassphraseEncryptorForm : Form
    {
        public PassphraseEncryptorForm()
        {
            InitializeComponent();
        }

        private void BtnEncrypt_Click(object sender, EventArgs e)
        {
            // Retrieve the passphrase from the text box
            string passphrase = txtPassphrase.Text;

            // Validate input
            if (string.IsNullOrWhiteSpace(passphrase))
            {
                MessageBox.Show("Please enter a passphrase.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            using (Aes aes = Aes.Create())
            {
                // Generate a random AES key and IV
                aes.GenerateKey();
                aes.GenerateIV();

                // Create an encryptor with the generated key and IV
                ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

                using (MemoryStream ms = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    using (StreamWriter sw = new StreamWriter(cs))
                    {
                        // Write the plaintext passphrase to the crypto stream
                        sw.Write(passphrase);
                    }

                    // Convert the encrypted stream to a byte array
                    byte[] encrypted = ms.ToArray();

                    // Prompt the user to choose where to save the encrypted file
                    SaveFileDialog saveFileDialog = new SaveFileDialog
                    {
                        Filter = "Encrypted File (*.enc)|*.enc",
                        Title = "Save Encrypted Passphrase"
                    };

                    if (saveFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        // Save the encrypted data to the chosen file
                        File.WriteAllBytes(saveFileDialog.FileName, encrypted);

                        // Derive file base name (without extension) for key and IV
                        string baseFilePath = Path.Combine(
                            Path.GetDirectoryName(saveFileDialog.FileName),
                            Path.GetFileNameWithoutExtension(saveFileDialog.FileName)
                        );

                        // Encrypt AES key and IV using Windows DPAPI (user-specific scope)
                        byte[] protectedKey = ProtectedData.Protect(aes.Key, null, DataProtectionScope.CurrentUser);
                        byte[] protectedIV = ProtectedData.Protect(aes.IV, null, DataProtectionScope.CurrentUser);

                        // Save the protected key and IV to files
                        File.WriteAllBytes(baseFilePath + ".key", protectedKey);
                        File.WriteAllBytes(baseFilePath + ".iv", protectedIV);

                        MessageBox.Show("Passphrase encrypted and saved successfully!\nAES key and IV securely protected and saved.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                }

                /*
                // ORIGINAL (commented) - Did not store or protect the key and IV
                //aes.GenerateKey();
                //aes.GenerateIV();
                //File.WriteAllBytes("aes.key", aes.Key);
                //File.WriteAllBytes("aes.iv", aes.IV);
                */
            }
        }
    }
}

Form1.Designer.cs

namespace PassphraseEncryptor
{
    partial class PassphraseEncryptorForm
    {
        private System.ComponentModel.IContainer components = null;
        private System.Windows.Forms.TextBox txtPassphrase;
        private System.Windows.Forms.Button btnEncrypt;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
                components.Dispose();
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.txtPassphrase = new System.Windows.Forms.TextBox();
            this.btnEncrypt = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // txtPassphrase
            // 
            this.txtPassphrase.Location = new System.Drawing.Point(12, 12);
            this.txtPassphrase.Name = "txtPassphrase";
            this.txtPassphrase.Size = new System.Drawing.Size(260, 20);
            this.txtPassphrase.TabIndex = 0;
            // 
            // btnEncrypt
            // 
            this.btnEncrypt.Location = new System.Drawing.Point(197, 38);
            this.btnEncrypt.Name = "btnEncrypt";
            this.btnEncrypt.Size = new System.Drawing.Size(75, 23);
            this.btnEncrypt.TabIndex = 1;
            this.btnEncrypt.Text = "Encrypt";
            this.btnEncrypt.UseVisualStyleBackColor = true;
            this.btnEncrypt.Click += new System.EventHandler(this.BtnEncrypt_Click);
            // 
            // PassphraseEncryptorForm
            // 
            this.ClientSize = new System.Drawing.Size(284, 71);
            this.Controls.Add(this.btnEncrypt);
            this.Controls.Add(this.txtPassphrase);
            this.Name = "PassphraseEncryptorForm";
            this.Text = "Passphrase Encryptor";
            this.ResumeLayout(false);
            this.PerformLayout();
        }
    }
}

πŸ–₯️ Main Service Code Snippet (Updated)

SshTunnelService

SshTunnelService is a Windows Service built in C# using the SSH.NET library. It creates an SSH tunnel using a private key with an encrypted passphrase.


πŸ”§ Features

  • Windows Service architecture
  • SSH tunnel with encrypted private key passphrase
  • Secure storage of AES-encrypted passphrase
  • Robust and clean service lifecycle management

πŸ“ Project Structure

SshTunnelService/
β”‚
β”œβ”€β”€ SshTunnelService.cs         # Main service class
β”œβ”€β”€ Program.cs                  # Service entry point
β”œβ”€β”€ Encryptor.cs                # AES passphrase encrypt/decrypt logic
β”œβ”€β”€ app.config                  # Configuration file for service settings
└── README.md                   # Documentation

πŸ›  Configuration

Update app.config with your settings:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>

    <!-- SSH Server Parameters -->
    <add key="Host" value="Your_HOST" />
    <add key="Port" value="PORT" />
    <add key="Username" value="YourUsername" />


    <!-- AES Key and IV used to decrypt the embedded passphrase -->
    <add key="AesKey" value="BASE64_ENCODED_AES_KEY_HERE" />
    <add key="AesIV"  value="BASE64_ENCODED_AES_IV_HERE" />

    <!-- ⚠️ Legacy settings (no longer used but left for reference) -->
    <!--
    <add key="PrivateKeyPath" value="C:\path\to\privateKey.pem" />
    <add key="EncryptedPassphrasePath" value="C:\path\to\EncryptedPassphrase.bin" />
    -->

  </appSettings>
</configuration>

πŸ” Encrypting the Passphrase

Use the Encryptor.EncryptStringToFile method to encrypt your passphrase:

Encryptor.EncryptStringToFile("my_passphrase", "C:\path\to\passphrase.enc", aesKey, aesIV);

🧠 Service Code

πŸ“„ SshTunnelService.cs

using System;
using System.Configuration;
using System.Diagnostics;
using System.ServiceProcess;
using System.Timers;
using Renci.SshNet;
using System.IO;
using System.Reflection;
using System.Collections.Generic;

namespace SshTunnelServiceApp
{
    public class SshTunnelService : ServiceBase
    {
        private SshClient sshClient;
        private Timer reconnectTimer;

        // Added for multiple remote port forwards
        private readonly List<ForwardedPortRemote> remotePorts = new List<ForwardedPortRemote>();

        public SshTunnelService()
        {
            this.ServiceName = "SshTunnelService";
        }

        protected override void OnStart(string[] args)
        {
            reconnectTimer = new Timer(60000); // Reconnect every 60 seconds
            reconnectTimer.Elapsed += (s, e) => EnsureConnected();
            reconnectTimer.Start();
            EnsureConnected(); // Initial connection attempt
        }

        protected override void OnStop()
        {
            reconnectTimer?.Stop();

            // Stop all remote forwarded ports
            foreach (var port in remotePorts)
            {
                port.Stop();
            }

            sshClient?.Disconnect();
            sshClient?.Dispose();
        }

        private void EnsureConnected()
        {
            try
            {
                if (sshClient != null && sshClient.IsConnected)
                    return;

                // βœ… Read connection info from AppSettings
                string host = ConfigurationManager.AppSettings["Host"];
                int port = int.Parse(ConfigurationManager.AppSettings["Port"]);
                string username = ConfigurationManager.AppSettings["Username"];

                // ❌ OLD: Aes key and IV from AppSettings (now replaced with embedded resources)
                /*
                string OLD_privateKeyPath = ConfigurationManager.AppSettings["PrivateKeyPath"];
                byte[] aesKey = Convert.FromBase64String(ConfigurationManager.AppSettings["AesKey"]);
                byte[] aesIV = Convert.FromBase64String(ConfigurationManager.AppSettings["AesIV"]);
                */

                // βœ… Embedded resource names
                string keyResource = "SshTunnelServiceApp.Resources.aes.key"; // Adjust namespace + folder if needed
                string ivResource = "SshTunnelServiceApp.Resources.aes.iv";   // Adjust namespace + folder if needed
                string passphraseResource = "SshTunnelServiceApp.Resources.EncryptedPassphrase.bin";
                string privateKeyResource = "SshTunnelServiceApp.Resources.privateKeyResource.ppk";

                // βœ… Load and decrypt AES key from embedded resource
                byte[] aesKey;
                using (Stream keyStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(keyResource))
                {
                    if (keyStream == null)
                        throw new Exception("Unable to find embedded AES key resource: " + keyResource);

                    using (MemoryStream msKey = new MemoryStream())
                    {
                        keyStream.CopyTo(msKey);
                        aesKey = System.Security.Cryptography.ProtectedData.Unprotect(msKey.ToArray(), null, DataProtectionScope.CurrentUser);
                    }
                }

                // βœ… Load and decrypt AES IV from embedded resource
                byte[] aesIV;
                using (Stream ivStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(ivResource))
                {
                    if (ivStream == null)
                        throw new Exception("Unable to find embedded AES IV resource: " + ivResource);

                    using (MemoryStream msIV = new MemoryStream())
                    {
                        ivStream.CopyTo(msIV);
                        aesIV = System.Security.Cryptography.ProtectedData.Unprotect(msIV.ToArray(), null, DataProtectionScope.CurrentUser);
                    }
                }

                // βœ… Load and decrypt passphrase using AES key and IV
                using (Stream passphraseStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(passphraseResource))
                {
                    if (passphraseStream == null)
                        throw new Exception("Unable to find embedded passphrase resource: " + passphraseResource);

                    using (MemoryStream passphraseMemory = new MemoryStream())
                    {
                        passphraseStream.CopyTo(passphraseMemory);
                        passphraseMemory.Position = 0;

                        string passphrase = Encryptor.DecryptStream(passphraseMemory, aesKey, aesIV);

                        // βœ… Load private key resource and initialize SSH client
                        using (Stream privateKeyStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(privateKeyResource))
                        {
                            if (privateKeyStream == null)
                                throw new Exception("Unable to find embedded private key resource: " + privateKeyResource);

                            using (MemoryStream privateKeyMemory = new MemoryStream())
                            {
                                privateKeyStream.CopyTo(privateKeyMemory);
                                privateKeyMemory.Position = 0;

                                var keyFile = new PrivateKeyFile(privateKeyMemory, passphrase);
                                sshClient = new SshClient(host, port, username, keyFile);
                                sshClient.Connect();

                                // ❌ Old Local Forwarding (commented out)
                                /*
                                int localPort = int.Parse(ConfigurationManager.AppSettings["LocalPort"]);
                                string remoteHost = ConfigurationManager.AppSettings["RemoteHost"];
                                int remotePort = int.Parse(ConfigurationManager.AppSettings["RemotePort"]);

                                portForwarder = new ForwardedPortLocal("127.0.0.1", (uint)localPort, remoteHost, (uint)remotePort);
                                sshClient.AddForwardedPort(portForwarder);
                                portForwarder.Start();
                                */

                                // βœ… Reverse Port Forwarding for ports 22, 3389, 5800, 5900
                                uint[] portsToForward = { 22, 3389, 5800, 5900 };

                                foreach (var remotePort in portsToForward)
                                {
                                    var forwardedPort = new ForwardedPortRemote("0.0.0.0", remotePort, "127.0.0.1", remotePort);
                                    sshClient.AddForwardedPort(forwardedPort);
                                    forwardedPort.Start();
                                    remotePorts.Add(forwardedPort);
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry("SshTunnelService", "SSH Tunnel error: " + ex.Message, EventLogEntryType.Error);
            }
        }
    }
}

πŸ“„ Encryptor.cs

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

public static class Encryptor
{
    public static void EncryptStringToFile(string plainText, string filePath, byte[] Key, byte[] IV)
    {
        using (Aes aes = Aes.Create())
        {
            aes.Key = Key;
            aes.IV = IV;
            ICryptoTransform encryptor = aes.CreateEncryptor();
            using (FileStream fs = new FileStream(filePath, FileMode.Create))
            using (CryptoStream cs = new CryptoStream(fs, encryptor, CryptoStreamMode.Write))
            using (StreamWriter sw = new StreamWriter(cs))
            {
                sw.Write(plainText);
            }
        }
    }

    public static string DecryptStringFromFile(string filePath, byte[] Key, byte[] IV)
    {
        using (Aes aes = Aes.Create())
        {
            aes.Key = Key;
            aes.IV = IV;
            ICryptoTransform decryptor = aes.CreateDecryptor();
            using (FileStream fs = new FileStream(filePath, FileMode.Open))
            using (CryptoStream cs = new CryptoStream(fs, decryptor, CryptoStreamMode.Read))
            using (StreamReader sr = new StreamReader(cs))
            {
                return sr.ReadToEnd();
            }
        }
    }
    
    //************
    public static string DecryptStream(Stream encryptedStream, byte[] Key, byte[] IV)
    {
        using (Aes aes = Aes.Create())
        {
            aes.Key = Key;
            aes.IV = IV;
            ICryptoTransform decryptor = aes.CreateDecryptor();
            using (CryptoStream cs = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read))
            using (StreamReader sr = new StreamReader(cs))
            {
                return sr.ReadToEnd();
            }
        }
    }
    //************
}

πŸ“„ Program.cs

/*using System.ServiceProcess;

static class Program
{
    static void Main()
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[] { new SshTunnelService() };
        ServiceBase.Run(ServicesToRun);
    }
}*/

using System.ServiceProcess;

static class Program
{
    static void Main()
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[] { new SshTunnelServiceApp.SshTunnelService() }; // Fully qualified
        ServiceBase.Run(ServicesToRun);
    }
}

πŸƒ Running the Service

  1. Compile the project in Release mode.
  2. Use sc create or installutil to register the service.
  3. Start it from the Services snap-in or via sc start SshTunnelService.

πŸ“¦ Dependencies



🎨 Architecture Diagram

+-----------------------+
|   SSH Client (C#)     |
|   using Renci.SshNet  |
+----------+------------+
           |
           v
+-------------------------------+
|    Reverse Port Forwarding    |
|    Ports: 22, 3389, 5800,     |
|            5900               |
+----------+--------------------+
           |
           v
+------------------------+
|   Windows Service      |
|   Auto-Restart Logic   |
| AES Decryption Support |
+------------------------+

πŸ”’ Security Notes

βœ… Encrypted Passphrase – Uses AES encryption for secure passphrase loading
βœ… Embedded Key – Prevents tampering
βœ… Auto Recovery – Handles disconnections automatically


πŸ“ License

This project is MIT Licensed. Feel free to contribute and customize!.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment