Native TLS

Unterstützt von

 

TsgcWebSocketClient

TsgcTCPClient

TsgcHTTP1Client

TsgcHTTP2Client

TsgcWebSocketServer

TsgcWebSocketHTTPServer

TsgcHTTPServer

TsgcWebSocketProxyServer

TsgcWebSocketLoadBalancerServer

 

*Erfordert das sgcCrypto-Pack, das ab der Standard Edition ausgeliefert wird.

 

Die native TLS-Engine ist eine TLS-1.3- und TLS-1.2-Implementierung, die vollständig in Object Pascal auf den sgcCrypto-Units geschrieben ist. Sie ist kein Binding: Es gibt keine OpenSSL-DLL bereitzustellen, keinen SChannel-Aufruf und keine beteiligte Plattform-TLS-Bibliothek. Derselbe Quellcode kompiliert von Delphi 7 bis RAD Studio 13 und läuft auf jeder Plattform, die die Komponenten unterstützen.

 

Der Grund, zu ihr zu greifen, ist der Schlüsselaustausch. Die Engine bietet die hybriden Post-Quanten-Gruppen von RFC 10024 an, und X25519MLKEM768 ist die erste Gruppe, die sie vorschlägt. Ein Client, der auf sie umstellt, handelt daher gegenüber jedem Server, der so etwas unterstützt, einen Post-Quanten-Schlüsselaustausch aus, ohne weitere Änderung an der Anwendung. Die hybriden Gruppen gibt es nur in TLS 1.3, eine Verbindung, die auf TLS 1.2 zurückfällt, verwendet also eine klassische Kurve. Siehe Post-Quanten-Kryptografie dafür, was diese Gruppen sind und warum ein Hybrid heute der sinnvolle Migrationsweg ist.

 

Einschalten

 

Setzen Sie den IOHandler, und sonst ändert sich nichts. Die Eigenschaften für Zertifikat, Schlüssel, Stammzertifikat, Verifizierung und ALPN, die Sie bereits verwenden, funktionieren weiter, die Engine liest dieselben.

 


// client
oClient := TsgcWebSocketClient.Create(nil);
oClient.URL := 'wss://www.esegece.com:2053';
oClient.TLS := True;
oClient.TLSOptions.IOHandler := iohNativeTLS;
oClient.TLSOptions.VerifyCertificate := True;
oClient.TLSOptions.RootCertFile := 'cacert.pem';
oClient.Active := True;

// server
oServer := TsgcWebSocketServer.Create(nil);
oServer.SSL := True;
oServer.SSLOptions.IOHandler := iohNativeTLS;
oServer.SSLOptions.CertFile := 'certificate.pem';
oServer.SSLOptions.KeyFile := 'key.pem';
oServer.Active := True;

 

Auf einem Server läuft die Engine auch auf den ereignisgesteuerten Engines. Setzen Sie IOHandlerOptions.IOHandlerType unter Windows auf iohIOCP oder unter Linux auf iohEPOLL, und der Handler wird auf dem passenden TLS-Provider aufgebaut, sodass dieselbe Konfiguration auf allen drei Engines funktioniert.

 

Die Auswahl von iohNativeTLS in einem Build ohne das sgcCrypto-Pack löst eine Exception aus, die genau das sagt, statt stillschweigend auf eine andere Bibliothek zurückzufallen.

 

TLS-Versionen

Die Engine spricht TLS 1.3 und TLS 1.2, als Client und als Server, auf dem Indy-Client, auf dem Indy-Server und auf den IOCP- und EPOLL-Server-Engines. TLS 1.3 wird immer gewählt, wenn beide Seiten es unterstützen. Bei der nativen Engine ist TLSOptions.Version auf einem Client und SSLOptions.Version auf einem Server die niedrigste zulässige Version.

Version Ausgehandelt
tlsUndefined (Standard) TLS 1.3 oder TLS 1.2
tls1_2 TLS 1.3 oder TLS 1.2
tls1_3 Nur TLS 1.3
tls1_0, tls1_1 Nichts. Ein Client löst beim Verbindungsaufbau einen Konfigurationsfehler aus, und ein Server beim Start, denn die Engine unterstützt ausschließlich TLS 1.2 und TLS 1.3.

Das unterscheidet sich davon, wie der OpenSSL-Handler die Eigenschaft liest. Mit iohOpenSSL bedeutet tls1_2 exakt TLS 1.2, während es mit iohNativeTLS TLS 1.2 oder höher bedeutet. Die Cipher-Suite-Liste entscheidet den Rest. Um ausschließlich TLS 1.2 zu erhalten, belassen Sie Version bei tls1_2 und nennen Sie in NativeTLS_Options.CipherSuites nur TLS-1.2-Suites. Eine Liste, die nur TLS-1.3-Suites nennt, ergibt ausschließlich TLS 1.3, und eine Liste, die Suites beider Versionen nennt, erlaubt beide. Version tls1_3 zusammen mit einer Liste von TLS-1.2-Suites lässt keine Version zum Aushandeln übrig, und die Verbindung löst beim Start einen Fehler aus.


