14.4.5. Verificando um servidor público (câmera como cliente)¶
Tudo o que foi dito na página anterior sobre um cliente “já possuir a raiz” é verdade para navegadores, telefones e PCs – mas não é verdade para a câmera. O módulo ssl do MicroPython não vem com nenhum armazenamento de confiança embutido: uma câmera recém-gravada não confia em nenhuma CA, e o padrão (ssl.CERT_NONE) não verifica nada e fica totalmente exposto a um ataque man-in-the-middle. Portanto, quando a câmera é o cliente que se conecta a um servidor TLS público (uma API HTTPS, um broker MQTT, …) e você quer que ela realmente verifique esse servidor, você mesmo precisa fornecer a âncora de confiança.
A mecânica é a mesma do exemplo de cliente autoassinado em Certificados autoassinados; a única diferença é que o arquivo que você carrega é um certificado de CA real em vez do próprio certificado do par:
Obtenha o certificado de CA que ancora a cadeia do servidor. “Ancorar” significa o certificado no (ou perto do) topo da cadeia do servidor que você escolhe como seu ponto de partida da confiança. Um servidor TLS envia seu certificado folha e geralmente seu(s) intermediário(s); ele nunca envia sua raiz. Você mesmo precisa obter essa âncora de confiança e independentemente do servidor – simplesmente confiar no que quer que um servidor lhe entregue anularia todo o propósito da verificação.
Primeiro descubra qual CA realmente emitiu o certificado do servidor. Por exemplo, contra
openmv.ioopenssl s_client -connect openmv.io:443 -showcerts < /dev/nullO bloco
Certificate chainlista cada certificado com seu sujeito (s:) e emissor (i:); o OpenSSL mais recente também imprime linhasa:(tipo de chave) ev:(validade) que você pode ignorar 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 intermediárioE8. A entrada 1 é esse intermediário, emitido pela raizISRG Root X1. O emissor (i:) da entrada mais ao topo nomeia a raiz – aquiISRG Root X1. (O intermediário éE8em vez doR10/R11que você pode ter visto em outros lugares porqueopenmv.iousa um certificado ECDSA; a Let’s Encrypt assina folhas ECDSA com seus intermediários da sérieEe folhas RSA com os da sérieR. Ambos encadeiam atéISRG Root X1.)O OpenSSL também imprime linhas
depth=e pode relatar 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 sua raiz), e a câmera, não tendo armazenamento de confiança, também não a terá. É exatamente por isso que você precisa fornecê-la.Baixe 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 arquivo direto para a ISRG Root X1 é isrgrootx1.pem (eles também a oferecem pré-codificada como isrgrootx1.der). Outras CAs publicam as suas em uma página similar de “root certificates” / “repository”; o conjunto público canônico é o programa de CAs da Mozilla (CCADB). Confirme que você baixou o arquivo correto comparando sua impressão digital com o valor que a CA publica (adicione
-inform DERse você baixou o.der):openssl x509 -in isrgrootx1.pem -noout -subject -fingerprint -sha256Se você preferir não acompanhar uma raiz, pode em vez disso copiar o intermediário diretamente da saída de
-showcerts(o segundo bloco-----BEGIN CERTIFICATE-----), confiar nele e aceitar que precisará atualizá-lo sempre que a CA rotacionar o intermediário – muito mais frequentemente do que a raiz (veja o trade-off abaixo).Converta-o para DER, exatamente como antes:
openssl x509 -in isrgrootx1.pem -outform DER -out ca.derCopie
ca.derpara a câmera (sistema de arquivos ou ROMFS) e carregue-o como a â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: ele orienta 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 amplamente utilizada, e tanto seus certificados RSA quanto ECDSA encadeiam atualmente até a ISRG Root X1 (como mostra o exemplo de openmv.io acima). Se os servidores com os quais sua câmera conversa usam a Let’s Encrypt, você pode pular a inspeção inteiramente: basta colocar isrgrootx1.der na câmera e fazer load_verify_locations dele.
Isso não faz o TLS funcionar com todos os sites. Um servidor cujo certificado venha de uma CA diferente (DigiCert, Google Trust Services, Amazon, Sectigo, …) ainda falhará na verificação, e como a câmera confia em um único certificado DER por ssl.SSLContext, você não pode agrupar todas as raízes da forma que um navegador faz. Em caso de dúvida, identifique a CA real do servidor conforme mostrado acima e confie nessa raiz.
Em qual certificado você confia é um trade-off:
A raiz (recomendado). Longa duração – frequentemente décadas – então
ca.derraramente muda. Ela exige que o servidor envie seu intermediário para que o mbedTLS possa construir o caminho folha → intermediário → sua raiz confiável; praticamente todo servidor público corretamente configurado faz isso.O intermediário. Também funciona, e continua funcionando mesmo que um servidor omita o intermediário, mas os intermediários são rotacionados com muito mais frequência do que as raízes, então você terá que atualizar
ca.dercom mais frequência.A própria folha (certificate pinning). O mais restrito, mas a folha muda a cada renovação – aproximadamente a cada 90 dias para a Let’s Encrypt – então isso só faz sentido quando você também controla o servidor e pode enviar o novo pin para todas as câmeras em sincronia. É exatamente isso que o exemplo de cliente autoassinado faz.
Nota
ssl.SSLContext.load_verify_locations() aceita um único certificado de CA codificado em DER, então a câmera confia em exatamente uma âncora por vez. Para alcançar servidores sob CAs diferentes, use um ssl.SSLContext separado por âncora. E como esse próprio certificado eventualmente expirará ou será rotacionado pela CA, trate-o como qualquer outro certificado no dispositivo.