Initial commit: up to and including data exchange functionality

This initial commit for the PyDTLS package includes the following functionality:

    * DTLS cookie exchange, using secure hmac cookies
    * A platform-independent routing UDP demultiplexer
    * SSL handshaking over UDP using the DTLS protocol
    * Datagram exchange using the DTLS protocol
    * SSL shutdown over UDP

The package is structured as follows:

    * dtls:               top-level package
    * dtls.demux:         demultiplexer package; automatically loads a
                          demultiplexer appropriate for the currently executing
                          platform
    * dtls.demux.router:  a routing demux for platforms whose network stacks
                          cannot assign incoming UDP packets to sockets based
                          on the sockets' connection information
    * dtls.demux.osnet:   a demux that uses the operating system's UDP packet
                          routing functionality
    * dtls.err:           package-wide error handling and error definitions
    * dtls.sslconnection: a client and server-side connection class for
                          UDP network connections secured with the DTLS protocol
    * dtls.openssl:       a ctypes-based wrapper for the OpenSSL library
    * dtls.test:          test scripts, utilities, and unit tests

The following binaries are provided:

    * libeay32.dll: cryptographic portion of the OpenSSL library
    * ssleay32.dll: protocol portion of the OpenSSL library (depends on former)
    * cygcrypto-1.0.0.dll: as libeay32.dll, but with debugging symbols
    * cygssl-1.0.0.dll: as ssleay32.dll, but with debugging symbols

All binaries have been built with the MinGW tool chain, targeted for msvcr90.
The unstripped dll's can be debugged on Windows with gdb. Cygwin is not used.
incoming
Ray Brown 2012-10-29 12:44:24 -07:00
parent 91323b475b
commit 9bb24c5d29
17 changed files with 1430 additions and 0 deletions

11
dtls/__init__.py 100644
View File

@ -0,0 +1,11 @@
# PyDTLS: datagram TLS for Python. Written by Ray Brown.
"""PyDTLS package
This package exports OpenSSL's DTLS support to Python. Importing it will add
the constant PROTOCOL_DTLSv1 to the Python standard library's ssl module.
Passing a datagram socket to that module's wrap_socket function (or
instantiating its SSLSocket class with a datagram socket) will activate this
module's DTLS implementation for the returned SSLSocket instance.
wrap_socket's parameters and their semantics have been maintained.
"""

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,26 @@
# Demux loader: imports a demux module appropriate for this platform.
# Written by Ray Brown.
"""UDP Demux
A UDP demux is a wrapper for a datagram socket. The demux must be initialized
with an unconnected datagram socket, referred to as the root socket. Once
initialized, the demux will create new connections to peer endpoints upon
arrival of datagrams from a new endpoint. Such a connection is of a
socket-derived type, and will receive datagrams only from the peer endpoint for
which it was created, and that are sent to the root socket.
Connections must be used for receiving datagrams only. Outgoing traffic should
be sent through the root socket.
Varying implementations of this functionality are provided for different
platforms.
"""
import sys
if sys.platform.startswith('win') or sys.platform.startswith('cygwin'):
from router import UDPDemux
else:
from osnet import UDPDemux
__all__ = ["UDPDemux"]

View File

@ -0,0 +1,62 @@
# OSNet demux: uses the OS network stack to demultiplex incoming datagrams
# among sockets bound to the same ports. Written by Ray Brown.
"""OS Network UDP Demux
This module implements a demux that uses the OS network stack to demultiplex
datagrams coming from different peers among datagram sockets that are all bound
to the port at which these datagrams are being received. The network stack is
instructed as to which socket an incoming datagram should be sent to by
connecting the destination socket to the peer endpoint.
The OSNet demux requires operating system functionality that exists in the
Linux kernel, but not in the Windows network stack.
Classes:
UDPDemux -- a network stack configuring UDP demux
Exceptions:
KeyError -- raised for unknown peer addresses
"""
class UDPDemux(object):
"""OS network stack configuring demux
This class implements a demux that creates sockets connected to peer
network endpoints, configuring the network stack to demultiplex
incoming datagrams from these endpoints among these sockets.
Methods:
get_connection -- create a new connection or retrieve an existing one
remove_connection -- remove an existing connection
service -- this method does nothing for this type of demux
"""
def get_connection(self, address):
"""Create or retrieve a muxed connection
Arguments:
address -- a peer endpoint in IPv4/v6 address format; None refers
to the connection for unknown peers
Return:
a bound, connected datagram socket instance, or the root socket
in case address was None
"""
def remove_connection(self, address):
"""Remove a muxed connection
Arguments:
address -- an address for which a muxed connection was previously
retrieved through get_connection, which has not yet
been removed
Return:
the socket object whose connection has been removed
"""
return self.connections.pop(address)

View File

