14.4.3. Certificados autoassinados¶
Um certificado autoassinado é a forma mais rápida de fazer o TLS funcionar entre dois dispositivos que você controla: ambas as pontas confiam em um único certificado que você mesmo gera. Ele cobre toda implantação em que você configura os dois lados da conexão – uma Autoridade Certificadora (CA) pública só entra em cena quando clientes de terceiros precisam se conectar sem serem instruídos a confiar em um certificado personalizado.
14.4.3.1. Criando um certificado autoassinado¶
Execute o OpenSSL na sua máquina de desenvolvimento. O subjectAltName (SAN) é o que os clientes TLS modernos verificam durante a verificação de nome de host, então defina-o como o(s) nome(s) de host e/ou endereço(s) IP que os clientes usarão para alcançar a câmera (o CN sozinho é legado e é ignorado por muitos clientes). Substitua DNS:openmv / IP:192.168.1.50 pelo endereço ao qual seus clientes realmente se conectam.
ECDSA P-256 – recomendado:
# Generate a P-256 private key.
openssl ecparam -name prime256v1 -genkey -noout -out server.key
# Self-signed certificate valid for one year, with a SAN.
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=openmv" -addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
ECDSA P-384 – mais forte, maior/mais lento:
openssl ecparam -name secp384r1 -genkey -noout -out server.key
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=openmv" -addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
RSA-2048 – máxima compatibilidade:
openssl req -new -x509 -newkey rsa:2048 -nodes -keyout server.key \
-out server.crt -days 365 -subj "/CN=openmv" \
-addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
Nota
Um certificado de cliente (usado para autenticação mútua, abaixo) é criado exatamente com esses mesmos comandos – não há nada específico de cliente no certificado em si. Basta gerar um segundo par independente de chave/certificado com nomes diferentes (por exemplo, client.key / client.crt) e usá-lo no cliente conforme mostrado no exemplo de mTLS. O subjectAltName só importa para o lado cujo nome de host o par verifica (o cliente verifica o nome do servidor; nada verifica o do cliente), portanto pode ser omitido em um certificado exclusivo de cliente. O -subj / CN também é apenas um rótulo em um certificado de cliente – o lado do servidor aqui verifica apenas se o certificado encadeia até uma CA confiável, ele nunca corresponde ao nome – então defina-o com o que identifique aquele cliente (por exemplo, /CN=sensor-01). Mantenha algum valor de -subj de qualquer forma, para que o OpenSSL possa gerar o certificado de modo não interativo.
O tempo de vida do certificado é definido por -days; os certificados expiram e devem ser regenerados e reimplantados antes disso.
14.4.3.2. Convertendo para DER¶
Converta tanto o certificado quanto a chave privada para DER antes de copiá-los para a câmera:
openssl x509 -in server.crt -outform DER -out server.der
openssl pkey -in server.key -outform DER -out server.key.der
14.4.3.3. Copiando arquivos para a câmera¶
Copie os arquivos DER para o sistema de arquivos da câmera – por exemplo, arrastando-os para a unidade USB da OpenMV Cam, ou com mpremote cp server.der : e mpremote cp server.key.der :. No lado verificador, copie também o certificado da CA / do par em formato DER.
Os arquivos DER não precisam residir no sistema de arquivos gravável. O MicroPython também pode montar uma imagem ROMFS somente leitura em /rom, e os certificados colocados ali são carregados exatamente como qualquer outro arquivo – por exemplo, ctx.load_cert_chain("/rom/server.der", "/rom/server.key.der"). Uma imagem ROMFS é preparada na sua máquina de desenvolvimento e é somente leitura em tempo de execução, de modo que o certificado não pode ser alterado no dispositivo – útil para travar uma unidade de produção. Observe que uma chave privada armazenada no ROMFS ainda é legível por código em execução na câmera; o ROMFS protege contra modificação, não contra extração. Um certificado residente no ROMFS só pode ser substituído reconstruindo e regravando a imagem.
14.4.3.4. Usando o certificado¶
Um cliente completo que ajusta o relógio, abre um socket, verifica um servidor autoassinado e troca dados:
import socket
import ssl
import ntptime
ntptime.settime() # correct clock for the validity check
# Open a plain TCP connection.
addr = socket.getaddrinfo("openmv", 8443)[0][-1]
sock = socket.socket()
sock.connect(addr)
# Wrap it for TLS, trusting the server's self-signed certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="server.der")
ssock = ctx.wrap_socket(sock, server_hostname="openmv")
ssock.write(b"hello\n")
print(ssock.read())
ssock.close()
Um servidor completo que apresenta seu certificado e sua chave:
import socket
import ssl
import ntptime
ntptime.settime() # correct clock for the validity check
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key.der")
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(socket.getaddrinfo("0.0.0.0", 8443)[0][-1])
sock.listen(1)
while True:
client, addr = sock.accept()
sclient = ctx.wrap_socket(client, server_side=True)
sclient.write(b"hello\n")
print(sclient.read())
sclient.close()
Para autenticação mútua (mTLS), o servidor adicionalmente exige e verifica um certificado de cliente, e o cliente apresenta um próprio:
# Server side: also demand and verify a client certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key.der")
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="client.der")
# Client side: present a certificate of our own.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.load_cert_chain("client.der", "client.key.der")
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="server.der")
Consulte a documentação do módulo ssl para a API completa.
Nota
Tudo nesta página se aplica inalterado ao DTLS (TLS sobre UDP). As chaves, certificados, o formato DER, o modelo de confiança, as preocupações com expiração e as chamadas load_cert_chain / load_verify_locations são idênticas; só o transporte difere – você envolve um socket socket.SOCK_DGRAM e seleciona ssl.PROTOCOL_DTLS_CLIENT / ssl.PROTOCOL_DTLS_SERVER em vez das constantes de protocolo TLS. A única complicação extra é um cookie anti-spoofing do lado do servidor – espera-se que a primeira conexão de um novo cliente falhe e o cliente simplesmente tenta novamente; veja Suporte a DTLS para detalhes.