ssl — Módulo SSL/TLS

Este módulo fornece acesso aos recursos de criptografia e autenticação de pares do Transport Layer Security (anteriormente e amplamente conhecido como “Secure Sockets Layer”) para sockets de rede, tanto no lado cliente quanto no lado servidor.

Dica

Novo no TLS na câmera? Comece pelo tutorial Trabalhando com certificados TLS. Ele guia você na escolha dos tipos de chave, na criação e conversão de certificados para o formato DER exigido pela câmera, em como colocá-los no dispositivo e em como verificar servidores e clientes – com exemplos completos e funcionais.

Nota

O MicroPython não implementa ssl.SSLError. Falhas de SSL/TLS são lançadas como OSError em vez disso.

Exemplos

Cliente TLS, verificando o certificado do servidor contra um certificado CA (no formato DER) armazenado no sistema de arquivos:

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 conexão rápida e insegura (sem validação de certificado), a função de conveniência ssl.wrap_socket() pode ser usada em vez disso:

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

Servidor TLS, apresentando 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

Encapsula o sock fornecido e retorna um novo objeto wrapped-socket. A implementação dessa função consiste em primeiro criar um SSLContext e então chamar o método SSLContext.wrap_socket() nesse objeto de contexto. Os argumentos sock, server_side e server_hostname são repassados sem alteração para a chamada do método. O argumento do_handshake é repassado como do_handshake_on_connect. Os argumentos restantes têm o seguinte comportamento:

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

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

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 do arquivo do certificado. O keyfile é uma string com o caminho do arquivo da chave privada.

Diferença em relação ao CPython

Extensão do MicroPython: certfile e keyfile podem ser objetos bytes em vez de strings, caso em que são interpretados como os próprios dados 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 do arquivo dos certificados CA. cadata é um objeto bytes contendo os certificados CA. Apenas um desses argumentos deve ser fornecido.

get_ciphers() List[str]

Obtém uma lista das cifras habilitadas, retornada como uma lista de strings.

set_ciphers(ciphers: List[str]) None

Define as cifras disponíveis para sockets criados com este contexto. ciphers deve ser uma lista de strings no formato de suíte de cifras da 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 (geralmente uma instância de socket.socket do tipo SOCK_STREAM) e retorna uma instância de ssl.SSLSocket, encapsulando o stream subjacente. O objeto retornado tem os métodos usuais da interface stream, como read(), write(), etc.

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

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

  • server_hostname serve para uso como cliente e define o nome de host a ser verificado contra o certificado de servidor recebido. Ele também define o nome para Server Name Indication (SNI), permitindo que o servidor apresente o certificado adequado.

  • client_id é um argumento de extensão específico do MicroPython, usado apenas ao implementar um servidor DTLS. Veja Suporte a DTLS para detalhes.

Aviso

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

O wrap_socket do CPython retorna um objeto SSLSocket que possui métodos típicos de sockets, como send, recv, etc. O wrap_socket do MicroPython retorna 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 de verificação dos certificados de pares. Deve ser uma das constantes CERT_*.

Nota

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

Suporte a DTLS

Diferença em relação ao CPython

Esta é uma extensão do MicroPython.

Este módulo suporta DTLS nos modos cliente e servidor por meio das constantes PROTOCOL_DTLS_CLIENT e PROTOCOL_DTLS_SERVER, que podem ser usadas como argumento protocol de SSLContext.

Nesse caso, espera-se que o socket subjacente se comporte como um socket de datagrama (isto é, como o socket aberto com socket.socket usando socket.AF_INET como af e socket.SOCK_DGRAM como type).

Suporte a servidor DTLS

O suporte a servidor DTLS do MicroPython é configurado com “Hello Verify”, conforme exigido pelo DTLS 1.2. Isso é transparente para clientes DTLS, mas há considerações relevantes ao implementar um servidor DTLS no MicroPython:

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

    A abordagem mais simples é converter a tupla (client_ip, client_port) retornada por socket.recv_from() em uma string de bytes, isto é:

    _, 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 conecta, a chamada do servidor a wrap_socket falhará com um erro OSError “Hello Verify Required”. Isso ocorre porque o cookie de “Hello Verify” do DTLS ainda não é conhecido pelo cliente. Se o mesmo cliente se conectar uma segunda vez, então wrap_socket será bem-sucedido.

  • Os cookies DTLS para “Hello Verify” são associados ao objeto SSLContext, portanto o mesmo objeto SSLContext deve ser usado para encapsular uma conexão subsequente do mesmo cliente. A implementação do cookie inclui um tempo limite e tem uso constante de memória, independentemente de quantos clientes se conectam, então não há problema em reutilizar o mesmo objeto SSLContext durante toda a vida útil do servidor.

Constantes

ssl.PROTOCOL_TLS_CLIENT: int

Valor suportado para o parâmetro protocol, selecionando o modo cliente TLS.

ssl.PROTOCOL_TLS_SERVER: int

Valor suportado para o parâmetro protocol, selecionando o modo servidor TLS.

ssl.PROTOCOL_DTLS_CLIENT: int

Valor suportado para o parâmetro protocol, selecionando o modo cliente DTLS.

ssl.PROTOCOL_DTLS_SERVER: int

Valor suportado para o parâmetro protocol, selecionando o modo servidor DTLS.

ssl.CERT_NONE: int

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

ssl.CERT_OPTIONAL: int

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

ssl.CERT_REQUIRED: int

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