Synss / python-mbedtls

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

Provide an example of DTLS server and client with certificates instead of PSK #80

Closed ratelwork closed 10 months ago

ratelwork commented 1 year ago

Can you please provide an example of DTLS echo server and simple DTLS client.

Provided in the bootom of this message code for some reason do not work anymore. I got an error:

bind <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=17, laddr=('0.0.0.0', 0)> ('0.0.0.0', 4433)
 . >>>
Traceback (most recent call last):
  File "./mbedtls_server.py", line 134, in <module>
    echo_until(srv, b"\0")
  File "./mbedtls_server.py", line 108, in echo_until
    cli, cli_address = sock.accept()
  File "/mbedtls/tls.py", line 298, in accept
    self.bind(sockname)
OSError: [Errno 98] Address already in use
          Hi, thank you for your feedback. I will have to look into the ```D/TLSConfiguration``` bug.

Here are not-so-short but tested examples of a DTLS server and a DTLS client that work on my machine. The code is probably not that great but this is what I have used to implement DTLS.

Listening on "0.0.0.0" for the server is important. It will not work if you only listen on "127.0.0.1" for example. This is because DTLS accept() steals the first client socket to handshake and communicate with the client. The server then bind()s another socket for the next client.

This is also what happens in net_socket.c from upstream libmbedtls and I do not know of a better way to handle handshake over UDP...

#!/usr/bin/env python

"""Example DTLS server"""

import datetime as dt
import socket
import struct

import mbedtls.hash as hashlib
from mbedtls.pk import RSA, ECC
from mbedtls.x509 import BasicConstraints, CRT, CSR
from mbedtls.tls import *
from mbedtls.tls import _enable_debug_output, _set_debug_level

now = dt.datetime.utcnow()
digestmod = hashlib.sha256

ca0_key = RSA()
ca0_key.generate()
ca1_key = ECC()
ca1_key.generate()
ee0_key = ECC()
ee0_key.generate()

ca0_crt = CRT.selfsign(
    CSR.new(ca0_key, "CN=Trusted CA", digestmod()),
    ca0_key,
    not_before=now,
    not_after=now + dt.timedelta(days=90),
    serial_number=0x123456,
    basic_constraints=BasicConstraints(True, -1),
)
ca1_crt = ca0_crt.sign(
    CSR.new(ca1_key, "CN=Intermediate CA", digestmod()),
    ca0_key,
    not_before=now,
    not_after=now + dt.timedelta(days=90),
    serial_number=0x234567,
    basic_constraints=BasicConstraints(True, -1),
)
ee0_crt = ca1_crt.sign(
    CSR.new(ee0_key, "CN=End Entity", digestmod()),
    ca1_key,
    not_before=now,
    not_after=now + dt.timedelta(days=90),
    serial_number=0x345678,
)

with open("ca0.crt", "wt") as ca:
    ca.write(ca0_crt.to_PEM())

trust_store = TrustStore()
trust_store.add(ca0_crt)

def block(cb, *args, **kwargs):
    while True:
        try:
            result = cb(*args, **kwargs)
        except (WantReadError, WantWriteError):
            print(" .", cb.__name__)
        else:
            print(" .", "done", cb.__name__, result)
            return result

conf = DTLSConfiguration(
    trust_store=trust_store,
    certificate_chain=([ee0_crt, ca1_crt], ee0_key),
    validate_certificates=False,
)

_enable_debug_output(conf)
_set_debug_level(1)

def echo_until(sock, end):
    cli0, cli_address0 = sock.accept()
    cli0.setcookieparam(cli_address0[0].encode("ascii"))
    try:
        block(cli0.do_handshake)
    except HelloVerifyRequest:
        print("HVR")

    cli1, cli_address1 = cli0.accept()
    cli0.close()
    cli1.setcookieparam(cli_address1[0].encode("ascii"))
    block(cli1.do_handshake)
    print(" .", "handshake", cli1.negotiated_tls_version())

    cli = cli1

    while True:
        data = block(cli.recv, 4096)
        print(" .", "R", data)
        nn = block(cli.send, data)
        print(" .", "S", nn, len(data))
        if data == end:
            break

    print(" .", "done")
    print(cli)
    cli.close()

address = ("0.0.0.0", 4433)
host, port = address

ctx = ServerContext(conf)
srv = ctx.wrap_socket(
    socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
)

print(" .", "bind", srv, address)
srv.bind(address)

while True:
    print(" .", ">>>")
    echo_until(srv, b"\0")
    print(" .", "<<<")
#!/usr/bin/env python

