Compatible con
TsgcWebSocketLoadBalancerServer
*Requiere el paquete sgcCrypto, que se incluye a partir de la edición Standard.
El motor TLS nativo es una implementación de TLS 1.3 y TLS 1.2 escrita íntegramente en Object Pascal sobre las unidades sgcCrypto. No es un enlace a otra biblioteca: no hay ninguna DLL de OpenSSL que desplegar, ninguna llamada a SChannel ni ninguna biblioteca TLS de la plataforma de por medio. El mismo código fuente compila desde Delphi 7 hasta RAD Studio 13 y funciona en todas las plataformas que admiten los componentes.
La razón para recurrir a él es el intercambio de claves. El motor ofrece los grupos híbridos poscuánticos de RFC 10024, y X25519MLKEM768 es el primer grupo que propone, de modo que un cliente que cambie a él negocia un intercambio de claves poscuántico contra cualquier servidor que admita uno, sin ningún otro cambio en la aplicación. Los grupos híbridos solo existen en TLS 1.3, así que una conexión que recurre a TLS 1.2 usa una curva clásica. Consulte Criptografía Poscuántica para saber qué son esos grupos y por qué un híbrido es hoy la migración razonable.
Cómo activarlo
Establezca el IOHandler y no cambia nada más. Las propiedades de certificado, clave, certificado raíz, verificación y ALPN que ya utiliza siguen funcionando, el motor lee esas mismas.
// 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;
En un servidor, el motor funciona también sobre los motores dirigidos por eventos. Establezca IOHandlerOptions.IOHandlerType en iohIOCP en Windows o en iohEPOLL en Linux y el manejador se construye sobre el Proveedor TLS correspondiente, de modo que la misma configuración funciona en los tres motores.
Seleccionar iohNativeTLS en una compilación sin el paquete sgcCrypto genera una excepción que así lo indica, en lugar de recurrir en silencio a otra biblioteca.
Versiones de TLS
El motor habla TLS 1.3 y TLS 1.2, como cliente y como servidor, en el cliente Indy, en el servidor Indy y en los motores de servidor IOCP y EPOLL. TLS 1.3 se elige siempre que ambos extremos lo admiten. En el motor nativo, TLSOptions.Version en un cliente y SSLOptions.Version en un servidor es la versión mínima permitida.
| Versión | Negocia |
| tlsUndefined (el valor predeterminado) | TLS 1.3 o TLS 1.2 |
| tls1_2 | TLS 1.3 o TLS 1.2 |
| tls1_3 | Solo TLS 1.3 |
| tls1_0, tls1_1 | Nada. Un cliente genera un error de configuración al conectarse, y un servidor al arrancar, porque el motor solo admite TLS 1.2 y TLS 1.3. |
Esto es distinto de cómo lee la propiedad el manejador de OpenSSL. Con iohOpenSSL, tls1_2 significa exactamente TLS 1.2, mientras que con iohNativeTLS significa TLS 1.2 o posterior. La lista de suites de cifrado decide el resto. Para obtener solo TLS 1.2, mantenga Version en tls1_2 e indique únicamente suites de TLS 1.2 en NativeTLS_Options.CipherSuites. Una lista que indique solo suites de TLS 1.3 da solo TLS 1.3, y una lista que indique suites de ambas versiones permite las dos. Version en tls1_3 junto con una lista de suites de TLS 1.2 no deja ninguna versión que negociar, y la conexión genera un error al iniciarse.
// 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';
Grupos y suites de cifrado
TLSOptions.NativeTLS_Options en un cliente, y SSLOptions.NativeTLS_Options en un servidor, contienen las dos listas de preferencia. Ambas son cadenas al estilo de OpenSSL, con los elementos separados por dos puntos y el preferido en primer lugar, y una única lista sirve tanto para TLS 1.3 como para TLS 1.2. Deje una vacía para conservar el valor predeterminado del motor. Una lista inválida genera un error al iniciarse la conexión, así que un error de escritura no se ignora en silencio.
| Propiedad | Nombres aceptados | Predeterminado |
| Groups | X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, X25519, secp256r1, secp384r1. También se aceptan los alias P-256, prime256v1 y P-384, y los nombres se comparan sin distinguir mayúsculas de minúsculas. Los tres grupos híbridos son exclusivos de 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: las suites de la tabla siguiente. Se aceptan tanto los nombres IANA como los nombres al estilo de OpenSSL, como ECDHE-RSA-AES128-GCM-SHA256, y el motor informa el nombre IANA. | TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256, más los valores predeterminados de TLS 1.2 indicados abajo |
El primer grupo de la lista es aquel para el que un cliente envía un key share, así que dejar X25519MLKEM768 al principio es lo que hace que el primer envío ya sea poscuántico. Anteponer un signo de interrogación a un elemento hace que se omita cuando esta compilación no lo incluye, y cualquier otro elemento desconocido o no disponible hace fallar la lista entera.
// 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 cifrado de TLS 1.2
De forma predeterminada solo están habilitadas las suites AEAD con secreto hacia adelante. Las suites AES-CBC y el intercambio de claves RSA existen para los pares que no puedan hacerlo mejor, y solo se ofrecen o se aceptan cuando CipherSuites las nombra.
| Suites | Habilitadas |
| 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 | De forma predeterminada, en este orden. |
| 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 | Solo cuando se nombran en 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 | Solo cuando se nombran en CipherSuites. El intercambio de claves RSA no tiene secreto hacia adelante. |
Las suites ECDHE de una conexión TLS 1.2 usan un grupo clásico de la lista Groups, X25519, secp256r1 o secp384r1, así que mantenga al menos uno de ellos en la lista cuando deban conectarse pares con TLS 1.2. El intercambio de claves RSA no usa ningún grupo.
// 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';
En una compilación hecha con SGC_CRYPTO_FIPS los dos grupos construidos sobre X25519, las suites ChaCha20-Poly1305 y el intercambio de claves RSA quedan fuera de la compilación, y los valores predeterminados pasan a ser SecP256r1MLKEM768:SecP384r1MLKEM1024:secp256r1:secp384r1 y TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256, con las cuatro suites ECDHE AES-GCM para TLS 1.2. Consulte Modo FIPS.
Comportamiento de TLS 1.2
TLS 1.2 está implementado con las protecciones que espera un par moderno, y sin las partes del protocolo que causaron sus ataques conocidos.
Extended master secret. La RFC 7627 la ofrece un cliente y la acepta un servidor. Un par que no la admite se acepta igualmente.
Sin renegociación. Una solicitud de renegociación se rechaza con una alerta no_renegotiation. La reanudación de sesión usa tickets de sesión y requiere el extended master secret, vea Reanudación de sesión más abajo.
Sin firmas SHA-1. Siguiendo la RFC 9155, las firmas SHA-1 no se envían ni se aceptan. Un ClientHello de TLS 1.2 sin la extensión signature_algorithms se rechaza, porque significaría solo SHA-1.
Protección contra downgrade. La protección contra downgrade de la RFC 8446 sección 4.1.3 funciona en ambos lados. Un servidor que también admite TLS 1.3 marca toda respuesta en TLS 1.2, y un cliente que ofreció TLS 1.3 rechaza una respuesta en TLS 1.2 que lleve esa marca.
CBC en tiempo constante. Los registros CBC se descifran en tiempo constante, lo que cierra Lucky Thirteen, y el intercambio de claves RSA aplica la contramedida de la RFC 5246 sección 7.4.7.1.
Uso de clave del certificado. Para el intercambio de claves RSA, el certificado del servidor necesita el uso de clave keyEncipherment. Las suites ECDHE y TLS 1.3 necesitan digitalSignature.
Funciones exclusivas de TLS 1.3. KeyUpdate y la exportación de material de claves solo existen en una sesión TLS 1.3.
Reanudación de sesión
El motor reanuda sesiones con tickets de sesión sin estado, en TLS 1.3 (RFC 8446) y en TLS 1.2 (RFC 5077). Tras un protocolo de enlace completo el servidor envía un ticket, sellado con AES-256-GCM bajo una clave que solo posee el servidor. El cliente lo guarda en una caché de sesión, indexada por host, puerto y lista ALPN, y lo ofrece en la siguiente conexión, que entonces omite el intercambio de certificados. Un ticket que el servidor no puede abrir, o uno que ha caducado, da lugar a un protocolo de enlace completo con un ticket nuevo.
| Propiedad | Predeterminado | Significado |
| NativeTLS_Options.SessionTickets | True | Un servidor emite tickets y un cliente los guarda y los ofrece. False desactiva la reanudación, y entonces cada protocolo de enlace es completo. |
| NativeTLS_Options.TicketLifetime | 7200 | Solo servidor. Segundos durante los que un ticket puede usarse para reanudar, como máximo 604800. La clave de ticket del servidor rota cada TicketLifetime segundos, y la clave anterior sigue siendo válida durante un periodo más. |
Las opciones son TLSOptions.NativeTLS_Options en un cliente y SSLOptions.NativeTLS_Options en un servidor. La caché de sesión pertenece a cada componente cliente, de modo que las reconexiones del mismo TsgcWebSocketClient, TsgcHTTP1Client, TsgcTCPClient o TsgcLDAPClient se reanudan, y también lo hace la conexión ascendente de TsgcWebSocketProxyServer.
La reanudación en TLS 1.2 requiere el extended master secret de la RFC 7627. Una sesión TLS 1.2 sin él nunca se reanuda y no recibe ningún ticket, y tampoco se emite ningún ticket cuando el servidor pide un certificado de cliente. Un ticket de TLS 1.3 nunca se abre como sesión TLS 1.2, ni al revés.
Si una conexión se reanudó lo informa la propiedad Resumed de TsgcIdSSLIOHandlerSocketNativeTLS, el manejador descrito más abajo.
Lo que se negoció
Una vez completado el protocolo de enlace, el manejador del cliente, TsgcIdSSLIOHandlerSocketNativeTLS en la unidad sgcSSL_NativeTLS_Indy, informa del resultado. VersionName es TLSv1.2 o TLSv1.3, CipherSuiteName es el nombre IANA de la suite, GroupName es el grupo del intercambio de claves, vacío en el intercambio de claves RSA, y PeerSignatureSchemeName es el esquema de firma que usó el servidor. El manejador es el que el cliente pasa a 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;
Certificados y verificación
Un servidor lee CertFile, KeyFile y Password. CertFile y KeyFile pueden ser PEM, o CertFile puede ser un archivo .pfx o .p12, reconocido por su extensión. Un archivo PKCS#12 contiene el certificado, la cadena y la clave privada, así que KeyFile no es necesario, y se abre con Password. Lo lee el lector PKCS#12 en Pascal puro de sgcCrypto_PKCS12, sin OpenSSL. Un cliente que establece VerifyCertificate en True comprueba la cadena del servidor frente a las raíces de confianza descritas más abajo y comprueba que el nombre coincide con el host al que se ha conectado. VerifyDepth se transmite tal cual, y un valor superior a 16 se limita a 16.
Las raíces de confianza son los certificados de RootCertFile. Establecer NativeTLS_Options.UseSystemRoots en True añade las autoridades de certificación en las que confía el sistema operativo. Es False de forma predeterminada. Las dos se combinan: primero se cargan las raíces de RootCertFile, las raíces del sistema se añaden al mismo almacén, y una cadena se acepta cuando lleva a cualquiera de ellas. Cuando no se encuentra ninguna raíz del sistema, no se añade nada y no se genera ningún error, de modo que la verificación depende únicamente de RootCertFile. La propiedad es TLSOptions.NativeTLS_Options.UseSystemRoots en un cliente y SSLOptions.NativeTLS_Options.UseSystemRoots en un servidor, donde las raíces solo sirven para validar certificados de cliente. El origen de las raíces del sistema depende de la plataforma:
Windows. El almacén de sistema ROOT, las Trusted Root Certification Authorities, leídas mediante crypt32.dll.
macOS. Los certificados de anclaje de Security.framework, con /etc/ssl/cert.pem como alternativa.
iOS. Las raíces del sistema no se pueden listar. Cuando no se ha cargado ninguna raíz, una cadena que el motor rechaza solo porque su raíz no es de confianza se evalúa mediante Security.framework con la política SSL y el nombre de host. Las comprobaciones de uso de clave y nombre de host del motor siguen ejecutándose después.
Android. Los archivos de certificado de /apex/com.android.conscrypt/cacerts (Android 14 y posteriores) o /system/etc/security/cacerts, en PEM o DER. Las raíces que el usuario ha desactivado, listadas en /data/misc/user/0/cacerts-removed, se omiten, y las raíces de usuario de /data/misc/user/0/cacerts-added se añaden cuando la aplicación puede leer esas carpetas.
Linux y otras plataformas. El primero de estos archivos que exista y contenga al menos un certificado: /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 y /etc/ssl/certs/ca-bundle.crt.
Para cargar usted mismo una carpeta de archivos de certificado, TsgcTLS13TrustStore.AddDirectory(aDirectory, aRemovedDirectory) en la unidad sgcSSL_NativeTLS_Certificates lee cada archivo PEM o DER de aDirectory, omite cualquier archivo cuyo nombre también exista en aRemovedDirectory, y devuelve cuántos certificados añadió. Funciona en todas las plataformas.
Para fijar el nombre que debe llevar el certificado en lugar de usar el host de la conexión, establezca TLSOptions.OpenSSL_Options.X509Checks.HostName o IPAddress, con el indicador correspondiente en X509Checks.Mode. El motor lee esos dos valores aunque no sea OpenSSL, de modo que la propiedad significa lo mismo sea cual sea el IOHandler seleccionado.
En un servidor, SSLOptions.VerifyCertificate pide un certificado al cliente y lo valida frente a las raíces de confianza descritas más arriba, y SSLOptions.VerifyCertificate_Options.FailIfNoCertificate rechaza a un cliente que no envía ninguno. VerifyClientOnce no tiene nada que hacer aquí. TLS 1.3 no tiene renegociación, y el motor la rechaza en TLS 1.2, así que nunca lo pide una segunda vez.
ALPN funciona como siempre mediante ALPNProtocols en un cliente y mediante la selección de ALPN del servidor, de modo que HTTP/2 sobre el motor nativo negocia h2 igual que lo hace sobre OpenSSL. Consulte ALPN.
Modo QUIC
El motor también ejecuta el protocolo de enlace TLS 1.3 de QUIC y HTTP/3 (RFC 9001). Establezca SSLQUICOptions.IOHandler en TsgcQUICClient, TLSOptions.IOHandler en TsgcHTTP3Client, QUICOptions.SSLOptions.IOHandler en TsgcQUICServer y TsgcHTTP3Server, o SSLOptions.IOHandler en un servidor con HTTP3Options.Enabled, en iohNativeTLS.
El motor QUIC sgc se usa siempre, sea lo que sea lo que exporte OpenSSL. En un servidor, QUICMode informa qmCustom.
El protocolo de enlace transporta los parámetros de transporte QUIC, requiere ALPN e informa las alertas TLS como códigos de error QUIC. El certificado del servidor se lee en PEM o PFX, y el par se verifica frente a RootCertFile o las raíces del sistema, nombre de host incluido.
Los tickets de sesión, la reanudación y el 0-RTT funcionan igual que con OpenSSL. El servidor envía un ticket tras el protocolo de enlace, el cliente lo guarda junto con el ALPN y los parámetros de transporte del servidor, y el 0-RTT se acepta solo una vez por ticket. Los tickets de QUIC nunca cruzan a TLS sobre TCP.
La protección de paquetes también es nativa, así que QUIC y HTTP/3 no cargan OpenSSL en absoluto. No hace falta ninguna definición.
Consulte QUIC para la comparación de motores.
Lo que no hace
El motor es deliberadamente limitado. Todo lo que sigue es un límite real, no un ajuste que haya que buscar.
Solo TLS 1.3 y TLS 1.2. No hay SSL, ni TLS 1.0 ni TLS 1.1. Un cliente genera un error al conectarse, y un servidor al arrancar, si Version es tls1_0 o tls1_1. Un par que no pueda hablar ni TLS 1.3 ni TLS 1.2 no puede conectarse.
Sin PSK sin ticket. La reanudación solo funciona con los tickets de sesión que el propio motor emite, vea Reanudación de sesión más arriba. Las claves precompartidas externas no se admiten.
Sin 0-RTT sobre TCP. En una conexión TLS sobre TCP, los datos anticipados ni se envían ni se aceptan. La autenticación de cliente posterior al protocolo de enlace tampoco está implementada.
QUIC mediante su propio modo. QUIC y HTTP/3 usan el modo QUIC descrito más arriba, configurado en esos componentes.
Sin raíces del sistema de forma predeterminada. A menos que NativeTLS_Options.UseSystemRoots sea True, los certificados raíz de confianza proceden únicamente de RootCertFile y de ningún otro sitio, así que un cliente que verifique certificados tiene que distribuir un archivo de raíces o apuntar a uno. Esa es la única diferencia de comportamiento que una configuración de OpenSSL o SChannel ya en funcionamiento notará con mayor probabilidad.
OnSSLVerifyPeer no se genera. Ese evento pertenece al IOHandler de OpenSSL. El motor nativo tiene sus propios puntos de enganche de verificación, que reciben de una vez toda la cadena del par y el veredicto del motor, en lugar de un certificado por cada nivel de profundidad, así que una aplicación que anule la decisión de confianza tiene que usar esos en lugar del evento del componente.
Nada de lo que envíe el par genera una excepción. Un fallo de protocolo termina la conexión con la alerta TLS que corresponde, y el motivo se informa mediante los eventos de error del componente como de costumbre.
La protección de registros AES-GCM y AES-CBC utiliza las instrucciones AES-NI y PCLMULQDQ en compilaciones Windows hechas con Delphi cuando la CPU las tiene, vea sgcAES_HardwareAvailable en Cifrado Simétrico. Para una compilación en la que ninguna parte de la biblioteca carga OpenSSL, vea Ejecutar sin OpenSSL.