TLS natif

Pris en charge par

 

TsgcWebSocketClient

TsgcTCPClient

TsgcHTTP1Client

TsgcHTTP2Client

TsgcWebSocketServer

TsgcWebSocketHTTPServer

TsgcHTTPServer

TsgcWebSocketProxyServer

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.

 

 

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 :

 

 

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.

 

 

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.

 

 

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.