ssl — módulo SSL/TLS

Este módulo fornece acesso a funcionalidades de encriptação Transport Layer Security (anteriormente e amplamente conhecida como «Secure Sockets Layer») e autenticação entre pares para sockets de rede, tanto do lado do cliente como do lado do servidor.

Dica

Novo no uso de TLS na câmara? Comece pelo tutorial Trabalhar com certificados TLS. Este guia explica como escolher tipos de chaves, criar e converter certificados para o formato DER exigido pela câmara, colocá-los no dispositivo, e verificar servidores e clientes – com exemplos completos e funcionais.

Nota

O MicroPython não implementa ssl.SSLError. As falhas SSL/TLS são lançadas como OSError.

Exemplos

Cliente TLS a verificar o certificado do servidor contra um certificado CA (em formato DER) armazenado no sistema de ficheiros:

import socket
import ssl
import ntptime

# CERT_REQUIRED checks the certificate's validity dates, so the clock
# must be set (see the certificates tutorial linked above).
ntptime.settime()

# Open a plain TCP connection.
addr = socket.getaddrinfo("example.com", 443)[0][-1]
sock = socket.socket()
sock.connect(addr)

# Wrap it for TLS and require a valid certificate.
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="example.com")

ssock.write(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
print(ssock.read())
ssock.close()

Para uma ligação rápida insegura (sem validação de certificado), pode usar-se a função de conveniência ssl.wrap_socket()

ssock = ssl.wrap_socket(sock, server_hostname="example.com")

Servidor TLS a apresentar o seu próprio certificado e chave privada (formato DER):

import socket
import ssl

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)

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key")

while True:
    client, addr = sock.accept()
    sclient = ctx.wrap_socket(client, server_side=True)
    sclient.write(b"hello\n")
    sclient.close()

Funções

ssl.wrap_socket(sock: Any, server_side: bool = False, key: bytes | None = None, cert: bytes | None = None, cert_reqs: int = CERT_NONE, cadata: bytes | None = None, server_hostname: str | None = None, do_handshake: bool = True) Any

Envolve o sock fornecido e devolve um novo objeto socket encapsulado. A implementação desta função consiste em primeiro criar um SSLContext e depois chamar o método SSLContext.wrap_socket() nesse objeto de contexto. Os argumentos sock, server_side e server_hostname são passados sem alteração para a chamada ao método. O argumento do_handshake é passado como do_handshake_on_connect. Os restantes argumentos têm o seguinte comportamento:

  • cert_reqs determina se o par (servidor ou cliente) deve apresentar um certificado válido. Note que ssl.CERT_NONE e ssl.CERT_OPTIONAL não validam qualquer certificado; apenas ssl.CERT_REQUIRED o faz.

  • cadata é um objeto bytes contendo a cadeia de certificados CA (em formato DER) que validará o certificado do par. Atualmente apenas é suportado um único certificado codificado em DER.

Classes

class ssl.SSLContext(protocol: int, /)

Cria uma nova instância de SSLContext. O argumento protocol deve ser uma das constantes PROTOCOL_*.

load_cert_chain(certfile: str | bytes, keyfile: str | bytes) None

Carrega uma chave privada e o certificado correspondente. O certfile é uma string com o caminho de ficheiro do certificado. O keyfile é uma string com o caminho de ficheiro da chave privada.

Diferença em relação ao CPython

Extensão MicroPython: certfile e keyfile podem ser objetos bytes em vez de strings; nesse caso são interpretados como os dados reais do certificado/chave.

load_verify_locations(cafile: str | None = None, cadata: bytes | None = None) None

Carrega a cadeia de certificados CA que validará o certificado do par. cafile é o caminho de ficheiro dos certificados CA. cadata é um objeto bytes contendo os certificados CA. Apenas um destes argumentos deve ser fornecido.

get_ciphers() List[str]

Obtém uma lista dos cifras ativados, devolvida como uma lista de strings.

set_ciphers(ciphers: List[str]) None

Define os cifras disponíveis para sockets criados com este contexto. ciphers deve ser uma lista de strings no formato de suite de cifra IANA .

wrap_socket(sock: Any, *, server_side: bool = False, do_handshake_on_connect: bool = True, server_hostname: str | None = None, client_id: bytes | None = None) Any

