Skip to content
Mobius One edited this page Sep 8, 2026 · 2 revisions

Home > Configurar HTTPS no RAL > Como funciona o HTTPS no cliente RAL

⚠ Versão 1.1 ou superior do PascalRAL

Certificado do servidor no cliente (TLS)

O TRALClient decide sozinho com qual certificado de servidor ele aceita conversar. Essa decisão fica no cliente, nunca no engine, então ela se escreve e se comporta igual, seja qual for o EngineType.

São quatro membros, e é raro precisar de mais de um:

Membro Para que serve
SSL.Pins Quais certificados são aceitos, e de qual host
SSL.Required Recusa http puro, diga o que disser a URL
SSL.Verify O que o próprio engine faz com o certificado
OnValidateServerCert Decidir em código, quando a lista não expressa a regra

Nada disso muda coisa alguma enquanto você não mexer: um cliente que não define nenhum deles se comporta exatamente como sempre se comportou.


Por que você ia querer isso

Certificado assinado por CA pública não precisa de configuração — o sistema operacional já confia nele, e o RAL deixa o engine validar como sempre.

O problema é todo o resto:

  • um servidor interno com certificado autoassinado, acessado por IP;
  • um servidor cujo IP muda, e então nenhum certificado consegue nomeá-lo de antemão;
  • uma aplicação que fala com vários servidores ao mesmo tempo, uns com certificado de CA pública e outros autoassinados.

A saída de sempre — instalar uma CA própria em cada máquina — não funciona em todo lugar: desde o Android 7, um app não confia em CA que o usuário instalou. Ou seja, a confiança precisa ficar na aplicação, não no aparelho.

É isso que o SSL.Pins faz: você informa ao cliente o SHA-256 do certificado que espera, e ele passa a aceitar aquele e recusar todos os outros, sem instalar nada em lugar nenhum.


Obtendo a impressão digital

A partir do arquivo do certificado:

openssl x509 -in servidor.crt -noout -fingerprint -sha256
sha256 Fingerprint=65:42:DB:04:A4:77:8A:33:BD:BD:F9:EC:5C:95:37:93:D4:F7:35:72:EA:80:4C:14:5F:BB:55:64:17:35:29:0A

A partir de um servidor no ar, sem ter o arquivo:

openssl s_client -connect 192.168.0.11:9988 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256

Cole na notação que vier — com dois-pontos, com espaços, em minúsculas. O RAL normaliza. O que ele não aceita é o que não for um SHA-256: valor errado ou truncado levanta erro na hora em que a linha é adicionada, em vez de virar uma recusa misteriosa em produção.


Receita 1 — um servidor com certificado autoassinado

RALClient1.BaseURL.Text := 'https://192.168.0.11:9988';
RALClient1.SSL.Pins.Add('6542DB04A4778A33BDBDF9EC5C953793D4F73572EA804C145FBB55641735290A');

É toda a configuração. A partir daí:

  • a loja de certificados da máquina deixa de importar — não precisa instalar nada, nem no Windows nem no Android;
  • somente aquele certificado é aceito. Até um certificado em que a máquina confia é recusado, se não for este;
  • CN e SAN deixam de ser conferidos, então o IP do servidor não precisa estar no certificado, e trocar esse IP não quebra o cliente;
  • http puro passa a ser recusado para esse host — pin sobre texto claro não estaria conferindo nada.

Receita 2 — vários servidores, uns com CA e outros autoassinados

É por isso que Pins é lista e não um valor só. Cada linha diz onde aquele certificado é aceito:

RALClient1.SSL.Pins.Text :=
  '192.168.0.11=CD34EF...'#13#10 +      // autoassinado, so' nesse host
  '10.0.0.7:8443=90FFEE...'#13#10 +     // autoassinado, nesse host e porta
  '[2001:db8::1]:9988=1234AB...';       // IPv6 vai entre colchetes

e a regra, aplicada por conexão:

Situação O que acontece
existe linha para o host chamado só aqueles certificados são aceitos, mesmo contra a loja do sistema
não existe linha para aquele host o certificado é validado normalmente

