Pris en charge par
TsgcWebSocketLoadBalancerServer
*Nécessite le pack sgcCrypto, livré à partir de l'édition Standard.
Le moteur TLS natif est une implémentation de TLS 1.3 et TLS 1.2 écrite entièrement en Object Pascal au-dessus des unités sgcCrypto. Ce n'est pas une liaison vers une bibliothèque externe : il n'y a aucune DLL OpenSSL à déployer, aucun appel SChannel et aucune bibliothèque TLS de la plateforme en jeu. Le même code source se compile de Delphi 7 à RAD Studio 13 et s'exécute sur toutes les plateformes prises en charge par les composants.
La raison d'y recourir est l'échange de clés. Le moteur propose les groupes hybrides post-quantiques de la RFC 10024, et X25519MLKEM768 est le premier groupe qu'il propose, de sorte qu'un client qui y passe négocie un échange de clés post-quantique avec tout serveur qui en prend un en charge, sans aucune autre modification de l'application. Les groupes hybrides n'existent qu'en TLS 1.3, une connexion qui se replie sur TLS 1.2 utilise donc une courbe classique. Voir Cryptographie post-quantique pour savoir ce que sont ces groupes et pourquoi une construction hybride est la migration raisonnable aujourd'hui.
Activation
Définissez l'IOHandler et rien d'autre ne change. Les propriétés de certificat, de clé, de certificat racine, de vérification et d'ALPN que vous utilisez déjà continuent de fonctionner, le moteur lit les mêmes.
// 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;
Sur un serveur, le moteur fonctionne aussi sur les moteurs événementiels. Définissez IOHandlerOptions.IOHandlerType sur iohIOCP sous Windows ou sur iohEPOLL sous Linux et le gestionnaire est construit sur le fournisseur TLS correspondant, de sorte que la même configuration fonctionne sur les trois moteurs.
Sélectionner iohNativeTLS dans une compilation sans le pack sgcCrypto lève une exception qui le signale, plutôt que de se replier silencieusement sur une autre bibliothèque.
Versions de TLS
Le moteur parle TLS 1.3 et TLS 1.2, en tant que client et en tant que serveur, sur le client Indy, sur le serveur Indy et sur les moteurs de serveur IOCP et EPOLL. TLS 1.3 est toujours choisi lorsque les deux parties le prennent en charge. Pour le moteur natif, TLSOptions.Version sur un client et SSLOptions.Version sur un serveur est la version minimale autorisée.
| Version | Négocie |
| tlsUndefined (la valeur par défaut) | TLS 1.3 ou TLS 1.2 |
| tls1_2 | TLS 1.3 ou TLS 1.2 |
| tls1_3 | TLS 1.3 uniquement |
| tls1_0, tls1_1 | Rien. Un client lève une erreur de configuration à la connexion, et un serveur au démarrage, car le moteur ne prend en charge que TLS 1.2 et TLS 1.3. |
Ce n'est pas ainsi que le gestionnaire OpenSSL lit la propriété. Avec iohOpenSSL, tls1_2 signifie exactement TLS 1.2, alors qu'avec iohNativeTLS il signifie TLS 1.2 ou une version ultérieure. La liste des suites de chiffrement décide du reste. Pour n'obtenir que TLS 1.2, laissez Version à tls1_2 et ne nommez que des suites TLS 1.2 dans NativeTLS_Options.CipherSuites. Une liste qui ne nomme que des suites TLS 1.3 donne uniquement TLS 1.3, et une liste qui nomme des suites des deux versions permet les deux. Version tls1_3 associée à une liste de suites TLS 1.2 ne laisse aucune version à négocier, et la connexion lève une exception à son démarrage.
// 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';
Groupes et suites de chiffrement
TLSOptions.NativeTLS_Options sur un client, et SSLOptions.NativeTLS_Options sur un serveur, portent les deux listes de préférence. Les deux sont des chaînes dans le style OpenSSL, les éléments séparés par des deux-points, le plus préféré en premier, et une seule liste sert à la fois pour TLS 1.3 et pour TLS 1.2. Laissez-en une vide pour conserver la valeur par défaut du moteur. Une liste invalide lève une exception au démarrage de la connexion, une faute de frappe n'est donc pas ignorée silencieusement.
| Propriété | Noms acceptés | Valeur par défaut |
| Groups | X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, X25519, secp256r1, secp384r1. Les alias P-256, prime256v1 et P-384 sont acceptés eux aussi, et les noms sont comparés sans tenir compte de la casse. Les trois groupes hybrides sont réservés à 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 : les suites du tableau ci-dessous. Les noms IANA et les noms au style OpenSSL tels que ECDHE-RSA-AES128-GCM-SHA256 sont tous deux acceptés, et le moteur indique le nom IANA. | TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256, plus les valeurs par défaut de TLS 1.2 ci-dessous |
Le premier groupe de la liste est celui pour lequel un client envoie un partage de clé, laisser X25519MLKEM768 en tête est donc ce qui rend le premier envoi déjà post-quantique. Préfixer un élément par un point d'interrogation le saute lorsque cette compilation ne le contient pas, et tout autre élément inconnu ou indisponible fait échouer la liste entière.
// 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';
Suites de chiffrement de TLS 1.2
Seules les suites AEAD à confidentialité persistante sont activées par défaut. Les suites AES-CBC et l'échange de clés RSA existent pour les pairs qui ne peuvent pas faire mieux, et elles ne sont proposées ou acceptées que lorsque CipherSuites les nomme.
| Suites | Activées |
| 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 | Par défaut, dans cet ordre. |
| 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 | Seulement si elles sont nommées dans CipherSuites. |
| 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 | Seulement si elles sont nommées dans CipherSuites. L'échange de clés RSA n'offre pas de confidentialité persistante. |
Les suites ECDHE d'une connexion TLS 1.2 utilisent un groupe classique de la liste Groups, X25519, secp256r1 ou secp384r1, conservez-en donc au moins un dans la liste lorsque des pairs en TLS 1.2 doivent pouvoir se connecter. L'échange de clés RSA n'utilise aucun groupe.
// 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';
Dans une compilation faite avec SGC_CRYPTO_FIPS, les deux groupes construits sur X25519, les suites ChaCha20-Poly1305 et l'échange de clés RSA sont retirés à la compilation, et les valeurs par défaut deviennent SecP256r1MLKEM768:SecP384r1MLKEM1024:secp256r1:secp384r1 et TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256, avec les quatre suites ECDHE AES-GCM pour TLS 1.2. Voir Mode FIPS.
Comportement de TLS 1.2
TLS 1.2 est implémenté avec les protections qu'attend un pair moderne, et sans les parties du protocole qui ont causé ses attaques connues.
Extended master secret. La RFC 7627 est proposée par un client et acceptée par un serveur. Un pair qui ne la prend pas en charge est tout de même accepté.
Pas de renégociation. Une demande de renégociation est refusée avec une alerte no_renegotiation. La reprise de session utilise des tickets de session et nécessite le secret maître étendu, voir Reprise de session ci-dessous.
Pas de signatures SHA-1. Conformément à la RFC 9155, les signatures SHA-1 ne sont ni envoyées ni acceptées. Un ClientHello TLS 1.2 sans l'extension signature_algorithms est refusé, car cela signifierait SHA-1 uniquement.
Protection contre le downgrade. La protection contre le downgrade de la RFC 8446 section 4.1.3 fonctionne des deux côtés. Un serveur qui accepte aussi TLS 1.3 marque chaque réponse en TLS 1.2, et un client qui a proposé TLS 1.3 refuse une réponse en TLS 1.2 qui porte cette marque.
CBC en temps constant. Les enregistrements CBC sont déchiffrés en temps constant, ce qui ferme Lucky Thirteen, et l'échange de clés RSA applique la contre-mesure de la RFC 5246 section 7.4.7.1.
Usage de clé du certificat. Pour l'échange de clés RSA, le certificat du serveur a besoin de l'usage de clé keyEncipherment. Les suites ECDHE et TLS 1.3 ont besoin de digitalSignature.
Fonctionnalités réservées à TLS 1.3. KeyUpdate et l'exportation de matériel de clés n'existent que sur une session TLS 1.3.
Reprise de session
Le moteur reprend les sessions avec des tickets de session sans état, en TLS 1.3 (RFC 8446) et en TLS 1.2 (RFC 5077). Après un handshake complet, le serveur envoie un ticket, scellé avec AES-256-GCM sous une clé que seul le serveur détient. Le client le conserve dans un cache de session, indexé par hôte, port et liste ALPN, et le propose lors de la connexion suivante, ce qui évite alors l'échange de certificats. Un ticket que le serveur ne peut pas ouvrir, ou qui a expiré, donne lieu à un handshake complet avec un nouveau ticket.
| Propriété | Défaut | Signification |
| NativeTLS_Options.SessionTickets | True | Un serveur émet des tickets et un client les stocke et les propose. False désactive la reprise, chaque handshake est alors complet. |
| NativeTLS_Options.TicketLifetime | 7200 | Serveur uniquement. Secondes pendant lesquelles un ticket peut servir à la reprise, au maximum 604800. La clé de ticket du serveur change toutes les TicketLifetime secondes, et la clé précédente reste valide une période de plus. |
Les options sont TLSOptions.NativeTLS_Options sur un client et SSLOptions.NativeTLS_Options sur un serveur. Le cache de session appartient à chaque composant client, si bien que les reconnexions d'un même TsgcWebSocketClient, TsgcHTTP1Client, TsgcTCPClient ou TsgcLDAPClient reprennent la session, tout comme la connexion amont de TsgcWebSocketProxyServer.
La reprise en TLS 1.2 nécessite le secret maître étendu de la RFC 7627. Une session TLS 1.2 qui ne le prend pas en charge n'est jamais reprise et ne reçoit aucun ticket, et aucun ticket n'est émis non plus lorsque le serveur demande un certificat client. Un ticket TLS 1.3 ne s'ouvre jamais comme une session TLS 1.2, et inversement.
Le fait qu'une connexion ait été reprise est signalé par la propriété Resumed de TsgcIdSSLIOHandlerSocketNativeTLS, le gestionnaire décrit ci-dessous.
Ce qui a été négocié
Une fois le handshake terminé, le gestionnaire du client, TsgcIdSSLIOHandlerSocketNativeTLS dans l'unité sgcSSL_NativeTLS_Indy, rapporte le résultat. VersionName vaut TLSv1.2 ou TLSv1.3, CipherSuiteName est le nom IANA de la suite, GroupName est le groupe de l'échange de clés, vide pour l'échange de clés RSA, et PeerSignatureSchemeName est le schéma de signature utilisé par le serveur. Le gestionnaire est celui que le client transmet à OnSSLAfterCreateHandler.
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;
Certificats et vérification
Un serveur lit CertFile, KeyFile et Password. CertFile et KeyFile peuvent être au format PEM, ou CertFile peut être un fichier .pfx ou .p12, reconnu par son extension. Un fichier PKCS#12 contient le certificat, la chaîne et la clé privée, KeyFile n'est donc pas nécessaire, et il s'ouvre avec Password. Il est lu par le lecteur PKCS#12 en Pascal pur de sgcCrypto_PKCS12, sans OpenSSL. Un client qui met VerifyCertificate à True vérifie la chaîne du serveur par rapport aux racines de confiance décrites ci-dessous et vérifie que le nom correspond à l'hôte auquel il s'est connecté. VerifyDepth est transmis tel quel, et une valeur supérieure à 16 est plafonnée à 16.
Les racines de confiance sont les certificats de RootCertFile. Mettre NativeTLS_Options.UseSystemRoots à True ajoute les autorités de certification auxquelles le système d'exploitation fait confiance. C'est False par défaut. Les deux se combinent : les racines de RootCertFile sont chargées en premier, les racines du système sont ajoutées au même magasin, et une chaîne est acceptée dès qu'elle mène à l'une d'elles. Lorsqu'aucune racine système n'est trouvée, rien n'est ajouté et aucune erreur n'est levée, la vérification repose alors uniquement sur RootCertFile. La propriété est TLSOptions.NativeTLS_Options.UseSystemRoots sur un client et SSLOptions.NativeTLS_Options.UseSystemRoots sur un serveur, où les racines ne servent qu'à valider les certificats du client. L'origine des racines système dépend de la plateforme :
Windows. Le magasin système ROOT, les Trusted Root Certification Authorities, lues via crypt32.dll.
macOS. Les certificats d'ancrage de Security.framework, avec /etc/ssl/cert.pem en repli.
iOS. Les racines système ne peuvent pas être listées. Lorsqu'aucune racine n'est chargée, une chaîne que le moteur rejette uniquement parce que sa racine n'est pas de confiance est évaluée par Security.framework avec la politique SSL et le nom d'hôte. Les contrôles d'usage de clé et de nom d'hôte du moteur s'exécutent tout de même ensuite.
Android. Les fichiers de certificats de /apex/com.android.conscrypt/cacerts (Android 14 et ultérieur) ou /system/etc/security/cacerts, au format PEM ou DER. Les racines que l'utilisateur a désactivées, listées dans /data/misc/user/0/cacerts-removed, sont ignorées, et les racines utilisateur de /data/misc/user/0/cacerts-added sont ajoutées lorsque l'application peut lire ces dossiers.
Linux et autres plateformes. Le premier de ces fichiers qui existe et contient au moins un certificat : /etc/ssl/certs/ca-certificates.crt, /etc/pki/tls/certs/ca-bundle.crt, /etc/ssl/ca-bundle.pem, /etc/pki/tls/cacert.pem, /etc/ssl/cert.pem et /etc/ssl/certs/ca-bundle.crt.
Pour charger vous-même un dossier de fichiers de certificats, TsgcTLS13TrustStore.AddDirectory(aDirectory, aRemovedDirectory) dans l'unité sgcSSL_NativeTLS_Certificates lit chaque fichier PEM ou DER de aDirectory, ignore tout fichier dont le nom existe aussi dans aRemovedDirectory, et renvoie le nombre de certificats ajoutés. Cela fonctionne sur toutes les plateformes.
Pour épingler le nom que le certificat doit porter au lieu d'utiliser l'hôte de la connexion, définissez TLSOptions.OpenSSL_Options.X509Checks.HostName ou IPAddress, avec l'indicateur correspondant dans X509Checks.Mode. Le moteur lit ces deux valeurs bien qu'il ne soit pas OpenSSL, la propriété a donc la même signification quel que soit l'IOHandler sélectionné.
Sur un serveur, SSLOptions.VerifyCertificate demande un certificat au client et le valide par rapport aux racines de confiance décrites ci-dessus, et SSLOptions.VerifyCertificate_Options.FailIfNoCertificate refuse un client qui n'en envoie aucun. VerifyClientOnce n'a rien à faire ici. TLS 1.3 n'a pas de renégociation, et le moteur la refuse en TLS 1.2, il ne redemande donc jamais une seconde fois.
L'ALPN fonctionne comme d'habitude via ALPNProtocols sur un client et via la sélection ALPN du serveur, HTTP/2 au-dessus du moteur natif négocie donc h2 de la même façon qu'au-dessus d'OpenSSL. Voir ALPN.
Mode QUIC
Le moteur exécute aussi le handshake TLS 1.3 de QUIC et HTTP/3 (RFC 9001). Définissez SSLQUICOptions.IOHandler sur TsgcQUICClient, TLSOptions.IOHandler sur TsgcHTTP3Client, QUICOptions.SSLOptions.IOHandler sur TsgcQUICServer et TsgcHTTP3Server, ou SSLOptions.IOHandler sur un serveur avec HTTP3Options.Enabled, sur iohNativeTLS.
Le moteur QUIC sgc est toujours utilisé, quoi qu'exporte OpenSSL. Sur un serveur, QUICMode indique qmCustom.
Le handshake transporte les paramètres de transport QUIC, exige l'ALPN et signale les alertes TLS comme des codes d'erreur QUIC. Le certificat du serveur est lu au format PEM ou PFX, et le pair est vérifié par rapport à RootCertFile ou aux racines système, nom d'hôte inclus.
Les tickets de session, la reprise et le 0-RTT fonctionnent comme sur OpenSSL. Le serveur envoie un ticket après le handshake, le client le stocke avec l'ALPN et les paramètres de transport du serveur, et le 0-RTT n'est accepté qu'une seule fois par ticket. Les tickets QUIC ne passent jamais vers TLS sur TCP.
La protection des paquets est native elle aussi, de sorte que QUIC et HTTP/3 ne chargent aucun OpenSSL. Aucune définition n'est nécessaire.
Voir QUIC pour la comparaison des moteurs.
Ce qu'il ne fait pas
Le moteur est volontairement restreint. Tout ce qui suit est une limite réelle, et non un réglage à chercher.
TLS 1.3 et TLS 1.2 uniquement. Il n'y a ni SSL, ni TLS 1.0, ni TLS 1.1. Un client lève une exception à la connexion, et un serveur au démarrage, si Version vaut tls1_0 ou tls1_1. Un pair incapable de faire du TLS 1.3 comme du TLS 1.2 ne peut pas se connecter.
Pas de PSK sans ticket. La reprise ne fonctionne qu'avec les tickets de session que le moteur émet lui-même, voir Reprise de session ci-dessus. Les clés pré-partagées externes ne sont pas prises en charge.
Pas de 0-RTT sur TCP. Sur une connexion TLS sur TCP, les données anticipées ne sont ni envoyées ni acceptées. L'authentification du client après le handshake n'est pas implémentée non plus.
QUIC via son propre mode. QUIC et HTTP/3 utilisent le mode QUIC décrit ci-dessus, configuré sur ces composants.
Pas de racines système par défaut. À moins que NativeTLS_Options.UseSystemRoots ne soit à True, les racines de confiance viennent de RootCertFile et de nulle part ailleurs, un client qui vérifie les certificats doit donc livrer un fichier de racines ou pointer vers un tel fichier. C'est la seule différence de comportement qu'une configuration OpenSSL ou SChannel en fonctionnement a le plus de chances de remarquer.
OnSSLVerifyPeer n'est pas déclenché. Cet événement appartient au gestionnaire OpenSSL. Le moteur natif dispose de ses propres points d'accroche de vérification, qui reçoivent la chaîne complète du pair et le verdict du moteur en une seule fois, au lieu d'un certificat par niveau de profondeur, une application qui redéfinit la décision de confiance doit donc les utiliser plutôt que l'événement du composant.
Rien de ce que le pair envoie ne lève d'exception. Un échec de protocole met fin à la connexion avec l'alerte TLS appropriée, et la raison est signalée via les événements d'erreur du composant, comme d'habitude.
La protection des enregistrements AES-GCM et AES-CBC utilise les instructions AES-NI et PCLMULQDQ sur les builds Windows réalisés avec Delphi lorsque le processeur les prend en charge, voir sgcAES_HardwareAvailable dans Chiffrement symétrique. Pour un build où aucune partie de la bibliothèque ne charge OpenSSL, voir Fonctionner sans OpenSSL.