14.4.5. Verificar um servidor público (câmara como cliente)¶
Tudo o que foi dito na página anterior sobre o cliente «já ter a raiz» é verdade para browsers, telemóveis e PCs – não é verdade para a câmara. O ssl do MicroPython não inclui nenhum repositório de confiança embutido: uma câmara recém-instalada não confia em nenhuma CA, e a predefinição (ssl.CERT_NONE) não verifica nada e fica completamente exposta a ataques man-in-the-middle. Por isso, quando a câmara é o cliente que se liga a um servidor TLS público (uma API HTTPS, um broker MQTT, …) e pretende verificar verdadeiramente esse servidor, tem de fornecer a âncora de confiança você mesmo.
O procedimento é o mesmo que o exemplo de cliente com certificado autoassinado em Certificados autoassinados; a única diferença é que o ficheiro carregado é um certificado de CA real em vez do certificado do próprio par:
Obtenha o certificado de CA que ancora a cadeia do servidor. «Ancora» significa o certificado no topo (ou perto do topo) da cadeia do servidor que escolhe como ponto de partida de confiança. Um servidor TLS envia o seu certificado folha e normalmente os intermédios; nunca envia a raiz. Tem de obter essa âncora de confiança por si próprio e independentemente do servidor – confiar simplesmente no que o servidor apresenta anularia todo o propósito da verificação.
Primeiro, descubra qual é a CA que emitiu o certificado do servidor. Por exemplo, para
openmv.ioopenssl s_client -connect openmv.io:443 -showcerts < /dev/nullO bloco
Certificate chainlista cada certificado com o seu sujeito (s:) e emissor (i:); versões mais recentes do OpenSSL também imprimem linhasa:(tipo de chave) ev:(validade) que podem ser ignoradas aqui:Certificate chain 0 s:CN=openmv.io i:C=US, O=Let's Encrypt, CN=E8 1 s:C=US, O=Let's Encrypt, CN=E8 i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
A entrada 0 é a folha (
openmv.io), emitida pelo intermédioE8. A entrada 1 é esse intermédio, emitido pela raizISRG Root X1. O emissor (i:) da entrada mais acima indica a raiz – neste casoISRG Root X1. (O intermédio éE8em vez deR10/R11que pode ter visto noutros contextos porqueopenmv.iousa um certificado ECDSA; a Let’s Encrypt assina folhas ECDSA com os seus intermédios da sérieEe folhas RSA com os da sérieR. Ambas as cadeias chegam aISRG Root X1.)O OpenSSL também imprime linhas
depth=e pode reportar a raiz comVerification: OK. Isso acontece apenas porque o seu PC já confia emISRG Root X1– o servidor não a enviou (um servidor nunca envia a sua raiz), e a câmara, sem repositório de confiança, também não a terá. É precisamente por isso que tem de a fornecer.Transfira essa raiz a partir das raízes publicadas pela própria CA. A Let’s Encrypt cataloga todas as suas na página de certificados da Let’s Encrypt; o ficheiro direto para ISRG Root X1 é isrgrootx1.pem (também disponível pré-codificado como isrgrootx1.der). Outras CAs publicam as suas numa página semelhante de «certificados raiz» / «repositório»; o conjunto público canónico é o programa Mozilla CA (CCADB). Confirme que transferiu o ficheiro correto comparando a sua impressão digital com o valor publicado pela CA (acrescente
-inform DERse transferiu o.der):openssl x509 -in isrgrootx1.pem -noout -subject -fingerprint -sha256Se preferir não monitorizar uma raiz, pode em alternativa copiar o intermédio diretamente da saída do
-showcerts(o segundo bloco-----BEGIN CERTIFICATE-----), confiar nele e aceitar que terá de o renovar sempre que a CA rodar o intermédio – muito mais frequentemente do que a raiz (ver o compromisso abaixo).Converta para DER, exatamente como antes:
openssl x509 -in isrgrootx1.pem -outform DER -out ca.derCopie
ca.derpara a câmara (sistema de ficheiros ou ROMFS) e carregue-o como âncora de confiança:import socket import ssl import ntptime ntptime.settime() # validity check needs the clock addr = socket.getaddrinfo("api.example.com", 443)[0][-1] sock = socket.socket() sock.connect(addr) ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx.verify_mode = ssl.CERT_REQUIRED ctx.load_verify_locations(cafile="ca.der") ssock = ctx.wrap_socket(sock, server_hostname="api.example.com")
server_hostnameé obrigatório aqui: ativa o SNI e é o nome verificado contra osubjectAltNamedo certificado do servidor.
Dica
Atalho para o caso comum. A Let’s Encrypt é a CA pública mais utilizada, e os seus certificados RSA e ECDSA encadeiam atualmente para ISRG Root X1 (como o exemplo openmv.io acima demonstra). Se os servidores com que a câmara comunica usam Let’s Encrypt, pode saltar a inspeção por completo: basta colocar isrgrootx1.der na câmara e usar load_verify_locations com esse ficheiro.
Isto não faz com que o TLS funcione para todos os sites. Um servidor cujo certificado provém de uma CA diferente (DigiCert, Google Trust Services, Amazon, Sectigo, …) continuará a falhar a verificação e, como a câmara confia num único certificado DER por ssl.SSLContext, não é possível incluir todas as raízes à semelhança de um browser. Em caso de dúvida, identifique a CA real do servidor como mostrado acima e confie nessa raiz.
A escolha do certificado em que confia é um compromisso:
A raiz (recomendada). De longa duração – frequentemente décadas – pelo que
ca.derraramente muda. Exige que o servidor envie o seu intermédio para que o mbedTLS possa construir o caminho folha → intermédio → raiz de confiança; praticamente todos os servidores públicos corretamente configurados fazem isso.O intermédio. Também funciona e continua a funcionar mesmo que um servidor omita o intermédio, mas os intermédios são rodados muito mais frequentemente do que as raízes, pelo que terá de atualizar
ca.dercom maior frequência.A própria folha (fixação de certificado). A opção mais restrita, mas a folha muda em cada renovação – aproximadamente a cada 90 dias para a Let’s Encrypt – pelo que só faz sentido quando controla também o servidor e pode distribuir o novo pin a todas as câmaras em simultâneo. É exatamente o que o exemplo de cliente com certificado autoassinado faz.
Nota
ssl.SSLContext.load_verify_locations() aceita um único certificado de CA codificado em DER, pelo que a câmara confia em exatamente uma âncora de cada vez. Para aceder a servidores sob CAs diferentes, utilize um ssl.SSLContext separado por âncora. E como esse certificado acabará por expirar ou ser rodado pela CA, trate-o como qualquer outro certificado no dispositivo.