@ -0,0 +1,172 @@
# Routing demux: forwards datagrams from the root socket to connected
# sockets bound to ephemeral ports. Written by Ray Brown.
"""Routing UDP Demux
This module implements an explicitly routing UDP demux. New connections create
datagram sockets bound to ephemeral ports on the loopback interface and
connected to a forwarding socket. The demux services incoming datagrams by
receiving them from the root socket and sending them to the socket belonging to
the connection that is associated with the sending peer.
A routing UDP demux can be used on any platform.
Classes:
UDPDemux -- an explicitly routing UDP demux
Exceptions:
InvalidSocketError -- exception raised for improper socket objects
KeyError -- raised for unknown peer addresses
"""
import socket
from logging import getLogger
from weakref import WeakValueDictionary
from ..err import InvalidSocketError
_logger = getLogger(__name__)
UDP_MAX_DGRAM_LENGTH = 65527
class UDPDemux(object):
"""Explicitly routing UDP demux
This class implements a demux that forwards packets from the root
socket to sockets belonging to connections. It does this whenever its
service method is invoked.
Methods:
remove_connection -- remove an existing connection
service -- distribute datagrams from the root socket to connections
forward -- forward a stored datagram to a connection
"""
_forwarding_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
_forwarding_socket.bind(('127.0.0.1', 0))
def __init__(self, datagram_socket):
"""Constructor
Arguments:
datagram_socket -- the root socket; this must be a bound, unconnected
datagram socket
"""
if datagram_socket.type != socket.SOCK_DGRAM:
raise InvalidSocketError("datagram_socket is not of " +
"type SOCK_DGRAM")
try:
datagram_socket.getsockname()
except:
raise InvalidSocketError("datagram_socket is unbound")
try:
datagram_socket.getpeername()
except:
pass
else:
raise InvalidSocketError("datagram_socket is connected")
self.datagram_socket = datagram_socket
self.payload = ""
self.payload_peer_address = None
self.connections = WeakValueDictionary()
def get_connection(self, address):
"""Create or retrieve a muxed connection
Arguments:
address -- a peer endpoint in IPv4/v6 address format; None refers
to the connection for unknown peers
Return:
a bound, connected datagram socket instance
"""
if self.connections.has_key(address):
return self.connections[address]
# We need a new datagram socket on a dynamically assigned ephemeral port
conn = socket.socket(self._forwarding_socket.family,
self._forwarding_socket.type,
self._forwarding_socket.proto)
conn.bind((self._forwarding_socket.getsockname()[0], 0))
conn.connect(self._forwarding_socket.getsockname())
if not address:
conn.setblocking(0)
self.connections[address] = conn
_logger.debug("Created new connection for address: %s", address)
return conn
def remove_connection(self, address):
"""Remove a muxed connection
Arguments:
address -- an address that was previously returned by the service
method and whose connection has not yet been removed
Return:
the socket object whose connection has been removed
"""
return self.connections.pop(address)
def service(self):
"""Service the root socket
Read from the root socket and forward one datagram to a
connection. The call will return without forwarding data
if any of the following occurs:
* An error is encountered while reading from the root socket
* Reading from the root socket times out
* The root socket is non-blocking and has no data available
* An empty payload is received
* A non-empty payload is received from an unknown peer (a peer
for which get_connection has not yet been called); in this case,
the payload is held by this instance and will be forwarded when
the forward method is called
Return:
if the datagram received was from a new peer, then the peer's
address; otherwise None
"""
self.payload, self.payload_peer_address = \
self.datagram_socket.recvfrom(UDP_MAX_DGRAM_LENGTH)
_logger.debug("Received datagram from peer: %s",
self.payload_peer_address)
if not self.payload:
self.payload_peer_address = None
return
if self.connections.has_key(self.payload_peer_address):
self.forward()
else:
return self.payload_peer_address
def forward(self):
"""Forward a stored datagram
When the service method returns the address of a new peer, it holds
the datagram from that peer in this instance. In this case, this
method will perform the forwarding step. The target connection is the
one associated with address None if get_connection has not been called
since the service method returned the new peer's address, and the
connection associated with the new peer's address if it has.
"""
assert self.payload
assert self.payload_peer_address
if self.connections.has_key(self.payload_peer_address):
conn = self.connections[self.payload_peer_address]
default = False
else:
conn = self.connections[None] # propagate exception if not created
default = True
_logger.debug("Forwarding datagram from peer: %s, default: %s",
self.payload_peer_address, default)
self._forwarding_socket.sendto(self.payload, conn.getsockname())
self.payload = ""
self.payload_peer_address = None

67
dtls/err.py 100644
View File

@ -0,0 +1,67 @@
# DTLS exceptions. Written by Ray Brown
"""DTLS Errors
This module defines error functionality and exception types for the dtls
package.
Classes:
SSLError -- exception raised for I/O errors
InvalidSocketError -- exception raised for improper socket objects
"""
from socket import error as socket_error
SSL_ERROR_NONE = 0
SSL_ERROR_SSL = 1
SSL_ERROR_WANT_READ = 2
SSL_ERROR_WANT_WRITE = 3
SSL_ERROR_WANT_X509_LOOKUP = 4
SSL_ERROR_SYSCALL = 5
SSL_ERROR_ZERO_RETURN = 6
SSL_ERROR_WANT_CONNECT = 7
SSL_ERROR_WANT_ACCEPT = 8
ERR_BOTH_KEY_CERT_FILES = 500
ERR_BOTH_KEY_CERT_FILES_SVR = 298
ERR_NO_CERTS = 331
ERR_COOKIE_MISMATCH = 0x1408A134
class SSLError(socket_error):
"""This exception is raised by modules in the dtls package."""
def __init__(self, *args):
super(SSLError, self).__init__(*args)
class OpenSSLError(SSLError):
"""This exception is raised when an error occurs in the OpenSSL library"""
def __init__(self, ssl_error, errqueue, result, func, args):
self.ssl_error = ssl_error
self.errqueue = errqueue
self.result = result
self.func = func
self.args = args
super(OpenSSLError, self).__init__(ssl_error, errqueue,
result, func, args)
class InvalidSocketError(Exception):
"""There is a problem with a socket passed to the dtls package."""
def __init__(self, *args):
super(InvalidSocketError, self).__init__(*args)
def raise_ssl_error(code):
"""Raise an SSL error with the given error code"""
raise SSLError(str(code) + ": " + _ssl_errors[code])
_ssl_errors = {
ERR_NO_CERTS: "No root certificates specified for verification " + \
"of other-side certificates",
ERR_BOTH_KEY_CERT_FILES: "Both the key & certificate files " + \
"must be specified",
ERR_BOTH_KEY_CERT_FILES_SVR: "Both the key & certificate files must be " + \
"specified for server-side operation"
}

BIN
dtls/libeay32.dll 100644

Binary file not shown.

455
dtls/openssl.py 100644
View File