Ou seja: os servidores com certificado de CA pública simplesmente não entram na lista. Continuam sendo validados pela cadeia, e nada quebra quando eles renovarem — que é o ponto, já que certificado público é trocado de poucos em poucos meses.

Uma linha sem = vale para qualquer host:

AB12CD...          aceito de qualquer host com que este cliente falar

Formatos de linha

Forma Significado
impressao_digital qualquer host
host=impressao_digital aquele host, qualquer porta
host:porta=impressao_digital aquele host naquela porta
[ipv6]:porta=impressao_digital IPv6 com porta — colchetes obrigatórios
ipv6=impressao_digital IPv6 sem porta, qualquer porta

O lugar e o hash são separados por = e não por : porque os dois lados são cheios de dois-pontos: AB:CD:EF:… na impressão digital, do jeito que o openssl imprime, e [::1]:8443 num endereço IPv6. O RALSplitHostPort é a única função que interpreta o lado esquerdo, e ela é pública — então o que escreve essa configuração consegue lê-la de volta do mesmo jeito.


Receita 3 — trocar o certificado sem janela de indisponibilidade

Várias linhas para o mesmo host valem todas:

RALClient1.SSL.Pins.Add('192.168.0.11=' + PIN_ATUAL);
RALClient1.SSL.Pins.Add('192.168.0.11=' + PIN_NOVO);

Distribua o cliente com as duas, troque o certificado do servidor quando quiser e remova a linha antiga na versão seguinte. Em nenhum momento o cliente fica sem conseguir conectar.


Receita 4 — decidir em código

Quando a regra é algo que a lista não expressa — validade, emissor, política por rota — atribua o evento. Ele é a última palavra: com um handler atribuído, nem o pin nem o veredito do engine decidem.

function TForm1.ValidaCert(ASender: TObject;
  const ACert: TRALCertInfo): boolean;
begin
  if ACert.Host = '192.168.0.11' then
    Result := ACert.Fingerprint = PIN_INTERNO
  else
    Result := ACert.Trusted;          // o resto: validacao normal
end;

RALClient1.OnValidateServerCert := ValidaCert;

O que chega no handler:

Campo
Fingerprint SHA-256, hexadecimal maiúsculo, sem separadores. Vazio quando o engine não consegue produzir
Subject, Issuer, SerialNumber como o engine reportou
NotBefore, NotAfter validade
Trusted o que a validação do próprio engine concluiu (cadeia, host, datas)
Error por que não, quando Trusted é False
Host, Port quem estava sendo chamado — vem da URL, não do certificado

Host e Port são o que permitem um handler só atender vários servidores. Eles vêm da entrada de BaseURL que aquela tentativa usou, então batem com o que você escreveu lá.

Devolver False recusa a conexão antes de sair um único byte da requisição — o token inclusive.


SSL.Verify — o que o engine faz por conta própria

Os engines nunca concordaram nesse ponto, e até agora não havia como dizer o que você queria:

TRALSSLVerify = (svEngine, svAlways, svNever);
Valor Significado
svEngine (padrão) o que cada engine sempre fez — não é a mesma coisa em todos, ver a tabela adiante
svAlways liga a validação onde ela está desligada
svNever aceita qualquer certificado, em todos os engines

Ele só decide quando não há pin para aquele host nem evento; esses dois, quando definidos, são a decisão.

svNever é para desenvolvimento contra um certificado descartável. Não vá para produção com ele: aceita qualquer certificado de qualquer um, que é exatamente o que o TLS existe para evitar.

svAlways tem uma pegadinha no Windows: o OpenSSL que o Indy e o fpHTTP carregam não tem loja de certificados lá, então ligar a validação faz os dois recusarem tudo, inclusive CA pública, até você fornecer um pacote de CAs ao OpenSSL. No Linux, onde os caminhos padrão encontram /etc/ssl/certs, ele se comporta como se espera.


O que cada engine consegue fazer

Engine Valida por padrão SSL.Pins OnValidateServerCert
Indy não sim sim
fpHTTP não sim sim
mORMot2 com OpenSSL sim sim sim
mORMot2 no SChannel sim não não
netHTTP sim não sim¹

