EldoS | Feel safer!

Software components for data protection, secure storage and transfer

Practical Advice for Connecting via SFTP

Also by EldoS: CallbackFilter
A component to monitor and control disk activity, track file and directory operations (create, read, write, rename etc.), alter file data, encrypt files, create virtual files.
#36019
Posted: 02/26/2016 15:20:24
by Roger Dunn (Standard support level)
Joined: 02/26/2016
Posts: 1

I am new to the world of SSH, but not to Delphi or the VCL. I have read many, many KB articles and forum posts on the topic of connecting to servers via SFTP on this web site. I have spent probably 6 hours or more researching, but I have more questions that need answering. So, I decided to become a member and ask. (I have a Purchase Order awaiting approval for the VCL edition of BlackBox SFTP-client).

Like other posters, I have been in e-mail contact with the server administrator of the server that I wish to connect to. I have provided him a 2056-bit RSA key (which I generated from the SSHKeysDemo, thank you very much!) and I have been able to obtain his public key using TElSimpleSFTPClient.OnKeyValidate, using the ServerKey parameter's SavePublicKey method. (I didn't know what extension to use, so I chose .key and Windows thinks it's a Windows Registry file) This is simply background; it's not my question. But first...

Many times when Eldos responds to a forum post, they do it in plain English rather than with code samples. I have a hard time with this approach, though English is my first language. When Eldos says that you simply need to "compare keys" to validate a connection, that is just not enough information for me. It doesn't help to just say that. In Delphi, are you talking about using the equals (=) operator? Probably not. Are you talking about the Equals function inherited from TObject? Are we supposed to compare all those ByteArray properties? What's this about comparing BLOBs or hashes and all that stuff? I need more detailed info, maybe because I'm in a hurry.

In short, what components, objects, properties, methods, and events am I suppose to use in order to "validate" the server I'm connecting to, and how do I give them what they need from me? I'd like examples in English AND Delphi code, because I just don't speak "security" language. It's fine with me if the keys in the code aren't real: using 'ABC' as a placeholder works for me.

"Roj"
#36020
Posted: 02/26/2016 16:29:31
by Ken Ivanov (EldoS Corp.)

Hi Roger,

Thank you for contacting us.

Indeed, in the majority of scenarios we normally start answering in plain language. This is because we don't know a person's technical level beforehand, so we try to start with more general concepts and then move on to more specific ones, depending on the development of the discussion. But of course we won't refuse from providing [pseudo-]code, should it be requested.

As for your question, I'm going to start with posting a draft of the relevant prospective how-to article. It's not been officially published yet, but provides some important information on key validation.

Quote

Validating SSH server keys

Unlike X.509 public key infrastructure and TLS, SSH uses a flat key scheme with no trust hierarchy (with extremely rare exceptions where X.509-based keys are used for authentication). The keys are therefore checked by comparing the key received from the server during an SSH negotiation to a cached copy or fingerprint of the trusted copy of the key communicated via different means (e.g. by confirming the hash of the key in advance by e-mail, over the phone, or via a secure web site).

When your SecureBlackbox SSH components receives a key from the server, it reports it via OnKeyValidate event. It is your responsibility, as a component user, to handle this event and perform key validation inside the handler.

(VERY) IMPORTANT: neglecting correct implementation of the validation routine, particularly by implementing it in 'by-pass' way by forcefully setting Validate to true without any checks, is extremely dangerous and will compromise the whole security of the SSH system you are building.

Key validation normally involves maintaining a local database of 'known' keys. In the simplest case this may be fulfilled by a CSV file. Each record should contain the server record (IP address and/or domain name) and the key record. The latter should at least contain some data which could serve as nearly-unique identifier of the key (SHA1 fingerprint is OK), but storing the whole key together with its algorithm might be a better option.

Note that a server might possess several keys, so your database format should be ready to take several records belonging to the same server.

When you receive a fresh server key in the OnKeyValidate handler, you must perform the following steps:

1. Look for a cached copy of the server key in local database by the server and key identifiers.

2. If the record was found, the genuineness of the key has been confirmed. Set Validate to true and exit the handler.

3. If some server records were found, but none contains the identifier corresponding to the key received, an attack might be taking place. Warn the user and instruct them to confirm the key with server administrators. Never proceed without confirming as you might be connecting to a fake server! To increase user's willingness to confirm the key and not just skip this step, you might shift some responsibility about the consequences to them:

"By clicking continue, I confirm that I have made all reasonable effort to verify the validity of the key (by contacting the server's or my company's IT person). I understand the risks of going ahead with the connection without duly check of the server key, and accept responsibility for all consequences that might arise from my negligence of this procedure".

Note that this scenario might take place with a genuine server if a different public key algorithm was negotiated and a different key returned (see above). You might consider to design the UI of this step carefully so that your users got the right impression from this step and could take an educated action.