@ -0,0 +1,455 @@
# OpenSSL library wrapper: provide access to both OpenSSL dynamic libraries
# through ctypes. Wrtten by Ray Brown.
"""OpenSSL Wrapper
This module provides run-time access to the OpenSSL cryptographic and
protocols libraries.
Exceptions:
OpenSSLError -- exception raised when errors occur in the OpenSSL library
Functions:
Integer constants:
BIO_NOCLOSE -- don't destroy encapsulated resource when closing BIO
BIO_CLOSE -- do destroy encapsulated resource when closing BIO
"""
import sys
import array
import socket
from logging import getLogger
from os import path
from err import OpenSSLError
from err import SSL_ERROR_NONE
import ctypes
from ctypes import CDLL
from ctypes import CFUNCTYPE
from ctypes import c_void_p, c_int, c_uint, c_ulong, c_char_p, c_size_t
from ctypes import c_short, c_ushort, c_ubyte, c_char
from ctypes import byref, POINTER
from ctypes import Structure, Union
from ctypes import create_string_buffer, sizeof, memmove
#
# Module initialization
#
_logger = getLogger(__name__)
#
# Library loading
#
if sys.platform.startswith('win'):
dll_path = path.abspath(path.dirname(__file__))
#libcrypto = CDLL(path.join(dll_path, "libeay32.dll"))
#libssl = CDLL(path.join(dll_path, "ssleay32.dll"))
libcrypto = CDLL(path.join(dll_path, "cygcrypto-1.0.0.dll"))
libssl = CDLL(path.join(dll_path, "cygssl-1.0.0.dll"))
else:
libcrypto = CDLL("libcrypto.so.1.0.0")
libssl = CDLL("libssl.so.1.0.0")
#
# Integer constants - exported
#
BIO_NOCLOSE = 0x00
BIO_CLOSE = 0x01
SSL_VERIFY_NONE = 0x00
SSL_VERIFY_PEER = 0x01
SSL_VERIFY_FAIL_IF_NO_PEER_CERT = 0x02
SSL_VERIFY_CLIENT_ONCE = 0x04
SSL_SESS_CACHE_OFF = 0x0000
SSL_SESS_CACHE_CLIENT = 0x0001
SSL_SESS_CACHE_SERVER = 0x0002
SSL_SESS_CACHE_BOTH = SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_SERVER
SSL_SESS_CACHE_NO_AUTO_CLEAR = 0x0080
SSL_SESS_CACHE_NO_INTERNAL_LOOKUP = 0x0100
SSL_SESS_CACHE_NO_INTERNAL_STORE = 0x0200
SSL_SESS_CACHE_NO_INTERNAL = \
SSL_SESS_CACHE_NO_INTERNAL_LOOKUP | SSL_SESS_CACHE_NO_INTERNAL_STORE
SSL_FILE_TYPE_PEM = 1
#
# Integer constants - internal
#
SSL_CTRL_SET_SESS_CACHE_MODE = 44
SSL_CTRL_SET_READ_AHEAD = 41
BIO_CTRL_DGRAM_SET_CONNECTED = 32
BIO_CTRL_DGRAM_GET_PEER = 46
BIO_CTRL_DGRAM_SET_PEER = 44
BIO_C_SET_NBIO = 102
DTLS_CTRL_LISTEN = 75
#
# Parameter data types
#
class c_long(object):
"""Long integer paramter class
c_long must be distinguishable from c_int, as the latter is associated
with a default error checking routine, while the former is not.
"""
class FuncParam(object):
"""Function parameter or return type"""
@classmethod
def from_param(cls, value):
if not isinstance(value, cls):
_logger.error("Parameter type mismatch: %s not of type %s",
value, cls)
raise TypeError(repr(value) + " is not of type " + repr(cls))
return value._as_parameter
def __init__(self, value):
self._as_parameter = value
class DTLSv1Method(FuncParam):
def __init__(self, value):
super(DTLSv1Method, self).__init__(value)
class SSLCTX(FuncParam):
def __init__(self, value):
super(SSLCTX, self).__init__(value)
class SSL(FuncParam):
def __init__(self, value):
super(SSL, self).__init__(value)
class BIO(FuncParam):
def __init__(self, value):
super(BIO, self).__init__(value)
#
# Socket address conversions
#
class sockaddr_storage(Structure):
_fields_ = [("ss_family", c_short),
("pad", c_char * 126)]
class sockaddr_in(Structure):
_fields_ = [("sin_family", c_short),
("sin_port", c_ushort),
("sin_addr", c_ulong * 1),
("sin_zero", c_char * 8)]
class sockaddr_in6(Structure):
_fields_ = [("sin6_family", c_short),
("sin6_port", c_ushort),
("sin6_flowinfo", c_ulong),
("sin6_addr", c_ulong * 4),
("sin6_scope_id", c_ulong)]
class sockaddr_u(Union):
_fields_ = [("ss", sockaddr_storage),
("s4", sockaddr_in),
("s6", sockaddr_in6)]
py_inet_ntop = getattr(socket, "inet_ntop", None)
if not py_inet_ntop:
windll = getattr(ctypes, "windll", None)
if windll:
wsa_inet_ntop = getattr(windll.ws2_32, "inet_ntop", None)
else:
wsa_inet_ntop = None
py_inet_pton = getattr(socket, "inet_pton", None)
if not py_inet_pton:
windll = getattr(ctypes, "windll", None)
if windll:
wsa_inet_pton = getattr(windll.ws2_32, "inet_pton", None)
else:
wsa_inet_pton = None
def inet_ntop(address_family, packed_ip):
if py_inet_ntop:
return py_inet_ntop(address_family,
array.array('L', packed_ip).tostring())
if wsa_inet_ntop:
string_buf = create_string_buffer(47)
wsa_inet_ntop(address_family, packed_ip,
string_buf, sizeof(string_buf))
if not string_buf.value:
raise ValueError("wsa_inet_ntop failed with: %s" %
array.array('L', packed_ip).tostring())
return string_buf.value
if address_family == socket.AF_INET6:
raise ValueError("Platform does not support IPv6")
return socket.inet_ntoa(array.array('L', packed_ip).tostring())
def inet_pton(address_family, string_ip):
if address_family == socket.AF_INET6:
ret_packed_ip = (c_ulong * 4)()
else:
ret_packed_ip = (c_ulong * 1)()
if py_inet_pton:
ret_string = py_inet_pton(address_family, string_ip)
ret_packed_ip[:] = array.array('L', ret_string)
elif wsa_inet_pton:
if wsa_inet_pton(address_family, string_ip, ret_packed_ip) != 1:
raise ValueError("wsa_inet_pton failed with: %s" % string_ip)
else:
if address_family == socket.AF_INET6:
raise ValueError("Platform does not support IPv6")
ret_string = socket.inet_aton(string_ip)
ret_packed_ip[:] = array.array('L', ret_string)
return ret_packed_ip
def addr_tuple_from_sockaddr_u(su):
if su.ss.ss_family == socket.AF_INET6:
return (inet_ntop(socket.AF_INET6, su.s6.sin6_addr),
socket.ntohs(su.s6.sin6_port),
socket.ntohl(su.s6.sin6_flowinfo),
socket.ntohl(su.s6.sin6_scope_id))
assert su.ss.ss_family == socket.AF_INET
return inet_ntop(socket.AF_INET, su.s4.sin_addr), \
socket.ntohs(su.s4.sin_port)
def sockaddr_u_from_addr_tuple(address):
su = sockaddr_u()
if len(address) > 2:
su.ss.ss_family = socket.AF_INET6
su.s6.sin6_addr[:] = inet_pton(socket.AF_INET6, address[0])
su.s6.sin6_port = socket.htons(address[1])
su.s6.sin6_flowinfo = socket.htonl(address[2])
su.s6.sin6_scope_id = socket.htonl(address[3])
else:
su.ss.ss_family = socket.AF_INET
su.s4.sin_addr[:] = inet_pton(socket.AF_INET, address[0])
su.s4.sin_port = socket.htons(address[1])
return su
#
# Error handling
#
def raise_ssl_error(result, func, args, ssl):
if not ssl:
ssl_error = SSL_ERROR_NONE
else:
ssl_error = _SSL_get_error(ssl, result)
errqueue = []
while True:
err = _ERR_get_error()
if not err:
break
buf = create_string_buffer(512)
_ERR_error_string_n(err, buf, sizeof(buf))
errqueue.append((err, buf.value))
_logger.debug("SSL error raised: ssl_error: %d, result: %d, " +
"errqueue: %s, func_name: %s",
ssl_error, result, errqueue, func.func_name)
raise OpenSSLError(ssl_error, errqueue, result, func, args)
def find_ssl_arg(args):
for arg in args:
if isinstance(arg, SSL):
return arg
def errcheck_ord(result, func, args):
if result <= 0:
raise_ssl_error(result, func, args, find_ssl_arg(args))
return args
def errcheck_p(result, func, args):
if not result:
raise_ssl_error(result, func, args, None)
return args
#
# Function prototypes
#
def _make_function(name, lib, args, export=True, errcheck="default"):
assert len(args)
def type_subst(map_type):
if _subst.has_key(map_type):
return _subst[map_type]
return map_type
sig = tuple(type_subst(i[0]) for i in args)
if not _sigs.has_key(sig):
_sigs[sig] = CFUNCTYPE(*sig)
if export:
glbl_name = name
__all__.append(name)
else:
glbl_name = "_" + name
func = _sigs[sig]((name, lib), tuple((i[2] if len(i) > 2 else 1,
i[1],
i[3] if len(i) > 3 else None)
[:3 if len(i) > 3 else 2]
for i in args[1:]))
func.func_name = name
if errcheck == "default":
# Assign error checker based on return type
if args[0][0] in (c_int,):
errcheck = errcheck_ord
elif args[0][0] in (c_void_p, c_char_p) or \
isinstance(args[0][0], FuncParam):
errcheck = errcheck_p
else:
errcheck = None
if errcheck:
func.errcheck = errcheck
globals()[glbl_name] = func
_subst = {c_long: ctypes.c_long}
_sigs = {}
__all__ = ["BIO_NOCLOSE", "BIO_CLOSE",
"SSL_VERIFY_NONE", "SSL_VERIFY_PEER",
"SSL_VERIFY_FAIL_IF_NO_PEER_CERT", "SSL_VERIFY_CLIENT_ONCE",
"SSL_SESS_CACHE_OFF", "SSL_SESS_CACHE_CLIENT",
"SSL_SESS_CACHE_SERVER", "SSL_SESS_CACHE_BOTH",
"SSL_SESS_CACHE_NO_AUTO_CLEAR", "SSL_SESS_CACHE_NO_INTERNAL_LOOKUP",
"SSL_SESS_CACHE_NO_INTERNAL_STORE", "SSL_SESS_CACHE_NO_INTERNAL",
"SSL_FILE_TYPE_PEM",
"DTLSv1_listen",
"BIO_dgram_set_connected",
"BIO_dgram_get_peer", "BIO_dgram_set_peer",
"BIO_set_nbio",
"SSL_CTX_set_session_cache_mode", "SSL_CTX_set_read_ahead",
"SSL_read", "SSL_write",
"SSL_CTX_set_cookie_cb"]
map(lambda x: _make_function(*x), (
("SSL_library_init", libssl, ((c_int, "ret"),)),
("SSL_load_error_strings", libssl, ((None, "ret"),)),
("DTLSv1_server_method", libssl, ((DTLSv1Method, "ret"),)),
("DTLSv1_client_method", libssl, ((DTLSv1Method, "ret"),)),
("SSL_CTX_new", libssl, ((SSLCTX, "ret"), (DTLSv1Method, "meth"))),
("SSL_CTX_free", libssl, ((None, "ret"), (SSLCTX, "ctx"))),
("SSL_CTX_set_cookie_generate_cb", libssl,
((None, "ret"), (SSLCTX, "ctx"), (c_void_p, "app_gen_cookie_cb")), False),
("SSL_CTX_set_cookie_verify_cb", libssl,
((None, "ret"), (SSLCTX, "ctx"), (c_void_p, "app_verify_cookie_cb")),
False),
("SSL_new", libssl, ((SSL, "ret"), (SSLCTX, "ctx"))),
("SSL_free", libssl, ((None, "ret"), (SSL, "ssl"))),
("SSL_set_bio", libssl,
((None, "ret"), (SSL, "ssl"), (BIO, "rbio"), (BIO, "wbio"))),
("BIO_new_dgram", libcrypto,
((BIO, "ret"), (c_int, "fd"), (c_int, "close_flag"))),
("BIO_free", libcrypto, ((c_int, "ret"), (BIO, "a"))),
("SSL_CTX_ctrl", libssl,
((c_long, "ret"), (SSLCTX, "ctx"), (c_int, "cmd"), (c_long, "larg"),
(c_void_p, "parg")), False),
("BIO_ctrl", libcrypto,
((c_long, "ret"), (BIO, "bp"), (c_int, "cmd"), (c_long, "larg"),
(c_void_p, "parg")), False),
("SSL_ctrl", libssl,
((c_long, "ret"), (SSL, "ssl"), (c_int, "cmd"), (c_long, "larg"),
(c_void_p, "parg")), False),
("ERR_get_error", libcrypto, ((c_long, "ret"),), False),
("ERR_error_string_n", libcrypto,
((None, "ret"), (c_ulong, "e"), (c_char_p, "buf"), (c_size_t, "len")),
False),
("SSL_get_error", libssl, ((c_int, "ret"), (SSL, "ssl"), (c_int, "ret")),
False, None),
("SSL_CTX_set_cipher_list", libssl,
((c_int, "ret"), (SSLCTX, "ctx"), (c_char_p, "str"))),
("SSL_CTX_use_certificate_file", libssl,
((c_int, "ret"), (SSLCTX, "ctx"), (c_char_p, "file"), (c_int, "type"))),
("SSL_CTX_use_certificate_chain_file", libssl,
((c_int, "ret"), (SSLCTX, "ctx"), (c_char_p, "file"))),
("SSL_CTX_use_PrivateKey_file", libssl,
((c_int, "ret"), (SSLCTX, "ctx"), (c_char_p, "file"), (c_int, "type"))),
("SSL_CTX_load_verify_locations", libssl,
((c_int, "ret"), (SSLCTX, "ctx"), (c_char_p, "CAfile"),
(c_char_p, "CApath"))),
("SSL_CTX_set_verify", libssl,
((None, "ret"), (SSLCTX, "ctx"), (c_int, "mode"),
(c_void_p, "verify_callback", 1, None))),
("SSL_accept", libssl, ((c_int, "ret"), (SSL, "ssl"))),
("SSL_connect", libssl, ((c_int, "ret"), (SSL, "ssl"))),
("SSL_set_connect_state", libssl, ((None, "ret"), (SSL, "ssl"))),
("SSL_set_accept_state", libssl, ((None, "ret"), (SSL, "ssl"))),
("SSL_do_handshake", libssl, ((c_int, "ret"), (SSL, "ssl"))),
("SSL_read", libssl,
((c_int, "ret"), (SSL, "ssl"), (c_void_p, "buf"), (c_int, "num")), False),
("SSL_write", libssl,
((c_int, "ret"), (SSL, "ssl"), (c_void_p, "buf"), (c_int, "num")), False),
("SSL_shutdown", libssl, ((c_int, "ret"), (SSL, "ssl"))),
("SSL_set_read_ahead", libssl,
((None, "ret"), (SSL, "ssl"), (c_int, "yes"))),
))
#
# Wrappers - functions generally equivalent to OpenSSL library macros
#
_rint_voidp_ubytep_uintp = CFUNCTYPE(c_int, c_void_p, POINTER(c_ubyte),
POINTER(c_uint))
_rint_voidp_ubytep_uint = CFUNCTYPE(c_int, c_void_p, POINTER(c_ubyte), c_uint)
def SSL_CTX_set_session_cache_mode(ctx, mode):
# Returns the previous value of mode
_SSL_CTX_ctrl(ctx, SSL_CTRL_SET_SESS_CACHE_MODE, mode, None)
def SSL_CTX_set_read_ahead(ctx, m):
# Returns the previous value of m
_SSL_CTX_ctrl(ctx, SSL_CTRL_SET_READ_AHEAD, m, None)
def SSL_CTX_set_cookie_cb(ctx, generate, verify):
def py_generate_cookie_cb(ssl, cookie, cookie_len):
try:
ret_cookie = generate(SSL(ssl))
except:
_logger.exception("Cookie generation failed")
return 0
cookie_len[0] = len(ret_cookie)
memmove(cookie, ret_cookie, cookie_len[0])
_logger.debug("Returning cookie: %s", cookie[:cookie_len[0]])
return 1
def py_verify_cookie_cb(ssl, cookie, cookie_len):
_logger.debug("Verifying cookie: %s", cookie[:cookie_len])
try:
verify(SSL(ssl), ''.join([chr(i) for i in cookie[:cookie_len]]))
except:
_logger.debug("Cookie verification failed")
return 0
return 1
gen_cb = _rint_voidp_ubytep_uintp(py_generate_cookie_cb)
ver_cb = _rint_voidp_ubytep_uint(py_verify_cookie_cb)
_SSL_CTX_set_cookie_generate_cb(ctx, gen_cb)
_SSL_CTX_set_cookie_verify_cb(ctx, ver_cb)
return gen_cb, ver_cb
def BIO_dgram_set_connected(bio, peer_address):
su = sockaddr_u_from_addr_tuple(peer_address)
_BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, byref(su))
def BIO_dgram_get_peer(bio):
su = sockaddr_u()
_BIO_ctrl(bio, BIO_CTRL_DGRAM_GET_PEER, 0, byref(su))
return addr_tuple_from_sockaddr_u(su)
def BIO_dgram_set_peer(bio, peer_address):
su = sockaddr_u_from_addr_tuple(peer_address)
_BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_PEER, 0, byref(su))
def BIO_set_nbio(bio, n):
_BIO_ctrl(bio, BIO_C_SET_NBIO, 1 if n else 0, None)
def DTLSv1_listen(ssl):
su = sockaddr_u()
ret = _SSL_ctrl(ssl, DTLS_CTRL_LISTEN, 0, byref(su))
errcheck_ord(ret, _SSL_ctrl, (ssl, DTLS_CTRL_LISTEN, 0, byref(su)))
return addr_tuple_from_sockaddr_u(su)
def SSL_read(ssl, length):
buf = create_string_buffer(length)
res_len = _SSL_read(ssl, buf, length)
return buf.raw[:res_len]
def SSL_write(ssl, data):
str_data = str(data)
return _SSL_write(ssl, str_data, len(str_data))