Recebe um stream sock (normalmente uma instância de socket.socket do tipo SOCK_STREAM) e devolve uma instância de ssl.SSLSocket, encapsulando o stream subjacente. O objeto devolvido tem os métodos de interface de stream habituais como read(), write(), etc.

  • server_side seleciona se o socket encapsulado está no lado do servidor ou do cliente. Um socket SSL do lado do servidor deve ser criado a partir de um socket normal devolvido por accept() num socket de escuta não-SSL.

  • do_handshake_on_connect determina se o handshake é feito como parte do wrap_socket ou se é adiado para ser realizado como parte das leituras ou escritas iniciais. Para sockets bloqueantes, fazer o handshake imediatamente é o padrão. Para sockets não bloqueantes (ou seja, quando o sock passado para wrap_socket está em modo não bloqueante), o handshake deve geralmente ser adiado, caso contrário wrap_socket bloqueia até que seja concluído.

  • server_hostname destina-se a ser usado como cliente e define o nome do host a verificar contra o certificado do servidor recebido. Também define o nome para a Indicação de Nome de Servidor (SNI), permitindo ao servidor apresentar o certificado adequado.

  • client_id é um argumento de extensão específico do MicroPython, utilizado apenas ao implementar um Servidor DTLS. Consulte Suporte a DTLS para mais detalhes.

Aviso

Por omissão, não é realizada qualquer validação de certificado (ssl.CERT_NONE). Para uma ligação segura deve verificar o certificado do par definindo cert_reqs / SSLContext.verify_mode como ssl.CERT_REQUIRED; caso contrário a ligação é vulnerável a ataques man-in-the-middle.

O wrap_socket do CPython devolve um objeto SSLSocket com métodos típicos de socket, como send, recv, etc. O wrap_socket do MicroPython devolve um objeto mais semelhante ao SSLObject do CPython, que não possui esses métodos de socket.

verify_mode

Define ou obtém o comportamento para verificação de certificados de pares. Deve ser uma das constantes CERT_*.

Nota

ssl.CERT_REQUIRED requer que a data/hora do dispositivo esteja corretamente definida, por exemplo usando mpremote rtc --set ou ntptime, e server_hostname deve ser especificado quando no lado do cliente.

Suporte a DTLS

Diferença em relação ao CPython

Esta é uma extensão do MicroPython.

Este módulo suporta DTLS em modo cliente e servidor através das constantes PROTOCOL_DTLS_CLIENT e PROTOCOL_DTLS_SERVER que podem ser usadas como argumento protocol de SSLContext.

Neste caso, espera-se que o socket subjacente se comporte como um socket de datagrama (ou seja, como o socket aberto com socket.socket com socket.AF_INET como af e socket.SOCK_DGRAM como type).

Suporte a servidor DTLS

O suporte a servidor DTLS do MicroPython está configurado com «Hello Verify» conforme exigido pelo DTLS 1.2. Isto é transparente para os clientes DTLS, mas existem considerações relevantes ao implementar um servidor DTLS em MicroPython:

  • O servidor deve passar um argumento adicional client_id ao chamar SSLContext.wrap_socket(). Este ID deve ser um objeto bytes (ou similar) com um identificador específico do transporte representando o cliente.

    A abordagem mais simples é converter o tuplo de (client_ip, client_port) devolvido por socket.recv_from() numa cadeia de bytes, ou seja:

    _, client_addr = sock.recvfrom(1, socket.MSG_PEEK)
    sock.connect(client_addr)  # Connect back to the client
    sock = ssl_ctx.wrap_socket(sock, server_side=True,
                               client_id=repr(client_addr).encode())
    
  • Na primeira vez que um cliente se liga, a chamada do servidor a wrap_socket falhará com um erro OSError «Hello Verify Required». Isto deve-se ao facto de o cookie «Hello Verify» do DTLS ainda não ser conhecido pelo cliente. Se o mesmo cliente se ligar uma segunda vez, wrap_socket terá sucesso.

  • Os cookies DTLS para «Hello Verify» estão associados ao objeto SSLContext, pelo que o mesmo objeto SSLContext deve ser utilizado para encapsular uma ligação subsequente do mesmo cliente. A implementação do cookie inclui um tempo limite e tem uso constante de memória independentemente do número de clientes que se ligam, pelo que é seguro reutilizar o mesmo objeto SSLContext durante o tempo de vida do servidor.

Constantes

ssl.PROTOCOL_TLS_CLIENT: int

Valor suportado para o parâmetro protocol, que seleciona o modo de cliente TLS.

ssl.PROTOCOL_TLS_SERVER: int

Valor suportado para o parâmetro protocol, que seleciona o modo de servidor TLS.

ssl.PROTOCOL_DTLS_CLIENT: int

Valor suportado para o parâmetro protocol, que seleciona o modo de cliente DTLS.

ssl.PROTOCOL_DTLS_SERVER: int

Valor suportado para o parâmetro protocol, que seleciona o modo de servidor DTLS.

ssl.CERT_NONE: int

Valor suportado para o parâmetro cert_reqs e o atributo SSLContext.verify_mode. Não é realizada qualquer verificação de certificado no par.

ssl.CERT_OPTIONAL: int

Valor suportado para o parâmetro cert_reqs e o atributo SSLContext.verify_mode. A verificação de certificado é opcional. Note que na OpenMV Cam este comporta-se como ssl.CERT_NONE.

ssl.CERT_REQUIRED: int

Valor suportado para o parâmetro cert_reqs e o atributo SSLContext.verify_mode. É exigido um certificado válido do par.