// TLS 1.3 and TLS 1.2, tls1_2 is the lowest version allowed
oClient.TLSOptions.IOHandler := iohNativeTLS;
oClient.TLSOptions.Version := tls1_2;

// TLS 1.3 only
oClient.TLSOptions.Version := tls1_3;

// TLS 1.2 only: name TLS 1.2 suites and no TLS 1.3 suite
oClient.TLSOptions.Version := tls1_2;
oClient.TLSOptions.NativeTLS_Options.CipherSuites :=
  'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256';

Gruppen und Cipher Suites

 

TLSOptions.NativeTLS_Options auf einem Client und SSLOptions.NativeTLS_Options auf einem Server tragen die beiden Präferenzlisten. Beide sind Strings im OpenSSL-Stil, Einträge durch Doppelpunkte getrennt, der bevorzugte zuerst, und eine Liste bedient TLS 1.3 und TLS 1.2 gemeinsam. Lassen Sie eine leer, um den Standard der Engine beizubehalten. Eine ungültige Liste löst beim Verbindungsaufbau einen Fehler aus, ein Tippfehler wird also nicht stillschweigend ignoriert.

 

Eigenschaft Akzeptierte Namen Standard
Groups X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, X25519, secp256r1, secp384r1. Die Aliase P-256, prime256v1 und P-384 werden ebenfalls akzeptiert, und Namen werden ohne Berücksichtigung der Groß- und Kleinschreibung verglichen. Die drei Hybridgruppen gelten nur für TLS 1.3. X25519MLKEM768:X25519:secp256r1:secp384r1
CipherSuites TLS 1.3: TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256. TLS 1.2: die Suites in der Tabelle unten. Sowohl IANA-Namen als auch Namen im OpenSSL-Stil wie ECDHE-RSA-AES128-GCM-SHA256 werden akzeptiert, und die Engine meldet den IANA-Namen. TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256, zuzüglich der TLS-1.2-Standardwerte unten

 

Die erste Gruppe der Liste ist diejenige, für die ein Client einen Key-Share sendet, X25519MLKEM768 vorn zu belassen ist also das, was schon den ersten Flight post-quantum macht. Ein Eintrag mit vorangestelltem Fragezeichen wird übersprungen, wenn dieser Build ihn nicht hat, und jeder andere unbekannte oder nicht verfügbare Eintrag lässt die gesamte Liste scheitern.

 


// only the hybrid post-quantum groups, and only AES-256-GCM
oClient.TLSOptions.NativeTLS_Options.Groups :=
  'X25519MLKEM768:SecP256r1MLKEM768:SecP384r1MLKEM1024';
oClient.TLSOptions.NativeTLS_Options.CipherSuites := 'TLS_AES_256_GCM_SHA384';

// tolerate a build where X25519 is not compiled in
oClient.TLSOptions.NativeTLS_Options.Groups := '?X25519MLKEM768:secp256r1';

 

TLS-1.2-Cipher-Suites

Standardmäßig sind nur Suites mit Forward Secrecy und AEAD aktiviert. Die AES-CBC-Suites und der RSA-Schlüsselaustausch sind für Gegenstellen da, die nichts Besseres können, und werden nur angeboten oder akzeptiert, wenn CipherSuites sie nennt.

Suites Aktiviert
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 Standardmäßig, in dieser Reihenfolge.
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 Nur wenn in CipherSuites genannt.
TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA256 Nur wenn in CipherSuites genannt. Der RSA-Schlüsselaustausch bietet keine Forward Secrecy.

Die ECDHE-Suites einer TLS-1.2-Verbindung verwenden eine klassische Gruppe aus der Groups-Liste, X25519, secp256r1 oder secp384r1, behalten Sie also mindestens eine davon in der Liste, wenn sich TLS-1.2-Gegenstellen verbinden müssen sollen. Der RSA-Schlüsselaustausch verwendet überhaupt keine Gruppe.


// TLS 1.3, and TLS 1.2 with AES-GCM plus AES-CBC for older clients
oServer.SSLOptions.IOHandler := iohNativeTLS;
oServer.SSLOptions.Version := tls1_2;
oServer.SSLOptions.NativeTLS_Options.CipherSuites :=
  'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:' +
  'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:' +
  'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA';