View File

@ -0,0 +1,445 @@
# SSL connection: state and behavior associated with the connection between
# the OpenSSL library and an individual peer. Written by Ray Brown.
"""SSL Connection
This module encapsulates the state and behavior associated with the connection
between the OpenSSL library and an individual peer when using the DTLS
protocol. It defines the application side of the interface of a client with a
DTLS server, and of a server with a DTLS client.
Classes:
SSLConnection -- DTLS peer association
Integer constants:
PROTOCOL_DTLSv1
The cert group must coincide in meaning and value with the one of the standard
library's ssl module, since its values can be passed to this module.
CERT_NONE
CERT_OPTIONAL
CERT_REQUIRED
"""
import errno
import socket
import hmac
from logging import getLogger
from os import urandom
from weakref import proxy
from err import OpenSSLError, InvalidSocketError
from err import raise_ssl_error
from err import SSL_ERROR_WANT_READ, ERR_COOKIE_MISMATCH, ERR_NO_CERTS
from openssl import *
_logger = getLogger(__name__)
PROTOCOL_DTLSv1 = 256
CERT_NONE = 0
CERT_OPTIONAL = 1
CERT_REQUIRED = 2
#
# One-time global OpenSSL library initialization
#
SSL_library_init()
SSL_load_error_strings()
class _Rsrc(object):
"""Wrapper base for library-owned resources"""
def __init__(self, value):
self._value = value
@property
def value(self):
return self._value
class _CTX(_Rsrc):
"""SSL_CTX wrapper"""
def __init__(self, value):
super(_CTX, self).__init__(value)
def __del__(self):
_logger.debug("Freeing SSL CTX: %d", self._value._as_parameter)
SSL_CTX_free(self._value)
self._value = None
class _BIO(_Rsrc):
"""BIO wrapper"""
def __init__(self, value):
super(_BIO, self).__init__(value)
self.owned = True
def disown(self):
self.owned = False
def __del__(self):
if self.owned:
_logger.debug("Freeing BIO: %d", self._value._as_parameter)
BIO_free(self._value)
self.owned = False
self._value = None
class _SSL(_Rsrc):
"""SSL structure wrapper"""
def __init__(self, value):
super(_SSL, self).__init__(value)
def __del__(self):
_logger.debug("Freeing SSL: %d", self._value._as_parameter)
SSL_free(self._value)
self._value = None
class _CallbackProxy(object):
"""Callback gateway to an SSLConnection object
This class forms a weak connection between a callback method and
an SSLConnection object. It can be passed as a callback callable
without creating a strong reference through bound methods of
the SSLConnection.
"""
def __init__(self, cbm):
self.ssl_connection = proxy(cbm.im_self)
self.ssl_func = cbm.im_func
def __call__(self, *args, **kwargs):
return self.ssl_func(self.ssl_connection, *args, **kwargs)
class SSLConnection(object):
"""DTLS peer association
This class associates two DTLS peer instances, wrapping OpenSSL library
state including SSL (struct ssl_st), SSL_CTX, and BIO instances.
"""
_rnd_key = urandom(16)
def _init_server(self):
if self.sock.type != socket.SOCK_DGRAM:
raise InvalidSocketError("sock must be of type SOCK_DGRAM")
from demux import UDPDemux
self.udp_demux = UDPDemux(self.sock)
self.rsock = self.udp_demux.get_connection(None)
self.wbio = _BIO(BIO_new_dgram(self.sock.fileno(), BIO_NOCLOSE))
self.rbio = _BIO(BIO_new_dgram(self.rsock.fileno(), BIO_NOCLOSE))
self.ctx = _CTX(SSL_CTX_new(DTLSv1_server_method()))
SSL_CTX_set_session_cache_mode(self.ctx.value, SSL_SESS_CACHE_OFF)
if self.cert_reqs == CERT_NONE:
verify_mode = SSL_VERIFY_NONE
elif self.cert_reqs == CERT_OPTIONAL:
verify_mode = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE
else:
verify_mode = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | \
SSL_VERIFY_FAIL_IF_NO_PEER_CERT
self.listening = False
self.listening_peer_address = None
self.pending_peer_address = None
self._config_ssl_ctx(verify_mode)
self.cb_keepalive = SSL_CTX_set_cookie_cb(
self.ctx.value,
_CallbackProxy(self._generate_cookie_cb),
_CallbackProxy(self._verify_cookie_cb))
self.ssl = _SSL(SSL_new(self.ctx.value))
SSL_set_accept_state(self.ssl.value)
def _init_client(self):
if self.sock.type != socket.SOCK_DGRAM:
raise InvalidSocketError("sock must be of type SOCK_DGRAM")
self.wbio = _BIO(BIO_new_dgram(self.sock.fileno(), BIO_NOCLOSE))
self.rbio = self.wbio
self.ctx = _CTX(SSL_CTX_new(DTLSv1_client_method()))
if self.cert_reqs == CERT_NONE:
verify_mode = SSL_VERIFY_NONE
else:
verify_mode = SSL_VERIFY_PEER
self._config_ssl_ctx(verify_mode)
self.ssl = _SSL(SSL_new(self.ctx.value))
SSL_set_connect_state(self.ssl.value)
def _config_ssl_ctx(self, verify_mode):
SSL_CTX_set_verify(self.ctx.value, verify_mode)
SSL_CTX_set_read_ahead(self.ctx.value, 1)
if self.certfile:
SSL_CTX_use_certificate_chain_file(self.ctx.value, self.certfile)
if self.keyfile:
SSL_CTX_use_PrivateKey_file(self.ctx.value, self.keyfile,
SSL_FILE_TYPE_PEM)
if self.ca_certs:
SSL_CTX_load_verify_locations(self.ctx.value, self.ca_certs, None)
if self.ciphers:
SSL_CTX_set_cipher_list(self.ctx.value, self.ciphers)
def _copy_server(self):
source = self.sock
self.sock = source.sock
self.udp_demux = source.udp_demux
self.rsock = self.udp_demux.get_connection(source.pending_peer_address)
self.wbio = _BIO(BIO_new_dgram(self.sock.fileno(), BIO_NOCLOSE))
self.rbio = _BIO(BIO_new_dgram(self.rsock.fileno(), BIO_NOCLOSE))
BIO_dgram_set_peer(self.wbio.value, source.pending_peer_address)
self.ctx = source.ctx
self.ssl = source.ssl
new_source_wbio = _BIO(BIO_new_dgram(source.sock.fileno(),
BIO_NOCLOSE))
new_source_rbio = _BIO(BIO_new_dgram(source.rsock.fileno(),
BIO_NOCLOSE))
source.ssl = _SSL(SSL_new(self.ctx.value))
source.rbio = new_source_rbio
source.wbio = new_source_wbio
SSL_set_bio(source.ssl.value,
new_source_rbio.value,
new_source_wbio.value)
new_source_rbio.disown()
new_source_wbio.disown()
def _check_nbio(self):
BIO_set_nbio(self.wbio.value, self.sock.gettimeout() is not None)
if self.wbio is not self.rbio:
BIO_set_nbio(self.rbio.value, self.rsock.gettimeout() is not None)
def _get_cookie(self, ssl):
assert self.listening
assert self.ssl.value._as_parameter == ssl._as_parameter
if self.listening_peer_address:
peer_address = self.listening_peer_address
else:
peer_address = BIO_dgram_get_peer(self.rbio.value)
cookie_hmac = hmac.new(self._rnd_key, str(peer_address))
return cookie_hmac.digest()
def _generate_cookie_cb(self, ssl):
return self._get_cookie(ssl)
def _verify_cookie_cb(self, ssl, cookie):
if self._get_cookie(ssl) != cookie:
raise Exception("DTLS cookie mismatch")
def __init__(self, sock, keyfile=None, certfile=None,
server_side=False, cert_reqs=CERT_NONE,
ssl_version=PROTOCOL_DTLSv1, ca_certs=None,
do_handshake_on_connect=True,
suppress_ragged_eofs=True, ciphers=None):
"""Constructor
Arguments:
these arguments match the ones of the SSLSocket class in the
standard library's ssl module
"""
if keyfile and not certfile or certfile and not keyfile:
raise_ssl_error(ERR_BOTH_KEY_CERT_FILES)
if server_side and not keyfile:
raise_ssl_error(ERR_BOTH_KEY_CERT_FILES_SVR)
if cert_reqs != CERT_NONE and not ca_certs:
raise_ssl_error(ERR_NO_CERTS)
if not ciphers:
ciphers = "DEFAULT"
self.sock = sock
self.keyfile = keyfile
self.certfile = certfile
self.cert_reqs = cert_reqs
self.ca_certs = ca_certs
self.do_handshake_on_connect = do_handshake_on_connect
self.suppress_ragged_eofs = suppress_ragged_eofs
self.ciphers = ciphers
if isinstance(sock, SSLConnection):
self._copy_server()
elif server_side:
self._init_server()
else:
self._init_client()
SSL_set_bio(self.ssl.value, self.rbio.value, self.wbio.value)
self.rbio.disown()
self.wbio.disown()
def listen(self):
"""Server-side cookie exchange
This method reads datagrams from the socket and initiates cookie
exchange, upon whose successful conclusion one can then proceed to
the accept method. Alternatively, accept can be called directly, in
which case it will call this method. In order to prevent denial-of-
service attacks, only a small, constant set of computing resources
are used during the listen phase.
On some platforms, listen must be called so that packets will be
forwarded to accepted connections. Doing so is therefore recommened
in all cases for portable code.
Return value: a peer address if a datagram from a new peer was
encountered, None if a datagram for a known peer was forwarded
"""
self.pending_peer_address = None
try:
peer_address = self.udp_demux.service()
except socket.timeout:
peer_address = None
except socket.error as sock_err:
if sock_err.errno != errno.EWOULDBLOCK:
_logger.exception("Unexpected socket error in listen")
raise
peer_address = None
if not peer_address:
_logger.debug("Listen returning without peer")
return
# The demux advises that a datagram from a new peer may have arrived
if type(peer_address) is tuple:
# For this type of demux, the write BIO must be pointed at the peer
BIO_dgram_set_peer(self.wbio.value, peer_address)
self.udp_demux.forward()
self.listening_peer_address = peer_address
self._check_nbio()
self.listening = True
try:
_logger.debug("Invoking DTLSv1_listen for ssl: %d",
self.ssl.value._as_parameter)
dtls_peer_address = DTLSv1_listen(self.ssl.value)
except OpenSSLError as err:
if err.ssl_error == SSL_ERROR_WANT_READ:
# This method must be called again to forward the next datagram
_logger.debug("DTLSv1_listen must be resumed")
return
elif err.errqueue and err.errqueue[0][0] == ERR_COOKIE_MISMATCH:
_logger.debug("Mismatching cookie received; aborting handshake")
return
_logger.exception("Unexpected error in DTLSv1_listen")
raise
finally:
self.listening = False
self.listening_peer_address = None
if type(peer_address) is tuple:
_logger.debug("New local peer: %s", dtls_peer_address)
self.pending_peer_address = peer_address
else:
self.pending_peer_address = dtls_peer_address
_logger.debug("New peer: %s", self.pending_peer_address)
return self.pending_peer_address
def accept(self):
"""Server-side UDP connection establishment
This method returns a server-side SSLConnection object, connected to
that peer most recently returned from the listen method and not yet
connected. If there is no such peer, then the listen method is invoked.
Return value: SSLConnection connected to a new peer, None if packet
forwarding only to an existing peer occurred.
"""
if not self.pending_peer_address:
if not self.listen():
_logger.debug("Accept returning without connection")
return
new_conn = SSLConnection(self, self.keyfile, self.certfile, True,
self.cert_reqs, PROTOCOL_DTLSv1,
self.ca_certs, self.do_handshake_on_connect,
self.suppress_ragged_eofs, self.ciphers)
self.pending_peer_address = None
if self.do_handshake_on_connect:
# Note that since that connection's socket was just created in its
# constructor, the following operation must be blocking; hence
# handshake-on-connect can only be used with a routing demux if
# listen is serviced by a separate application thread, or else we
# will hang in this call
new_conn.do_handshake()
_logger.debug("Accept returning new connection for new peer")
return new_conn
def connect(self, peer_address):
"""Client-side UDP connection establishment
This method connects this object's underlying socket. It subsequently
performs a handshake if do_handshake_on_connect was set during
initialization.
Arguments:
peer_address - address tuple of server peer
"""
self.sock.connect(peer_address)
BIO_dgram_set_connected(self.wbio.value, peer_address)
assert self.wbio is self.rbio
if self.do_handshake_on_connect:
self.do_handshake()
def do_handshake(self):
"""Perform a handshake with the peer
This method forces an explicit handshake to be performed with either
the client or server peer.
"""
_logger.debug("Initiating handshake...")
self._check_nbio()
SSL_do_handshake(self.ssl.value)
_logger.debug("...completed handshake")
def read(self, len=1024):
"""Read data from connection
Read up to len bytes and return them.
Arguments:
len -- maximum number of bytes to read
Return value:
string containing read bytes
"""
self._check_nbio()
return SSL_read(self.ssl.value, len)
def write(self, data):
"""Write data to connection
Write data as string of bytes.
Arguments:
data -- buffer containing data to be written
Return value:
number of bytes actually transmitted
"""
self._check_nbio()
return SSL_write(self.ssl.value, data)
def shutdown(self):
"""Shut down the DTLS connection
This method attemps to complete a bidirectional shutdown between
peers. For non-blocking sockets, it should be called repeatedly until
it no longer raises continuation request exceptions.
"""
self._check_nbio()
try:
SSL_shutdown(self.ssl.value)
except OpenSSLError as err:
if err.result == 0:
# close-notify alert was just sent; wait for same from peer
# Note: while it might seem wise to suppress further read-aheads
# with SSL_set_read_ahead here, doing so causes a shutdown
# failure (ret: -1, SSL_ERROR_SYSCALL) on the DTLS shutdown
# initiator side.
SSL_shutdown(self.ssl.value)
else:
raise

