Implementing SSH host key cache (known hosts)

The following example shows how to implement an SSH host key cache, similar to the .ssh/known_hosts file of OpenSSH suite, using WinSCP .NET assembly.

The example uses an XML file for the cache, as this format has native support in both in PowerShell and .NET framework. You can replace it with other format, if needed.

The format of the XML file is like:

<KnownHosts>
  <KnownHost host="example.com:22" fingerprint="ecdsa-sha2-nistp521 521 db:48:8b:ab:2f:d5:d4:94:3c:8f:83:7f:ac:ae:de:14" />
  <KnownHost host="example.org:22" fingerprint="ssh-rsa 2048 c4:26:18:cf:a0:15:9a:5f:f3:bf:96:d8:3b:19:ef:7b" />
</KnownHosts>

PowerShell

$KnownHostsFile = "KnownHosts.xml"
$SshPortNumber = 22
 
try
{
    # Load WinSCP .NET assembly
    Add-Type -Path "WinSCPnet.dll"
 
    # Setup session options
    $sessionOptions = New-Object WinSCP.SessionOptions -Property @{
        Protocol = [WinSCP.Protocol]::Sftp
        HostName = "example.com"
        UserName = "user"
        Password = "mypassword"
    }
 
    # Cache key is hostname:portnumber
    $portNumber = if ($sessionOptions.PortNumber -ne 0) { $sessionOptions.PortNumber } else { $SshPortNumber }
    $sessionKey = ("{0}:{1}" -f $sessionOptions.HostName, $portNumber)
 
    # Load known hosts (if any)
    if (Test-Path $KnownHostsFile)
    {
        [xml]$knownHosts = Get-Content $KnownHostsFile
    }
    else
    {
        $knownHosts = New-Object System.XML.XmlDocument
        $knownHosts.AppendChild($knownHosts.CreateElement("KnownHosts")) | Out-Null
    }
 
    # Lookup host key for this session 
    $fingerprint = $knownHosts.DocumentElement | Select-Xml -XPath "KnownHost[@host='$sessionKey']/@fingerprint"
 
    if ($fingerprint)
    {
        Write-Host "Connecting to a known host"
    }
    else
    {
        # Host is not known yet. Scan its host key and let the user decide.
        $session = New-Object WinSCP.Session
        try
        {
            $fingerprint = $session.ScanFingerprint($sessionOptions)
        }
        finally
        {
            $session.Dispose()
        }
 
        Write-Host -NoNewline (
            "Continue connecting to an unknown server and add its host key to a cache?`n" +
            "The server's host key was not found in the cache.`n" + 
            "You have no guarantee that the server is the computer you think it is.`n" +
            "`n" +
            "The server's key fingerprint is:`n" +
            $fingerprint + "`n" +
            "`n" +
            "If you trust this host, press Y. To abandon the connection, press N. ")
 
        do
        {
            $key = [char]::ToUpperInvariant([System.Console]::ReadKey($True).KeyChar)
            if ($key -eq "N")
            {
                Write-Host($key)
                exit 2
            }
        }
        while ($key -ne "Y")
 
        Write-Host($key)
 
        # Cache the host key
        $knownHost = $knownHosts.CreateElement("KnownHost")
        $knownHosts.DocumentElement.AppendChild($knownHost) | Out-Null
        $knownHost.SetAttribute("host", $sessionKey)
        $knownHost.SetAttribute("fingerprint", $fingerprint)
 
        $knownHosts.Save($KnownHostsFile)
    }
 
    # Now we have the fingerprint
    $sessionOptions.SshHostKeyFingerprint = $fingerprint
 
    $session = New-Object WinSCP.Session
 
    try
    {
        # Connect
        $session.Open($sessionOptions)
 
        # Your code
    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()
    }
}
catch [Exception]
{
    Write-Host ("Error: {0}" -f $_.Exception.Message)
    exit 1
}

C#

using System;
using System.IO;
using System.Xml;
using WinSCP;
 
class Example
{
    const string KnownHostsFile = "KnownHosts.xml";
    const int SshPortNumber = 22;
 
    static int Main(string[] args)
    {
        try
        {
            // Setup session options
            SessionOptions sessionOptions = new SessionOptions
            {
                Protocol = Protocol.Sftp,
                HostName = "example.com",
                UserName = "user",
                Password = "mypassword",
            };
 
            // Cache key is hostname:portnumber
            int portNumber = (sessionOptions.PortNumber != 0) ? sessionOptions.PortNumber : SshPortNumber;
            string sessionKey = string.Format("{0}:{1}", sessionOptions.HostName, portNumber);
 
            // Load known hosts (if any)
            XmlDocument knownHosts = new XmlDocument();
            if (File.Exists(KnownHostsFile))
            {
                knownHosts.Load(KnownHostsFile);
            }
            else
            {
                knownHosts.AppendChild(knownHosts.CreateElement("KnownHosts"));
            }
 
            // Lookup host key for this session 
            XmlNode fingerprintNode = knownHosts.DocumentElement.SelectSingleNode("KnownHost[@host='" + sessionKey + "']/@fingerprint");
 
            string fingerprint = null;
            if (fingerprintNode != null)
            {
                fingerprint = fingerprintNode.Value;
                Console.WriteLine("Connecting to a known host");
            }
            else
            {
                // Host is not known yet. Scan its host key and let the user decide.
                using (Session session = new Session())
                {
                    fingerprint = session.ScanFingerprint(sessionOptions);
                }
 
                Console.Write(
                    "Continue connecting to an unknown server and add its host key to a cache?" + Environment.NewLine +
                    "The server's host key was not found in the cache." + Environment.NewLine +
                    "You have no guarantee that the server is the computer you think it is." + Environment.NewLine +
                    Environment.NewLine +
                    "The server's key fingerprint is:" + Environment.NewLine +
                    fingerprint + Environment.NewLine +
                    Environment.NewLine +
                    "If you trust this host, press Y. To abandon the connection, press N. ");
 
                char key;
                do
                {
                    key = char.ToUpperInvariant(Console.ReadKey(true).KeyChar);
                    if (key == 'N')
                    {
                        Console.WriteLine(key);
                        return 2;
                    }
                }
                while (key != 'Y');
 
                Console.WriteLine(key);
 
                // Cache the host key
                XmlElement knownHost = knownHosts.CreateElement("KnownHost");
                knownHosts.DocumentElement.AppendChild(knownHost);
                knownHost.SetAttribute("host", sessionKey);
                knownHost.SetAttribute("fingerprint", fingerprint);
 
                knownHosts.Save(KnownHostsFile);
            }
 
            // Now we have the fingerprint
            sessionOptions.SshHostKeyFingerprint = fingerprint;
 
            using (Session session = new Session())
            {
                // Connect
                session.Open(sessionOptions);
 
                // Your code
            }
 
            return 0;
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: {0}", e);
            return 1;
        }
    }
}
 
  library_example_known_hosts.txt · Last modified: by martin
 

Search Documentation

This page

Donate

About donations

$9   $19   $49   $99

About donations

Recommend

Associations

Site design by Black Gate