ssl — Módulo SSL/TLS

Este módulo proporciona acceso a las funciones de cifrado y autenticación de pares de Transport Layer Security (anteriormente conocido ampliamente como «Secure Sockets Layer») para sockets de red, tanto del lado del cliente como del servidor.

Truco

¿Es nuevo en TLS en la cámara? Comience con el tutorial Trabajar con certificados TLS. Le guía a través de la elección de tipos de clave, la creación y conversión de certificados al formato DER que requiere la cámara, su transferencia al dispositivo y la verificación de servidores y clientes, con ejemplos completos y funcionales.

Nota

MicroPython no implementa ssl.SSLError. Los fallos de SSL/TLS se generan como OSError en su lugar.

Ejemplos

Cliente TLS que verifica el certificado del servidor frente a un certificado de CA (en formato DER) almacenado en el sistema de archivos:

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 una conexión rápida y no segura (sin validación de certificados) se puede usar en su lugar la función de conveniencia ssl.wrap_socket():

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

Servidor TLS que presenta su propio certificado y clave 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()

Funciones

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

Envuelve el sock indicado y devuelve un nuevo objeto socket envuelto. La implementación de esta función consiste en crear primero un SSLContext y luego llamar al método SSLContext.wrap_socket() sobre ese objeto de contexto. Los argumentos sock, server_side y server_hostname se pasan sin cambios a la llamada al método. El argumento do_handshake se pasa como do_handshake_on_connect. Los argumentos restantes tienen el siguiente comportamiento:

  • cert_reqs determina si el par (servidor o cliente) debe presentar un certificado válido. Tenga en cuenta que ssl.CERT_NONE y ssl.CERT_OPTIONAL no validan ningún certificado; solo ssl.CERT_REQUIRED lo hace.

  • cadata es un objeto bytes que contiene la cadena de certificados de CA (en formato DER) que validará el certificado del par. Actualmente solo se admite un único certificado codificado en DER.

Clases

class ssl.SSLContext(protocol: int, /)

Crea una nueva instancia de SSLContext. El argumento protocol debe ser una de las constantes PROTOCOL_*.

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

Carga una clave privada y el certificado correspondiente. certfile es una cadena con la ruta del archivo del certificado. keyfile es una cadena con la ruta del archivo de la clave privada.

Diferencia con CPython

Extensión de MicroPython: certfile y keyfile pueden ser objetos bytes en lugar de cadenas, en cuyo caso se interpretan como los datos reales del certificado/clave.

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

Carga la cadena de certificados de CA que validará el certificado del par. cafile es la ruta del archivo de los certificados de CA. cadata es un objeto bytes que contiene los certificados de CA. Solo se debe proporcionar uno de estos argumentos.

get_ciphers() List[str]

Obtiene una lista de los cifrados habilitados, devuelta como una lista de cadenas.

set_ciphers(ciphers: List[str]) None

Establece los cifrados disponibles para los sockets creados con este contexto. ciphers debe ser una lista de cadenas en el formato de conjunto de cifrados de la 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

Toma un sock de tipo stream (normalmente una instancia de socket.socket de tipo SOCK_STREAM) y devuelve una instancia de ssl.SSLSocket que envuelve el flujo subyacente. El objeto devuelto tiene los métodos habituales de la interfaz stream, como read(), write(), etc.

  • server_side selecciona si el socket envuelto está del lado del servidor o del cliente. Un socket SSL del lado del servidor debe crearse a partir de un socket normal devuelto por accept() sobre un socket de escucha no SSL.

  • do_handshake_on_connect determina si el handshake se realiza como parte de wrap_socket o si se aplaza para realizarse como parte de las primeras lecturas o escrituras. Para sockets bloqueantes, realizar el handshake inmediatamente es lo habitual. Para sockets no bloqueantes (es decir, cuando el sock pasado a wrap_socket está en modo no bloqueante), el handshake generalmente debe aplazarse, ya que de lo contrario wrap_socket se bloquea hasta que se completa.

  • server_hostname es para usarse como cliente, y establece el nombre de host a comprobar frente al certificado del servidor recibido. También establece el nombre para la Server Name Indication (SNI), lo que permite al servidor presentar el certificado adecuado.

  • client_id es un argumento de extensión específico de MicroPython utilizado únicamente al implementar un servidor DTLS. Consulte Compatibilidad con DTLS para más detalles.

