Coder Social home page Coder Social logo

Comments (16)

Synss avatar Synss commented on June 12, 2024

Hi Roman,

Putting the example from the readme in an infinite loop should do. Let me see if I can find a simple example.

Regards,
Mathias

from python-mbedtls.

inpos avatar inpos commented on June 12, 2024

Hi Mathias.
No, after first accept and start communication in other thread, second accept block forever and don't accept any new connections.

By the way, tls.(D)TLSConfiguration live only and only in local namespace.
Even in instance of some class. After, for ex., self.conf = tls.DTLSConfiguration() in one method accessing the self.conf in other method of this instance raise "Buffer out of memory".
And accessing wrapped socket in other method raise the same exception.
Work only if init variable with tls.(D)TLSConfiguration as global variable and wrap with it socket in instance.

from python-mbedtls.

Synss avatar Synss commented on June 12, 2024

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()

from python-mbedtls.

Synss avatar Synss commented on June 12, 2024

By the way, tls.(D)TLSConfiguration live only and only in local namespace.
Even in instance of some class. After, for ex., self.conf = tls.DTLSConfiguration() in one method accessing the self.conf in other method of this instance raise "Buffer out of memory".
And accessing wrapped socket in other method raise the same exception.

I cannot reproduce this behaviour. Could you give me a short example reproducing the bug?

from python-mbedtls.

inpos avatar inpos commented on June 12, 2024

I cannot reproduce this behaviour. Could you give me a short example reproducing the bug?

Here:

import socket
from mbedtls import tls
import datetime as dt
from mbedtls import hash as hashlib
from mbedtls import pk
from mbedtls import x509
from uuid import uuid4

class DTLSCerts:
    def __init__(self):
        now = dt.datetime.utcnow()
        self.ca0_key = pk.RSA()
        _ = self.ca0_key.generate()
        ca0_csr = x509.CSR.new(self.ca0_key, 'CN=thrusted CA', hashlib.sha256())
        self.ca0_crt = x509.CRT.selfsign(
            ca0_csr, self.ca0_key,
            not_before=now, not_after=now + dt.timedelta(days=3650),
            serial_number=0x123456,
            basic_constraints=x509.BasicConstraints(True, 1))
        self.ca1_key = pk.ECC()
        _ = self.ca1_key.generate()
        ca1_csr = x509.CSR.new(self.ca1_key, 'CN=intermediate CA', hashlib.sha256())
        self.ca1_crt = self.ca0_crt.sign(
            ca1_csr, self.ca0_key, now, now + dt.timedelta(days=3650), 0x123456,
            basic_constraints=x509.BasicConstraints(ca=True, max_path_length=3))
        self.srv_crt, self.srv_key = self.server_cert()
    def server_cert(self):
        now = dt.datetime.utcnow()
        ee0_key = pk.ECC()
        _ = ee0_key.generate()
        ee0_csr = x509.CSR.new(ee0_key, f'CN=peer [{uuid4().hex}]', hashlib.sha256())
        ee0_crt = self.ca1_crt.sign(
            ee0_csr, self.ca1_key, now, now + dt.timedelta(days=3650), 0x987654)
        return ee0_crt, ee0_key

dtls_certs = DTLSCerts()
trust_store = tls.TrustStore()
trust_store.add(dtls_certs.ca0_crt)

class DTLSServer:
    def __init__(self, listener_timeout = 0.5, reuse_addr = True):
        srv_crt, srv_key = dtls_certs.server_cert()
        dtls_srv_ctx = tls.ServerContext(tls.DTLSConfiguration(
            trust_store=trust_store,
            certificate_chain=([srv_crt, dtls_certs.ca1_crt], srv_key),
            validate_certificates=False,
            ))
        dtls_srv = dtls_srv_ctx.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_DGRAM))
        dtls_srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        dtls_srv.bind(('', 6060))
        self.listener = dtls_srv
        print(self.listener.context.configuration)
        print(self.listener.context.configuration.certificate_chain)
    def test(self):
        print(self.listener.context.configuration)
        print(self.listener.context.configuration.certificate_chain)



>>> s = DTLSServer()
DTLSConfiguration(validate_certificates=False, certificate_chain=((<mbedtls.x509.CRT object at 0x560ea30fc768>, <mbedtls.x509.CRT object at 0x560ea31057f8>), <mbedtls.pk.ECC object at 0x7f136a9b0508>), ciphers=[], inner_protocols=(), lowest_supported_version=<DTLSVersion.DTLSv1_0: 2>, highest_supported_version=<DTLSVersion.DTLSv1_2: 3>, trust_store=TrustStore([<mbedtls.x509.CRT object at 0x560ea3105a68>]), sni_callback=None)
((<mbedtls.x509.CRT object at 0x560ea30fc768>, <mbedtls.x509.CRT object at 0x560ea31057f8>), <mbedtls.pk.ECC object at 0x7f136a9b0508>)
>>> s.test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 16, in test
  File "src/mbedtls/tls.pyx", line 352, in mbedtls.tls._BaseConfiguration.__repr__
  File "src/mbedtls/tls.pyx", line 406, in mbedtls.tls._BaseConfiguration.certificate_chain.__get__
  File "src/mbedtls/x509.pyx", line 409, in mbedtls.x509.CRT.from_DER