"""Example DTLS client"""

import socket
import struct

from mbedtls.x509 import CRT
from mbedtls.tls import *
from mbedtls.tls import _enable_debug_output, _set_debug_level

with open("ca0.crt", "rt") as ca:
    ca0_crt = CRT.from_PEM(ca.read())

trust_store = TrustStore()
trust_store.add(ca0_crt)

conf = DTLSConfiguration(trust_store=trust_store, validate_certificates=False)

_enable_debug_output(conf)
_set_debug_level(1)

address = ("127.0.0.1", 4433)
host, port = address

ctx = ClientContext(conf)
cli = ctx.wrap_socket(
    socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP),
    server_hostname="localhost",
)

print(" .", "connect", address)
cli.connect(address)

def block(cb, *args, **kwargs):
    while True:
        try:
            result = cb(*args, **kwargs)
        except (WantReadError, WantWriteError):
            print(" .", cb.__name__)
        else:
            print(" .", "done", cb.__name__, result)
            return result

block(cli.do_handshake)
print(" .", "handshake", cli.negotiated_tls_version())

msg = b"hello"
for _ in range(1):
    nn = block(cli.send, msg)
    print(" .", "S", nn, len(msg))
    data, addr = block(cli.recvfrom, 4096)
    print(" .", "R", nn, data)
else:
    block(cli.send, b"\0")
    block(cli.recvfrom, 4096)

print(cli)
cli.close()

Originally posted by @Synss in https://github.com/Synss/python-mbedtls/issues/20#issuecomment-480888266

Synss commented 1 year ago

I’ll have a look. It’d certainly be a good idea to add such a test to the test suite anyway.

Synss commented 1 year ago

In short, you need to set the certificate chain to the configuration https://github.com/Synss/python-mbedtls/blob/master/tests/test_tls.py#L403-L409 I'm a bit short on time these days. I'll still try to provide a complete example in the next few days.

ratelwork commented 1 year ago

In short, you need to set the certificate chain to the configuration https://github.com/Synss/python-mbedtls/blob/master/tests/test_tls.py#L403-L409 I'm a bit short on time these days. I'll still try to provide a complete example in the next few days.

Thanks, it will be really useful. I did try it as in the example but unfortunaly it did not work in my case.

Synss commented 1 year ago

I've tried it here again and, whereas TLS works as expected, DTLS hangs. I need to debug that.

Synss commented 1 year ago

https://github.com/Synss/python-mbedtls/commit/88b4a3b38ab3130a7ce5b8c9ec501ef50137cd77 adds a sans-io example.

superbiao654 commented 1 year ago

sorry,I run the test_tls and it still failed.

Synss commented 1 year ago

CI is green. The tests do pass. What did you do exactly that fails? You should probably open another issue for this anyway with the error message and so on.

superbiao654 commented 1 year ago

Cannot import MaxFragmentLength from mbedtls.tls

C:\Program Files\Python39\lib\site-packages_pytest\assertion\rewrite.py:172: in exec_module exec(co, module.dict) test_tls.py:40: in from mbedtls.tls import ( E ImportError: cannot import name 'MaxFragmentLength' from 'mbedtls.tls' (C:\Program Files\Python39\lib\site-packages\mbedtls\tls.py)

from mbedtls.tls import ( ClientContext, DTLSConfiguration, DTLSVersion, HandshakeStep, HelloVerifyRequest, MaxFragmentLength, NextProtocol, Purpose, ServerContext, TLSConfiguration, TLSRecordHeader, TLSSession, TLSVersion, TLSWrappedBuffer, TLSWrappedSocket, TrustStore, WantReadError, WantWriteError, ciphers_available, )

superbiao654 commented 1 year ago

I want to know the version of your tls.py module

Synss commented 1 year ago

@superbiao654 Please stop hijacking this issue with a different problem and open your own with a full repro and following the provided template.

hwmaier commented 10 months ago

@Synss Thank you for providing the server.py example. Using it I was able to implement a PSK based DTLS server. Without the example in _make_dtls_connection() it would be hard to figure out the correct sequence of accepts and handshakes.

Synss commented 10 months ago

@hwmaier yes, indeed! Would you mind sending a pull request with your changes on 'server.py'?

hwmaier commented 10 months ago

Sorry my comment was probably misleading. I did not have to change anything in your code. Your example code is working well for me and I used it as a template for my own server implementation. I only added my comment here to express that in my opinion the recent release has a good and working example others should be able to follow.

Synss commented 10 months ago

That's fair as well. Thank you for confirming that there is no problem with the library and that the examples are good enough.