If key confirmation routine was successful, add new key record to the database, set Validate to true and exit the routine. Otherwise, set it to false, and the components will terminate the connection for you.

4. If the record was not found, you are likely to be connected to a new server. Ask your users to confirm the integrity of the key via other means. If the key is confirmed, add the key record to the database and set Validate to true.

You will find some TElSSHKey properties useful for the implementation of key validation:

- SavePublicKey(): saves public part of the key to a buffer. Remember to always set the same KeyFormat to get the key in the same form. We recommend using kfOpenSSH or kfIETF.

- Algorithm: returns the key algorithm (ALGORITHM_RSA, ALGORITHM_DSS, ALGORITHM_ECDSA).

- Bits: returns key length.

- FingerprintSHA1, FingerprintSHA1String: return key SHA1 fingerprint in byte array or string form.

Note: SSH servers often maintain several different private keys for different algorithms they support (e.g. RSA, DSS, ECDSA). If you change the set of public key algorithms supported locally, or change the algorithms' priorities, you might end up receiving a different public key not matching your local copy.

Note. Microsoft changed their implementation of .NET quick sort algorithm between .NET version 2 and 4.5, so you might end up receiving different keys even when migrating your project from .NET 2.0 to .NET 4.5 (and with no other changes in place!) This is because SSH components use sorting algorithms internally to sort algorithms by their priorities, and algorithms having the same priorities (e.g. RSA and DSS) might come in different order due to the algorithm changes.


Now, let's try to address your particular questions.

Quote
When Eldos says that you simply need to "compare keys" to validate a connection, that is just not enough information for me.

Generally, there are two methods you may use:

1. Store and compare hashes of known server keys. A hash of the key can be obtained from TElSSHKey.FingerprintSHA1String property. These can be compared as strings. In this case your database file might look like the following:

Quote

192.168.23.43 ab01bc20ca29cb10a1931820c7ac92377c8abb16
192.168.23.43 0032831ab58c01cffe92a9c8726100bc839284c1
server.com 3921acb19b3841ab28eefac893bc923b98d918e827a


You will have to manage such database manually.

2. Store and compare the whole keys. This method might be easier to implement, as you can take TElSSHMemoryKeyStorage component, which works with key collections. Whenever you receive a key from the server, get your known keys database in a TElSSHMemoryKeyStorage object. Then iterate over key, looking for the entries corresponding to your server address. The corresponding pseudocode will look like this:

Code
procedure TfrmMain.ClientKeyValidate(Sender: TObject;
  ServerKey: TElSSHKey; var Validate: Boolean);
var
  KnownKeys : TElSSHMemoryKeyStorage;
  R : integer;
  I : integer;
begin
  KnownKeys := TElSSHMemoryKeyStorage.Create(nil);
  try
    R := KnownKeys.LoadPublic('known_keys');
    if R <> 0 then
      ReportErrorAndRaise®;

    for I := 0 to KnownKeys.Count - 1 do
    begin
      if ((Lowercase(KnownKeys.Keys[I].Comment) = Lowercase(ServerIP)) or
        (Lowercase(KnownKeys.Keys[I].Comment) = Lowercase(ServerDnsName))) and
        (KnownKeys.Keys[I].Algorithm = Key.Algorithm) then
      begin
        if KnownKeys.Keys[I].FingerprintSHA1String = Key.FingerprintSHA1String then
        begin
          // key is known
          Validate := true;
          Exit;
        end
        else
        begin
          if UI.WarnUserKeyChanged(Key.FingerprintSHA1String) then
          begin
            // if the user confirmed that they are happy with the change:
            // removing existing key and adding the received key as known
            KnownKeys.Remove(I);
            KnownKeys.Add(Key);
            KnownKeys.Keys[KnownKeys.Count - 1].Comment := ServerDnsName;
            Validate := true;
            Exit;
          end
          else
          begin
            // the user rejected the received key, terminating the negotiation
            Validate := false;
            Exit;
          end;
        end;
      end;
    end;

    // we can only get here after we've iterated over all known keys
    // and haven't found the record - i.e. the server is brand new for us
    if UI.WarnUserNewServer(Key.FingerprintSHA1String) then
    begin
      // adding key as known
      KnownKeys.Add(Key);
      KnownKeys.Keys[KnownKeys.Count - 1].Comment := ServerDnsName;
      Validate := true;
    end
    else
    begin
      // user disapproved the key
      Validate := false;
    end;
  finally
    FreeAndNil(KnownKeys);
  end;
end;


Hope this is helpful to you. If it's not, feel free to ask.

Ken

Reply

Statistics

Topic viewed 1314 times

Number of guests: 1, registered members: 0, in total hidden: 0




|

Back to top

As of July 15, 2016 EldoS Corporation will operate as a division of /n software inc. For more information, please read the announcement.

Got it!