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.
I'm making very simple unthreaded Python port scanner:
import sys, socket
1
2 try:
3 host = sys.argv[1]
4 print(host)
5 except IndexError:
6 print('missing arg')
7 sys.exit()
8
9 ports = [22, 23, 80, 53, 443, 8080]
10 try:
11 for port in ports:
12 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
13 socket.setdefaulttimeout(1.0)
14 result = sock.connect_ex((host, port)) # hangs on this line
15 print(result)
16 print(f'Checking port {port}')
17 if result == 0:
18 print(f'Port {port} is open')
19 sock.close()
20 except KeyboardInterrupt:
21 print('Exiting ...')
22 sys.exit()
23 except Exception as e:
24 print(str(e))
25 sys.exit()
Before I removed all print statements I found out that the script hung on indicated line. The line where simple indication of established connection is made (or not made).
Port scanner is ran against my router by running python port_scanner.py 192.168.1.254
What might be an issue here ?
Moving socket.setdefaulttimeout(1.0) above the for loop solved the issue. Thanks goes to #Barmar
I'm fairly new to Python. I have two scripts running that are communicating with each other, but once the sender process stops sending bytes, the receiver process receives an endless stream of what decodes (UTF-8) to new lines. I've reduced the code as much as I could to keep things simple:
Sender Python script.
import socket
s = socket.socket()
host = "127.0.0.1"
port = 5409
s.bind((host, port))
data_to_send = ['1','2','3','4','5','6','7','8','9']
s.listen(1)
c, addr = s.accept()
print ('Got connection from ', addr,'. Sending data...', sep='')
for data in data_to_send:
message = data.encode('utf-8')
c.sendall(message)
Receiver Python script.
import socket
messages_received = 0
s = socket.socket()
host = "127.0.0.1"
port = 5409
s.connect((host, port))
while True:
incoming_message = s.recv(1024).decode('utf-8')
messages_received += 1
# This condition is just to avoid printing thousands of lines
if messages_received < 10:
print(messages_received, ':', incoming_message)
Receiver output.
1 : 1
2 : 23456789
3 :
4 :
5 :
6 :
7 :
8 :
9 :
What am I doing wrong? I would ideally want the sender script to break out of the "While True" loop if the socket closes.
As #jasonharper pointed out, all I needed to do was to check for empty messages and break the loop as soon as that happens. When the sender doesn't send anything, the receiver doesn't receive empty massages, it just waits for a valid message, which I didn't know. The following code worked for me:
Sender Python script.
import socket
import time
s = socket.socket()
host = "127.0.0.1"
port = 5409
s.bind((host, port))
data_to_send = ['1','2','3','4','5','6','7','8','9']
s.listen(1)
c, addr = s.accept()
print ('Got connection from ', addr,'. Sending data...', sep='')
for data in data_to_send:
message = data.encode('utf-8')
c.sendall(message)
time.sleep(1)
Receiver Python script.
import socket
messages_received = 0
s = socket.socket()
host = "127.0.0.1"
port = 5409
s.connect((host, port))
while True:
incoming_message = s.recv(1024).decode('utf-8')
messages_received += 1
if not incoming_message:
break
if messages_received < 10:
print(messages_received, ':', incoming_message)
Receiver output.
1 : 1
2 : 2
3 : 3
4 : 4
5 : 5
6 : 6
7 : 7
8 : 8
9 : 9
well you can try setting the buffer size on sender side :
socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1024) # Buffer size 1024
if it dosent work you can try even dict format so you send data as json format.
I am trying to connect to a socket server and view/download a temperature readings polling stream
i.e.
72.81
72.83
72.79
72.85
But what I get are float values split in half.
72
.35
72
.36
72
.36
72
.37
72
.38
72
.38
72
.38
72
.39
How do I output unbroken float values from a socket connection?
client code:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(("192.168.1.249" , 8080))
s.sendall(b"GET / HTTP/1.1\r\nHost: webcode.me\r\nAccept: text/html\r\nConnection: close\r\n\r\n")
while True:
data = s.recv(1024)
if not data:
break
print(data.decode())
I need to communicate and pass values to a serial connected device using RS232 Protocol. I need to pass commands through the 8 bytes of data and then be able to receive the response afterwards.. Im not sure how to write this in PySerial so if anyone can help out it would be great (9600 Baud, 8 data bits, No parity, and 1 stop bit.)
import serial
ser = serial.Serial('/dev/ttyUSB0') # open serial port
print(ser.name) # check which port was really used
ser.write(b'hello') # write a string
ser.close() # close port
The Timer Manager Command structure consists of one start byte, one command byte, five bytes of data, and a one byte checksum. Each message packet is formatted as follows:
BYTE 0 BYTE 1 BYTE 2 BYTE 3 BYTE 4 BYTE 5 BYTE 6 BYTE 7
200 COMMAND DATA1 DATA2 DATA3 DATA4 DATA5 CK SUM
Im looking to receive the following back from the machine:
If command was successfully received, the Timer Manager will respond with:
BYTE 0 BYTE 1 BYTE 2
6 0 6
The actual data that I want to send is this
Data i need to pass to the timer is structured this way:
BYTE 0 BYTE 1 BYTE 2 BYTE 3 BYTE 4 BYTE 5 BYTE 6 BYTE 7
200 31 4 0 0 0 0 235
Is this passed via bytearray ?
ser.write( bytearray(200,31,4,0,0,0,0,235) );
I generally have something like this to do binary IO over a serial port:
from timeit import default_timer as clk
from serial import Serial, SerialException
class TimeManager(object):
def __init__(self, port, baudrate=9600):
self.ser = Serial(port, baudrate=baudrate)
self.ser.open()
self.ser.flushInput()
self.ser.flushOutput()
def send(self, tx):
tx = bytearray(tx)
try:
self.ser.write(tx)
self.ser.flush()
except SerialException as e:
if e.args == (5, "WriteFile", "Access is denied."):
# This occurs on win32 when a USB serial port is
# unplugged and replugged. It should be fixed by
# closing and reopening the port, which should happen
# in the error handling of our caller.
raise IOError(errno.ENOENT, "Serial port disappeared.",
self.ser.portstr)
else:
raise
def receive(self):
rx = bytearray()
delay = 10e-3 # s
timeout = 1 # s
end_time = clk() + timeout
while True:
time_remaining = end_time - clk()
if time_remaining < 0:
break
rx += self.ser.read(self.ser.inWaiting())
if 0 in rx:
break
time.sleep(delay)
if time_remaining <= 0:
raise IOError(errno.ETIMEDOUT, "Communication timed out.")
return rx
tm = TimeManager("/dev/ttyS0")
My device sends null terminated messages (the if 0 in rx: line). You'd have to figure out a similar condition for your messages.
First of all, due to the fact that you use RS232, you must set the ASCII characters you wanna send in variables. And then, when you got in a variable all the sentence you want to send, sent it decoding it into bytes.
It would be something like this.
def sendserial(sendstring):
ser.port(yourport)
try:
ser.open()
except Exception as e:
flag=1
if ser.isOpen():
try:
ser.flushInput()
ser.flushOutput()
ser.write(bytes(sendstring,'iso-8859-1'))
#iso 8859-1 is the only encode that works for me
time.sleep(0.5)
numOfLines = 0
while True:
resp = bytes.decode(ser.readline())
result = ord(str(response))
if result == ord(ACK)
#I set previously the ACK var to the ASCII symbol that the machine returns
response = 'Y'
else:
response = 'N'
numOfLines = numOfLines +1
if (numOfLines>=1):
break
ser.close()
except Exception as e1:
print('Communication error...:' + str(e1))
else:
pass
return(response)