BIN
dtls/ssleay32.dll 100644

Binary file not shown.

View File

@ -0,0 +1,5 @@
# Test: unit tests for PyDTLS. Written by Ray Brown.
"""PyDTLs unit tests
This package contains unit tests and other test scripts and resources.
"""

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB3TCCAYegAwIBAgIJAJdD48tCuQ4ZMA0GCSqGSIb3DQEBBQUAMEoxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRMwEQYDVQQKEwpSYXkgQ0EgSW5j
MREwDwYDVQQDEwhSYXlDQUluYzAeFw0xMjA5MjEyMTE0MTZaFw0xMzA5MjEyMTE0
MTZaMEoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRMwEQYDVQQK
EwpSYXkgQ0EgSW5jMREwDwYDVQQDEwhSYXlDQUluYzBcMA0GCSqGSIb3DQEBAQUA
A0sAMEgCQQC33ThS1uvx6c9/jdQgPrLnVepv9NJdtyRMIDH3ZVfIKwwC6Nde3CJh
bdo3j2njxlY7pw0P6J/F6mQpGtsRGaX1AgMBAAGjUDBOMB0GA1UdDgQWBBQBj0cB
lkz531jiz4oLP0osGlVR3zAfBgNVHSMEGDAWgBQBj0cBlkz531jiz4oLP0osGlVR
3zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA0EAUyS5rT6LFjhhPeoW1Gk1
sibwzgPSKdEzllt0vGZtWESekkoJ0UxnDvRzKv8OEVSclt+2YuzJXuZGteFABxDA
Cw==
-----END CERTIFICATE-----