Advertencia

De forma predeterminada no se realiza ninguna validación de certificados (ssl.CERT_NONE). Para una conexión segura debe verificar el certificado del par estableciendo cert_reqs / SSLContext.verify_mode en ssl.CERT_REQUIRED; de lo contrario, la conexión es vulnerable a ataques de intermediario (man-in-the-middle).

El wrap_socket de CPython devuelve un objeto SSLSocket que tiene métodos típicos de los sockets, como send, recv, etc. El wrap_socket de MicroPython devuelve un objeto más similar al SSLObject de CPython, que no tiene estos métodos de socket.

verify_mode

Establece u obtiene el comportamiento para la verificación de los certificados de los pares. Debe ser una de las constantes CERT_*.

Nota

ssl.CERT_REQUIRED requiere que la fecha/hora del dispositivo esté correctamente configurada, por ejemplo usando mpremote rtc --set o ntptime, y debe especificarse server_hostname cuando se está del lado del cliente.

Compatibilidad con DTLS

Diferencia con CPython

Esta es una extensión de MicroPython.

Este módulo admite DTLS en modo cliente y servidor mediante las constantes PROTOCOL_DTLS_CLIENT y PROTOCOL_DTLS_SERVER, que pueden usarse como el argumento protocol de SSLContext.

En este caso se espera que el socket subyacente se comporte como un socket de datagramas (es decir, como el socket abierto con socket.socket usando socket.AF_INET como af y socket.SOCK_DGRAM como type).

Compatibilidad con servidor DTLS

La compatibilidad con servidor DTLS de MicroPython está configurada con «Hello Verify» según lo requerido por DTLS 1.2. Esto es transparente para los clientes DTLS, pero hay consideraciones relevantes al implementar un servidor DTLS en MicroPython:

  • El servidor debe pasar un argumento adicional client_id al llamar a SSLContext.wrap_socket(). Este ID debe ser un objeto bytes (o similar) con un identificador específico del transporte que represente al cliente.

    El enfoque más sencillo es convertir la tupla (client_ip, client_port) devuelta por socket.recv_from() en una cadena de bytes, es decir:

    _, 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())
    
  • La primera vez que un cliente se conecta, la llamada del servidor a wrap_socket fallará con un error OSError «Hello Verify Required». Esto se debe a que la cookie «Hello Verify» de DTLS aún no es conocida por el cliente. Si el mismo cliente se conecta una segunda vez, entonces wrap_socket tendrá éxito.

  • Las cookies DTLS para «Hello Verify» están asociadas al objeto SSLContext, por lo que se debe usar el mismo objeto SSLContext para envolver una conexión posterior del mismo cliente. La implementación de cookies incluye un tiempo de espera y tiene un uso de memoria constante independientemente de cuántos clientes se conecten, por lo que es correcto reutilizar el mismo objeto SSLContext durante toda la vida del servidor.

Constantes

ssl.PROTOCOL_TLS_CLIENT: int

Valor admitido para el parámetro protocol, que selecciona el modo cliente TLS.

ssl.PROTOCOL_TLS_SERVER: int

Valor admitido para el parámetro protocol, que selecciona el modo servidor TLS.

ssl.PROTOCOL_DTLS_CLIENT: int

Valor admitido para el parámetro protocol, que selecciona el modo cliente DTLS.

ssl.PROTOCOL_DTLS_SERVER: int

Valor admitido para el parámetro protocol, que selecciona el modo servidor DTLS.

ssl.CERT_NONE: int

Valor admitido para el parámetro cert_reqs y para el atributo SSLContext.verify_mode. No se realiza ninguna verificación de certificado sobre el par.

ssl.CERT_OPTIONAL: int

Valor admitido para el parámetro cert_reqs y para el atributo SSLContext.verify_mode. La verificación del certificado es opcional. Tenga en cuenta que en la OpenMV Cam esto se comporta como ssl.CERT_NONE.

ssl.CERT_REQUIRED: int

Valor admitido para el parámetro cert_reqs y para el atributo SSLContext.verify_mode. Se requiere un certificado válido del par.