In einem Build mit SGC_CRYPTO_FIPS werden die beiden auf X25519 aufbauenden Gruppen, die ChaCha20-Poly1305-Suites und der RSA-Schlüsselaustausch herauskompiliert, und die Standardwerte ändern sich zu SecP256r1MLKEM768:SecP384r1MLKEM1024:secp256r1:secp384r1 und TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256, mit den vier ECDHE-AES-GCM-Suites für TLS 1.2. Siehe FIPS-Modus.

 

TLS-1.2-Verhalten

TLS 1.2 ist mit den Schutzmaßnahmen implementiert, die eine moderne Gegenstelle erwartet, und ohne die Teile des Protokolls, die für dessen bekannte Angriffe verantwortlich waren.

Sitzungswiederaufnahme

Die Engine nimmt Sitzungen mit zustandslosen Session-Tickets wieder auf, bei TLS 1.3 (RFC 8446) und bei TLS 1.2 (RFC 5077). Nach einem vollständigen Handshake sendet der Server ein Ticket, versiegelt mit AES-256-GCM unter einem Schlüssel, den nur der Server besitzt. Der Client hält es in einem Sitzungscache, geschlüsselt nach Host, Port und ALPN-Liste, und bietet es bei der nächsten Verbindung an, wodurch dann der Zertifikataustausch entfällt. Ein Ticket, das der Server nicht öffnen kann, oder eines, das abgelaufen ist, führt zu einem vollständigen Handshake mit einem neuen Ticket.

Eigenschaft Standard Bedeutung
NativeTLS_Options.SessionTickets True Ein Server stellt Tickets aus, und ein Client speichert und bietet sie an. False schaltet die Wiederaufnahme aus, jeder Handshake ist dann ein vollständiger.
NativeTLS_Options.TicketLifetime 7200 Nur beim Server. Sekunden, die ein Ticket höchstens zur Wiederaufnahme verwendet werden kann, maximal 604800. Der Ticket-Schlüssel des Servers rotiert alle TicketLifetime Sekunden, und der vorherige Schlüssel bleibt eine weitere Periode lang gültig.

Die Optionen sind TLSOptions.NativeTLS_Options auf einem Client und SSLOptions.NativeTLS_Options auf einem Server. Der Sitzungscache gehört zu jeder Client-Komponente, sodass Wiederverbindungen desselben TsgcWebSocketClient, TsgcHTTP1Client, TsgcTCPClient oder TsgcLDAPClient wieder aufgenommen werden, ebenso wie die Upstream-Verbindung von TsgcWebSocketProxyServer.

Die Wiederaufnahme bei TLS 1.2 setzt das Extended Master Secret aus RFC 7627 voraus. Eine TLS-1.2-Sitzung ohne dieses wird nie wieder aufgenommen und erhält kein Ticket, und auch dann wird kein Ticket ausgestellt, wenn der Server ein Client-Zertifikat verlangt. Ein TLS-1.3-Ticket öffnet nie als TLS-1.2-Sitzung, und umgekehrt.

Ob eine Verbindung wieder aufgenommen wurde, meldet die Eigenschaft Resumed von TsgcIdSSLIOHandlerSocketNativeTLS, dem unten beschriebenen Handler.

Was ausgehandelt wurde

Sobald der Handshake abgeschlossen ist, meldet der Client-Handler, TsgcIdSSLIOHandlerSocketNativeTLS in der Unit sgcSSL_NativeTLS_Indy, das Ergebnis. VersionName ist TLSv1.2 oder TLSv1.3, CipherSuiteName ist der IANA-Name der Suite, GroupName ist die Schlüsselaustauschgruppe, leer beim RSA-Schlüsselaustausch, und PeerSignatureSchemeName ist das vom Server verwendete Signaturschema. Der Handler ist derjenige, den der Client an OnSSLAfterCreateHandler übergibt.


procedure TForm1.oClientSSLAfterCreateHandler(Sender: TObject;
  aType: TwsSSLHandler; aSSLHandler: TIdSSLIOHandlerSocketBase);
begin
  FHandler := aSSLHandler;
end;

procedure TForm1.oClientConnect(Connection: TsgcWSConnection);
var
  oHandler: TsgcIdSSLIOHandlerSocketNativeTLS;
begin
  if FHandler is TsgcIdSSLIOHandlerSocketNativeTLS then
  begin
    oHandler := TsgcIdSSLIOHandlerSocketNativeTLS(FHandler);
    Memo1.Lines.Add(oHandler.VersionName + ' ' + oHandler.CipherSuiteName +
      ' ' + oHandler.GroupName + ' ' + oHandler.PeerSignatureSchemeName);
  end;
end;

Zertifikate und Verifizierung

 