View File

@ -0,0 +1,36 @@
Certificate:
Data:
Version: 1 (0x0)
Serial Number: 1 (0x1)
Signature Algorithm: md5WithRSAEncryption
Issuer: C=US, ST=Washington, O=Ray CA Inc, CN=RayCAInc
Validity
Not Before: Sep 21 21:16:18 2012 GMT
Not After : Sep 21 21:16:18 2013 GMT
Subject: C=US, ST=Washington, O=Ray Srv Inc, CN=RaySrvInc
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (512 bit)
Modulus:
00:b8:f7:77:26:6c:9d:25:f5:e1:ca:28:b4:6a:0b:
15:81:13:0e:51:c3:b2:ba:57:5b:69:ff:cb:bb:86:
d9:f9:4d:33:1f:43:92:a1:89:2f:d0:08:5b:cf:b7:
a1:2b:ea:06:31:e5:32:fd:2b:86:54:09:fb:24:bc:
a3:ce:56:22:f1
Exponent: 65537 (0x10001)
Signature Algorithm: md5WithRSAEncryption
12:4c:55:17:c1:c4:19:5f:0d:e2:66:00:84:37:22:62:9b:6d:
9e:76:fd:a5:82:f5:a2:41:c6:ae:16:88:16:ab:bd:0f:5d:0b:
7c:fd:52:2a:9b:3f:ea:cf:ea:35:29:37:61:a2:15:8f:27:56:
38:71:1f:eb:1a:f8:cc:99:22:17
-----BEGIN CERTIFICATE-----
MIIBgDCCASoCAQEwDQYJKoZIhvcNAQEEBQAwSjELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCldhc2hpbmd0b24xEzARBgNVBAoTClJheSBDQSBJbmMxETAPBgNVBAMTCFJh
eUNBSW5jMB4XDTEyMDkyMTIxMTYxOFoXDTEzMDkyMTIxMTYxOFowTDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xFDASBgNVBAoTC1JheSBTcnYgSW5j
MRIwEAYDVQQDEwlSYXlTcnZJbmMwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAuPd3
JmydJfXhyii0agsVgRMOUcOyuldbaf/Lu4bZ+U0zH0OSoYkv0Ahbz7ehK+oGMeUy
/SuGVAn7JLyjzlYi8QIDAQABMA0GCSqGSIb3DQEBBAUAA0EAEkxVF8HEGV8N4mYA
hDciYpttnnb9pYL1okHGrhaIFqu9D10LfP1SKps/6s/qNSk3YaIVjydWOHEf6xr4
zJkiFw==
-----END CERTIFICATE-----

