Synss / python-mbedtls

Cryptographic library with an mbed TLS back end
MIT License
79 stars 28 forks source link

DTLS on windows #47

Closed nicho2 closed 2 years ago

nicho2 commented 2 years ago

I am submitting a …

Description

I have : -1- Download-mbedTLS.PS1 (version 2.16.9) -2- Install-mbedTLS.PS1 -3- launch python setup.py install (my python version is 3.7.8 all seems OK

I try DTLS example (DTLS client and server)

Current behavior

when the client try to do handshake, the server give an exception:

Traceback (most recent call last): File "", line 1, in File "src\mbedtls\tls.pyx", line 1557, in mbedtls.tls.TLSWrappedSocket.accept OSError: [WinError 10040] Un message envoyé sur un socket datagramme était plus volumineux que le tampon de messages interne ou qu’une autre limite réseau ou bien le tampon utilisé pour recevoir un datagramme était plus petit que le datagramme lui-même

it's at the moment where the server receive the "client hello" def accept(self): if self.type == _socket.SOCK_STREAM: conn, address = self._socket.accept() else: data, address = self._socket.recvfrom(1, _socket.MSG_PEEK)

Expected behavior

No exception

Steps to reproduce

1.Run server 2.Run client

Minimal demo of the problem

code server :

from mbedtls import tls

import datetime as dt
from mbedtls import hashlib
from mbedtls import pk
from mbedtls import x509

import socket
from contextlib import suppress
import multiprocessing as mp

def block(callback, *args, **kwargs):
    while True:
        with suppress(tls.WantReadError, tls.WantWriteError):
            return callback(*args, **kwargs)

def dtls_server_main_loop(sock):
    """A simple DTLS echo server."""
    conn, addr = sock.accept()
    print(conn,addr)
    conn.setcookieparam(addr[0].encode())
    with suppress(tls.HelloVerifyRequest):
        block(conn.do_handshake)
        conn, addr = conn.accept()
        conn.setcookieparam(addr[0].encode())
        block(conn.do_handshake)
        data = conn.recv(4096)
        conn.send(data)

def server_main_loop(sock):
    conn, addr = sock.accept()
    print(conn, addr)
    block(conn.do_handshake)
    data = conn.recv(4096)
    if data == get_request:
        conn.sendall(http_response)
    else:
        conn.sendall(http_error)

if __name__ == "__main__":
    # Here, the trusted root is a self-signed CA certificate ca0_crt signed by ca0_key
    now = dt.datetime.utcnow()
    ca0_key = pk.RSA()
    _ = ca0_key.generate()
    ca0_csr = x509.CSR.new(ca0_key, "CN=Trusted CA", hashlib.sha256())
    ca0_crt = x509.CRT.selfsign(
        ca0_csr, ca0_key,
        not_before=now, not_after=now + dt.timedelta(days=90),
        serial_number=0x123456,
        basic_constraints=x509.BasicConstraints(True, 1))

    #An intermediate then issues a Certificate Singing Request(CSR) that    the root CA signs:
    ca1_key = pk.ECC()
    _ = ca1_key.generate()
    ca1_csr = x509.CSR.new(ca1_key, "CN=Intermediate CA", hashlib.sha256())
    ca1_crt = ca0_crt.sign(
        ca1_csr, ca0_key, now, now + dt.timedelta(days=90), 0x123456,
        basic_constraints = x509.BasicConstraints(ca=True, max_path_length=3))
    # And finally, the intermediate CA signs a certificate for the End Entity on the basis of a new CSR:
    ee0_key = pk.ECC()
    _ = ee0_key.generate()
    ee0_csr = x509.CSR.new(ee0_key, "CN=End Entity", hashlib.sha256())
    ee0_crt = ca1_crt.sign(
            ee0_csr, ca1_key, now, now + dt.timedelta(days=90), 0x987654)
    # The emitting certificate can be used to verify the next certificate in the chain:
    print(f'la validation du certificat est {ca1_crt.verify(ee0_crt)}')
    print(f'la validation du certificat est {ca0_crt.verify(ca1_crt)}')

    # the trust store just consists in the root certificate ca0_crt
    trust_store = tls.TrustStore()
    trust_store.add(ca0_crt)

    # context serveur
    dtls_srv_ctx = tls.ServerContext(tls.DTLSConfiguration(
        trust_store = trust_store,
        certificate_chain = ([ee0_crt, ca1_crt], ee0_key),
        validate_certificates = False
        ))

    # The DTLS contexts can now wrap UDP sockets.
    dtls_srv = dtls_srv_ctx.wrap_socket(
        socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        )

    port = 4443
    dtls_srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    dtls_srv.bind(("0.0.0.0", port))

    #In contrast with TCP (TLS), there is not call to listen() for UDP.
    #runner = mp.Process(target=dtls_server_main_loop, args=(dtls_srv, ))
    #runner.start()

    server_main_loop(dtls_srv)

-- code client

from mbedtls import tls

import datetime as dt
from mbedtls import hashlib
from mbedtls import pk
from mbedtls import x509

import socket
from contextlib import suppress
import multiprocessing as mp

def block(callback, *args, **kwargs):
    while True:
        with suppress(tls.WantReadError, tls.WantWriteError):
            return callback(*args, **kwargs)

if __name__ == "__main__":
    # Here, the trusted root is a self-signed CA certificate ca0_crt signed by ca0_key
    now = dt.datetime.utcnow()
    ca0_key = pk.RSA()
    _ = ca0_key.generate()
    ca0_csr = x509.CSR.new(ca0_key, "CN=Trusted CA", hashlib.sha256())
    ca0_crt = x509.CRT.selfsign(
        ca0_csr, ca0_key,
        not_before=now, not_after=now + dt.timedelta(days=90),
        serial_number=0x123456,
        basic_constraints=x509.BasicConstraints(True, 1))

    #An intermediate then issues a Certificate Singing Request(CSR) that    the root CA signs:
    ca1_key = pk.ECC()
    _ = ca1_key.generate()
    ca1_csr = x509.CSR.new(ca1_key, "CN=Intermediate CA", hashlib.sha256())
    ca1_crt = ca0_crt.sign(
        ca1_csr, ca0_key, now, now + dt.timedelta(days=90), 0x123456,
        basic_constraints = x509.BasicConstraints(ca=True, max_path_length=3))
    # And finally, the intermediate CA signs a certificate for the End Entity on the basis of a new CSR:
    ee0_key = pk.ECC()
    _ = ee0_key.generate()
    ee0_csr = x509.CSR.new(ee0_key, "CN=End Entity", hashlib.sha256())
    ee0_crt = ca1_crt.sign(
            ee0_csr, ca1_key, now, now + dt.timedelta(days=90), 0x987654)
    # The emitting certificate can be used to verify the next certificate in the chain:
    print(f'la validation du certificat est {ca1_crt.verify(ee0_crt)}')
    print(f'la validation du certificat est {ca0_crt.verify(ca1_crt)}')

    # the trust store just consists in the root certificate ca0_crt
    trust_store = tls.TrustStore()
    trust_store.add(ca0_crt)

    #client
    dtls_cli_ctx = tls.ClientContext(tls.DTLSConfiguration(
        trust_store = trust_store,
        validate_certificates = True,
        ))
    dtls_cli = dtls_cli_ctx.wrap_socket(
        socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
        server_hostname=None,
        )

    port = 4443
    dtls_cli.connect(("localhost", port))
    block(dtls_cli.do_handshake)
    DATAGRAM = b"hello datagram"
    block(dtls_cli.send, DATAGRAM)
    block(dtls_cli.recv, 4096)

Other information

Synss commented 2 years ago

Bonjour nicho2!

And thank you for your report. As you probably noticed, master is unreleased and Windows is still unsupported in the latest released version. Moreover, although the error message your report is different, this can be reproduced with #41 on master and I would prefer if you could keep the discussion there.

Finally, I have also tried to reduce the size of the buffers but that did not help. I do, however, accept patches.

nicho2 commented 2 years ago

in my case, i have replace in tls.pyx

in accept method: data, address = self._socket.recvfrom(1, _socket.MSG_PEEK) by data, address = self._socket.recvfrom(1024, _socket.MSG_PEEK)

Synss commented 2 years ago

C'est possible!

I am currently a bit short of time but I should be able to check your fix next week. You cannot imagine how much time I have already spent on that bug! It might as well be something trivial.

Cheers!

Synss commented 2 years ago

The tests are indeed green with your fix. Thank you again!