Einzelnen Beitrag anzeigen

Assertor

Registriert seit: 4. Feb 2006
Ort: Hamburg
1.296 Beiträge
 
Turbo C++
 
#4

Re: IdFTP und Implizite TLS verschlüsselung (FTPS) ?

  Alt 2. Nov 2007, 08:40
Hallo DeluxXx,

hier kurz alle Antworten zu Deinen Fragen:

Ja, SSL/TLS implizit und explizit über FTP geht mit Indy und Indy OpenSSL. Ich habe hier irgendwo die letzten Sicherheitspatches für OpenSSL gepostet. Leider hat mir der angeschrieben Moderator nicht geantwortet, ob es nicht bald mal eine Sicherheitsrubrik geben sollte...

Kurz erklärt (übliche Anwendung):

Explizites FTP (FTPES):
Normaler FTP Port (21), es wird per "Auth" versucht, den Controlchannel zu verschlüsseln. Datenkanal optional auch. Passwort/Benutzername werden dann verschlüsselt übertragen. Wenn die Verbindung per SSL/TLS und "Auth" vom Server abgelehnt wird, liegt es an Deinen Einstellungen, ob Du trotzdem unverschlüsselt Benutzer/Pass-Authentisierung zulassen willst.

Implizites FTP (FTPS):
FTP Port 990, es wird direkt die verschlüsselte Kommunikation eingeleitet. Auth entfällt. Ist die Verbindung per SSL/TLS nicht möglich, wird i.d.R. der Aufbau abgebrochen.

Umsetzung
- Indy 9/10 (am besten aktueller Indy 10 Snapshot wegen diverser ReadTimeouts)
- Indy OpenSSL (aktuelle Version wegen diverser Sicherheitslücken in den Delphi OpenSSL DLLs bis einschließlich Delphi 2007)
- Etwas Code:

Beispiel
Delphi-Quellcode:
...
uses
  // mindestens:
  IdFTP, IdSSLOpenSSL,
  // besser auch (für FTP RFC ErrorCodes):
  IdFTPCommon, IdExplicitTLSClientServerBase, IdReplyRFC, IdFTPList,
  // Für korrektes List-Parsing manuell einbinden:
  IdAllFTPListParsers,
  // Für korrekte Charsets manuell einbinden:
  IdCharsets;

...
  type MeinForm = class(TForm)
  ...
  private
    function MeinSSLZertifikatChecker(Certificate: TIdX509; AOk: Boolean): Boolean;
    // bzw bei Indy 10 Snapshot:
    // function MeinSSLZertifikatChecker(Certificate: TIdX509; AOk: Boolean; ADepth: Integer): Boolean;
  ...
  end;

...

function MeinForm.MeinSSLZertifikatChecker(Certificate: TIdX509; AOk: Boolean): Boolean;
// bzw. bei Indy 10 Snapshot:
// function MeinForm.MeinSSLZertifikatChecker(Certificate: TIdX509; AOk: Boolean; ADepth: Integer): Boolean;
begin
  // hier kann man das SSL Zertifikat auf Aussteller etc. prüfen...
  Result := True; // muß man aber nicht, wir akzeptieren alle gültigen Zertifikate
end;

...

