Skip to content

013: Implement ssh rsa

Shane DeSeranno edited this page Feb 15, 2017 · 7 revisions

Alright, now we need to implement the sha-rsa host key algorithm. First, let's create a folder called HostKeyAlrorithms and create an interface in that folder called IHostKeyAlgorithm.

    public interface IHostKeyAlgorithm  : IAlgorithm
    {
        void ImportKey(string keyXml);
        byte[] CreateKeyAndCertificatesData();
        byte[] CreateSignatureData(byte[] hash);
    }

This interface also inherits from IAlgorithm, forcing it to have a name property. The ImportKey algorithm is used to load the host key. This is the key that uniquely identifies the server, so normally, you would want to keep this key secure. We will use our configuration to store this value, and your server would want to make sure the key is secret. Let's edit the sshserver.json and add the ssh-rsa key:

{
    "Logging": {
        "IncludeScopes": true,
        "LogLevel": {
            "Default": "Debug",
            "System": "Information",
            "Microsoft": "Information"
        }
    },
    "port": 22,
    "keys": {
        "ssh-rsa": "<RSAKeyValue><Modulus>xXKzcIH/rzcfv2D7VcvLdxR5S5iw2TTsP65Aa82S4+9ZIqLPTNtuzr76Mz6Cx0yDOhawHlIujtalqaaQzaUvkudCtMcVMnj37OcCYz7XDAYejalCxf/vtJo7U4mnYdCM+nAOQKNIDKLGbLtGuEAGwdi560DOJY2plhnBf1oOI+k=</Modulus><Exponent>AQAB</Exponent><P>37kMr9YiU4cSqHqTJSjBJ/szG2O4n5xSIlPy4MZ4aAN5NALHxfsN0dq1y8NL6GTLMI5qoykvp4Bjrm2ZgU1cDQ==</P><Q>4e84rF+UsFBfQEKJc2pbACWWJjttNW0hccdQZzA3IUxRmd/Z4yEMr1L70TP0XV7dw1RDs1JyU7xnXBIbGy5ETQ==</Q><DP>vP0TbI6VnL3j0xMIrkFJOj8Ho0GQOrTQ5VLJP3wpRqR4hKk8nVBBEl+RZznpK73Jr5D/ICmwqezZSAYpwILbGQ==</DP><DQ>bHdzZtWwRYEgaXJIGL+7lnN1BT/MazTMNJpykEeGgBbqqgvcx/zq4RTezg26SEUuBANlSSbQukCeAoayurbYlQ==</DQ><InverseQ>Irq9vR7CXIVR+r09caYIxIY8BOig+HShN1bXvATERJcjTW2jUgJrUttDGNEx70/hBd7m1NWCZz5YO3RH9Bdf5w==</InverseQ><D>AqxsufxFcW9TDCAmQK4mwVdsoQjRp2jfcULmkM8fl9u40dtxTr6Csv5dz7qfKLWxHTGlDUDabCK2t/DCcZZoA3rsqwLADe4ZerDdg6xiq4MBzNprM8Y0IfNESEdFB9T0T73ONQCsMalUzEvUknC4Ed4Fya34LUHntgQtEhXpDJE=</D></RSAKeyValue>"
    }
}

Now, add a new class to the HostKeyAlgorithms folder called SSHRSA and implement the IHostKeyAlgorithm interface.

    public class SSHRSA : IHostKeyAlgorithm
    {
        public string Name
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public byte[] CreateKeyAndCertificatesData()
        {
            throw new NotImplementedException();
        }

        public byte[] CreateSignatureData(byte[] hash)
        {
            throw new NotImplementedException();
        }

        public void ImportKey(string keyXml)
        {
            throw new NotImplementedException();
        }
    }

At this point, we'll need to edit the project.json to add XmlDocument dependency:

    "System.Xml.XmlDocument": "4.0.1"