View File

@ -0,0 +1,10 @@
-----BEGIN PRIVATE KEY-----
MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAuPd3JmydJfXhyii0
agsVgRMOUcOyuldbaf/Lu4bZ+U0zH0OSoYkv0Ahbz7ehK+oGMeUy/SuGVAn7JLyj
zlYi8QIDAQABAkAygtnV82lC2Y/Mbis+nkJEGlkZuRCQ1JRRMRqI3n2eF6CviqF3
PiBXIEEExzKihC9bvbHKTAkYDLr+/4YpbiQBAiEA7JLS5Lp7KI/ayWwEzl2r5XXu
k/cbH++A4zZz6A9XIsECIQDIJ8ciDa5/VGyQnYMzBNgKnwaFDDBOiEUFDaU/9ZN8
MQIgCG3Gw819G9ncQrbtiOi/eiJ0iKMSPVYMMow7HvaE9UECIQCLyQwPwlJd5s4z
aW4ZkYZ4VHuvK8YI8q6RSuhf9Nhd4QIgFbRNdEeehgrzGzGug2yVCMzVzS3MQNBJ
6LqBZaPlFsM=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,102 @@
# PyDTLS sequential echo. Written by Ray Brown.
"""PyDTLS sequential echo
This script runs a sequential echo server. It is sequential in that it will
respond without error only to a single sclient that invokes the following steps
in order:
* DTLS cookie exchange on port 28000 of localhost
* DTLS handshake (application-default ciphers)
* Write and receive echo back for an arbitrary number of datagrams
* Isue shutdown notification and receive the shutdown notification response
Note that this script's operation is slow and inefficient on purpose: it
invokes the demux without socket select, but with 5-second timeouts after
the cookie exchange; this is done so that one can follow the debug logs when
operating this server from a client shell interactively.
"""
import socket
from os import path
from logging import basicConfig, DEBUG
basicConfig(level=DEBUG) # set now for dtls import code
from dtls.sslconnection import SSLConnection
from dtls.err import SSLError, SSL_ERROR_WANT_READ, SSL_ERROR_ZERO_RETURN
def main():
sck = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sck.bind(("127.0.0.1", 28000))
sck.settimeout(30)
cert_path = path.join(path.abspath(path.dirname(__file__)), "certs")
scn = SSLConnection(
sck,
keyfile=path.join(cert_path, "server-key.pem"),
certfile=path.join(cert_path, "server-cert.pem"),
server_side=True,
ca_certs=path.join(cert_path, "ca-cert.pem"),
do_handshake_on_connect=False)
cnt = 0
while True:
cnt += 1
print "Listen invocation: %d" % cnt
peer_address = scn.listen()
if peer_address:
print "Completed listening for peer: %s" % str(peer_address)
break
print "Accepting..."
conn = scn.accept()
sck.settimeout(5)
conn.rsock.settimeout(5)
cnt = 0
while True:
cnt += 1
print "Listen invocation: %d" % cnt
peer_address = scn.listen()
assert not peer_address
print "Handshake invocation: %d" % cnt
try:
conn.do_handshake()
except SSLError as err:
if err.args[0] == SSL_ERROR_WANT_READ:
continue
raise
print "Completed handshaking with peer"
break
cnt = 0
while True:
cnt += 1
print "Listen invocation: %d" % cnt
peer_address = scn.listen()
assert not peer_address
print "Read invocation: %d" % cnt
try:
message = conn.read()
except SSLError as err:
if err.args[0] == SSL_ERROR_WANT_READ:
continue
if err.args[0] == SSL_ERROR_ZERO_RETURN:
break
raise
print message
conn.write("Back to you: " + message)
cnt = 0
while True:
cnt += 1
print "Listen invocation: %d" % cnt
peer_address = scn.listen()
assert not peer_address
print "Shutdown invocation: %d" % cnt
try:
conn.shutdown()
except SSLError as err:
if err.args[0] == SSL_ERROR_WANT_READ:
continue
raise
break
if __name__ == "__main__":
main()

26
dtls/test/rl.py 100644
View File

@ -0,0 +1,26 @@
# PyDTLS reloader. Written by Ray Brown.
"""PyDTLS package reloader
This script reloads all modules of the DTLS package. This can be useful in
runtime environments that usually persist across package file edits, such as
the IPython shell.
"""
import dtls
import dtls.err
import dtls.sslconnection
import dtls.openssl
import dtls.demux
import dtls.demux.router
def main():
reload(dtls)
reload(dtls.err)
reload(dtls.sslconnection)
reload(dtls.openssl)
reload(dtls.demux)
reload(dtls.demux.router)
reload(dtls.sslconnection)
if __name__ == "__main__":
main()