procedure SSLTest;
var
  FIdFTPClient: TIdFTP;
  FIdSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
  FIdFTPClient := TIdFTP.Create;
  FIdSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create;

  try
    // jetzt die allgemeinen Settings
    FIdFTPClient.Passive := True; // setzt den Passive-Mode, damit wir auch keine Router/NAT Probleme bekommen
    FIdFTPClient.Username := 'TestAnonymous';
    FIdFTPClient.Passwort := 'GeInsHeim';
    FIdFTPClient.Host := 'ftp.meinserver.de';
    
    // jetzt die SSL/TLS Settings
    
    // 1. Fall: unverschlüsselt, muß nicht extra angegeben werden...
    FIdFTPClient.UseTLS := utNoTLSSupport;
    FIdFTPClient.DataPortProtection := ftpdpsClear;

    // ODER:

    // 2. Fall: FTPES (also Port 21 mit Auth SSL/TLS)
    FIdSSLHandler.SSLOptions.Method := sslvSSLv23;
    FIdSSLHandler.SSLOptions.Mode := sslmClient;
    FIdSSLHandler.SSLOptions.VerifyMode := [sslvrfPeer]; // z.B.
    FIdSSLHandler.Port := 21;
    FIdSSLHandler.PassThrough := False; // SSL würde übergangen, wenn True
    FIdSSLHandler.OnVerifyPeer := MeinSSLZertifikatChecker;

    FIdFTPClient.IOHandler := FIdSSLHandler;
    FIdFTPClient.UseTLS := utUseExplicitTLS;
    FIdFTPClient.AUTHCmd := tAuto; // wir wissen ja nicht, ob SSL oder TLS
    FIdFTPClient.DataPortProtection := ftpdpsPrivate; // !!! Datenkanal auch verschlüsseln

    // ODER:

    // 3. Fall: FTPS (also Port 990, kein Auth da direkt verschlüsselt)
    FIdSSLHandler.SSLOptions.Method := sslvSSLv23;
    FIdSSLHandler.SSLOptions.Mode := sslmClient;
    FIdSSLHandler.SSLOptions.VerifyMode := [sslvrfPeer]; // z.B.
    FIdSSLHandler.Port := 990;
    FIdSSLHandler.PassThrough := False; // SSL würde übergangen, wenn True
    FIdSSLHandler.OnVerifyPeer := MeinSSLZertifikatChecker;
    FIdFTPClient.IOHandler := FIdSSLHandler;
    FIdFTPClient.UseTLS := utUseImplicitTLS; // man beachte den Unterschied
    FIdFTPClient.AUTHCmd := tAuto;
    FIdFTPClient.DataPortProtection := ftpdpsPrivate;

    // und nun connecten
    FIdFTPClient.Connect;
    // nun z.B. Verzeichnis wechseln:
    FIdFTPClient.ChangeDir('/Test');
    // und disconnecten
    FIdFTPClient.Disconnect;

  finally
    FIdSSLHandler.Free;
    FIdFTPClient.Free;
  end;
end;
Zusätzlich gibt es tolle Möglichkeiten, insbesondere die RFC Fehler zu prüfen. Nicht alles, was Indy als Exception "Raised" ist per se ein Fehler. Es geht auch um das korrekte Handling von RFC Rückgabewerten.

Beispiel Verzeichniswechsel
Delphi-Quellcode:
  function MeinDirExists(ADirname: String): Boolean;
  begin
    Result := False;
    try
      FIdFTPClient.ChangeDir(ADirname);
      Result := True;
    except
      On E:EIdReplyRFCError do
      begin
        if E.ErrorCode <> 550 then // 550 ist der RFC Code für "CWD failed"
          Raise;
      end;
    end;
  end;
Hier wird also geprüft, ob es das Verzeichnis gibt. Bei einem RFC-konformen Server kommt der RFC Code 550 nach Changedir wenn es das Verzeichnis nicht gibt bzw. ein Wechsel nicht möglich ist.

Wir "Raisen" also alles -AUßER- 550. Dann wissen wir per Result, es gibt kein Dir mit dem Namen.

Unicode
Weiterhin kann man Indy sogar auf Unicode-Filenamen biegen:

z.B. bei einem Unicode-Filenamen im Upload:
Delphi-Quellcode:
var
  UniFilename: WideString;
  ...
begin
  ...
  UniFilename := 'MeinUnicodeName.Local'; // ja, ist jetzt kein echtes Unicode ;)
  FIdFTPClient.Put(UniFilename, WideStringToUTF8(UniFilename));
  // also lokaler name = remote name, bitte Verzeichnisse etc. vorher rausfiltern
  ...
end;
Bei Unicode gibt es natürlich noch mehr zu beachten. Aber als grobes Beispiel: Es geht!

Das sollte als kleines Tutorial erst mal reichen.

Gruß winkel79

Frederik
  Mit Zitat antworten Zitat