Search code examples
c#vb.netrsassh.neted25519

How to convert SSH host keys for validation?


Using SSH.NET, I'm trying to get the same ed25519 and rsa host keys from my SSH host (a Raspberry Pi in this case) that the Windows SSH client gets, so that I can compare them to what's in the %USERPROFILE%\.ssh\known_hosts file.

The documentation demonstrates this technique, but e.FingerPrint contains a 16-element byte array. I wish to replicate the functionality of the in-built Win11 SSH client that retrieves and stores the host keys like so:

offsite ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICuy9wsbY7fBFFoB6K0RU/BsISB3mL4PQ3311ta0S/cW
offsite ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDAEmXakR9rh5Sokj/PvmyiL/tZtQp7OPbQYb9PdlQjPXELJpBZkFs5gnoqxFj5ysqKnBREY/GpOKkXxdh5dMGGzoir93ypicI7A1VQTd3d8WQdtV1tnP7u9hbAfSqtj+7OGCsi0eMUBYx1fS3XbSC1jKr2MG2ovMCp3kdHtDoGrysP5ai0EXY07ZMmTpxTJbZFtZ8IX3U+ON71KIfZSC6UqlptFyFh1SUmpZXbnsbSr4BDoI8fG0A9Km9BTohWK9ioPSLL530KXz72S4EaBgx0Za1c+TGPqnjAu+s9kc0DeBrwLTEZzfS40xj+sdM4IvPERMBZAPH6cmMutHNWVtIdPyiTi93gCn8oz3D0UX8QT3Yc9Qhx0KLw7AzLajAXSc7zdOebtovZmOejyl4YrYO4Q2rZ3ODTqotFgXGJWNFkg/PoV+Yzy4tILOujDlTHUv3/BkFaj4TSnzgl7NzstdZpNM1R0KBkHlDuM10K9yyLEmNlHSvx8zTZWBTEkzb1FoU=

I've tried looping through SSH.NET's Client.ConnectionInfo.HostKeyAlgorithms dictionary, but I'm not sure where to get the byte array that's required by an entry's value invocation:

Dim aData As Byte() = Nothing

For Each oAlgorithm In oClient.ConnectionInfo.HostKeyAlgorithms
  Dim oInstance = oAlgorithm.Value(aData)
End For

That code fails, as the Func(Of Byte(), KeyHostAlgorithm) invocation requires an actual array with elements. It also rejects an empty array (and any other array, for that matter). For example:

oAlgorithm.Value({})

The requested length (4) is greater than the actual number of bytes read (0).Parameter name: length

oAlgorithm.Value({0, 1, 2})

The requested length (4) is greater than the actual number of bytes read (3).Parameter name: length

oAlgorithm.Value({0, 1, 2, 3})

Data longer than 2147483647 is not supported.

What gives here? What's that byte array parameter supposed to be and where does one get it?

I also tried this in the Client.HostKeyReceived event handler:

Dim sHostKey = Convert.ToBase64String(New SHA256Managed().ComputeHash(e.HostKey))

But that calculated Base64 string differs from the entry currently in my known_hosts file for that host (see above).

How do I retrieve the various keys from the SSH host, in a way that matches what's in known_hosts (ed25519 and rsa in particular)?

--EDIT--

I've succeeded in discovering the byte array to be used in the function invocation:

oHandler = Sub(Sender As SshClient, e As HostKeyEventArgs)
             Dim aData As Byte()

             For Each oAlgorithm In Sender.ConnectionInfo.HostKeyAlgorithms
               Dim oInstance = oAlgorithm.Value(e.HostKey)

               If TypeOf oInstance.Key Is Ed25519Key Then
                 aData = DirectCast(oInstance.Key, Ed25519Key).PublicKey
                 Exit For
               End If
             Next
           End Sub

However, the computed Base64 of aData still doesn't match the output of the Win11 SSH client. Does anyone know how those values are arrived at?

--EDIT--

OK, I'm getting closer. I now have the Windows-supplied SSH client's value for ed25519. It's a straight Base64 encoding of the server's HostKey, prior to any hashing:

Dim sBase64 = Convert.ToBase64String(e.HostKey)
' Returns AAAAC3NzaC1lZDI1NTE5AAAAICuy9wsbY7fBFFoB6K0RU/BsISB3mL4PQ3311ta0S/cW

Now to tackle getting the same for the server's rsa key.


Solution

  • The solution is much simpler than I had anticipated: simply Base64-encode the host's HostKey byte array without hashing it (SHA256 or MD5).

    I was able to get the rsa key by requesting it at connection time:

    sKeyType = "ssh-rsa"
    oConnectionInfo = New PasswordConnectionInfo(Host.Name, Host.Username, Host.Password)
    oConnectionInfo.HostKeyAlgorithms.Clear
    oConnectionInfo.HostKeyAlgorithms.Add(sKeyType, Function(Data) New KeyHostAlgorithm(sKeyType, New RsaKey, Data))
    
    Using oClient = New SshClient(oConnectionInfo)
      AddHandler oClient.HostKeyReceived, oHandler
    
      oClient.Connect
      oClient.CreateCommand("temp").Execute
    
      RemoveHandler oClient.HostKeyReceived, oHandler
    End Using
    
    oHandler = Sub(Sender As SshClient, e As HostKeyEventArgs)
                 sBase64 = Convert.ToBase64String(e.HostKey)
    
                 e.CanTrust = Host.Base64 = sBase64
               End Sub
    

    This works, and I can use it to compare the host's keys with the Windows-supplied SSH client's stored keys.