Hallo mezen,
erst einmal auch von mir ein großes Danke schön und Respekt für deine Arbeit.
Hoffentlich fließt deine Arbeit irgenwann auch in die
Indy-Version von Delphi ein.
Nun zu meinem Problem:
Ich habe eine Software die sich per SSL/TLS mit einem Webserver verbindet, den wir selbst in der Verwaltung haben. Auf dem Webserver ist ein Zertifikat installiert. Beim Verbindungsaufbau zum Webserver überprüft die Software den Fingerprint des Server-Zertifikats, um sicherzustellen, dass die Verbindung zum richtigen Webserver erfolgt.
Umgesetzt ist dies mit
Indy 10 von Delphi 10.3 und funktioniert einwandfrei.
Hier der relevante Code-Ausschnitt dazu (FHttpClient ist eine Instanz von TIdHttp):
Delphi-Quellcode:
var
lHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
FHttpClient.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(FHttpClient);
lHandler := TIdSSLIOHandlerSocketOpenSSL(FHttpClient.IOHandler);
lHandler.SSLOptions.Mode := sslmClient;
lHandler.SSLOptions.VerifyMode := [sslvrfPeer];
lHandler.OnVerifyPeer := DoVerifyPeer;
end;
Die Signatur von
DoVerifyPeer ist folgende:
Code:
function DoVerifyPeer(ACertificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean;
Mit dem Parameter
ACertificate bekommt man Zugriff auf die Zertifikat-Informationen/-Eigenschaften und ich kann in
DoVerifyPeer dann folgendes machen:
Delphi-Quellcode:
var
sFingerprint: string
begin
sFingerprint := ACertificate.Fingerprints.SHA1AsString;
Result := <Vergleich mit dem erwarteten Fingerprint>
end
Im Getter von
SHA1AsString wird folgendes aufgerufen:
Code:
Result := MDAsString(SHA1);
In der Funktion
MDAsString wird lediglich die Rückgabe von
SHA1 als Hex-Zahlen mit Doppelpunkt getrennt formatiert (was nicht weiter wichtig ist), denn das Entscheidende passiert in der Funktion
SHA1, nämlich der Aufuf von
X509_digest, was ja eine Funktion aus den OpenSSL-DLLs ist:
Delphi-Quellcode:
begin
X509_digest(FX509, EVP_sha1, PByte(@Result.MD), Result.Length);
end
Wenn ich jetzt deinen OpenSSL-Client benutze (damit ich auch TLS 1.3 unterstützen kann), sieht die Implementierung so aus:
Delphi-Quellcode:
var
lHandler: TIdOpenSSLIOHandlerClient;
begin
FHttpClient.IOHandler := TIdOpenSSLIOHandlerClient.Create(FHttpClient);
lHandler := TIdOpenSSLIOHandlerClient(FHttpClient.IOHandler);
lHandler.Options.CipherList := IdOpenSSLHeaders_ssl.SSL_DEFAULT_CIPHER_LIST;
lHandler.Options.CipherSuites := IdOpenSSLHeaders_ssl.TLS_DEFAULT_CIPHERSUITES;
lHandler.Options.UseServerCipherPreferences := False;
lHandler.Options.VerifyServerCertificate := True;
lHandler.Options.OnVerify := DoVerifyPeer;
end;
Die Signatur von
DoVerifyPeer ist hier folgende:
Code:
procedure DoVerifyPeer(ASender: TObject; const ACertificate: TIdOpenSSLX509;
const VerifyResult, Depth: Integer; var Accepted: Boolean);
Auch hier kann ich mit dem Parameter
ACertificate auf die Zertifikat-Informationen/-Eigenschaften zugreifen und in
DoVerifyPeer dann folgendes machen:
Delphi-Quellcode:
var
sFingerprint: string
begin
sFingerprint := ACertificate.ThumbprintAsSHA1;
Accepted := <Vergleich mit dem erwarteten Fingerprint>
end
Im Getter von
ThumbprintAsSHA1 wird folgndes aufgerufen:
Code:
Result := GetInternalThumbprint(EVP_sha1());
Das Entscheidende hier ist auch der Aufruf der Funktion
X509_digest in der Methode
GetInternalThumbprint:
Delphi-Quellcode:
var
LBuffer: array[0 .. EVP_MAX_MD_SIZE -1] of Byte;
LByteCount: TIdC_UINT;
i: Integer;
begin
Result := '';
LByteCount := EVP_MAX_MD_SIZE;
if X509_digest(FX509, md, @LBuffer, @LByteCount) = 1 then
begin
for i := 0 to LByteCount - 1 do
Result := Result + ByteToHex(LBuffer[i]);
end;
end;
Nach meinem Verständnis sollte doch hier die gleiche Bytefolge (also der Fingerprint des Server-Zertifikats) zurückkommen?
Das passiert aber nicht, denn die Bytefolge ist eine vollkommen andere.
Habe ich bei der Konfiguration des IO-Handlers etwas falsch gemacht oder vergessen oder wo liegt der Fehler?
Ich hoffe, dass der Fehler nicht in den OpenSSL-DLLs liegt?