Currently I'm trying to develop a simple UDP DTLS server using pre shared keys on Ubuntu 20.04, to receive UDP datagrams from an embedded application. My remote example echo server based on python-mbedtls correctly echoes messages from a python-mbedtls client used for testing, but fails to verify the message MAC during the handshake with an openssl command line s_client. It similarly fails to verify the MAC of my embedded DTLS client, even though that is correctly sending messages to a command line openssl s_server. These are all using the same pre-shared keys and client-identity. Ideas on why MAC verification might be failing would be appreciated, or suggestions for diagnostics. There's not a lot that's useful in the debug outputs so far.
Here is my server code:
#!/home/ron/venvs/udpserver/bin/python3
"""Example DTLS server"""
import sys
import time
from contextlib import suppress
from functools import partial
from typing import Any, Callable, NoReturn, Optional, Tuple, Union
import datetime as dt
import socket
import struct
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 # type: ignore
from mbedtls.tls import (
DTLSConfiguration,
HelloVerifyRequest,
ServerContext,
TLSWrappedSocket,
)
conf = DTLSConfiguration(
ciphers=(
"TLS-PSK-WITH-AES-256-CBC-SHA",
"TLS-ECDHE-PSK-WITH-CHACHA20-POLY1305-SHA256",
"TLS-RSA-PSK-WITH-CHACHA20-POLY1305-SHA256"
),
pre_shared_key=('Client_identity',b'010102030405060708090a0b0c0d0e0f'),
validate_certificates=False
)
print("conf: {}".format(conf))
_enable_debug_output(conf)
_set_debug_level(3)
def echo_until(sock, end):
cli0, cli_address0 = sock.accept()
cli0.setcookieparam(cli_address0[0].encode("ascii"))
print("cli_address0: {}".format(cli_address0))
try:
# block(cli0.do_handshake)
cli0.do_handshake()
except HelloVerifyRequest:
print("HVR")
cli1, cli_address1 = cli0.accept()
cli0.close()
cli1.setcookieparam(cli_address1[0].encode("ascii"))
print("cli_address1: {}".format(cli_address1))
# block(cli1.do_handshake)
cli1.do_handshake()
print(" .", "handshake", cli1.negotiated_tls_version())
cli = cli1
while True:
# data = block(cli.recv, 4096)
data = cli.recv(4096)
print(" .", "R", data)
# nn = block(cli.send, data)
nn = cli.send(data)
print(" .", "S", nn, len(data))
if data == end:
break
print(" .", "done")
print(cli)
cli.close()
address = ("0.0.0.0", 9009)
host, port = address
ctx = ServerContext(conf)
srv = ctx.wrap_socket(
socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
print(" .", "bind", srv, address)
srv.bind(address)
while True:
print(" .", ">>>")
echo_until(srv, b"\0")
print(" .", "<<<")
and here is the client code that works:
#!/media/fred/venvs/dtlsclient/bin/python3
"""An example DTLS PSK client.
"""
from __future__ import annotations
import socket
import sys
import time
from contextlib import suppress
from typing import Any, Optional, Tuple, Union
from mbedtls._tls import _enable_debug_output, _set_debug_level # type: ignore
from mbedtls.exceptions import TLSError
from mbedtls.tls import (
ClientContext,
DTLSConfiguration,
TLSWrappedSocket,
)
if sys.version_info < (3, 10):
from typing_extensions import TypeAlias
else:
from typing import TypeAlias
if sys.version_info < (3, 8):
from typing_extensions import Final
else:
from typing import Final
def _echo_dtls(sock: TLSWrappedSocket, buffer: bytes, chunksize: int) -> bytes:
view = memoryview(buffer)
received = bytearray()
while len(received) != len(buffer):
part = view[len(received) : len(received) + chunksize]
sock.send(part)
data, _addr = sock.recvfrom(chunksize)
received += data
if not data:
# Avoid tight loop.
time.sleep(0.01)
return received
class Client:
def __init__(
self,
cli_conf: DTLSConfiguration,
proto: socket.SocketKind,
srv_address: _Address,
srv_hostname: Optional[str],
) -> None:
super().__init__()
self.cli_conf: Final = cli_conf
self.proto: Final = proto
self.srv_address: Final = srv_address
self.srv_hostname: Final = srv_hostname
self._sock: Optional[TLSWrappedSocket] = None
self._echo: Final = {
socket.SOCK_DGRAM: _echo_dtls,
}[self.proto]
def __enter__(self) -> Client:
self.start()
return self
def __exit__(self, *exc_info: object) -> None:
self.stop()
def __del__(self) -> None:
self.stop()
@property
def context(self) -> Optional[ClientContext]:
if self._sock is None:
return None
assert isinstance(self._sock.context, ClientContext)
return self._sock.context
def do_handshake(self) -> None:
if not self._sock:
return
self._sock.do_handshake()
def echo(self, buffer: bytes, chunksize: int) -> bytes:
if not self._sock:
return b""
return bytes(self._echo(self._sock, buffer, chunksize))
def start(self) -> None:
if self._sock:
self.stop()
self._sock = ClientContext(self.cli_conf).wrap_socket(
socket.socket(socket.AF_INET, self.proto),
server_hostname=self.srv_hostname,
)
self._sock.connect(self.srv_address)
def stop(self) -> None:
if not self._sock:
return
with suppress(TLSError, OSError):
self._sock.close()
self._sock = None
def restart(self) -> None:
self.stop()
self.start()
def main() -> None:
address = "149.28.170.96"
port = 9009
message = "Trundled off to the jungle"
server_name = "dtlsserver"
proto = socket.SOCK_DGRAM
debug = 3
conf = DTLSConfiguration(
ciphers=(
"TLS-PSK-WITH-AES-256-CBC-SHA",
"TLS-ECDHE-PSK-WITH-CHACHA20-POLY1305-SHA256",
"TLS-RSA-PSK-WITH-CHACHA20-POLY1305-SHA256"
),
pre_shared_key=('Client_identity',b'010102030405060708090a0b0c0d0e0f'),
validate_certificates=False
)
print(conf)
if debug is not None:
_enable_debug_output(conf)
_set_debug_level(debug)
with Client(
conf, proto, (address, port), server_name
) as cli:
cli.do_handshake()
received = cli.echo(message.encode("utf-8"), 1024)
print("Received:" + received.decode("utf-8"))
if __name__ == "__main__":
main( )
Whereas an openssl command line client invocation like this fails:
openssl s_client -psk "010102030405060708090a0b0c0d0e0f" -psk_identity "Client_identity" -dtls1_2 -port 9009 -host 149.28.170.96 -async -debug
This same call works ok with an openssl s_server running on the same remote Ubuntu server.
Here's a partial listing of debug text from the server showing the failure using the openssl client:
ssl_msg.c:3620: input record: msgtype = 22, version = [3:3], msglen = 68
ssl_msg.c:1131: => decrypt buf
ssl_msg.c:1342: using encrypt then mac
ssl_msg.c:1385: message mac does not match
ssl_msg.c:3755: ssl_decrypt_buf() returned -29056 (-0x7180)
ssl_msg.c:4921: => send alert message
ssl_msg.c:4922: send alert level=2 message=20
ssl_msg.c:2684: => write record
ssl_msg.c:2797: output record: msgtype = 21, version = [254:253], msglen = 2
ssl_msg.c:2087: => flush output
ssl_msg.c:2105: message length: 15, out_left: 15
ssl_msg.c:2112: ssl->f_send() returned 15 (-0xfffffff1)
ssl_msg.c:2140: <= flush output
ssl_msg.c:2853: <= write record
ssl_msg.c:4934: <= send alert message
ssl_msg.c:3917: ssl_get_next_record() returned -29056 (-0x7180)
ssl_tls.c:3650: mbedtls_ssl_read_record() returned -29056 (-0x7180)
ssl_msg.c:0072: set_timer to 0 ms
ssl_msg.c:0072: set_timer to 0 ms
Traceback (most recent call last):
File "/home/ron/venvs/udpserver/./dtlsthreaded.py", line 96, in <module>
echo_until(srv, b"\0")
File "/home/ron/venvs/udpserver/./dtlsthreaded.py", line 64, in echo_until
block(cli1.do_handshake)
File "/home/ron/venvs/udpserver/./dtlsthreaded.py", line 28, in block
result = cb(*args, **kwargs)
File "/home/ron/venvs/udpserver/lib/python3.10/site-packages/mbedtls/tls.py", line 341, in do_handshake
self._buffer.do_handshake()
File "src/mbedtls/_tls.pyx", line 1404, in mbedtls._tls.MbedTLSBuffer.do_handshake
File "src/mbedtls/_tls.pyx", line 1429, in mbedtls._tls.MbedTLSBuffer._handle_handshake_response
File "src/mbedtls/exceptions.pyx", line 53, in mbedtls.exceptions.check_error
File "src/mbedtls/exceptions.pyx", line 56, in mbedtls.exceptions.check_error
mbedtls.exceptions.TLSError: TLSError([0x7180] 'SSL - Verification of the message MAC failed')
ssl_tls.c:6800: => free
The same errors occur when all cipher suites are enabled, so am pretty sure the problem isn't the cipher. Suggestions for diagnosis appreciated. Thanks.
Though a similar question got answered in the Nordic DevZone it may be also the answer for this question.
For openssl, "010102030405060708090a0b0c0d0e0f" results in a 16 bytes secret. About
b'010102030405060708090a0b0c0d0e0f'
I'm not that sure. From other SO questions, I think it's a 32 bytes secret. If the peers don't share the same secret, the handshake fails. Some implementations will simple timeout the handshake, other may report a MAC validation error, because a mismatching secret creates different association keys, and with that, the MAC validation of the handshake 'Finish' fails.
Either use "30 31 30 31 30 32 30 33 30 34 30 35 30 36 30 37 30 38 30 39 30 61 30 62 30 63 30 64 30 65 30 66" (remove the spaces,) for openssl. or use b'\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' for python
Hope, that works.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.