简体   繁体   中英

Why does message MAC verification fail with an OpenSSL client but succeed with a python-mbedtls client

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM