14.4.3. Certificados autoassinados#

Um certificado autoassinado é a forma mais rápida de fazer TLS funcionar entre dois dispositivos que controla: ambos os extremos confiam num único certificado que gera você mesmo. Cobre todas as implementações onde configura ambos os lados da ligação – uma Autoridade de Certificação pública só entra em cena quando clientes de terceiros precisam de se ligar sem serem informados para confiar num certificado personalizado.

14.4.3.1. Criar 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 do nome de anfitrião, por isso defina-o para o(s) nome(s) de anfitrião e/ou endereço(s) IP que os clientes utilizarão para aceder à câmara (CN isolado é legado e é ignorado por muitos clientes). Substitua DNS:openmv / IP:192.168.1.50 pelo endereço ao qual os seus clientes realmente se ligam.

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 – compatibilidade máxima:

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 (utilizado para autenticação mútua, abaixo) é criado exatamente com estes mesmos comandos – não há nada específico do cliente no próprio certificado. Basta gerar um segundo par chave/certificado independente com nomes diferentes (p.ex. client.key / client.crt) e utilizá-lo no cliente conforme mostrado no exemplo mTLS. O subjectAltName só é importante para o lado cujo nome de anfitrião o par verifica (o cliente verifica o nome do servidor; nada verifica o do cliente), por isso pode ser omitido num certificado apenas para cliente. O -subj / CN é igualmente apenas uma etiqueta num certificado de cliente – o lado servidor aqui verifica apenas que o certificado encadeia a uma CA de confiança, nunca faz corresponder o nome – por isso defina-o para o que identifica esse cliente (p.ex. /CN=sensor-01). Mantenha algum valor -subj independentemente, para que o OpenSSL possa gerar o certificado de forma não interativa.

O tempo de vida do certificado é definido por -days; os certificados expiram e têm de ser regenerados e reimplementados antes disso.

14.4.3.2. Converter para DER#

Converta tanto o certificado como a chave privada para DER antes de os copiar para a câmara:

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. Copiar ficheiros para a câmara#

Copie os ficheiros DER para o sistema de ficheiros da câmara – 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 CA / par em formato DER.

Os ficheiros DER não têm de residir no sistema de ficheiros gravável. O MicroPython também pode montar uma imagem ROMFS de apenas leitura em /rom, e os certificados colocados aí são carregados exatamente como qualquer outro ficheiro – p.ex. ctx.load_cert_chain("/rom/server.der", "/rom/server.key.der"). Uma imagem ROMFS é preparada na sua máquina de desenvolvimento e é de apenas leitura em tempo de execução, pelo que o certificado não pode ser alterado no dispositivo – útil para bloquear uma unidade de produção. Note que uma chave privada armazenada no ROMFS ainda é legível pelo código em execução na câmara; o ROMFS protege contra a modificação, não contra a extração. Um certificado residente em ROMFS só pode ser substituído reconstruindo e reinstalando o firmware.

14.4.3.4. Utilizar o certificado#

Um cliente completo que define 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 o seu certificado e 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 exige e verifica adicionalmente um certificado de cliente, e o cliente apresenta o seu 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 aplica-se sem alterações ao DTLS (TLS sobre UDP). As chaves, certificados, formato DER, modelo de confiança, preocupações de expiração e as chamadas load_cert_chain / load_verify_locations são idênticos; apenas o transporte difere – 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 adicional é um cookie anti-falsificação do lado do servidor – a primeira ligação de um novo cliente deverá falhar e o cliente simplesmente tenta novamente; consulte Suporte a DTLS para detalhes.