Once we do this, it will enable the ability to load the ssh-rsa XML document key. In ImportKey() we will read all the relevant properties and set the values on our RSA cryptography object. CreateKeyAndCertificatesData() is used to generate key and certificate data to send to the client. And CreateSignatureData() signs a block of data with the correct key.

    public class SSHRSA : IHostKeyAlgorithm
    {
        private readonly RSA m_RSA = RSA.Create();

        public string Name
        {
            get
            {
                return "ssh-rsa";
            }
        }

        public void ImportKey(string keyXml)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(keyXml);

            XmlElement root = doc["RSAKeyValue"];

            RSAParameters p = new RSAParameters()
            {
                Modulus = Convert.FromBase64String(root["Modulus"].InnerText),
                Exponent = Convert.FromBase64String(root["Exponent"].InnerText),
                P = Convert.FromBase64String(root["P"].InnerText),
                Q = Convert.FromBase64String(root["Q"].InnerText),
                DP = Convert.FromBase64String(root["DP"].InnerText),
                DQ = Convert.FromBase64String(root["DQ"].InnerText),
                InverseQ = Convert.FromBase64String(root["InverseQ"].InnerText),
                D = Convert.FromBase64String(root["D"].InnerText)
            };

            m_RSA.ImportParameters(p);
        }

        public byte[] CreateKeyAndCertificatesData()
        {
            // The "ssh-rsa" key format has the following specific encoding:
            //      string    "ssh-rsa"
            //      mpint e
            //      mpint n
            RSAParameters parameters = m_RSA.ExportParameters(false);

            using (ByteWriter writer = new ByteWriter())
            {
                writer.WriteString(Name);
                writer.WriteMPInt(parameters.Exponent);
                writer.WriteMPInt(parameters.Modulus);
                return writer.ToByteArray();
            }
        }

        public byte[] CreateSignatureData(byte[] value)
        {
            // Signing and verifying using this key format is performed according to
            // the RSASSA-PKCS1-v1_5 scheme in [RFC3447] using the SHA-1 hash.
            // The resulting signature is encoded as follows:
            //      string    "ssh-rsa"
            //      string    rsa_signature_blob
            using (ByteWriter writer = new ByteWriter())
            {
                writer.WriteString(Name);
                writer.WriteBytes(m_RSA.SignData(value, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1));
                return writer.ToByteArray();
            }
        }
    }

Now edit the Server to add a dictionary to store our name to key lookup and the list of SupportedHostKeyAlgorithms. We also need to read the keys from the configuration and fill the dictionary with values. We'll also add a nice generic helper to create an instance of any of our algorithm types, and in here, we'll set the key for our IHostKeyAlgorithm types.

    public class Server
    {
        ...
        private static Dictionary<string, string> s_HostKeys = new Dictionary<string, string>();
        ...
        public static IReadOnlyList<Type> SupportedHostKeyAlgorithms { get; private set; } = new List<Type>()
        {
            typeof(SSHRSA)
        };
        ...

        public Server()
        {
            m_Configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("sshserver.json", optional: false)
                .Build();

            m_LoggerFactory = new LoggerFactory();
            m_LoggerFactory.AddConsole(m_Configuration.GetSection("Logging"));
            m_Logger = m_LoggerFactory.CreateLogger("SSHServer");

            IConfigurationSection keys = m_Configuration.GetSection("keys");
            foreach (IConfigurationSection key in keys.GetChildren())
            {
                s_HostKeys[key.Key] = key.Value;
            }
        }
        ...
        public static T GetType<T>(IReadOnlyList<Type> types, string selected) where T : class
        {
            foreach (Type type in types)
            {
                IAlgorithm algo = Activator.CreateInstance(type) as IAlgorithm;
                if (algo.Name.Equals(selected, StringComparison.OrdinalIgnoreCase))
                {
                    if (algo is IHostKeyAlgorithm)
                    {
                        ((IHostKeyAlgorithm)algo).ImportKey(s_HostKeys[algo.Name]);
                    }

                    return algo as T;
                }
            }

            return default(T);
        }
        ...
    }

The last thing we need to do is add this algorithm to the KEX_INIT packet we send the client:

    m_KexInitServerToClient.ServerHostKeyAlgorithms.AddRange(Server.GetNames(Server.SupportedHostKeyAlgorithms));

At this point, the server behaves exactly as before, but now our OpenSSH log has:

debug2: peer server KEXINIT proposal
debug2: KEX algorithms: diffie-hellman-group14-sha1
debug2: host key algorithms: ssh-rsa
...
debug1: kex: algorithm: diffie-hellman-group14-sha1
debug1: kex: host key algorithm: ssh-rsa
Unable to negotiate with 127.0.0.1 port 22: no matching cipher found. Their offer:

Now, OpenSSH sees our new host key algorithm and has selected it, but it can't find our next piece, the cipher! The code for this section can be viewed at with the tag Implement_SSH-RSA. On the next page we'll implement 3des-cbc.