Supportato da
TsgcWebSocketLoadBalancerServer
*Richiede il pack sgcCrypto, disponibile a partire dall'edizione Standard.
Il motore TLS nativo è un'implementazione di TLS 1.3 e TLS 1.2 scritta interamente in Object Pascal sopra le unit sgcCrypto. Non è un binding: non c'è alcuna DLL OpenSSL da distribuire, nessuna chiamata a SChannel e nessuna libreria TLS di piattaforma coinvolta. Lo stesso sorgente compila da Delphi 7 a RAD Studio 13 e gira su tutte le piattaforme supportate dai componenti.
Il motivo per adottarlo è lo scambio di chiavi. Il motore offre i gruppi ibridi post-quantistici della RFC 10024, e X25519MLKEM768 è il primo gruppo che propone, quindi un client che passa a questo motore negozia uno scambio di chiavi post-quantistico con qualsiasi server che ne supporti uno, senza altre modifiche all'applicazione. I gruppi ibridi esistono solo in TLS 1.3, quindi una connessione che ripiega su TLS 1.2 utilizza una curva classica. Vedere Crittografia post-quantistica per capire che cosa sono quei gruppi e perché un ibrido è oggi la migrazione sensata.
Come attivarlo
Impostare l'IOHandler e nient'altro cambia. Le proprietà di certificato, chiave, certificato radice, verifica e ALPN già in uso continuano a funzionare, il motore legge le stesse.
// 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;
Su un server il motore funziona anche con gli engine a eventi. Impostare IOHandlerOptions.IOHandlerType a iohIOCP su Windows o a iohEPOLL su Linux e l'handler viene costruito sul provider TLS corrispondente, così la stessa configurazione funziona su tutti e tre gli engine.
Selezionare iohNativeTLS in una build senza il pack sgcCrypto genera un'eccezione che lo dichiara, anziché ripiegare silenziosamente su un'altra libreria.
Versioni TLS
Il motore parla TLS 1.3 e TLS 1.2, come client e come server, sul client Indy, sul server Indy e sugli engine server IOCP ed EPOLL. TLS 1.3 viene sempre scelto quando entrambe le parti lo supportano. Per il motore nativo, TLSOptions.Version su un client e SSLOptions.Version su un server è la versione minima consentita.
| Versione | Negozia |
| tlsUndefined (il valore predefinito) | 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 | Niente. Un client genera un errore di configurazione quando si connette e un server quando si avvia, perché il motore supporta solo TLS 1.2 e TLS 1.3. |
Non è così che l'handler OpenSSL legge questa proprietà. Con iohOpenSSL, tls1_2 significa esattamente TLS 1.2, mentre con iohNativeTLS significa TLS 1.2 o successivo. A decidere il resto è la lista delle cipher suite. Per ottenere solo TLS 1.2, mantenere Version a tls1_2 e indicare in NativeTLS_Options.CipherSuites solo suite di TLS 1.2. Una lista che indica solo suite di TLS 1.3 dà solo TLS 1.3, e una lista che indica suite di entrambe le versioni permette entrambe. Version a tls1_3 insieme a una lista di suite di TLS 1.2 non lascia alcuna versione da negoziare, e la connessione genera un'eccezione all'avvio.
// 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';
Gruppi e cipher suite
TLSOptions.NativeTLS_Options su un client, e SSLOptions.NativeTLS_Options su un server, contengono le due liste di preferenza. Entrambe sono stringhe nello stile OpenSSL, con gli elementi separati da due punti e il più preferito per primo. Lasciarne una vuota per mantenere il valore predefinito del motore. Una lista non valida genera un'eccezione all'avvio della connessione, quindi un errore di battitura non viene ignorato in silenzio.
| Proprietà | Nomi accettati | Predefinito |
| Groups | X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, X25519, secp256r1, secp384r1. Sono accettati anche gli alias P-256, prime256v1 e P-384, e i nomi vengono confrontati senza distinzione fra maiuscole e minuscole. I tre gruppi ibridi sono esclusivi di 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: le suite della tabella seguente. Sono accettati sia i nomi IANA sia i nomi in stile OpenSSL come ECDHE-RSA-AES128-GCM-SHA256, e il motore riporta il nome IANA. | TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256, più i valori predefiniti di TLS 1.2 indicati sotto |
Il primo gruppo della lista è quello per cui un client invia una key share, quindi lasciare X25519MLKEM768 in testa è ciò che rende già post-quantistico il primo flight. Anteporre un punto interrogativo a un elemento lo salta quando questa build non lo contiene, mentre qualsiasi altro elemento sconosciuto o non disponibile fa fallire l'intera lista.
// 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';
Cipher suite di TLS 1.2
Per impostazione predefinita sono abilitate solo le suite AEAD con forward secrecy. Le suite AES-CBC e lo scambio di chiavi RSA esistono per i peer che non possono fare di meglio, e vengono proposte o accettate solo quando CipherSuites le indica.
| Suite | Abilitate |
| 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 | Per impostazione predefinita, in questo ordine. |
| 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 quando indicate in 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 quando indicate in CipherSuites. Lo scambio di chiavi RSA non ha forward secrecy. |
Le suite ECDHE di una connessione TLS 1.2 utilizzano un gruppo classico della lista Groups, X25519, secp256r1 o secp384r1, quindi mantenerne almeno uno nella lista quando devono potersi connettere peer con TLS 1.2. Lo scambio di chiavi RSA non utilizza alcun gruppo.
// 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 una build creata con SGC_CRYPTO_FIPS i due gruppi costruiti su X25519, le suite ChaCha20-Poly1305 e lo scambio di chiavi RSA vengono esclusi dalla compilazione, e i valori predefiniti diventano SecP256r1MLKEM768:SecP384r1MLKEM1024:secp256r1:secp384r1 e TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256, con le quattro suite ECDHE AES-GCM per TLS 1.2. Vedere Modalità FIPS.
Comportamento di TLS 1.2
TLS 1.2 è implementato con le protezioni che un peer moderno si aspetta, e senza le parti del protocollo che hanno causato i suoi attacchi noti.
Extended master secret. La RFC 7627 viene offerta da un client e accettata da un server. Un peer che non la supporta viene comunque accettato.
Nessuna rinegoziazione. Una richiesta di rinegoziazione viene rifiutata con un alert no_renegotiation. La ripresa di sessione utilizza i session ticket e richiede l'extended master secret, vedere Ripresa di sessione più sotto.
Nessuna firma SHA-1. Seguendo la RFC 9155, le firme SHA-1 non vengono né inviate né accettate. Un ClientHello di TLS 1.2 privo dell'estensione signature_algorithms viene rifiutato, perché significherebbe solo SHA-1.
Protezione dal downgrade. La protezione dal downgrade della RFC 8446 sezione 4.1.3 funziona su entrambi i lati. Un server che ammette anche TLS 1.3 marca ogni risposta in TLS 1.2, e un client che ha proposto TLS 1.3 rifiuta una risposta in TLS 1.2 che porta quel marchio.
CBC a tempo costante. I record CBC vengono decifrati a tempo costante, il che chiude Lucky Thirteen, e lo scambio di chiavi RSA applica la contromisura della RFC 5246 sezione 7.4.7.1.
Key usage del certificato. Per lo scambio di chiavi RSA il certificato del server ha bisogno del key usage keyEncipherment. Le suite ECDHE e TLS 1.3 hanno bisogno di digitalSignature.
Funzionalità esclusive di TLS 1.3. KeyUpdate e l'esportazione del keying material esistono solo su una sessione TLS 1.3.
Ripresa di sessione
Il motore riprende le sessioni con session ticket stateless, in TLS 1.3 (RFC 8446) e in TLS 1.2 (RFC 5077). Dopo un handshake completo il server invia un ticket, sigillato con AES-256-GCM sotto una chiave che solo il server possiede. Il client lo conserva in una cache di sessione, indicizzata per host, porta ed elenco ALPN, e lo propone alla connessione successiva, che così salta lo scambio del certificato. Un ticket che il server non riesce ad aprire, o uno scaduto, produce un handshake completo con un nuovo ticket.
| Proprietà | Predefinito | Significato |
| NativeTLS_Options.SessionTickets | True | Un server emette i ticket e un client li memorizza e li propone. False disattiva la ripresa, ogni handshake diventa allora completo. |
| NativeTLS_Options.TicketLifetime | 7200 | Solo server. Secondi durante i quali un ticket può essere usato per la ripresa, al massimo 604800. La chiave del ticket del server ruota ogni TicketLifetime secondi, e la chiave precedente resta valida per un altro periodo. |
Le opzioni sono TLSOptions.NativeTLS_Options su un client e SSLOptions.NativeTLS_Options su un server. La cache di sessione appartiene a ciascun componente client, quindi le riconnessioni dello stesso TsgcWebSocketClient, TsgcHTTP1Client, TsgcTCPClient o TsgcLDAPClient riprendono, e così pure la connessione upstream di TsgcWebSocketProxyServer.
La ripresa in TLS 1.2 richiede l'extended master secret della RFC 7627. Una sessione TLS 1.2 priva di questo non viene mai ripresa e non riceve alcun ticket, e nessun ticket viene emesso nemmeno quando il server chiede un certificato client. Un ticket TLS 1.3 non si apre mai come sessione TLS 1.2, e viceversa.
Se una connessione è stata ripresa viene riportato dalla proprietà Resumed di TsgcIdSSLIOHandlerSocketNativeTLS, l'handler descritto più sotto.
Che cosa è stato negoziato
Una volta completato l'handshake, l'handler del client, TsgcIdSSLIOHandlerSocketNativeTLS nell'unit sgcSSL_NativeTLS_Indy, riporta il risultato. VersionName è TLSv1.2 o TLSv1.3, CipherSuiteName è il nome IANA della suite, GroupName è il gruppo dello scambio di chiavi, vuoto per lo scambio di chiavi RSA, e PeerSignatureSchemeName è lo schema di firma utilizzato dal server. L'handler è quello che il client passa 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;
Certificati e verifica
Un server legge CertFile, KeyFile e Password. CertFile e KeyFile possono essere in formato PEM, oppure CertFile può essere un file .pfx o .p12, riconosciuto dall'estensione. Un file PKCS#12 contiene il certificato, la catena e la chiave privata, quindi KeyFile non serve, e viene aperto con Password. Viene letto dal lettore PKCS#12 in Pascal puro di sgcCrypto_PKCS12, senza OpenSSL. Un client che imposta VerifyCertificate a True verifica la catena del server rispetto alle radici di fiducia descritte più sotto e verifica che il nome corrisponda all'host a cui si è connesso. VerifyDepth viene applicato, e un valore superiore a 16 viene limitato a 16.
Le radici di fiducia sono i certificati presenti in RootCertFile. Impostare NativeTLS_Options.UseSystemRoots a True aggiunge le autorità di certificazione di cui si fida il sistema operativo. Il valore predefinito è False. Le due si combinano: le radici di RootCertFile vengono caricate per prime, le radici di sistema vengono aggiunte allo stesso archivio, e una catena viene accettata quando porta a una qualsiasi di esse. Quando non viene trovata alcuna radice di sistema, non viene aggiunto nulla e non viene generato alcun errore, quindi la verifica si basa unicamente su RootCertFile. La proprietà è TLSOptions.NativeTLS_Options.UseSystemRoots su un client e SSLOptions.NativeTLS_Options.UseSystemRoots su un server, dove le radici servono solo a validare i certificati client. Da dove provengono le radici di sistema dipende dalla piattaforma:
Windows. L'archivio di sistema ROOT, le Trusted Root Certification Authorities, lette tramite crypt32.dll.
macOS. I certificati di ancoraggio di Security.framework, con /etc/ssl/cert.pem come ripiego.
iOS. Le radici di sistema non possono essere elencate. Quando non viene caricata alcuna radice, una catena che il motore rifiuterebbe solo perché la sua radice non è attendibile viene valutata da Security.framework con la policy SSL e il nome host. I controlli sul key usage e sul nome host del motore vengono comunque eseguiti in seguito.
Android. I file di certificati di /apex/com.android.conscrypt/cacerts (Android 14 e successivi) o /system/etc/security/cacerts, in PEM o DER. Le radici disabilitate dall'utente, elencate in /data/misc/user/0/cacerts-removed, vengono saltate, e le radici utente di /data/misc/user/0/cacerts-added vengono aggiunte quando l'applicazione può leggere quelle cartelle.
Linux e altre piattaforme. Il primo di questi file che esiste e contiene almeno un certificato: /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 e /etc/ssl/certs/ca-bundle.crt.
Per caricare autonomamente una cartella di file di certificati, TsgcTLS13TrustStore.AddDirectory(aDirectory, aRemovedDirectory) nell'unit sgcSSL_NativeTLS_Certificates legge ogni file PEM o DER di aDirectory, salta ogni file il cui nome esiste anche in aRemovedDirectory, e restituisce quanti certificati ha aggiunto. Funziona su ogni piattaforma.
Per fissare il nome che il certificato deve riportare anziché utilizzare l'host della connessione, impostare TLSOptions.OpenSSL_Options.X509Checks.HostName o IPAddress, con il flag corrispondente in X509Checks.Mode. Il motore legge quei due valori anche se non è OpenSSL, quindi la proprietà significa la stessa cosa qualunque sia l'IOHandler selezionato.
Su un server, SSLOptions.VerifyCertificate chiede un certificato al client e lo valida rispetto alle radici di fiducia descritte sopra, e SSLOptions.VerifyCertificate_Options.FailIfNoCertificate rifiuta un client che non ne invia alcuno. VerifyClientOnce non ha nulla da fare qui. TLS 1.3 non prevede la rinegoziazione e il motore la rifiuta in TLS 1.2, quindi non chiede mai una seconda volta.
ALPN funziona come sempre tramite ALPNProtocols su un client e tramite la selezione ALPN del server, quindi HTTP/2 sul motore nativo negozia h2 esattamente come su OpenSSL. Vedere ALPN.
Modalità QUIC
Il motore esegue anche l'handshake TLS 1.3 di QUIC e HTTP/3 (RFC 9001). Impostare SSLQUICOptions.IOHandler su TsgcQUICClient, TLSOptions.IOHandler su TsgcHTTP3Client, QUICOptions.SSLOptions.IOHandler su TsgcQUICServer e TsgcHTTP3Server, oppure SSLOptions.IOHandler su un server con HTTP3Options.Enabled, a iohNativeTLS.
Il motore QUIC sgc viene sempre utilizzato, qualunque cosa esporti OpenSSL. Su un server QUICMode riporta qmCustom.
L'handshake trasporta i parametri di trasporto QUIC, richiede ALPN e riporta gli alert TLS come codici di errore QUIC. Il certificato del server viene letto da PEM o PFX, e il peer viene verificato rispetto a RootCertFile o alle radici di sistema, nome host incluso.
I session ticket, la ripresa e lo 0-RTT funzionano come su OpenSSL. Il server invia un ticket dopo l'handshake, il client lo memorizza con l'ALPN e i parametri di trasporto del server, e lo 0-RTT viene accettato una sola volta per ticket. I ticket QUIC non passano mai a TLS su TCP.
Anche la protezione dei pacchetti è nativa, quindi QUIC e HTTP/3 non caricano OpenSSL. Non è necessaria alcuna direttiva.
Vedere QUIC per il confronto tra motori.
Che cosa non fa
Il motore è volutamente ristretto. Tutto ciò che segue è un limite reale, non un'impostazione da cercare.
Solo TLS 1.3 e TLS 1.2. Non esiste SSL, non esiste TLS 1.0 e non esiste TLS 1.1. Un client genera un'eccezione quando si connette, e un server quando si avvia, se Version è tls1_0 o tls1_1. Un peer che non sa fare né TLS 1.3 né TLS 1.2 non può connettersi.
Nessuna PSK senza ticket. Il motore riprende le sessioni solo con i session ticket che emette lui stesso, vedere Ripresa di sessione più sopra. Le chiavi pre-condivise esterne non sono supportate.
Nessuno 0-RTT su TCP. Su una connessione TLS su TCP i dati early non vengono né inviati né accettati. Nemmeno l'autenticazione del client dopo l'handshake è implementata.
QUIC tramite la propria modalità. QUIC e HTTP/3 utilizzano la modalità QUIC descritta sopra, configurata su quei componenti.
Nessuna radice di sistema per impostazione predefinita. A meno che NativeTLS_Options.UseSystemRoots non sia True, le radici di fiducia arrivano da RootCertFile e da nessun'altra parte, quindi un client che verifica i certificati deve distribuire un file di radici o puntare a uno. È l'unica differenza di comportamento che una configurazione OpenSSL o SChannel funzionante ha più probabilità di notare.
OnSSLVerifyPeer non viene generato. Quell'evento appartiene all'handler OpenSSL. Il motore nativo ha i propri hook di verifica, che ricevono in un'unica volta l'intera catena del peer e il verdetto del motore invece di un certificato per ogni livello, quindi un'applicazione che sovrascrive la decisione di fiducia deve utilizzare quelli anziché l'evento del componente.
Nulla di ciò che il peer invia genera un'eccezione. Un errore di protocollo chiude la connessione con l'alert TLS appropriato, e il motivo viene riportato come sempre attraverso gli eventi di errore del componente.
La protezione dei record AES-GCM e AES-CBC utilizza le istruzioni AES-NI e PCLMULQDQ sulle build Windows create con Delphi quando la CPU le supporta, vedere sgcAES_HardwareAvailable in Crittografia simmetrica. Per una build in cui nessuna parte della libreria carica OpenSSL, vedere Eseguire senza OpenSSL.