¹ no Windows esse engine chama o handler apenas quando a validação dele passou — ou seja, é um direito de veto sobre um certificado bom, não uma chance de salvar um ruim.

Três coisas que vale saber:

  • Indy e fpHTTP não validam certificado nenhum a menos que um pin, um evento ou svAlways peçam. É o comportamento histórico dos dois e foi mantido de propósito: ligar para todo mundo quebraria HTTPS comum no Windows, onde o OpenSSL deles não tem loja. Se você usa um dos dois sobre https e não definiu nenhum dos três, você não está autenticando o servidor.
  • O netHTTP nunca vai pinar, em plataforma nenhuma: o TCertificate da RTL não carrega impressão digital (no Android, nem a chave pública). Comparar por Subject ou SerialNumber pareceria pin e não seria — são textos copiáveis. Por isso o RAL recusa a requisição em vez de fingir.
  • O mORMot2 no Windows usa SChannel a menos que a aplicação carregue o OpenSSL, e o SChannel nunca chama os callbacks de TLS. Carregue-o (OpenSslIsAvailable, de mormot.lib.openssl11, com as DLLs do OpenSSL ao lado do executável) e o pin funciona normalmente.

Para pinar em Delphi e FPC, no Windows e no Android, com uma configuração só: use o engine Indy. Ele precisa dos binários do OpenSSL ao lado da aplicação (libeay32/ssleay32 para o Indy que vem com o Delphi).


Quando o RAL recusa, e como saber por quê

Certificado recusado é reportado como um erro de transporte próprio, em todos os engines, então você nunca precisa comparar texto de mensagem:

procedure TForm1.Resposta(ASender: TObject; AResponse: TRALResponse;
  AException: StringRAL);
begin
  if AResponse.TransportError = rteCertificate then
    Mostrar('O servidor apresentou um certificado que não é o esperado.')
  else if AResponse.TransportError = rteConnect then
    Mostrar('Servidor fora do ar.');
end;

rteCertificate nunca dispara nova tentativa na próxima BaseURL — certificado que não confere não é coisa para tentar de novo em outro lugar por acidente.

O TransportError é legível nas sobrecargas de callback (Get/Post(rota, handler, comportamento)), que entregam a resposta mesmo quando a chamada falha. As sobrecargas var AResponse levantam exceção, e a mensagem carrega o mesmo motivo.

Mensagens que você pode ver:

Mensagem Significado
Certificado do servidor recusado o pin ou o seu handler disse não
SSL.Pins não é suportado pelo engine … nesta plataforma aquele engine não consegue ler impressão digital — ver a tabela
O engine … não conseguiu inspecionar o certificado do servidor nesta conexão o mORMot2 caiu no SChannel, que não pergunta
Toda linha de SSL.Pins deve terminar no SHA-256 do certificado linha malformada, avisada no momento em que foi adicionada
SSL.Required está definido, ou uma linha de SSL.Pins vale para este host, então http puro é recusado você pediu TLS e a URL é http

Notas de segurança

O que o pin garante. O cliente aceita exatamente o certificado que você nomeou e nada mais — nem outro autoassinado, nem um de CA em que a máquina confia, nem um emitido para o mesmo host com outra chave. É mais forte que a validação por CA, não mais fraco.

Onde o pin deve morar. Na aplicação, não em arquivo de configuração editável. Um pin que alguém pode editar permite apontar o cliente para outro servidor mudando uma linha — que é justamente o que o pin existe para impedir. Deixe o hash em constante ou recurso que viaja com o binário; host e porta podem ficar na configuração, porque mudá-los não enfraquece nada: host sem linha simplesmente cai na validação normal.

Rotação. Um certificado pinado precisa ser trocado no cliente antes de ser trocado no servidor, ou durante uma janela em que os dois são aceitos — ver a Receita 3.

O que o pin não faz. Não cifra nada por si só, não protege uma conexão feita por http puro (para isso existe o SSL.Required) e não diz nada sobre o que o servidor faz com os dados depois que eles chegam.

Clone this wiki locally