IndexError: Out of bounds on buffer access (axis 0)
>>> print(s.listener.context)
<mbedtls.tls.ServerContext object at 0x560ea310d2f8>
>>> print(s.listener.context.configuration.certificate_chain)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "src/mbedtls/tls.pyx", line 406, in mbedtls.tls._BaseConfiguration.certificate_chain.__get__
  File "src/mbedtls/x509.pyx", line 409, in mbedtls.x509.CRT.from_DER
IndexError: Out of bounds on buffer access (axis 0)
>>> print(s.listener.context.configuration.)
s.listener.context.configuration.anti_replay                s.listener.context.configuration.lowest_supported_version
s.listener.context.configuration.certificate_chain          s.listener.context.configuration.sni_callback
s.listener.context.configuration.ciphers                    s.listener.context.configuration.trust_store
s.listener.context.configuration.highest_supported_version  s.listener.context.configuration.update(
s.listener.context.configuration.inner_protocols            s.listener.context.configuration.validate_certificates
>>> print(s.listener.context.configuration.highest_supported_version)
DTLSVersion.DTLSv1_2

from python-mbedtls.

inpos avatar inpos commented on June 12, 2024

Therefore, on handshake server can't find own certificate and raise exception with message that there is no suitable ciphers.

from python-mbedtls.

Synss avatar Synss commented on June 12, 2024

Indeed, I will look into it. Thank you.

from python-mbedtls.

stepheny avatar stepheny commented on June 12, 2024
class DTLSServer:
    def __init__(self, listener_timeout = 0.5, reuse_addr = True):
        srv_crt, srv_key = dtls_certs.server_cert()
...

The very last problem probably came from here. You've defined srv_crt, srv_key as local variable instead of DTLSServer member. Whence DTLSServer.__init__ finished, the instance would lost refs to them, thus they would be freed.
Replace srv_crt, srv_key with self.srv_crt, self.srv_key, and your code should work as expected.

from python-mbedtls.

stepheny avatar stepheny commented on June 12, 2024
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(" .", "<<<")

srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) should be called before srv.bind(address)

from python-mbedtls.

Synss avatar Synss commented on June 12, 2024

@stepheny

Replace srv_crt, srv_key with self.srv_crt, self.srv_key, and your code should work as expected.

This is correct, there is an ownership problem and I am not always reporting errors in an understandable way. I will be working on it for the next release.

Thank you for your help!

from python-mbedtls.

inpos avatar inpos commented on June 12, 2024

Last question. DTLS don't allow partial recv?
I mean, if one peer sent 4096 bytes packet and second recv 100 bytes chunk then next recv raise SSL lower protocol error. Is this correct?

from python-mbedtls.

Synss avatar Synss commented on June 12, 2024

This is not related with encryption but with the underlying protocols. UDP works with datagrams. You should pretty much always try to receive a full datagram or consider it lost. You can always try to pass the MSG_PEEK flag to the socket for a partial read... or use TCP.

from python-mbedtls.

inpos avatar inpos commented on June 12, 2024

I thought that in DTLS integrity is monitored and something similar to the stream is provided.
I implemented these functions in my project.
Thanks for answers.

from python-mbedtls.

Synss avatar Synss commented on June 12, 2024

These bugs should be fixed on the master branch now. I'll release sometime this week or on the week end now.

Thank you for reporting bugs!

Do not hesitate to give some more feedback if you can.

from python-mbedtls.

inpos avatar inpos commented on June 12, 2024

In this part of code:

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())

we are suddenly seeing

cli0.close()

This MUST be documented.
Without this close second accept() blocks forever.

from python-mbedtls.

Synss avatar Synss commented on June 12, 2024

we are suddenly seeing

cli0.close()
This MUST be documented.

You can reuse the name

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

    cli, cli_address = cli.accept()
    cli.setcookieparam(cli_address[0].encode("ascii"))
    block(cli.do_handshake)
    print(" .", "handshake", cli.negotiated_tls_version())

and let the ref count take care of the first connection. This is what is done in the README (dtls_server_main_loop()) and it works as well.

I could try to clean up my example programs and distribute them as well, like libmbedtls does. My problem with that is that extra example programs are not easy to test and I am afraid they go out of sync without notice.

DTLS is tough because UDP offers so little guarantees. Datagrams may even be lost or reordered during the handshake. This makes the handshake difficult or easily abused, see RFC4347.

libmbedtls makes the cookie optional (i.e. the first connection (cli0) would be usable) but leaving the cookie out makes any such DTLS server a potential vector in DOS attacks against third party servers. With the cookie, you first get a connection that only informs the server that some client wants to connect and a second one that is finally usable. This is just the way a DTLS handshake works server side. The good thing is that it is transparent to the clients.

There really is not much I can do about it I am afraid.

from python-mbedtls.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.