Ein Server liest CertFile, KeyFile und Password. CertFile und KeyFile können PEM sein, oder CertFile kann eine .pfx- oder .p12-Datei sein, erkannt an ihrer Dateiendung. Eine PKCS#12-Datei enthält das Zertifikat, die Kette und den privaten Schlüssel, KeyFile wird dann nicht benötigt, und sie wird mit Password geöffnet. Sie wird vom in reinem Pascal geschriebenen PKCS#12-Reader aus sgcCrypto_PKCS12 gelesen, ohne OpenSSL. Ein Client, der VerifyCertificate auf True setzt, prüft die Serverkette gegen die unten beschriebenen Vertrauensanker und prüft, dass der Name zu dem Host passt, mit dem er sich verbunden hat. VerifyDepth wird durchgereicht, und ein Wert über 16 wird auf 16 begrenzt.

Die Vertrauensanker sind die Zertifikate in RootCertFile. Das Setzen von NativeTLS_Options.UseSystemRoots auf True fügt die Zertifizierungsstellen hinzu, denen das Betriebssystem vertraut. Der Standard ist False. Beide kombinieren sich: Die Roots aus RootCertFile werden zuerst geladen, die Systemwurzeln werden demselben Speicher hinzugefügt, und eine Kette wird akzeptiert, wenn sie zu einer von beiden führt. Wird keine Systemwurzel gefunden, wird nichts hinzugefügt und kein Fehler ausgelöst, die Verifizierung stützt sich dann allein auf RootCertFile. Die Eigenschaft ist TLSOptions.NativeTLS_Options.UseSystemRoots auf einem Client und SSLOptions.NativeTLS_Options.UseSystemRoots auf einem Server, wo die Vertrauensanker nur zur Validierung von Client-Zertifikaten dienen. Woher die Systemwurzeln stammen, hängt von der Plattform ab:

Um selbst ein Verzeichnis mit Zertifikatsdateien zu laden, liest TsgcTLS13TrustStore.AddDirectory(aDirectory, aRemovedDirectory) in der Unit sgcSSL_NativeTLS_Certificates jede PEM- oder DER-Datei aus aDirectory, überspringt jede Datei, deren Name auch in aRemovedDirectory existiert, und gibt zurück, wie viele Zertifikate hinzugefügt wurden. Es funktioniert auf jeder Plattform.

 

Um den Namen festzulegen, den das Zertifikat tragen muss, statt den Verbindungs-Host zu verwenden, setzen Sie TLSOptions.OpenSSL_Options.X509Checks.HostName oder IPAddress, mit dem passenden Flag in X509Checks.Mode. Die Engine liest diese beiden Werte, obwohl sie nicht OpenSSL ist, die Eigenschaft bedeutet also dasselbe, welcher IOHandler auch ausgewählt ist.

 

Auf einem Server fordert SSLOptions.VerifyCertificate vom Client ein Zertifikat an und validiert es gegen die oben beschriebenen Vertrauensanker, und SSLOptions.VerifyCertificate_Options.FailIfNoCertificate weist einen Client ab, der keines sendet. VerifyClientOnce hat hier nichts zu tun. TLS 1.3 kennt keine Neuaushandlung, und die Engine weist sie bei TLS 1.2 ab, sie fragt also nie ein zweites Mal.

 

ALPN funktioniert wie gewohnt über ALPNProtocols auf einem Client und über die ALPN-Auswahl des Servers, HTTP/2 über die native Engine handelt h2 also genauso aus wie über OpenSSL. Siehe ALPN.

QUIC-Modus

Die Engine führt auch den TLS-1.3-Handshake von QUIC und HTTP/3 aus (RFC 9001). Setzen Sie SSLQUICOptions.IOHandler auf TsgcQUICClient, TLSOptions.IOHandler auf TsgcHTTP3Client, QUICOptions.SSLOptions.IOHandler auf TsgcQUICServer und TsgcHTTP3Server, oder SSLOptions.IOHandler auf einem Server mit HTTP3Options.Enabled, auf iohNativeTLS.

Siehe QUIC für den Vergleich der Engines.

 

Was sie nicht leistet

 

Die Engine ist bewusst eng gehalten. Alles Folgende ist eine echte Grenze, keine Einstellung, nach der man suchen müsste.

 

 

Nichts, was die Gegenstelle sendet, löst eine Exception aus. Ein Protokollfehler beendet die Verbindung mit dem richtigen TLS-Alert, und der Grund wird wie gewohnt über die Fehlerereignisse der Komponente gemeldet.

Der AES-GCM- und AES-CBC-Datensatzschutz nutzt die AES-NI- und PCLMULQDQ-Instruktionen auf mit Delphi erstellten Windows-Builds, wenn die CPU sie besitzt, siehe sgcAES_HardwareAvailable in Symmetrische Verschlüsselung. Für einen Build, bei dem kein Teil der Bibliothek OpenSSL lädt, siehe Betrieb ohne OpenSSL.