Suportado por
TsgcWebSocketLoadBalancerServer
*Requer o pacote sgcCrypto, que é fornecido a partir da edição Standard.
O mecanismo TLS nativo é uma implementação de TLS 1.3 e TLS 1.2 escrita inteiramente em Object Pascal sobre as units do sgcCrypto. Ele não é um binding: não há DLL do OpenSSL para implantar, nenhuma chamada ao SChannel e nenhuma biblioteca TLS da plataforma envolvida. O mesmo código-fonte compila do Delphi 7 ao RAD Studio 13 e roda em todas as plataformas que os componentes suportam.
O motivo para recorrer a ele é a troca de chaves. O mecanismo oferece os grupos híbridos pós-quânticos da RFC 10024, e X25519MLKEM768 é o primeiro grupo que ele propõe, de modo que um cliente que passe a usá-lo negocia uma troca de chaves pós-quântica contra qualquer servidor que suporte uma, sem nenhuma outra alteração na aplicação. Os grupos híbridos existem apenas no TLS 1.3, portanto uma conexão que recorre ao TLS 1.2 usa uma curva clássica. Veja Criptografia Pós-Quântica para saber o que são esses grupos e por que um híbrido é a migração sensata hoje.
Como habilitá-lo
Defina o IOHandler e nada mais muda. As propriedades de certificado, chave, certificado raiz, verificação e ALPN que você já usa continuam funcionando, o mecanismo lê as mesmas.
// 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;
Em um servidor, o mecanismo também roda sobre os mecanismos orientados a eventos. Defina IOHandlerOptions.IOHandlerType como iohIOCP no Windows ou iohEPOLL no Linux e o handler é construído sobre o Provedor TLS correspondente, de modo que a mesma configuração funciona nos três mecanismos.
Selecionar iohNativeTLS em um build sem o pacote sgcCrypto gera uma exceção que diz exatamente isso, em vez de recorrer silenciosamente a outra biblioteca.
Versões de TLS
O mecanismo fala TLS 1.3 e TLS 1.2, como cliente e como servidor, no cliente Indy, no servidor Indy e nos mecanismos de servidor IOCP e EPOLL. O TLS 1.3 é sempre escolhido quando os dois lados o suportam. No mecanismo nativo, TLSOptions.Version em um cliente e SSLOptions.Version em um servidor é a versão mínima permitida.
| Versão | Negociado |
| tlsUndefined (o padrão) | TLS 1.3 ou TLS 1.2 |
| tls1_2 | TLS 1.3 ou TLS 1.2 |
| tls1_3 | Somente TLS 1.3 |
| tls1_0, tls1_1 | Nada. Um cliente gera um erro de configuração ao se conectar, e um servidor ao iniciar, porque o mecanismo suporta apenas TLS 1.2 e TLS 1.3. |
Isso é diferente de como o handler OpenSSL lê a propriedade. Com iohOpenSSL, tls1_2 significa exatamente TLS 1.2, enquanto com iohNativeTLS significa TLS 1.2 ou posterior. A lista de cipher suites decide o resto. Para obter somente TLS 1.2, mantenha Version em tls1_2 e informe apenas suites de TLS 1.2 em NativeTLS_Options.CipherSuites. Uma lista que informa apenas suites de TLS 1.3 resulta em somente TLS 1.3, e uma lista que informa suites das duas versões permite ambas. Version tls1_3 junto com uma lista de suites de TLS 1.2 não deixa nenhuma versão para negociar, e a conexão gera um erro ao iniciar.
// 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 e cipher suites
TLSOptions.NativeTLS_Options em um cliente, e SSLOptions.NativeTLS_Options em um servidor, carregam as duas listas de preferência. As duas são strings no estilo do OpenSSL, itens separados por dois-pontos, o mais preferido primeiro, e uma única lista atende tanto o TLS 1.3 quanto o TLS 1.2. Deixe uma delas vazia para manter o padrão do mecanismo. Uma lista inválida gera um erro quando a conexão inicia, portanto um erro de digitação não é ignorado silenciosamente.
| Propriedade | Nomes aceitos | Padrão |
| Groups | X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, X25519, secp256r1, secp384r1. Os apelidos P-256, prime256v1 e P-384 também são aceitos, e os nomes são comparados sem diferenciar maiúsculas de minúsculas. Os três grupos híbridos são exclusivos do 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: as suites da tabela abaixo. Tanto os nomes IANA quanto os nomes no estilo OpenSSL, como ECDHE-RSA-AES128-GCM-SHA256, são aceitos, e o mecanismo informa o nome IANA. | TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256, mais os padrões de TLS 1.2 abaixo |
O primeiro grupo da lista é aquele para o qual um cliente envia um key share, portanto deixar X25519MLKEM768 à frente é o que torna o primeiro voo já pós-quântico. Prefixar um item com um ponto de interrogação faz com que ele seja pulado quando este build não o possui, e qualquer outro item desconhecido ou indisponível faz a lista inteira falhar.
// 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 suites do TLS 1.2
Por padrão, somente suites com sigilo direto e AEAD são habilitadas. As suites AES-CBC e a troca de chaves RSA existem para peers que não conseguem algo melhor, e só são oferecidas ou aceitas quando CipherSuites as nomeia.
| 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 | Por padrão, nessa ordem. |
| 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 | Somente quando nomeadas em 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 | Somente quando nomeadas em CipherSuites. A troca de chaves RSA não tem sigilo direto. |
As suites ECDHE de uma conexão TLS 1.2 usam um grupo clássico da lista Groups, X25519, secp256r1 ou secp384r1, portanto mantenha pelo menos um deles na lista quando peers com TLS 1.2 precisarem se conectar. A troca de chaves RSA não usa grupo algum.
// 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';
Em um build feito com SGC_CRYPTO_FIPS, os dois grupos construídos sobre X25519, as suites ChaCha20-Poly1305 e a troca de chaves RSA são removidos da compilação, e os padrões passam a ser SecP256r1MLKEM768:SecP384r1MLKEM1024:secp256r1:secp384r1 e TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256, com as quatro suites ECDHE AES-GCM para TLS 1.2. Veja Modo FIPS.
Comportamento do TLS 1.2
O TLS 1.2 é implementado com as proteções que um peer moderno espera, e sem as partes do protocolo que causaram seus ataques conhecidos.
Extended master secret. A RFC 7627 é oferecida por um cliente e aceita por um servidor. Um peer que não a suporta ainda assim é aceito.
Sem renegociação. Um pedido de renegociação é recusado com um alerta no_renegotiation. A retomada de sessão usa tíquetes de sessão e exige o extended master secret, veja Retomada de sessão abaixo.
Sem assinaturas SHA-1. Seguindo a RFC 9155, assinaturas SHA-1 não são enviadas nem aceitas. Um ClientHello de TLS 1.2 sem a extensão signature_algorithms é recusado, porque significaria apenas SHA-1.
Proteção contra downgrade. A proteção contra downgrade da RFC 8446 seção 4.1.3 funciona nos dois lados. Um servidor que também permite TLS 1.3 marca toda resposta em TLS 1.2, e um cliente que ofereceu TLS 1.3 recusa uma resposta em TLS 1.2 que carregue essa marca.
CBC em tempo constante. Os registros CBC são decifrados em tempo constante, o que fecha o Lucky Thirteen, e a troca de chaves RSA aplica a contramedida da RFC 5246 seção 7.4.7.1.
Key usage do certificado. Para a troca de chaves RSA, o certificado do servidor precisa do key usage keyEncipherment. As suites ECDHE e o TLS 1.3 precisam de digitalSignature.
Recursos exclusivos do TLS 1.3. KeyUpdate e a exportação de keying material existem apenas em uma sessão TLS 1.3.
Retomada de sessão
O mecanismo retoma sessões com tíquetes de sessão sem estado, no TLS 1.3 (RFC 8446) e no TLS 1.2 (RFC 5077). Após um handshake completo, o servidor envia um tíquete, selado com AES-256-GCM sob uma chave que só o servidor possui. O cliente o mantém em um cache de sessão, indexado por host, porta e lista de ALPN, e o oferece na próxima conexão, que então pula a troca de certificados. Um tíquete que o servidor não consegue abrir, ou que expirou, resulta em um handshake completo com um novo tíquete.
| Propriedade | Padrão | Significado |
| NativeTLS_Options.SessionTickets | True | Um servidor emite tíquetes e um cliente os armazena e oferece. False desativa a retomada, e todo handshake passa a ser completo. |
| NativeTLS_Options.TicketLifetime | 7200 | Somente no servidor. Segundos durante os quais um tíquete pode ser usado para retomar, no máximo 604800. A chave de tíquete do servidor gira a cada TicketLifetime segundos, e a chave anterior permanece válida por mais um período. |
As opções são TLSOptions.NativeTLS_Options em um cliente e SSLOptions.NativeTLS_Options em um servidor. O cache de sessão pertence a cada componente cliente, portanto reconexões do mesmo TsgcWebSocketClient, TsgcHTTP1Client, TsgcTCPClient ou TsgcLDAPClient são retomadas, assim como a conexão upstream do TsgcWebSocketProxyServer.
A retomada no TLS 1.2 exige o extended master secret da RFC 7627. Uma sessão TLS 1.2 sem ele nunca é retomada e não recebe tíquete, e nenhum tíquete é emitido também quando o servidor pede um certificado de cliente. Um tíquete de TLS 1.3 nunca é aberto como uma sessão TLS 1.2, e vice-versa.
Se uma conexão foi retomada é informado pela propriedade Resumed do TsgcIdSSLIOHandlerSocketNativeTLS, o handler descrito abaixo.
O que foi negociado
Assim que o handshake termina, o handler do cliente, TsgcIdSSLIOHandlerSocketNativeTLS na unit sgcSSL_NativeTLS_Indy, informa o resultado. VersionName é TLSv1.2 ou TLSv1.3, CipherSuiteName é o nome IANA da suite, GroupName é o grupo da troca de chaves, vazio na troca de chaves RSA, e PeerSignatureSchemeName é o esquema de assinatura usado pelo servidor. O handler é aquele que o cliente passa para 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 e verificação
Um servidor lê CertFile, KeyFile e Password. CertFile e KeyFile podem ser PEM, ou CertFile pode ser um arquivo .pfx ou .p12, reconhecido pela extensão. Um arquivo PKCS#12 contém o certificado, a cadeia e a chave privada, portanto KeyFile não é necessário, e ele é aberto com Password. É lido pelo leitor PKCS#12 em Object Pascal puro do sgcCrypto_PKCS12, sem OpenSSL. Um cliente que define VerifyCertificate como True verifica a cadeia do servidor contra as raízes de confiança descritas abaixo e verifica que o nome corresponde ao host ao qual se conectou. VerifyDepth é repassado, e um valor acima de 16 é limitado a 16.
As raízes de confiança são os certificados em RootCertFile. Definir NativeTLS_Options.UseSystemRoots como True adiciona as autoridades certificadoras em que o sistema operacional confia. O padrão é False. As duas se combinam: as raízes de RootCertFile são carregadas primeiro, as raízes do sistema são adicionadas ao mesmo repositório, e uma cadeia é aceita quando leva a qualquer uma delas. Quando nenhuma raiz do sistema é encontrada, nada é adicionado e nenhum erro é gerado, de modo que a verificação depende apenas de RootCertFile. A propriedade é TLSOptions.NativeTLS_Options.UseSystemRoots em um cliente e SSLOptions.NativeTLS_Options.UseSystemRoots em um servidor, onde as raízes servem apenas para validar certificados de cliente. De onde vêm as raízes do sistema depende da plataforma:
Windows. O repositório de sistema ROOT, as Trusted Root Certification Authorities, lidas por meio da crypt32.dll.
macOS. Os certificados âncora do Security.framework, com /etc/ssl/cert.pem como alternativa.
iOS. As raízes do sistema não podem ser listadas. Quando nenhuma raiz é carregada, uma cadeia que o mecanismo rejeitaria apenas porque sua raiz não é confiável é avaliada pelo Security.framework com a política SSL e o nome do host. As verificações de key usage e de nome do host do mecanismo ainda assim são executadas depois.
Android. Os arquivos de certificado de /apex/com.android.conscrypt/cacerts (Android 14 e posterior) ou /system/etc/security/cacerts, em PEM ou DER. Raízes que o usuário desativou, listadas em /data/misc/user/0/cacerts-removed, são ignoradas, e as raízes de usuário de /data/misc/user/0/cacerts-added são adicionadas quando a aplicação consegue ler essas pastas.
Linux e outras plataformas. O primeiro destes arquivos que existir e contiver ao menos um 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 e /etc/ssl/certs/ca-bundle.crt.
Para carregar você mesmo uma pasta de arquivos de certificado, TsgcTLS13TrustStore.AddDirectory(aDirectory, aRemovedDirectory) na unit sgcSSL_NativeTLS_Certificates lê todo arquivo PEM ou DER de aDirectory, ignora qualquer arquivo cujo nome também exista em aRemovedDirectory, e retorna quantos certificados adicionou. Funciona em todas as plataformas.
Para fixar o nome que o certificado deve carregar em vez de usar o host da conexão, defina TLSOptions.OpenSSL_Options.X509Checks.HostName ou IPAddress, com a flag correspondente em X509Checks.Mode. O mecanismo lê esses dois valores mesmo não sendo OpenSSL, portanto a propriedade significa a mesma coisa qualquer que seja o IOHandler selecionado.
Em um servidor, SSLOptions.VerifyCertificate pede um certificado ao cliente e o valida contra as raízes de confiança descritas acima, e SSLOptions.VerifyCertificate_Options.FailIfNoCertificate recusa um cliente que não envia nenhum. VerifyClientOnce não tem função aqui. O TLS 1.3 não tem renegociação, e o mecanismo a recusa no TLS 1.2, portanto nunca pede uma segunda vez.
O ALPN funciona como de costume por meio de ALPNProtocols em um cliente e por meio da seleção de ALPN do servidor, de modo que HTTP/2 sobre o mecanismo nativo negocia h2 da mesma forma que faz sobre o OpenSSL. Veja ALPN.
Modo QUIC
O mecanismo também executa o handshake TLS 1.3 do QUIC e do HTTP/3 (RFC 9001). Defina SSLQUICOptions.IOHandler em TsgcQUICClient, TLSOptions.IOHandler em TsgcHTTP3Client, QUICOptions.SSLOptions.IOHandler em TsgcQUICServer e TsgcHTTP3Server, ou SSLOptions.IOHandler em um servidor com HTTP3Options.Enabled, como iohNativeTLS.
O mecanismo QUIC sgc é sempre usado, independentemente do que o OpenSSL exporta. Em um servidor, QUICMode informa qmCustom.
O handshake carrega os parâmetros de transporte QUIC, exige ALPN e informa alertas TLS como códigos de erro QUIC. O certificado do servidor é lido em PEM ou PFX, e o peer é verificado contra RootCertFile ou as raízes do sistema, incluindo o nome do host.
Tíquetes de sessão, retomada e 0-RTT funcionam como no OpenSSL. O servidor envia um tíquete após o handshake, o cliente o armazena junto com o ALPN e os parâmetros de transporte do servidor, e o 0-RTT é aceito apenas uma vez por tíquete. Tíquetes QUIC nunca são usados em TLS sobre TCP.
A proteção de pacotes também é nativa, portanto o QUIC e o HTTP/3 não carregam OpenSSL. Nenhuma definição é necessária.
Veja QUIC para a comparação dos mecanismos.
O que ele não faz
O mecanismo é deliberadamente restrito. Tudo o que está abaixo é um limite real, não uma configuração a procurar.
Somente TLS 1.3 e TLS 1.2. Não há SSL, TLS 1.0 nem TLS 1.1. Um cliente gera um erro ao se conectar, e um servidor ao iniciar, se Version for tls1_0 ou tls1_1. Um peer que não consegue nem TLS 1.3 nem TLS 1.2 não consegue se conectar.
Sem PSK sem um tíquete. A retomada só funciona com os tíquetes de sessão que o próprio mecanismo emite, veja Retomada de sessão acima. Chaves pré-compartilhadas externas não são suportadas.
Sem 0-RTT sobre TCP. Em uma conexão TLS sobre TCP, dados antecipados (early data) não são enviados nem aceitos. A autenticação de cliente pós-handshake também não está implementada.
QUIC pelo seu próprio modo. QUIC e HTTP/3 usam o modo QUIC descrito acima, configurado nesses componentes.
Sem raízes do sistema por padrão. A menos que NativeTLS_Options.UseSystemRoots seja True, as raízes de confiança vêm apenas de RootCertFile e de nenhum outro lugar, portanto um cliente que verifica certificados precisa distribuir um arquivo de raízes ou apontar para um. Essa é a única diferença de comportamento que uma configuração OpenSSL ou SChannel em funcionamento tem mais chance de notar.
OnSSLVerifyPeer não é disparado. Esse evento pertence ao handler do OpenSSL. O mecanismo nativo tem seus próprios hooks de verificação, que recebem a cadeia completa do peer e o veredito do mecanismo de uma só vez, em vez de um certificado por profundidade, portanto uma aplicação que sobrescreve a decisão de confiança tem que usar esses hooks e não o evento do componente.
Nada do que o peer envia gera uma exceção. Uma falha de protocolo encerra a conexão com o alerta TLS apropriado, e o motivo é informado pelos eventos de erro do componente, como de costume.
A proteção de registros AES-GCM e AES-CBC usa as instruções AES-NI e PCLMULQDQ em builds do Windows feitos com Delphi quando a CPU as possui, veja sgcAES_HardwareAvailable em Criptografia Simétrica. Para um build em que nenhuma parte da biblioteca carrega o OpenSSL, veja Executar sem OpenSSL.