Merge pull request #2 from mcfreis/clean-redo-in-steps

Merge "DTLSv1.2 methods and extensions added" from branch to master
incoming
mcfreis 2017-03-21 08:10:17 +01:00 committed by GitHub
commit d12b23ba9f
24 changed files with 2600 additions and 651 deletions

153
ChangeLog
View File

@ -1,3 +1,156 @@
2017-03-17 Björn Freise <mcfreis@gmx.net>
Added a wrapper for a DTLS-Socket either as client or server - including unit tests
* dtls/__init__.py: Import SSLContext() and SSL() for external use
* dtls/wrapper.py: Added class DtlsSocket() to be used as client or server
* dtls/test/unit_wrapper.py: unit test for DtlsSocket()
2017-03-17 Björn Freise <mcfreis@gmx.net>
Added more on error evaluation and a method to get the peer certificate chain
* dtls/__init__.py: import error codes from err.py as error_codes for external access
* dtls/err.py: Added errors for ERR_WRONG_SSL_VERSION, ERR_CERTIFICATE_VERIFY_FAILED, ERR_NO_SHARED_CIPHER and ERR_SSL_HANDSHAKE_FAILURE
* dtls/openssl.py:
- Added constant SSL_BUILD_CHAIN_FLAG_NONE for SSL_CTX_build_cert_chain()
- Added method SSL_get_peer_cert_chain()
* dtls/patch.py: Added getpeercertchain() as method to ssl.SSLSocket()
* dtls/sslconnection.py:
- Bugfix SSLContext.set_ecdh_curve() returns 1 for success and 0 for failure
- SSLContext.build_cert_chain() changed default flags to SSL_BUILD_CHAIN_FLAG_NONE
- In SSLConnection() the mtu size gets only set if no user config function is given
- SSLConnection.listen() raises an exception for ERR_WRONG_VERSION_NUMBER, ERR_COOKIE_MISMATCH, ERR_NO_SHARED_CIPHER and all other unknown errors
- SSLConnection.read() and write() now can also raise ERR_PORT_UNREACHABLE
- If SSLConnection.write() successfully writes bytes to the peer, then the handshake is assumed to be okay
- Added method SSLConnection.getpeercertchain()
* dtls/test/unit.py: ThreadedEchoServer() with an extra exception branch for the newly raised exceptions in SSLConnection.listen()
2017-03-17 Björn Freise <mcfreis@gmx.net>
Added certificate creation using ECDSA
* dtls/test/makecerts_ec.bat: creates ca-cert_ec.pem, keycert_ec.pem and server-cert_ec.pem
* dtls/test/openssl_ca.cnf and openssl_server.cnf: Added HOME to be able to use the conf file under windows
2017-03-17 Björn Freise <mcfreis@gmx.net>
Added an interface in SSLConnection() to access SSLContext() and SSL() for manipulating settings during creation
* dtls/openssl.py:
- Added utility functions EC_curve_nist2nid() and EC_curve_nid2nist()
* dtls/patch.py:
- Extended wrap_socket() arguments with callbacks for user config functions of ssl context and ssl session values
- Extended SSLSocket() arguments with callbacks for user config functions of ssl context and ssl session values
* dtls/sslconnection.py:
- Extended SSLConnection() arguments with callbacks for user config functions of ssl context and ssl session values
- During the init of client and server the corresponding user config functions are called (if given)
- Added new classes SSLContext() [set_ciphers(), set_sigalgs(), set_curves(), set_ecdh_curve(), build_cert_chain(),
set_ssl_logging()] and SSL() [set_mtu(), set_link_mtu()]
2017-03-17 Björn Freise <mcfreis@gmx.net>
Added methods getting the curves supported by the runtime openSSL lib
* dtls/openssl.py:
- Added class _EllipticCurve() for easy handling of the builtin curves
- Added wrapper get_elliptic_curves() - which uses _EllipticCurve()
- Added EC_get_builtin_curves(), EC_KEY_new_by_curve_name() and EC_KEY_free()
- Added OBJ_nid2sn() for translating numeric ids to names
* dtls/util.py: Added _EC_KEY() derived from _Rsrc() with own free/del method
2017-03-17 Björn Freise <mcfreis@gmx.net>
Added methods for setting and getting the curves used during negotiation and encryption
* dtls/openssl.py:
- Added SSL_CTX_set1_curves() and SSL_CTX_set1_curves_list()
- Added SSL_CTX_set_ecdh_auto() and SSL_CTX_set_tmp_ecdh()
- Added SSL_get1_curves(), SSL_get_shared_curve(), SSL_set1_curves() and SSL_set1_curves_list()
2017-03-17 Björn Freise <mcfreis@gmx.net>
Added methods for setting the signature algorithms
* dtls/openssl.py:
- Added SSL_CTX_set1_client_sigalgs_list(), SSL_CTX_set1_client_sigalgs(), SSL_CTX_set1_sigalgs_list() and SSL_CTX_set1_sigalgs()
- Added SSL_set1_client_sigalgs_list(), SSL_set1_client_sigalgs(), SSL_set1_sigalgs_list() and SSL_set1_sigalgs()
2017-03-17 Björn Freise <mcfreis@gmx.net>
Added method SSL_CTX_build_cert_chain()
* dtls/openssl.py: Added SSL_CTX_build_cert_chain() and corresponding constants
2017-03-17 Björn Freise <mcfreis@gmx.net>
Added methods *_clear_options() and *_get_options()
* dtls/openssl.py:
- Added SSL_CTX_clear_options() and SSL_CTX_get_options()
- Added SSL_clear_options() and SSL_get_options()
2017-03-17 Björn Freise <mcfreis@gmx.net>
Added new methods for DTLSv1.2
* dtls/err.py: Added error code ERR_WRONG_VERSION_NUMBER
* dtls/openssl.py: Added DTLS_server_method(), DTLSv1_2_server_method() and DTLSv1_2_client_method()
* dtls/patch.py: Default protocol DTLS for ssl.wrap_socket() and ssl.SSLSocket()
* dtls/sslconnection.py:
- Introduced PROTOCOL_DTLSv1_2 and PROTOCOL_DTLS (the latter one is a synonym for the "higher" version)
- Updated _init_client() and _init_server() with the new protocol methods
- Default protocol DTLS for SSLConnection()
- Return on ERR_WRONG_VERSION_NUMBER if client and server cannot agree on protocol version
* dtls/test/unit.py:
- Extended test_get_server_certificate() to iterate over the different protocol combinations
- Extended test_protocol_dtlsv1() to try the different protocol combinations between client and server
2017-03-17 Björn Freise <mcfreis@gmx.net>
Updating openSSL libs to v1.0.2l-dev
* dtls/openssl.py: Added mtu-functions SSL_set_mtu() and DTLS_set_link_mtu()
* dtls/prebuilt/win32-*: Updated libs for x86 and x86_64 to version 1.0.2l-dev
* dtls/sslconnection.py: mtu size set hardcoded to 1500 - otherwise the windows implementation has problems
2017-03-17 Björn Freise <mcfreis@gmx.net>
Added interface for SSL_CTX_set_info_callback()
* dtls/openssl.py:
- Added methods SSL_CTX_set_info_callback(), SSL_state_string_long(), SSL_alert_type_string_long() and SSL_alert_desc_string_long()
- Added constants for state and error evaluation during callback
* dtls/sslconnection.py: Added _ssl_logging_cb() as default callback function - only outputs messages when logger is active
2017-03-17 Björn Freise <mcfreis@gmx.net>
SSL_write() extended to handle ctypes.Array as data
* dtls/openssl.py: SSL_write() can handle ctypes.Array data
* dtls/sslconnection.py: Added missing import ERR_BOTH_KEY_CERT_FILES
* dtls/test/simple_client.py: Added basic test client to use with dtls/test/echo_seq.py
2017-03-17 Björn Freise <mcfreis@gmx.net>
Beautified lists and maps, grouped imports for easy merges in the future - no changed functionality!
* dtls/openssl.py:
- Ordered constants according to header file from openSSL
- Beautified __all__-list and map for _make_function() in order to easy merges in the future
- Added a few returns in order to evaluate the success of the called methods
* dtls/patch.py: Grouped imports in the following order - system, local
* dtls/sslconnection.py: ssl protocol not hardcoded anymore for forked objects
* dtls/x509.py: logger messages working again
2017-02-27 Ray Brown <code@liquibits.com>
* dtls/openssl.py: support reading directly into given buffer instead of forcing buffer copy (for ssl module compatibility)
* dtls/sslconnection.py: in-situ receive support, as above
* dtls/patch.py: various changes for compatibility with the ssl module of Python 2.7.12; note that the ssl module's new SSLContext is not supported
* dtls/test/unit.py: changes to support the updated ssl module, including fix of deprecation warnings
* setup.py: increase version to 1.0.2
2014-01-18 Ray Brown <code@liquibits.com>
* setup.py: Increase version to 1.0.1 for release to PyPI

View File

@ -59,5 +59,6 @@ def _prep_bins():
_prep_bins() # prepare before module imports
from patch import do_patch
from sslconnection import SSLConnection
from sslconnection import SSLContext, SSL, SSLConnection
from demux import force_routing_demux, reset_default_demux
import err as error_codes

View File

@ -47,7 +47,13 @@ ERR_READ_TIMEOUT = 502
ERR_WRITE_TIMEOUT = 503
ERR_HANDSHAKE_TIMEOUT = 504
ERR_PORT_UNREACHABLE = 505
ERR_WRONG_SSL_VERSION = 0x1409210A
ERR_WRONG_VERSION_NUMBER = 0x1408A10B
ERR_COOKIE_MISMATCH = 0x1408A134
ERR_CERTIFICATE_VERIFY_FAILED = 0x14090086
ERR_NO_SHARED_CIPHER = 0x1408A0C1
ERR_SSL_HANDSHAKE_FAILURE = 0x1410C0E5
class SSLError(socket_error):
@ -96,8 +102,8 @@ def raise_ssl_error(code, nested=None):
"""Raise an SSL error with the given error code"""
err_string = str(code) + ": " + _ssl_errors[code]
if nested:
raise SSLError(err_string, nested)
raise SSLError(err_string)
raise SSLError(code, err_string + str(nested))
raise SSLError(code, err_string)
_ssl_errors = {
ERR_NO_CERTS: "No root certificates specified for verification " + \

View File

@ -40,7 +40,7 @@ from os import path
from datetime import timedelta
from err import openssl_error
from err import SSL_ERROR_NONE
from util import _BIO
from util import _EC_KEY, _BIO
import ctypes
from ctypes import CDLL
from ctypes import CFUNCTYPE
@ -83,6 +83,7 @@ else:
BIO_NOCLOSE = 0x00
BIO_CLOSE = 0x01
SSLEAY_VERSION = 0
SSL_OP_NO_QUERY_MTU = 0x00001000
SSL_OP_NO_COMPRESSION = 0x00020000
SSL_VERIFY_NONE = 0x00
SSL_VERIFY_PEER = 0x01
@ -97,28 +98,128 @@ 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_BUILD_CHAIN_FLAG_NONE = 0x0
SSL_BUILD_CHAIN_FLAG_UNTRUSTED = 0x1
SSL_BUILD_CHAIN_FLAG_NO_ROOT = 0x2
SSL_BUILD_CHAIN_FLAG_CHECK = 0x4
SSL_BUILD_CHAIN_FLAG_IGNORE_ERROR = 0x8
SSL_BUILD_CHAIN_FLAG_CLEAR_ERROR = 0x10
SSL_FILE_TYPE_PEM = 1
GEN_DIRNAME = 4
NID_subject_alt_name = 85
CRYPTO_LOCK = 1
SSL_ST_MASK = 0x0FFF
SSL_ST_CONNECT = 0x1000
SSL_ST_ACCEPT = 0x2000
SSL_ST_INIT = (SSL_ST_CONNECT | SSL_ST_ACCEPT)
SSL_ST_BEFORE = 0x4000
SSL_ST_OK = 0x03
SSL_ST_RENEGOTIATE = (0x04 | SSL_ST_INIT)
SSL_ST_ERR = 0x05
SSL_CB_LOOP = 0x01
SSL_CB_EXIT = 0x02
SSL_CB_READ = 0x04
SSL_CB_WRITE = 0x08
SSL_CB_ALERT = 0x4000
SSL_CB_READ_ALERT = (SSL_CB_ALERT | SSL_CB_READ)
SSL_CB_WRITE_ALERT = (SSL_CB_ALERT | SSL_CB_WRITE)
SSL_CB_ACCEPT_LOOP = (SSL_ST_ACCEPT | SSL_CB_LOOP)
SSL_CB_ACCEPT_EXIT = (SSL_ST_ACCEPT | SSL_CB_EXIT)
SSL_CB_CONNECT_LOOP = (SSL_ST_CONNECT | SSL_CB_LOOP)
SSL_CB_CONNECT_EXIT = (SSL_ST_CONNECT | SSL_CB_EXIT)
SSL_CB_HANDSHAKE_START = 0x10
SSL_CB_HANDSHAKE_DONE = 0x20
#
# Integer constants - internal
#
SSL_CTRL_SET_SESS_CACHE_MODE = 44
SSL_CTRL_SET_READ_AHEAD = 41
SSL_CTRL_SET_TMP_ECDH = 4
SSL_CTRL_SET_MTU = 17
SSL_CTRL_OPTIONS = 32
SSL_CTRL_SET_READ_AHEAD = 41
SSL_CTRL_SET_SESS_CACHE_MODE = 44
SSL_CTRL_CLEAR_OPTIONS = 77
SSL_CTRL_GET_CURVES = 90
SSL_CTRL_SET_CURVES = 91
SSL_CTRL_SET_CURVES_LIST = 92
SSL_CTRL_GET_SHARED_CURVE = 93
SSL_CTRL_SET_ECDH_AUTO = 94
SSL_CTRL_SET_SIGALGS = 97
SSL_CTRL_SET_SIGALGS_LIST = 98
SSL_CTRL_SET_CLIENT_SIGALGS = 101
SSL_CTRL_SET_CLIENT_SIGALGS_LIST = 102
SSL_CTRL_BUILD_CERT_CHAIN = 105
BIO_CTRL_INFO = 3
BIO_CTRL_DGRAM_SET_CONNECTED = 32
BIO_CTRL_DGRAM_GET_PEER = 46
BIO_CTRL_DGRAM_SET_PEER = 44
BIO_CTRL_DGRAM_GET_PEER = 46
BIO_C_SET_NBIO = 102
DTLS_CTRL_GET_TIMEOUT = 73
DTLS_CTRL_HANDLE_TIMEOUT = 74
DTLS_CTRL_LISTEN = 75
DTLS_CTRL_SET_LINK_MTU = 120
X509_NAME_MAXLEN = 256
GETS_MAXLEN = 2048
class _EllipticCurve(object):
_curves = None
@classmethod
def _get_elliptic_curves(cls):
if cls._curves is None:
# Load once
cls._curves = cls._load_elliptic_curves()
return cls._curves
@classmethod
def _load_elliptic_curves(cls):
num_curves = EC_get_builtin_curves(None, 0)
if num_curves > 0:
builtin_curves = create_string_buffer(sizeof(EC_builtin_curve) * num_curves)
EC_get_builtin_curves(cast(builtin_curves, POINTER(EC_builtin_curve)), num_curves)
return [cls(c.nid, OBJ_nid2sn(c.nid)) for c in cast(builtin_curves, POINTER(EC_builtin_curve))[:num_curves]]
return []
def __init__(self, nid, name):
self.nid = nid
self.name = name
def __repr__(self):
return "<Curve %d %r>" % (self.nid, self.name)
def to_EC_KEY(self):
key = _EC_KEY(EC_KEY_new_by_curve_name(self.nid))
return key if bool(key.value) else None
def get_elliptic_curves():
u''' Return the available curves. If not yet loaded, then load them once.
:rtype: list
'''
return _EllipticCurve._get_elliptic_curves()
def get_elliptic_curve(name):
u''' Return the curve from the given name.
:rtype: _EllipticCurve
'''
for curve in get_elliptic_curves():
if curve.name == name:
return curve
raise ValueError("unknown curve name", name)
#
# Parameter data types
#
@ -151,9 +252,9 @@ class FuncParam(object):
return self._as_parameter.value
class DTLSv1Method(FuncParam):
class DTLS_Method(FuncParam):
def __init__(self, value):
super(DTLSv1Method, self).__init__(value)
super(DTLS_Method, self).__init__(value)
class BIO_METHOD(FuncParam):
@ -176,6 +277,11 @@ class BIO(FuncParam):
super(BIO, self).__init__(value)
class EC_KEY(FuncParam):
def __init__(self, value):
super(EC_KEY, self).__init__(value)
class X509(FuncParam):
def __init__(self, value):
super(X509, self).__init__(value)
@ -246,6 +352,13 @@ class GENERAL_NAMES(STACK):
super(GENERAL_NAMES, self).__init__(value)
class STACK_OF_X509(STACK):
stack_element_type = X509
def __init__(self, value):
super(STACK_OF_X509, self).__init__(value)
class X509_NAME_ENTRY(Structure):
_fields_ = [("object", c_void_p),
("value", c_void_p),
@ -281,6 +394,11 @@ class TIMEVAL(Structure):
("tv_usec", c_long)]
class EC_builtin_curve(Structure):
_fields_ = [("nid", c_int),
("comment", c_char_p)]
#
# Socket address conversions
#
@ -470,85 +588,139 @@ def _make_function(name, lib, args, export=True, errcheck="default"):
_subst = {c_long_parm: c_long}
_sigs = {}
__all__ = ["BIO_NOCLOSE", "BIO_CLOSE",
"SSLEAY_VERSION",
"SSL_OP_NO_COMPRESSION",
"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",
"GEN_DIRNAME", "NID_subject_alt_name",
"CRYPTO_LOCK",
"CRYPTO_set_locking_callback",
"DTLSv1_get_timeout", "DTLSv1_handle_timeout",
"DTLSv1_listen",
"BIO_gets", "BIO_read", "BIO_get_mem_data",
"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_CTX_set_options",
"SSL_read", "SSL_write",
"SSL_CTX_set_cookie_cb",
"OBJ_obj2txt", "decode_ASN1_STRING", "ASN1_TIME_print",
"X509_get_notAfter",
"ASN1_item_d2i", "GENERAL_NAME_print",
"sk_value",
"sk_pop_free",
"i2d_X509"] # note: the following map adds to this list
__all__ = [
# Constants
"BIO_NOCLOSE", "BIO_CLOSE",
"SSLEAY_VERSION",
"SSL_OP_NO_QUERY_MTU", "SSL_OP_NO_COMPRESSION",
"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_ST_MASK", "SSL_ST_CONNECT", "SSL_ST_ACCEPT", "SSL_ST_INIT", "SSL_ST_BEFORE", "SSL_ST_OK",
"SSL_ST_RENEGOTIATE", "SSL_ST_ERR", "SSL_CB_LOOP", "SSL_CB_EXIT", "SSL_CB_READ", "SSL_CB_WRITE",
"SSL_CB_ALERT", "SSL_CB_READ_ALERT", "SSL_CB_WRITE_ALERT",
"SSL_CB_ACCEPT_LOOP", "SSL_CB_ACCEPT_EXIT",
"SSL_CB_CONNECT_LOOP", "SSL_CB_CONNECT_EXIT",
"SSL_CB_HANDSHAKE_START", "SSL_CB_HANDSHAKE_DONE",
"SSL_BUILD_CHAIN_FLAG_NONE", "SSL_BUILD_CHAIN_FLAG_UNTRUSTED", "SSL_BUILD_CHAIN_FLAG_NO_ROOT",
"SSL_BUILD_CHAIN_FLAG_CHECK", "SSL_BUILD_CHAIN_FLAG_IGNORE_ERROR", "SSL_BUILD_CHAIN_FLAG_CLEAR_ERROR",
"SSL_FILE_TYPE_PEM",
"GEN_DIRNAME", "NID_subject_alt_name",
"CRYPTO_LOCK",
# Methods
"CRYPTO_set_locking_callback",
"DTLSv1_get_timeout", "DTLSv1_handle_timeout",
"DTLSv1_listen",
"DTLS_set_link_mtu",
"BIO_gets", "BIO_read", "BIO_get_mem_data",
"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_CTX_set_options", "SSL_CTX_clear_options", "SSL_CTX_get_options",
"SSL_CTX_set1_client_sigalgs_list", "SSL_CTX_set1_client_sigalgs",
"SSL_CTX_set1_sigalgs_list", "SSL_CTX_set1_sigalgs",
"SSL_CTX_set1_curves", "SSL_CTX_set1_curves_list",
"SSL_CTX_set_info_callback",
"SSL_CTX_build_cert_chain",
"SSL_CTX_set_ecdh_auto",
"SSL_CTX_set_tmp_ecdh",
"SSL_read", "SSL_write",
"SSL_set_options", "SSL_clear_options", "SSL_get_options",
"SSL_set1_client_sigalgs_list", "SSL_set1_client_sigalgs",
"SSL_set1_sigalgs_list", "SSL_set1_sigalgs",
"SSL_get1_curves", "SSL_get_shared_curve",
"SSL_set1_curves", "SSL_set1_curves_list",
"SSL_set_mtu",
"SSL_state_string_long", "SSL_alert_type_string_long", "SSL_alert_desc_string_long",
"SSL_get_peer_cert_chain",
"SSL_CTX_set_cookie_cb",
"OBJ_obj2txt", "decode_ASN1_STRING", "ASN1_TIME_print",
"OBJ_nid2sn",
"X509_get_notAfter",
"ASN1_item_d2i", "GENERAL_NAME_print",
"sk_value",
"sk_pop_free",
"i2d_X509",
"get_elliptic_curves",
] # note: the following map adds to this list
map(lambda x: _make_function(*x), (
("SSL_library_init", libssl, ((c_int, "ret"),)),
("SSL_load_error_strings", libssl, ((None, "ret"),)),
("SSLeay", libcrypto, ((c_long_parm, "ret"),)),
("SSLeay_version", libcrypto, ((c_char_p, "ret"), (c_int, "t"))),
("SSL_library_init", libssl,
((c_int, "ret"),)),
("SSL_load_error_strings", libssl,
((None, "ret"),)),
("SSLeay", libcrypto,
((c_long_parm, "ret"),)),
("SSLeay_version", libcrypto,
((c_char_p, "ret"), (c_int, "t"))),
("CRYPTO_set_locking_callback", libcrypto,
((None, "ret"), (c_void_p, "func")), False),
("CRYPTO_get_id_callback", libcrypto, ((c_void_p, "ret"),), True, None),
("CRYPTO_num_locks", libcrypto, ((c_int, "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"))),
("CRYPTO_get_id_callback", libcrypto,
((c_void_p, "ret"),), True, None),
("CRYPTO_num_locks", libcrypto,
((c_int, "ret"),)),
("DTLS_server_method", libssl,
((DTLS_Method, "ret"),)),
("DTLSv1_server_method", libssl,
((DTLS_Method, "ret"),)),
("DTLSv1_2_server_method", libssl,
((DTLS_Method, "ret"),)),
("DTLSv1_client_method", libssl,
((DTLS_Method, "ret"),)),
("DTLSv1_2_client_method", libssl,
((DTLS_Method, "ret"),)),
("SSL_CTX_new", libssl,
((SSLCTX, "ret"), (DTLS_Method, "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"))),
((None, "ret"), (SSLCTX, "ctx"), (c_void_p, "app_verify_cookie_cb")), False),
("SSL_CTX_set_info_callback", libssl,
((None, "ret"), (SSLCTX, "ctx"), (c_void_p, "app_info_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", libcrypto, ((BIO, "ret"), (BIO_METHOD, "type"))),
("BIO_s_mem", libcrypto, ((BIO_METHOD, "ret"),)),
("BIO_new", libcrypto,
((BIO, "ret"), (BIO_METHOD, "type"))),
("BIO_s_mem", libcrypto,
((BIO_METHOD, "ret"),)),
("BIO_new_file", libcrypto,
((BIO, "ret"), (c_char_p, "filename"), (c_char_p, "mode"))),
("BIO_new_dgram", libcrypto,
((BIO, "ret"), (c_int, "fd"), (c_int, "close_flag"))),
("BIO_free", libcrypto, ((c_int, "ret"), (BIO, "a"))),
("BIO_free", libcrypto,
((c_int, "ret"), (BIO, "a"))),
("BIO_gets", libcrypto,
((c_int, "ret"), (BIO, "b"), (POINTER(c_char), "buf"), (c_int, "size")),
False),
((c_int, "ret"), (BIO, "b"), (POINTER(c_char), "buf"), (c_int, "size")), False),
("BIO_read", libcrypto,
((c_int, "ret"), (BIO, "b"), (c_void_p, "buf"), (c_int, "len")), False),
("SSL_CTX_ctrl", libssl,
((c_long_parm, "ret"), (SSLCTX, "ctx"), (c_int, "cmd"), (c_long, "larg"),
(c_void_p, "parg")), False),
((c_long_parm, "ret"), (SSLCTX, "ctx"), (c_int, "cmd"), (c_long, "larg"), (c_void_p, "parg")), False),
("BIO_ctrl", libcrypto,
((c_long_parm, "ret"), (BIO, "bp"), (c_int, "cmd"), (c_long, "larg"),
(c_void_p, "parg")), False),
((c_long_parm, "ret"), (BIO, "bp"), (c_int, "cmd"), (c_long, "larg"), (c_void_p, "parg")), False),
("SSL_ctrl", libssl,
((c_long_parm, "ret"), (SSL, "ssl"), (c_int, "cmd"), (c_long, "larg"),
(c_void_p, "parg")), False),
("ERR_get_error", libcrypto, ((c_long_parm, "ret"),), False),
((c_long_parm, "ret"), (SSL, "ssl"), (c_int, "cmd"), (c_long, "larg"), (c_void_p, "parg")), False),
("ERR_get_error", libcrypto,
((c_long_parm, "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),
((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_state_string_long", libssl,
((c_char_p, "ret"), (SSL, "ssl")), False),
("SSL_alert_type_string_long", libssl,
((c_char_p, "ret"), (c_int, "value")), False),
("SSL_alert_desc_string_long", libssl,
((c_char_p, "ret"), (c_int, "value")), False),
("SSL_CTX_set_cipher_list", libssl,
((c_int, "ret"), (SSLCTX, "ctx"), (c_char_p, "str"))),
("SSL_CTX_use_certificate_file", libssl,
@ -558,36 +730,45 @@ map(lambda x: _make_function(*x), (
("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"))),
((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_get_peer_certificate", libssl, ((X509, "ret"), (SSL, "ssl"))),
((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_get_peer_certificate", libssl,
((X509, "ret"), (SSL, "ssl"))),
("SSL_get_peer_cert_chain", libssl,
((STACK_OF_X509, "ret"), (SSL, "ssl")), False),
("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_pending", libssl, ((c_int, "ret"), (SSL, "ssl")), True, None),
("SSL_shutdown", libssl, ((c_int, "ret"), (SSL, "ssl"))),
("SSL_pending", libssl,
((c_int, "ret"), (SSL, "ssl")), True, None),
("SSL_shutdown", libssl,
((c_int, "ret"), (SSL, "ssl"))),
("SSL_set_read_ahead", libssl,
((None, "ret"), (SSL, "ssl"), (c_int, "yes"))),
("X509_free", libcrypto, ((None, "ret"), (X509, "a"))),
("X509_free", libcrypto,
((None, "ret"), (X509, "a"))),
("PEM_read_bio_X509_AUX", libcrypto,
((X509, "ret"), (BIO, "bp"), (c_void_p, "x", 1, None),
(c_void_p, "cb", 1, None), (c_void_p, "u", 1, None))),
((X509, "ret"), (BIO, "bp"), (c_void_p, "x", 1, None), (c_void_p, "cb", 1, None), (c_void_p, "u", 1, None))),
("OBJ_obj2txt", libcrypto,
((c_int, "ret"), (POINTER(c_char), "buf"), (c_int, "buf_len"),
(ASN1_OBJECT, "a"), (c_int, "no_name")), False),
("CRYPTO_free", libcrypto, ((None, "ret"), (c_void_p, "ptr"))),
((c_int, "ret"), (POINTER(c_char), "buf"), (c_int, "buf_len"), (ASN1_OBJECT, "a"), (c_int, "no_name")), False),
("OBJ_nid2sn", libcrypto,
((c_char_p, "ret"), (c_int, "n")), False),
("CRYPTO_free", libcrypto,
((None, "ret"), (c_void_p, "ptr"))),
("ASN1_STRING_to_UTF8", libcrypto,
((c_int, "ret"), (POINTER(POINTER(c_ubyte)), "out"), (ASN1_STRING, "in")),
False),
((c_int, "ret"), (POINTER(POINTER(c_ubyte)), "out"), (ASN1_STRING, "in")), False),
("X509_NAME_entry_count", libcrypto,
((c_int, "ret"), (POINTER(X509_name_st), "name")), True, None),
("X509_NAME_get_entry", libcrypto,
@ -602,34 +783,41 @@ map(lambda x: _make_function(*x), (
("ASN1_TIME_print", libcrypto,
((c_int, "ret"), (BIO, "fp"), (ASN1_TIME, "a")), False),
("X509_get_ext_by_NID", libcrypto,
((c_int, "ret"), (X509, "x"), (c_int, "nid"), (c_int, "lastpos")),
True, None),
((c_int, "ret"), (X509, "x"), (c_int, "nid"), (c_int, "lastpos")), True, None),
("X509_get_ext", libcrypto,
((POINTER(X509_EXTENSION), "ret"), (X509, "x"), (c_int, "loc")),
True, errcheck_p),
((POINTER(X509_EXTENSION), "ret"), (X509, "x"), (c_int, "loc")), True, errcheck_p),
("X509V3_EXT_get", libcrypto,
((POINTER(X509V3_EXT_METHOD), "ret"), (POINTER(X509_EXTENSION), "ext")),
True, errcheck_p),
((POINTER(X509V3_EXT_METHOD), "ret"), (POINTER(X509_EXTENSION), "ext")), True, errcheck_p),
("ASN1_item_d2i", libcrypto,
((c_void_p, "ret"), (c_void_p, "val"), (POINTER(POINTER(c_ubyte)), "in"),
(c_long, "len"), (c_void_p, "it")), False, None),
("sk_num", libcrypto, ((c_int, "ret"), (STACK, "stack")), True, None),
((c_void_p, "ret"), (c_void_p, "val"), (POINTER(POINTER(c_ubyte)), "in"), (c_long, "len"), (c_void_p, "it")), False, None),
("sk_num", libcrypto,
((c_int, "ret"), (STACK, "stack")), True, None),
("sk_value", libcrypto,
((c_void_p, "ret"), (STACK, "stack"), (c_int, "loc")), False),
("GENERAL_NAME_print", libcrypto,
((c_int, "ret"), (BIO, "out"), (POINTER(GENERAL_NAME), "gen")), False),
("sk_pop_free", libcrypto,
((None, "ret"), (STACK, "st"), (c_void_p, "func")), False),
("i2d_X509_bio", libcrypto, ((c_int, "ret"), (BIO, "bp"), (X509, "x")),
False),
("SSL_get_current_cipher", libssl, ((SSL_CIPHER, "ret"), (SSL, "ssl"))),
("i2d_X509_bio", libcrypto,
((c_int, "ret"), (BIO, "bp"), (X509, "x")), False),
("SSL_get_current_cipher", libssl,
((SSL_CIPHER, "ret"), (SSL, "ssl"))),
("SSL_CIPHER_get_name", libssl,
((c_char_p, "ret"), (SSL_CIPHER, "cipher"))),
("SSL_CIPHER_get_version", libssl,
((c_char_p, "ret"), (SSL_CIPHER, "cipher"))),
("SSL_CIPHER_get_bits", libssl,
((c_int, "ret"), (SSL_CIPHER, "cipher"),
(POINTER(c_int), "alg_bits", 1, None)), True, None),
((c_int, "ret"), (SSL_CIPHER, "cipher"), (POINTER(c_int), "alg_bits", 1, None)), True, None),
("EC_get_builtin_curves", libcrypto,
((c_int, "ret"), (POINTER(EC_builtin_curve), "r"), (c_int, "nitems"))),
("EC_KEY_new_by_curve_name", libcrypto,
((EC_KEY, "ret"), (c_int, "nid"))),
("EC_KEY_free", libcrypto,
((None, "ret"), (EC_KEY, "key"))),
("EC_curve_nist2nid", libcrypto,
((c_int, "ret"), (POINTER(c_char), "name")), True, None),
("EC_curve_nid2nist", libcrypto,
((c_char_p, "ret"), (c_int, "nid")), True, None),
))
#
@ -650,15 +838,78 @@ def CRYPTO_set_locking_callback(locking_function):
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)
return _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)
return _SSL_CTX_ctrl(ctx, SSL_CTRL_SET_READ_AHEAD, m, None)
def SSL_CTX_set_options(ctx, options):
# Returns the new option bitmaks after adding the given options
_SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, options, None)
return _SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, options, None)
def SSL_CTX_clear_options(ctx, options):
return _SSL_CTX_ctrl(ctx, SSL_CTRL_CLEAR_OPTIONS, options, None)
def SSL_CTX_get_options(ctx):
return _SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, 0, None)
def SSL_CTX_set1_client_sigalgs(ctx, slist, slistlen):
_slist = (c_int * len(slist))(*slist)
return _SSL_CTX_ctrl(ctx, SSL_CTRL_SET_CLIENT_SIGALGS, len(_slist), _slist)
def SSL_CTX_set1_client_sigalgs_list(ctx, s):
_s = cast(s, POINTER(c_char))
return _SSL_CTX_ctrl(ctx, SSL_CTRL_SET_CLIENT_SIGALGS_LIST, 0, _s)
def SSL_CTX_set1_sigalgs(ctx, slist, slistlen):
_slist = (c_int * len(slist))(*slist)
return _SSL_CTX_ctrl(ctx, SSL_CTRL_SET_SIGALGS, len(_slist), _slist)
def SSL_CTX_set1_sigalgs_list(ctx, s):
_s = cast(s, POINTER(c_char))
return _SSL_CTX_ctrl(ctx, SSL_CTRL_SET_SIGALGS_LIST, 0, _s)
def SSL_CTX_set1_curves(ctx, clist, clistlen):
_curves = (c_int * len(clist))(*clist)
return _SSL_CTX_ctrl(ctx, SSL_CTRL_SET_CURVES, len(_curves), _curves)
def SSL_CTX_set1_curves_list(ctx, s):
_s = cast(s, POINTER(c_char))
return _SSL_CTX_ctrl(ctx, SSL_CTRL_SET_CURVES_LIST, 0, _s)
_rvoid_voidp_int_int = CFUNCTYPE(None, c_void_p, c_int, c_int)
_info_callback = dict()
def SSL_CTX_set_info_callback(ctx, app_info_cb):
"""
Set the info callback
:param callback: The Python callback to use
:return: None
"""
def py_info_callback(ssl, where, ret):
try:
app_info_cb(SSL(ssl), where, ret)
except:
pass
return
global _info_callback
_info_callback[ctx] = _rvoid_voidp_int_int(py_info_callback)
_SSL_CTX_set_info_callback(ctx, _info_callback[ctx])
def SSL_CTX_build_cert_chain(ctx, flags):
return _SSL_CTX_ctrl(ctx, SSL_CTRL_BUILD_CERT_CHAIN, flags, None)
def SSL_CTX_set_ecdh_auto(ctx, onoff):
return _SSL_CTX_ctrl(ctx, SSL_CTRL_SET_ECDH_AUTO, onoff, None)
def SSL_CTX_set_tmp_ecdh(ctx, ec_key):
# return 1 on success and 0 on failure
_ec_key_p = cast(ec_key.raw, c_void_p)
return _SSL_CTX_ctrl(ctx, SSL_CTRL_SET_TMP_ECDH, 0, _ec_key_p)
_rint_voidp_ubytep_uintp = CFUNCTYPE(c_int, c_void_p, POINTER(c_ubyte),
POINTER(c_uint))
@ -693,7 +944,7 @@ def SSL_CTX_set_cookie_cb(ctx, generate, verify):
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))
return _BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, byref(su))
def BIO_dgram_get_peer(bio):
su = sockaddr_u()
@ -702,10 +953,10 @@ def BIO_dgram_get_peer(bio):
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))
return _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)
return _BIO_ctrl(bio, BIO_C_SET_NBIO, 1 if n else 0, None)
def DTLSv1_get_timeout(ssl):
tv = TIMEVAL()
@ -727,7 +978,7 @@ def DTLSv1_handle_timeout(ssl):
assert ret < 0
if ret > 0:
ret = -10
errcheck_p(ret, _SSL_ctrl, (ssl, DTLS_CTRL_HANDLE_TIMEOUT, 0, None))
return errcheck_p(ret, _SSL_ctrl, (ssl, DTLS_CTRL_HANDLE_TIMEOUT, 0, None))
def DTLSv1_listen(ssl):
su = sockaddr_u()
@ -735,9 +986,18 @@ def DTLSv1_listen(ssl):
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, sizeof(buf))
def DTLS_set_link_mtu(ssl, mtu):
return _SSL_ctrl(ssl, DTLS_CTRL_SET_LINK_MTU, mtu, None)
def SSL_read(ssl, length, buffer):
if buffer:
length = min(length, len(buffer))
buf = (c_char * length).from_buffer(buffer)
else:
buf = create_string_buffer(length)
res_len = _SSL_read(ssl, buf, length)
if buffer:
return res_len
return buf.raw[:res_len]
def SSL_write(ssl, data):
@ -745,15 +1005,94 @@ def SSL_write(ssl, data):
str_data = data
elif hasattr(data, "tobytes") and callable(data.tobytes):
str_data = data.tobytes()
elif isinstance(data, ctypes.Array):
str_data = data.raw
else:
str_data = str(data)
return _SSL_write(ssl, str_data, len(str_data))
def SSL_set_options(ssl, op):
return _SSL_ctrl(ssl, SSL_CTRL_OPTIONS, op, None)
def SSL_clear_options(ssl, op):
return _SSL_ctrl(ssl, SSL_CTRL_CLEAR_OPTIONS, op, None)
def SSL_get_options(ssl):
return _SSL_ctrl(ssl, SSL_CTRL_OPTIONS, 0, None)
def SSL_set1_client_sigalgs(ssl, slist, slistlen):
_slist = (c_int * len(slist))(*slist)
return _SSL_ctrl(ssl, SSL_CTRL_SET_CLIENT_SIGALGS, len(_slist), _slist)
def SSL_set1_client_sigalgs_list(ssl, s):
_s = cast(s, POINTER(c_char))
return _SSL_ctrl(ssl, SSL_CTRL_SET_CLIENT_SIGALGS_LIST, 0, _s)
def SSL_set1_sigalgs(ssl, slist, slistlen):
_slist = (c_int * len(slist))(*slist)
return _SSL_ctrl(ssl, SSL_CTRL_SET_SIGALGS, len(_slist), _slist)
def SSL_set1_sigalgs_list(ssl, s):
_s = cast(s, POINTER(c_char))
return _SSL_ctrl(ssl, SSL_CTRL_SET_SIGALGS_LIST, 0, _s)
def SSL_get1_curves(ssl, curves=None):
assert curves is None or isinstance(curves, list)
if curves is not None:
cnt = SSL_get1_curves(ssl, None)
if cnt:
mem = create_string_buffer(sizeof(POINTER(c_int)) * cnt)
_SSL_ctrl(ssl, SSL_CTRL_GET_CURVES, 0, mem)
for x in cast(mem, POINTER(c_int))[:cnt]:
curves.append(x)
return cnt
else:
return _SSL_ctrl(ssl, SSL_CTRL_GET_CURVES, 0, None)
def SSL_get_shared_curve(ssl, n):
return _SSL_ctrl(ssl, SSL_CTRL_GET_SHARED_CURVE, n, 0)
def SSL_set1_curves(ssl, clist, clistlen):
_curves = (c_int * len(clist))(*clist)
return _SSL_ctrl(ssl, SSL_CTRL_SET_CURVES, len(_curves), _curves)
def SSL_set1_curves_list(ssl, s):
_s = cast(s, POINTER(c_char))
return _SSL_ctrl(ssl, SSL_CTRL_SET_CURVES_LIST, 0, _s)
def SSL_set_mtu(ssl, mtu):
return _SSL_ctrl(ssl, SSL_CTRL_SET_MTU, mtu, None)
def SSL_state_string_long(ssl):
try:
ret = _SSL_state_string_long(ssl)
except:
pass
return ret
def SSL_alert_type_string_long(value):
try:
ret = _SSL_alert_type_string_long(value)
except:
pass
return ret
def SSL_alert_desc_string_long(value):
try:
ret = _SSL_alert_desc_string_long(value)
except:
pass
return ret
def OBJ_obj2txt(asn1_object, no_name):
buf = create_string_buffer(X509_NAME_MAXLEN)
res_len = _OBJ_obj2txt(buf, sizeof(buf), asn1_object, 1 if no_name else 0)
return buf.raw[:res_len]
def OBJ_nid2sn(nid):
_name = _OBJ_nid2sn(nid)
return cast(_name, c_char_p).value.decode("ascii")
def decode_ASN1_STRING(asn1_string):
utf8_buf_ptr = POINTER(c_ubyte)()
res_len = _ASN1_STRING_to_UTF8(byref(utf8_buf_ptr), asn1_string)
@ -827,3 +1166,13 @@ def i2d_X509(x509):
bio = _BIO(BIO_new(BIO_s_mem()))
_i2d_X509_bio(bio.value, x509)
return BIO_get_mem_data(bio.value)
def SSL_get_peer_cert_chain(ssl):
stack = _SSL_get_peer_cert_chain(ssl)
num = sk_num(stack)
certs = []
if num:
# why not use sk_value(): because it doesn't cast correct in this case?!
# certs = [(sk_value(stack, i)) for i in xrange(num)]
certs = [X509(_sk_value(stack, i)) for i in xrange(num)]
return stack, num, certs

View File

@ -34,16 +34,18 @@ has the following effects:
PROTOCOL_DTLSv1 for the parameter ssl_version is supported
"""
from socket import SOCK_DGRAM, socket, _delegate_methods, error as socket_error
from socket import AF_INET, SOCK_DGRAM, getaddrinfo
from sslconnection import SSLConnection, PROTOCOL_DTLSv1, CERT_NONE
from sslconnection import DTLS_OPENSSL_VERSION_NUMBER, DTLS_OPENSSL_VERSION
from sslconnection import DTLS_OPENSSL_VERSION_INFO
from err import raise_as_ssl_module_error
from socket import socket, getaddrinfo, _delegate_methods, error as socket_error
from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM
from ssl import PROTOCOL_SSLv3, PROTOCOL_SSLv23, CERT_NONE
from types import MethodType
from weakref import proxy
import errno
from sslconnection import SSLConnection, PROTOCOL_DTLS, PROTOCOL_DTLSv1, PROTOCOL_DTLSv1_2
from sslconnection import DTLS_OPENSSL_VERSION_NUMBER, DTLS_OPENSSL_VERSION, DTLS_OPENSSL_VERSION_INFO
from err import raise_as_ssl_module_error
def do_patch():
import ssl as _ssl # import to be avoided if ssl module is never patched
global _orig_SSLSocket_init, _orig_get_server_certificate
@ -51,8 +53,14 @@ def do_patch():
ssl = _ssl
if hasattr(ssl, "PROTOCOL_DTLSv1"):
return
_orig_wrap_socket = ssl.wrap_socket
ssl.wrap_socket = _wrap_socket
ssl.PROTOCOL_DTLS = PROTOCOL_DTLS
ssl.PROTOCOL_DTLSv1 = PROTOCOL_DTLSv1
ssl.PROTOCOL_DTLSv1_2 = PROTOCOL_DTLSv1_2
ssl._PROTOCOL_NAMES[PROTOCOL_DTLS] = "DTLS"
ssl._PROTOCOL_NAMES[PROTOCOL_DTLSv1] = "DTLSv1"
ssl._PROTOCOL_NAMES[PROTOCOL_DTLSv1_2] = "DTLSv1.2"
ssl.DTLS_OPENSSL_VERSION_NUMBER = DTLS_OPENSSL_VERSION_NUMBER
ssl.DTLS_OPENSSL_VERSION = DTLS_OPENSSL_VERSION
ssl.DTLS_OPENSSL_VERSION_INFO = DTLS_OPENSSL_VERSION_INFO
@ -62,10 +70,25 @@ def do_patch():
ssl.get_server_certificate = _get_server_certificate
raise_as_ssl_module_error()
PROTOCOL_SSLv3 = 1
PROTOCOL_SSLv23 = 2
def _wrap_socket(sock, keyfile=None, certfile=None,
server_side=False, cert_reqs=CERT_NONE,
ssl_version=PROTOCOL_DTLS, ca_certs=None,
do_handshake_on_connect=True,
suppress_ragged_eofs=True,
ciphers=None,
cb_user_config_ssl_ctx=None,
cb_user_config_ssl=None):
def _get_server_certificate(addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None):
return ssl.SSLSocket(sock, keyfile=keyfile, certfile=certfile,
server_side=server_side, cert_reqs=cert_reqs,
ssl_version=ssl_version, ca_certs=ca_certs,
do_handshake_on_connect=do_handshake_on_connect,
suppress_ragged_eofs=suppress_ragged_eofs,
ciphers=ciphers,
cb_user_config_ssl_ctx=cb_user_config_ssl_ctx,
cb_user_config_ssl=cb_user_config_ssl)
def _get_server_certificate(addr, ssl_version=PROTOCOL_SSLv23, ca_certs=None):
"""Retrieve a server certificate
Retrieve the certificate from the server at the specified address,
@ -74,10 +97,10 @@ def _get_server_certificate(addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None):
If 'ssl_version' is specified, use it in the connection attempt.
"""
if ssl_version != PROTOCOL_DTLSv1:
if ssl_version not in (PROTOCOL_DTLS, PROTOCOL_DTLSv1, PROTOCOL_DTLSv1_2):
return _orig_get_server_certificate(addr, ssl_version, ca_certs)
if (ca_certs is not None):
if ca_certs is not None:
cert_reqs = ssl.CERT_REQUIRED
else:
cert_reqs = ssl.CERT_NONE
@ -90,11 +113,16 @@ def _get_server_certificate(addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None):
s.close()
return ssl.DER_cert_to_PEM_cert(dercert)
def _SSLSocket_init(self, sock, keyfile=None, certfile=None,
def _SSLSocket_init(self, sock=None, keyfile=None, certfile=None,
server_side=False, cert_reqs=CERT_NONE,
ssl_version=PROTOCOL_SSLv23, ca_certs=None,
ssl_version=PROTOCOL_DTLS, ca_certs=None,
do_handshake_on_connect=True,
suppress_ragged_eofs=True, ciphers=None):
family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None,
suppress_ragged_eofs=True, npn_protocols=None, ciphers=None,
server_hostname=None,
_context=None,
cb_user_config_ssl_ctx=None,
cb_user_config_ssl=None):
is_connection = is_datagram = False
if isinstance(sock, SSLConnection):
is_connection = True
@ -102,11 +130,19 @@ def _SSLSocket_init(self, sock, keyfile=None, certfile=None,
is_datagram = True
if not is_connection and not is_datagram:
# Non-DTLS code path
return _orig_SSLSocket_init(self, sock, keyfile, certfile,
server_side, cert_reqs,
ssl_version, ca_certs,
return _orig_SSLSocket_init(self, sock=sock, keyfile=keyfile,
certfile=certfile, server_side=server_side,
cert_reqs=cert_reqs,
ssl_version=ssl_version, ca_certs=ca_certs,
do_handshake_on_connect=
do_handshake_on_connect,
suppress_ragged_eofs, ciphers)
family=family, type=type, proto=proto,
fileno=fileno,
suppress_ragged_eofs=suppress_ragged_eofs,
npn_protocols=npn_protocols,
ciphers=ciphers,
server_hostname=server_hostname,
_context=_context)
# DTLS code paths: datagram socket and newly accepted DTLS connection
if is_datagram:
socket.__init__(self, _sock=sock._sock)
@ -138,10 +174,17 @@ def _SSLSocket_init(self, sock, keyfile=None, certfile=None,
server_side, cert_reqs,
ssl_version, ca_certs,
do_handshake_on_connect,
suppress_ragged_eofs, ciphers)
suppress_ragged_eofs, ciphers,
cb_user_config_ssl_ctx=cb_user_config_ssl_ctx,
cb_user_config_ssl=cb_user_config_ssl)
else:
self._connected = True
self._sslobj = sock
class FakeContext(object):
check_hostname = False
self._context = FakeContext()
self.keyfile = keyfile
self.certfile = certfile
self.cert_reqs = cert_reqs
@ -151,6 +194,8 @@ def _SSLSocket_init(self, sock, keyfile=None, certfile=None,
self.do_handshake_on_connect = do_handshake_on_connect
self.suppress_ragged_eofs = suppress_ragged_eofs
self._makefile_refs = 0
self._user_config_ssl_ctx = cb_user_config_ssl_ctx
self._user_config_ssl = cb_user_config_ssl
# Perform method substitution and addition (without reference cycle)
self._real_connect = MethodType(_SSLSocket_real_connect, proxy(self))
@ -159,6 +204,12 @@ def _SSLSocket_init(self, sock, keyfile=None, certfile=None,
self.get_timeout = MethodType(_SSLSocket_get_timeout, proxy(self))
self.handle_timeout = MethodType(_SSLSocket_handle_timeout, proxy(self))
# Extra
self.getpeercertchain = MethodType(_getpeercertchain, proxy(self))
def _getpeercertchain(self, binary_form=False):
return self._sslobj.getpeercertchain(binary_form)
def _SSLSocket_listen(self, ignored):
if self._connected:
raise ValueError("attempt to listen on connected SSLSocket!")
@ -169,7 +220,9 @@ def _SSLSocket_listen(self, ignored):
self.cert_reqs, self.ssl_version,
self.ca_certs,
self.do_handshake_on_connect,
self.suppress_ragged_eofs, self.ciphers)
self.suppress_ragged_eofs, self.ciphers,
cb_user_config_ssl_ctx=self._user_config_ssl_ctx,
cb_user_config_ssl=self._user_config_ssl)
def _SSLSocket_accept(self):
if self._connected:
@ -184,7 +237,9 @@ def _SSLSocket_accept(self):
self.cert_reqs, self.ssl_version,
self.ca_certs,
self.do_handshake_on_connect,
self.suppress_ragged_eofs, self.ciphers)
self.suppress_ragged_eofs, self.ciphers,
cb_user_config_ssl_ctx=self._user_config_ssl_ctx,
cb_user_config_ssl=self._user_config_ssl)
return new_ssl_sock, addr
def _SSLSocket_real_connect(self, addr, return_errno):
@ -195,7 +250,9 @@ def _SSLSocket_real_connect(self, addr, return_errno):
self.cert_reqs, self.ssl_version,
self.ca_certs,
self.do_handshake_on_connect,
self.suppress_ragged_eofs, self.ciphers)
self.suppress_ragged_eofs, self.ciphers,
cb_user_config_ssl_ctx=self._user_config_ssl_ctx,
cb_user_config_ssl=self._user_config_ssl)
try:
self._sslobj.connect(addr)
except socket_error as e:

Binary file not shown.

Binary file not shown.

View File

@ -48,13 +48,14 @@ from logging import getLogger
from os import urandom
from select import select
from weakref import proxy
from err import openssl_error, InvalidSocketError
from err import raise_ssl_error
from err import SSL_ERROR_WANT_READ, SSL_ERROR_SYSCALL
from err import ERR_COOKIE_MISMATCH, ERR_NO_CERTS
from err import ERR_WRONG_VERSION_NUMBER, ERR_COOKIE_MISMATCH, ERR_NO_SHARED_CIPHER
from err import ERR_NO_CIPHER, ERR_HANDSHAKE_TIMEOUT, ERR_PORT_UNREACHABLE
from err import ERR_READ_TIMEOUT, ERR_WRITE_TIMEOUT
from err import ERR_BOTH_KEY_CERT_FILES_SVR
from err import ERR_BOTH_KEY_CERT_FILES, ERR_BOTH_KEY_CERT_FILES_SVR, ERR_NO_CERTS
from x509 import _X509, decode_cert
from tlock import tlock_init
from openssl import *
@ -63,6 +64,8 @@ from util import _Rsrc, _BIO
_logger = getLogger(__name__)
PROTOCOL_DTLSv1 = 256
PROTOCOL_DTLSv1_2 = 258
PROTOCOL_DTLS = 259
CERT_NONE = 0
CERT_OPTIONAL = 1
CERT_REQUIRED = 2
@ -83,6 +86,58 @@ DTLS_OPENSSL_VERSION_INFO = (
DTLS_OPENSSL_VERSION_NUMBER & 0xF) # status
def _ssl_logging_cb(conn, where, return_code):
_state = where & ~SSL_ST_MASK
state = "SSL"
if _state & SSL_ST_INIT == SSL_ST_INIT:
if _state & SSL_ST_RENEGOTIATE == SSL_ST_RENEGOTIATE:
state += "_renew"
else:
state += "_init"
elif _state & SSL_ST_CONNECT:
state += "_connect"
elif _state & SSL_ST_ACCEPT:
state += "_accept"
elif _state == 0:
if where & SSL_CB_HANDSHAKE_START:
state += "_handshake_start"
elif where & SSL_CB_HANDSHAKE_DONE:
state += "_handshake_done"
if where & SSL_CB_LOOP:
state += '_loop'
_logger.debug("%s:%s:%d" % (state,
SSL_state_string_long(conn),
return_code))
elif where & SSL_CB_ALERT:
state += '_alert'
state += "_read" if where & SSL_CB_READ else "_write"
_logger.debug("%s:%s:%s" % (state,
SSL_alert_type_string_long(return_code),
SSL_alert_desc_string_long(return_code)))
elif where & SSL_CB_EXIT:
state += '_exit'
if return_code == 0:
_logger.debug("%s:%s:%d(failed)" % (state,
SSL_state_string_long(conn),
return_code))
elif return_code < 0:
_logger.debug("%s:%s:%d(error)" % (state,
SSL_state_string_long(conn),
return_code))
else:
_logger.debug("%s:%s:%d" % (state,
SSL_state_string_long(conn),
return_code))
else:
_logger.debug("%s:%s:%d" % (state,
SSL_state_string_long(conn),
return_code))
class _CTX(_Rsrc):
"""SSL_CTX wrapper"""
def __init__(self, value):
@ -122,6 +177,124 @@ class _CallbackProxy(object):
return self.ssl_func(self.ssl_connection, *args, **kwargs)
class SSLContext(object):
def __init__(self, ctx):
self._ctx = ctx
def set_ciphers(self, ciphers):
u'''
s.a. https://www.openssl.org/docs/man1.1.0/apps/ciphers.html
:param str ciphers: Example "AES256-SHA:ECDHE-ECDSA-AES256-SHA", ...
:return: 1 for success and 0 for failure
'''
retVal = SSL_CTX_set_cipher_list(self._ctx, ciphers)
return retVal
def set_sigalgs(self, sigalgs):
u'''
s.a. https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_set1_sigalgs_list.html
:param str sigalgs: Example "RSA+SHA256", "ECDSA+SHA256", ...
:return: 1 for success and 0 for failure
'''
retVal = SSL_CTX_set1_sigalgs_list(self._ctx, sigalgs)
return retVal
def set_curves(self, curves):
u''' Set supported curves by name, nid or nist.
:param str | tuple(int) curves: Example "secp384r1:secp256k1", (715, 714), "P-384", "K-409:B-409:K-571", ...
:return: 1 for success and 0 for failure
'''
retVal = None
if isinstance(curves, str):
retVal = SSL_CTX_set1_curves_list(self._ctx, curves)
elif isinstance(curves, tuple):
retVal = SSL_CTX_set1_curves(self._ctx, curves, len(curves))
return retVal
@staticmethod
def get_ec_nist2nid(nist):
if not isinstance(nist, tuple):
nist = nist.split(":")
nid = tuple(EC_curve_nist2nid(x) for x in nist)
return nid
@staticmethod
def get_ec_nid2nist(nid):
if not isinstance(nid, tuple):
nid = (nid, )
nist = ":".join([EC_curve_nid2nist(x) for x in nid])
return nist
@staticmethod
def get_ec_available(bAsName=True):
curves = get_elliptic_curves()
return sorted([x.name for x in curves] if bAsName else [x.nid for x in curves])
def set_ecdh_curve(self, curve_name=None):
u''' Select a curve to use for ECDH(E) key exchange or set it to auto mode
Used for server only!
s.a. openssl.exe ecparam -list_curves
:param None | str curve_name: None = Auto-mode, "secp256k1", "secp384r1", ...
:return: 1 for success and 0 for failure
'''
if curve_name:
retVal = SSL_CTX_set_ecdh_auto(self._ctx, 0)
avail_curves = get_elliptic_curves()
key = [curve for curve in avail_curves if curve.name == curve_name][0].to_EC_KEY()
retVal &= SSL_CTX_set_tmp_ecdh(self._ctx, key)
else:
retVal = SSL_CTX_set_ecdh_auto(self._ctx, 1)
return retVal
def build_cert_chain(self, flags=SSL_BUILD_CHAIN_FLAG_NONE):
u'''
Used for server side only!
:param flags:
:return: 1 for success and 0 for failure
'''
retVal = SSL_CTX_build_cert_chain(self._ctx, flags)
return retVal
def set_ssl_logging(self, enable=False, func=_ssl_logging_cb):
u''' Enable or disable SSL logging
:param True | False enable: Enable or disable SSL logging
:param func: Callback function for logging
'''
if enable:
SSL_CTX_set_info_callback(self._ctx, func)
else:
SSL_CTX_set_info_callback(self._ctx, 0)
class SSL(object):
def __init__(self, ssl):
self._ssl = ssl
def set_mtu(self, mtu=None):
if mtu:
SSL_set_options(self._ssl, SSL_OP_NO_QUERY_MTU)
SSL_set_mtu(self._ssl, mtu)
else:
SSL_clear_options(self._ssl, SSL_OP_NO_QUERY_MTU)
def set_link_mtu(self, mtu=None):
if mtu:
SSL_set_options(self._ssl, SSL_OP_NO_QUERY_MTU)
DTLS_set_link_mtu(self._ssl, mtu)
else:
SSL_clear_options(self._ssl, SSL_OP_NO_QUERY_MTU)
class SSLConnection(object):
"""DTLS peer association
@ -149,7 +322,13 @@ class SSLConnection(object):
else:
self._rsock = rsock
self._rbio = _BIO(BIO_new_dgram(self._rsock.fileno(), BIO_NOCLOSE))
self._ctx = _CTX(SSL_CTX_new(DTLSv1_server_method()))
server_method = DTLS_server_method
if self._ssl_version == PROTOCOL_DTLSv1_2:
server_method = DTLSv1_2_server_method
elif self._ssl_version == PROTOCOL_DTLSv1:
server_method = DTLSv1_server_method
self._ctx = _CTX(SSL_CTX_new(server_method()))
self._intf_ssl_ctx = SSLContext(self._ctx.value)
SSL_CTX_set_session_cache_mode(self._ctx.value, SSL_SESS_CACHE_OFF)
if self._cert_reqs == CERT_NONE:
verify_mode = SSL_VERIFY_NONE
@ -169,6 +348,7 @@ class SSLConnection(object):
_CallbackProxy(self._generate_cookie_cb),
_CallbackProxy(self._verify_cookie_cb))
self._ssl = _SSL(SSL_new(self._ctx.value))
self._intf_ssl = SSL(self._ssl.value)
SSL_set_accept_state(self._ssl.value)
if peer_address and self._do_handshake_on_connect:
return lambda: self.do_handshake()
@ -179,13 +359,20 @@ class SSLConnection(object):
self._wbio = _BIO(BIO_new_dgram(self._sock.fileno(), BIO_NOCLOSE))
self._rbio = self._wbio
self._ctx = _CTX(SSL_CTX_new(DTLSv1_client_method()))
client_method = DTLSv1_2_client_method # no "any" exists, therefore use v1_2 (highest possible)
if self._ssl_version == PROTOCOL_DTLSv1_2:
client_method = DTLSv1_2_client_method
elif self._ssl_version == PROTOCOL_DTLSv1:
client_method = DTLSv1_client_method
self._ctx = _CTX(SSL_CTX_new(client_method()))
self._intf_ssl_ctx = SSLContext(self._ctx.value)
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))
self._intf_ssl = SSL(self._ssl.value)
SSL_set_connect_state(self._ssl.value)
if peer_address:
return lambda: self.connect(peer_address)
@ -208,6 +395,8 @@ class SSLConnection(object):
SSL_CTX_set_cipher_list(self._ctx.value, self._ciphers)
except openssl_error() as err:
raise_ssl_error(ERR_NO_CIPHER, err)
if self._user_config_ssl_ctx:
self._user_config_ssl_ctx(self._intf_ssl_ctx)
def _copy_server(self):
source = self._sock
@ -233,7 +422,10 @@ class SSLConnection(object):
BIO_dgram_set_connected(self._wbio.value,
source._pending_peer_address)
source._ssl = _SSL(SSL_new(self._ctx.value))
self._intf_ssl = SSL(source._ssl.value)
SSL_set_accept_state(source._ssl.value)
if self._user_config_ssl:
self._user_config_ssl(self._intf_ssl)
source._rbio = new_source_rbio
source._wbio = new_source_wbio
SSL_set_bio(source._ssl.value,
@ -252,7 +444,10 @@ class SSLConnection(object):
self._rbio = _BIO(BIO_new_dgram(self._rsock.fileno(), BIO_NOCLOSE))
BIO_dgram_set_peer(self._wbio.value, source._peer_address)
self._ssl = _SSL(SSL_new(self._ctx.value))
self._intf_ssl = SSL(self._ssl.value)
SSL_set_accept_state(self._ssl.value)
if self._user_config_ssl:
self._user_config_ssl(self._intf_ssl)
if self._do_handshake_on_connect:
return lambda: self.do_handshake()
@ -310,9 +505,11 @@ class SSLConnection(object):
def __init__(self, sock, keyfile=None, certfile=None,
server_side=False, cert_reqs=CERT_NONE,
ssl_version=PROTOCOL_DTLSv1, ca_certs=None,
ssl_version=PROTOCOL_DTLS, ca_certs=None,
do_handshake_on_connect=True,
suppress_ragged_eofs=True, ciphers=None):
suppress_ragged_eofs=True, ciphers=None,
cb_user_config_ssl_ctx=None,
cb_user_config_ssl=None):
"""Constructor
Arguments:
@ -334,6 +531,7 @@ class SSLConnection(object):
self._keyfile = keyfile
self._certfile = certfile
self._cert_reqs = cert_reqs
self._ssl_version = ssl_version
self._ca_certs = ca_certs
self._do_handshake_on_connect = do_handshake_on_connect
self._suppress_ragged_eofs = suppress_ragged_eofs
@ -341,6 +539,11 @@ class SSLConnection(object):
self._handshake_done = False
self._wbio_nb = self._rbio_nb = False
self._user_config_ssl_ctx = cb_user_config_ssl_ctx
self._intf_ssl_ctx = None
self._user_config_ssl = cb_user_config_ssl
self._intf_ssl = None
if isinstance(sock, SSLConnection):
post_init = self._copy_server()
elif isinstance(sock, _UnwrappedSocket):
@ -355,12 +558,16 @@ class SSLConnection(object):
else:
post_init = self._init_client(peer_address)
if self._user_config_ssl:
self._user_config_ssl(self._intf_ssl)
else:
SSL_set_options(self._ssl.value, SSL_OP_NO_QUERY_MTU)
DTLS_set_link_mtu(self._ssl.value, 1500)
SSL_set_bio(self._ssl.value, self._rbio.value, self._wbio.value)
self._rbio.disown()
self._wbio.disown()
if post_init:
post_init()
def get_socket(self, inbound):
"""Retrieve a socket used by this connection
@ -430,9 +637,15 @@ class SSLConnection(object):
# 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_WRONG_VERSION_NUMBER:
_logger.debug("Wrong version number; aborting handshake")
raise
elif err.errqueue and err.errqueue[0][0] == ERR_COOKIE_MISMATCH:
_logger.debug("Mismatching cookie received; aborting handshake")
return
raise
elif err.errqueue and err.errqueue[0][0] == ERR_NO_SHARED_CIPHER:
_logger.debug("No shared cipher; aborting handshake")
raise
_logger.exception("Unexpected error in DTLSv1_listen")
raise
finally:
@ -462,9 +675,11 @@ class SSLConnection(object):
_logger.debug("Accept returning without connection")
return
new_conn = SSLConnection(self, self._keyfile, self._certfile, True,
self._cert_reqs, PROTOCOL_DTLSv1,
self._cert_reqs, self._ssl_version,
self._ca_certs, self._do_handshake_on_connect,
self._suppress_ragged_eofs, self._ciphers)
self._suppress_ragged_eofs, self._ciphers,
cb_user_config_ssl_ctx=self._user_config_ssl_ctx,
cb_user_config_ssl=self._user_config_ssl)
new_peer = self._pending_peer_address
self._pending_peer_address = None
if self._do_handshake_on_connect:
@ -514,7 +729,7 @@ class SSLConnection(object):
self._handshake_done = True
_logger.debug("...completed handshake")
def read(self, len=1024):
def read(self, len=1024, buffer=None):
"""Read data from connection
Read up to len bytes and return them.
@ -525,8 +740,13 @@ class SSLConnection(object):
string containing read bytes
"""
return self._wrap_socket_library_call(
lambda: SSL_read(self._ssl.value, len), ERR_READ_TIMEOUT)
try:
return self._wrap_socket_library_call(
lambda: SSL_read(self._ssl.value, len, buffer), ERR_READ_TIMEOUT)
except openssl_error() as err:
if err.ssl_error == SSL_ERROR_SYSCALL and err.result == -1:
raise_ssl_error(ERR_PORT_UNREACHABLE, err)
raise
def write(self, data):
"""Write data to connection
@ -540,8 +760,16 @@ class SSLConnection(object):
number of bytes actually transmitted
"""
return self._wrap_socket_library_call(
lambda: SSL_write(self._ssl.value, data), ERR_WRITE_TIMEOUT)
try:
ret = self._wrap_socket_library_call(
lambda: SSL_write(self._ssl.value, data), ERR_WRITE_TIMEOUT)
except openssl_error() as err:
if err.ssl_error == SSL_ERROR_SYSCALL and err.result == -1:
raise_ssl_error(ERR_PORT_UNREACHABLE, err)
raise
if ret:
self._handshake_done = True
return ret
def shutdown(self):
"""Shut down the DTLS connection
@ -605,6 +833,21 @@ class SSLConnection(object):
peer_certificate = getpeercert # compatibility with _ssl call interface
def getpeercertchain(self, binary_form=False):
try:
stack, num, certs = SSL_get_peer_cert_chain(self._ssl.value)
except openssl_error():
return
peer_cert_chain = [_Rsrc(cert) for cert in certs]
ret = []
if binary_form:
ret = [i2d_X509(x.value) for x in peer_cert_chain]
elif len(peer_cert_chain):
ret = [decode_cert(x) for x in peer_cert_chain]
return ret
def cipher(self):
"""Retrieve information about the current cipher

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBgzCCASoCCQDdMwvUA/R3lzAKBggqhkjOPQQDAzBKMQswCQYDVQQGEwJVUzET
MBEGA1UECAwKV2FzaGluZ3RvbjETMBEGA1UECgwKUmF5IENBIEluYzERMA8GA1UE
AwwIUmF5Q0FJbmMwHhcNMTcwMzA3MDgzNjU3WhcNMjcwMzA1MDgzNjU3WjBKMQsw
CQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjETMBEGA1UECgwKUmF5IENB
IEluYzERMA8GA1UEAwwIUmF5Q0FJbmMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AASD4xiQkPryjEwUl/GYeGu1CSA3UC6BUY3TiGED3zrC5Bn/POaVVn9GGOQMZUFi
rCkuTgfg/qeIzTrTFndiR5C/MAoGCCqGSM49BAMDA0cAMEQCIHpd9qMvZZV6iaB5
HrmlyfmhIuLBxDQra20Uxl2Y8N64AiAmPKqwPPp7z6IT2AzAXyHCPoVxwWA0NfGx
nmXoYpDFlw==
-----END CERTIFICATE-----

View File

@ -0,0 +1,19 @@
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIEMWCku4TqKwrQdeECm5LQPCBnr7+cqE4InlRYeObLOxoAoGCCqGSM49
AwEHoUQDQgAEgroFe2fym1V7E3zr/zjuJixpyAjwfig+UTsxxm/04IvXzk2jQCQC
TgbDVohJ8dgh4iEENZv2axWye7XCBzbftQ==
-----END EC PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIBhjCCASwCCQCZ3L2TA/e93zAKBggqhkjOPQQDAzBKMQswCQYDVQQGEwJVUzET
MBEGA1UECAwKV2FzaGluZ3RvbjETMBEGA1UECgwKUmF5IENBIEluYzERMA8GA1UE
AwwIUmF5Q0FJbmMwHhcNMTcwMzA3MDgzNjU4WhcNMjcwMzA1MDgzNjU4WjBMMQsw
CQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjEUMBIGA1UECgwLUmF5IFNy
diBJbmMxEjAQBgNVBAMMCVJheVNydkluYzBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABIK6BXtn8ptVexN86/847iYsacgI8H4oPlE7McZv9OCL185No0AkAk4Gw1aI
SfHYIeIhBDWb9msVsnu1wgc237UwCgYIKoZIzj0EAwMDSAAwRQIhAK4caAt0QSTz
A1WYlrEAA2AH181P7USiXkqQ5qRyoWQNAiBm3vKaoB+0p4B98HeI+h5V/7loomQg
sW3uB0zEuJyqIQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBhjCCASwCCQCZ3L2TA/e93zAKBggqhkjOPQQDAzBKMQswCQYDVQQGEwJVUzET
MBEGA1UECAwKV2FzaGluZ3RvbjETMBEGA1UECgwKUmF5IENBIEluYzERMA8GA1UE
AwwIUmF5Q0FJbmMwHhcNMTcwMzA3MDgzNjU4WhcNMjcwMzA1MDgzNjU4WjBMMQsw
CQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjEUMBIGA1UECgwLUmF5IFNy
diBJbmMxEjAQBgNVBAMMCVJheVNydkluYzBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABIK6BXtn8ptVexN86/847iYsacgI8H4oPlE7McZv9OCL185No0AkAk4Gw1aI
SfHYIeIhBDWb9msVsnu1wgc237UwCgYIKoZIzj0EAwMDSAAwRQIhAK4caAt0QSTz
A1WYlrEAA2AH181P7USiXkqQ5qRyoWQNAiBm3vKaoB+0p4B98HeI+h5V/7loomQg
sW3uB0zEuJyqIQ==
-----END CERTIFICATE-----

View File

@ -39,6 +39,7 @@ 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))
@ -46,8 +47,8 @@ def main():
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"),
keyfile=path.join(cert_path, "keycert.pem"),
certfile=path.join(cert_path, "keycert.pem"),
server_side=True,
ca_certs=path.join(cert_path, "ca-cert.pem"),
do_handshake_on_connect=False)
@ -76,7 +77,7 @@ def main():
try:
conn.do_handshake()
except SSLError as err:
if str(err).startswith("504:"):
if err.errno == 504:
continue
raise
print "Completed handshaking with peer"
@ -92,7 +93,7 @@ def main():
try:
message = conn.read()
except SSLError as err:
if str(err).startswith("502:"):
if err.errno == 502:
continue
if err.args[0] == SSL_ERROR_ZERO_RETURN:
break
@ -111,7 +112,7 @@ def main():
s = conn.shutdown()
s.shutdown(socket.SHUT_RDWR)
except SSLError as err:
if str(err).startswith("502:"):
if err.errno == 502:
continue
raise
break

View File

@ -0,0 +1,24 @@
@echo off
set RANDFILE=.rnd
rem # Generate self-signed certificate for the certificate authority
echo Generating CA...
openssl ecparam -name prime256v1 -genkey -out tmp_ca_ec.key
openssl req -config "openssl_ca.cnf" -x509 -new -SHA384 -nodes -key tmp_ca_ec.key -days 3650 -out ca-cert_ec.pem
rem # Generate a certificate request
echo Generating certificate request...
openssl ecparam -name prime256v1 -genkey -out tmp_server_ec.key
openssl req -config "openssl_server.cnf" -new -SHA384 -nodes -key tmp_server_ec.key -out tmp_server_ec.req
rem # Sign the request with the certificate authority's certificate created above
echo Signing certificate request...
openssl req -in tmp_server_ec.req -noout -text
openssl x509 -req -SHA384 -days 3650 -in tmp_server_ec.req -CA ca-cert_ec.pem -CAkey tmp_ca_ec.key -CAcreateserial -out server-cert_ec.pem
rem # Build pem file with private and public keys, ready for unprompted server use
cat tmp_server_ec.key server-cert_ec.pem > keycert_ec.pem
rem # Clean up
rm tmp_ca_ec.key tmp_server_ec.key tmp_server_ec.req ca-cert_ec.srl

View File

@ -1,3 +1,4 @@
HOME = .
RANDFILE = $ENV::HOME/.rnd
[ req ]

View File

@ -1,3 +1,4 @@
HOME = .
RANDFILE = $ENV::HOME/.rnd
[ req ]

View File

@ -0,0 +1,15 @@
from os import path
import ssl
from socket import socket, AF_INET, SOCK_DGRAM, SHUT_RDWR
from logging import basicConfig, DEBUG
basicConfig(level=DEBUG) # set now for dtls import code
from dtls import do_patch
do_patch()
cert_path = path.join(path.abspath(path.dirname(__file__)), "certs")
sock = ssl.wrap_socket(socket(AF_INET, SOCK_DGRAM), cert_reqs=ssl.CERT_REQUIRED, ca_certs=path.join(cert_path, "ca-cert.pem"))
sock.connect(('localhost', 28000))
sock.send('Hi there')
print sock.recv()
sock.unwrap()
sock.shutdown(SHUT_RDWR)

View File

@ -78,9 +78,10 @@ class BasicSocketTests(unittest.TestCase):
def test_constants(self):
ssl.PROTOCOL_SSLv23
ssl.PROTOCOL_SSLv3
ssl.PROTOCOL_TLSv1
ssl.PROTOCOL_DTLSv1 # added
ssl.PROTOCOL_DTLSv1_2 # added
ssl.PROTOCOL_DTLS # added
ssl.CERT_NONE
ssl.CERT_OPTIONAL
ssl.CERT_REQUIRED
@ -93,8 +94,8 @@ class BasicSocketTests(unittest.TestCase):
self.assertIsInstance(t, tuple)
self.assertIsInstance(s, str)
# Some sanity checks follow
# >= 1.0
self.assertGreaterEqual(n, 0x10000000)
# >= 1.0.2
self.assertGreaterEqual(n, 0x10002000)
# < 2.0
self.assertLess(n, 0x20000000)
major, minor, fix, patch, status = t
@ -102,7 +103,7 @@ class BasicSocketTests(unittest.TestCase):
self.assertLess(major, 2)
self.assertGreaterEqual(minor, 0)
self.assertLess(minor, 256)
self.assertGreaterEqual(fix, 0)
self.assertGreaterEqual(fix, 2)
self.assertLess(fix, 256)
self.assertGreaterEqual(patch, 0)
self.assertLessEqual(patch, 26)
@ -301,28 +302,30 @@ class NetworkedTests(unittest.TestCase):
"to establish session.\n") % count)
def test_get_server_certificate(self):
with test_support.transient_internet() as remote:
pem = ssl.get_server_certificate(remote, ssl.PROTOCOL_DTLSv1)
if not pem:
self.fail("No server certificate!")
try:
for prot in (ssl.PROTOCOL_DTLSv1, ssl.PROTOCOL_DTLSv1_2, ssl.PROTOCOL_DTLS):
with test_support.transient_internet() as remote:
pem = ssl.get_server_certificate(remote,
ssl.PROTOCOL_DTLSv1,
ca_certs=OTHER_CERTFILE)
except ssl.SSLError:
#should fail
pass
else:
self.fail("Got server certificate %s!" % pem)
prot)
if not pem:
self.fail("No server certificate!")
pem = ssl.get_server_certificate(remote,
ssl.PROTOCOL_DTLSv1,
ca_certs=ISSUER_CERTFILE)
if not pem:
self.fail("No server certificate!")
if test_support.verbose:
sys.stdout.write("\nVerified certificate is\n%s\n" % pem)
try:
pem = ssl.get_server_certificate(remote,
prot,
ca_certs=OTHER_CERTFILE)
except ssl.SSLError:
# should fail
pass
else:
self.fail("Got server certificate %s!" % pem)
pem = ssl.get_server_certificate(remote,
prot,
ca_certs=ISSUER_CERTFILE)
if not pem:
self.fail("No server certificate!")
if test_support.verbose:
sys.stdout.write("\nVerified certificate is\n%s\n" % pem)
class ThreadedEchoServer(threading.Thread):
@ -535,6 +538,8 @@ class ThreadedEchoServer(threading.Thread):
handler.start()
except socket.timeout:
pass
except ssl.SSLError:
pass
except KeyboardInterrupt:
self.stop()
self.sock.close()
@ -574,6 +579,9 @@ class AsyncoreEchoServer(threading.Thread):
# Complete the handshake
self.handle_read_event()
def __hash__(self):
return hash(self.socket)
def readable(self):
while self.socket.pending() > 0:
self.handle_read_event()
@ -1037,11 +1045,34 @@ class ThreadedTests(unittest.TestCase):
"""Connecting to a DTLSv1 server with various client options"""
if test_support.verbose:
sys.stdout.write("\n")
# server: 1.0 - client: 1.0 -> ok
try_protocol_combo(ssl.PROTOCOL_DTLSv1, ssl.PROTOCOL_DTLSv1, True)
try_protocol_combo(ssl.PROTOCOL_DTLSv1, ssl.PROTOCOL_DTLSv1, True,
ssl.CERT_OPTIONAL)
try_protocol_combo(ssl.PROTOCOL_DTLSv1, ssl.PROTOCOL_DTLSv1, True,
ssl.CERT_REQUIRED)
# server: any - client: 1.0 and 1.2(any) -> ok
try_protocol_combo(ssl.PROTOCOL_DTLS, ssl.PROTOCOL_DTLSv1, True)
try_protocol_combo(ssl.PROTOCOL_DTLS, ssl.PROTOCOL_DTLSv1, True,
ssl.CERT_REQUIRED)
try_protocol_combo(ssl.PROTOCOL_DTLS, ssl.PROTOCOL_DTLSv1_2, True)
try_protocol_combo(ssl.PROTOCOL_DTLS, ssl.PROTOCOL_DTLSv1_2, True,
ssl.CERT_REQUIRED)
try_protocol_combo(ssl.PROTOCOL_DTLS, ssl.PROTOCOL_DTLS, True)
try_protocol_combo(ssl.PROTOCOL_DTLS, ssl.PROTOCOL_DTLS, True,
ssl.CERT_REQUIRED)
# server: 1.0 - client: 1.2 -> fail
try_protocol_combo(ssl.PROTOCOL_DTLSv1, ssl.PROTOCOL_DTLSv1_2, False)
try_protocol_combo(ssl.PROTOCOL_DTLSv1, ssl.PROTOCOL_DTLSv1_2, False,
ssl.CERT_REQUIRED)
# server: 1.2 - client: 1.0 -> fail
try_protocol_combo(ssl.PROTOCOL_DTLSv1_2, ssl.PROTOCOL_DTLSv1, False)
try_protocol_combo(ssl.PROTOCOL_DTLSv1_2, ssl.PROTOCOL_DTLSv1, False,
ssl.CERT_REQUIRED)
# server: 1.2 - client: 1.2 -> ok
try_protocol_combo(ssl.PROTOCOL_DTLSv1_2, ssl.PROTOCOL_DTLSv1_2, True)
try_protocol_combo(ssl.PROTOCOL_DTLSv1_2, ssl.PROTOCOL_DTLSv1_2, True,
ssl.CERT_REQUIRED)
def test_starttls(self):
"""Switching from clear text to encrypted and back again."""
@ -1060,7 +1091,7 @@ class ThreadedTests(unittest.TestCase):
# try to connect
wrapped = False
try:
s = ssl.wrap_socket(socket.socket(AF_INET4_6, socket.SOCK_DGRAM))
s = ssl.wrap_socket(socket.socket(AF_INET4_6, socket.SOCK_DGRAM), ssl_version=ssl.PROTOCOL_DTLSv1)
s.connect((HOST, server.port))
s = s.unwrap()
if test_support.verbose:

View File

@ -0,0 +1,654 @@
# -*- encoding: utf-8 -*-
# Test the support for DTLS through the SSL module. Adapted from the Python
# standard library's test_ssl.py regression test module by Björn Freise.
import unittest
import threading
import sys
import socket
import os
import pprint
from logging import basicConfig, DEBUG, getLogger
# basicConfig(level=DEBUG, format="%(asctime)s - %(threadName)-10s - %(name)s - %(levelname)s - %(message)s")
_logger = getLogger(__name__)
import ssl
from dtls import do_patch, error_codes
from dtls.wrapper import DtlsSocket, SSL_BUILD_CHAIN_FLAG_NONE, SSL_BUILD_CHAIN_FLAG_NO_ROOT
HOST = "localhost"
CHATTY = True
CHATTY_CLIENT = True
class ThreadedEchoServer(threading.Thread):
def __init__(self, certificate, ssl_version=None, certreqs=None, cacerts=None,
ciphers=None, curves=None, sigalgs=None,
mtu=None, server_key_exchange_curve=None, server_cert_options=None,
chatty=True):
if ssl_version is None:
ssl_version = ssl.PROTOCOL_DTLSv1
if certreqs is None:
certreqs = ssl.CERT_NONE
self.certificate = certificate
self.protocol = ssl_version
self.certreqs = certreqs
self.cacerts = cacerts
self.ciphers = ciphers
self.curves = curves
self.sigalgs = sigalgs
self.mtu = mtu
self.server_key_exchange_curve = server_key_exchange_curve
self.server_cert_options = server_cert_options
self.chatty = chatty
self.flag = None
self.sock = DtlsSocket(socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
keyfile=self.certificate,
certfile=self.certificate,
server_side=True,
cert_reqs=self.certreqs,
ssl_version=self.protocol,
ca_certs=self.cacerts,
ciphers=self.ciphers,
curves=self.curves,
sigalgs=self.sigalgs,
user_mtu=self.mtu,
server_key_exchange_curve=self.server_key_exchange_curve,
server_cert_options=self.server_cert_options)
if self.chatty:
sys.stdout.write(' server: wrapped server socket as %s\n' % str(self.sock))
self.sock.bind((HOST, 0))
self.port = self.sock.getsockname()[1]
self.active = False
threading.Thread.__init__(self)
self.daemon = True
def start(self, flag=None):
self.flag = flag
self.starter = threading.current_thread().ident
threading.Thread.start(self)
def run(self):
self.sock.settimeout(0.05)
self.sock.listen(0)
self.active = True
if self.flag:
# signal an event
self.flag.set()
while self.active:
try:
acc_ret = self.sock.recvfrom(4096)
if acc_ret:
newdata, connaddr = acc_ret
if self.chatty:
sys.stdout.write(' server: new data from ' + str(connaddr) + '\n')
self.sock.sendto(newdata.lower(), connaddr)
except socket.timeout:
pass
except KeyboardInterrupt:
self.stop()
except Exception as e:
if self.chatty:
sys.stdout.write(' server: error ' + str(e) + '\n')
pass
if self.chatty:
sys.stdout.write(' server: closing socket as %s\n' % str(self.sock))
self.sock.close()
def stop(self):
self.active = False
if self.starter != threading.current_thread().ident:
return
self.join() # don't allow spawning new handlers after we've checked
CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "certs", "keycert.pem")
CERTFILE_EC = os.path.join(os.path.dirname(__file__) or os.curdir, "certs", "keycert_ec.pem")
ISSUER_CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "certs", "ca-cert.pem")
ISSUER_CERTFILE_EC = os.path.join(os.path.dirname(__file__) or os.curdir, "certs", "ca-cert_ec.pem")
# certfile, protocol, certreqs, cacertsfile,
# ciphers=None, curves=None, sigalgs=None,
tests = [
{'testcase':
{'name': 'standard dtls v1',
'desc': 'Standard DTLS v1 test with out-of-the box configuration and RSA certificate',
'start_server': True},
'input':
{'certfile': CERTFILE,
'protocol': ssl.PROTOCOL_DTLSv1,
'certreqs': None,
'cacertsfile': ISSUER_CERTFILE,
'ciphers': None,
'curves': None,
'sigalgs': None,
'client_certfile': None,
'client_protocol': ssl.PROTOCOL_DTLSv1,
'client_certreqs': ssl.CERT_REQUIRED,
'client_cacertsfile': ISSUER_CERTFILE,
'client_ciphers': None,
'client_curves': None,
'client_sigalgs': None},
'result':
{'ret_success': True,
'error_code': None,
'exception': None}},
{'testcase':
{'name': 'standard dtls v1_2',
'desc': 'Standard DTLS v1_2 test with out-of-the box configuration and ECDSA certificate',
'start_server': True},
'input':
{'certfile': CERTFILE_EC,
'protocol': ssl.PROTOCOL_DTLSv1_2,
'certreqs': None,
'cacertsfile': ISSUER_CERTFILE_EC,
'ciphers': None,
'curves': None,
'sigalgs': None,
'client_certfile': None,
'client_protocol': ssl.PROTOCOL_DTLSv1_2,
'client_certreqs': ssl.CERT_REQUIRED,
'client_cacertsfile': ISSUER_CERTFILE_EC,
'client_ciphers': None,
'client_curves': None,
'client_sigalgs': None},
'result':
{'ret_success': True,
'error_code': None,
'exception': None}},
{'testcase':
{'name': 'protocol version mismatch',
'desc': 'Client and server have different protocol versions',
'start_server': True},
'input':
{'certfile': CERTFILE,
'protocol': ssl.PROTOCOL_DTLSv1,
'certreqs': None,
'cacertsfile': ISSUER_CERTFILE,
'ciphers': None,
'curves': None,
'sigalgs': None,
'client_certfile': None,
'client_protocol': ssl.PROTOCOL_DTLSv1_2,
'client_certreqs': ssl.CERT_REQUIRED,
'client_cacertsfile': ISSUER_CERTFILE,
'client_ciphers': None,
'client_curves': None,
'client_sigalgs': None},
'result':
{'ret_success': False,
'error_code': error_codes.ERR_WRONG_SSL_VERSION,
'exception': None}},
{'testcase':
{'name': 'certificate verify fails',
'desc': 'Server certificate cannot be verified by client',
'start_server': True},
'input':
{'certfile': CERTFILE_EC,
'protocol': ssl.PROTOCOL_DTLSv1_2,
'certreqs': None,
'cacertsfile': ISSUER_CERTFILE_EC,
'ciphers': None,
'curves': None,
'sigalgs': None,
'client_certfile': None,
'client_protocol': ssl.PROTOCOL_DTLSv1_2,
'client_certreqs': ssl.CERT_REQUIRED,
'client_cacertsfile': ISSUER_CERTFILE,
'client_ciphers': None,
'client_curves': None,
'client_sigalgs': None},
'result':
{'ret_success': False,
'error_code': error_codes.ERR_CERTIFICATE_VERIFY_FAILED,
'exception': None}},
{'testcase':
{'name': 'no matching curve',
'desc': 'Client doesn\'t support curve used by server ECDSA certificate',
'start_server': True},
'input':
{'certfile': CERTFILE_EC,
'protocol': ssl.PROTOCOL_DTLSv1_2,
'certreqs': None,
'cacertsfile': ISSUER_CERTFILE_EC,
'ciphers': None,
'curves': None,
'sigalgs': None,
'client_certfile': None,
'client_protocol': ssl.PROTOCOL_DTLSv1_2,
'client_certreqs': ssl.CERT_REQUIRED,
'client_cacertsfile': ISSUER_CERTFILE_EC,
'client_ciphers': None,
'client_curves': 'secp384r1',
'client_sigalgs': None},
'result':
{'ret_success': False,
'error_code': error_codes.ERR_SSL_HANDSHAKE_FAILURE,
'exception': None}},
{'testcase':
{'name': 'matching curve',
'desc': '',
'start_server': True},
'input':
{'certfile': CERTFILE_EC,
'protocol': ssl.PROTOCOL_DTLSv1_2,
'certreqs': None,
'cacertsfile': ISSUER_CERTFILE_EC,
'ciphers': None,
'curves': None,
'sigalgs': None,
'client_certfile': None,
'client_protocol': ssl.PROTOCOL_DTLSv1_2,
'client_certreqs': ssl.CERT_REQUIRED,
'client_cacertsfile': ISSUER_CERTFILE_EC,
'client_ciphers': None,
'client_curves': 'prime256v1',
'client_sigalgs': None},
'result':
{'ret_success': True,
'error_code': None,
'exception': None}},
{'testcase':
{'name': 'no host',
'desc': 'No server port is listening',
'start_server': False},
'input':
{'certfile': CERTFILE_EC,
'protocol': ssl.PROTOCOL_DTLSv1_2,
'certreqs': None,
'cacertsfile': ISSUER_CERTFILE_EC,
'ciphers': None,
'curves': None,
'sigalgs': None,
'client_certfile': None,
'client_protocol': ssl.PROTOCOL_DTLSv1_2,
'client_certreqs': ssl.CERT_REQUIRED,
'client_cacertsfile': ISSUER_CERTFILE_EC,
'client_ciphers': None,
'client_curves': None,
'client_sigalgs': None},
'result':
{'ret_success': False,
'error_code': error_codes.ERR_PORT_UNREACHABLE,
'exception': None}},
{'testcase':
{'name': 'no matching sigalgs',
'desc': '',
'start_server': True},
'input':
{'certfile': CERTFILE_EC,
'protocol': ssl.PROTOCOL_DTLSv1_2,
'certreqs': None,
'cacertsfile': ISSUER_CERTFILE_EC,
'ciphers': None,
'curves': None,
'sigalgs': None,
'client_certfile': None,
'client_protocol': ssl.PROTOCOL_DTLSv1_2,
'client_certreqs': ssl.CERT_REQUIRED,
'client_cacertsfile': ISSUER_CERTFILE_EC,
'client_ciphers': None,
'client_curves': None,
'client_sigalgs': "RSA+SHA256"},
'result':
{'ret_success': False,
'error_code': error_codes.ERR_SSL_HANDSHAKE_FAILURE,
'exception': None}},
{'testcase':
{'name': 'matching sigalgs',
'desc': '',
'start_server': True},
'input':
{'certfile': CERTFILE_EC,
'protocol': ssl.PROTOCOL_DTLSv1_2,
'certreqs': None,
'cacertsfile': ISSUER_CERTFILE_EC,
'ciphers': None,
'curves': None,
'sigalgs': None,
'client_certfile': None,
'client_protocol': ssl.PROTOCOL_DTLSv1_2,
'client_certreqs': ssl.CERT_REQUIRED,
'client_cacertsfile': ISSUER_CERTFILE_EC,
'client_ciphers': None,
'client_curves': None,
'client_sigalgs': "ECDSA+SHA256"},
'result':
{'ret_success': True,
'error_code': None,
'exception': None}},
{'testcase':
{'name': 'no matching cipher',
'desc': 'Server using a ECDSA certificate while client is only able to use RSA encryption',
'start_server': True},
'input':
{'certfile': CERTFILE_EC,
'protocol': ssl.PROTOCOL_DTLSv1_2,
'certreqs': None,
'cacertsfile': ISSUER_CERTFILE_EC,
'ciphers': None,
'curves': None,
'sigalgs': None,
'client_certfile': None,
'client_protocol': ssl.PROTOCOL_DTLSv1_2,
'client_certreqs': ssl.CERT_REQUIRED,
'client_cacertsfile': ISSUER_CERTFILE_EC,
'client_ciphers': "AES256-SHA",
'client_curves': None,
'client_sigalgs': None},
'result':
{'ret_success': False,
'error_code': error_codes.ERR_SSL_HANDSHAKE_FAILURE,
'exception': None}},
{'testcase':
{'name': 'matching cipher',
'desc': '',
'start_server': True},
'input':
{'certfile': CERTFILE_EC,
'protocol': ssl.PROTOCOL_DTLSv1_2,
'certreqs': None,
'cacertsfile': ISSUER_CERTFILE_EC,
'ciphers': None,
'curves': None,
'sigalgs': None,
'client_certfile': None,
'client_protocol': ssl.PROTOCOL_DTLSv1_2,
'client_certreqs': ssl.CERT_REQUIRED,
'client_cacertsfile': ISSUER_CERTFILE_EC,
'client_ciphers': "ECDHE-ECDSA-AES256-SHA",
'client_curves': None,
'client_sigalgs': None},
'result':
{'ret_success': True,
'error_code': None,
'exception': None}},
]
def params_test(start_server, certfile, protocol, certreqs, cacertsfile,
client_certfile=None, client_protocol=None, client_certreqs=None, client_cacertsfile=None,
ciphers=None, curves=None, sigalgs=None,
client_ciphers=None, client_curves=None, client_sigalgs=None,
mtu=1500, server_key_exchange_curve=None, server_cert_options=None,
indata="FOO\n", chatty=False, connectionchatty=False):
"""
Launch a server, connect a client to it and try various reads
and writes.
"""
server = ThreadedEchoServer(certfile,
ssl_version=protocol,
certreqs=certreqs,
cacerts=cacertsfile,
ciphers=ciphers,
curves=curves,
sigalgs=sigalgs,
mtu=mtu,
server_key_exchange_curve=server_key_exchange_curve,
server_cert_options=server_cert_options,
chatty=chatty)
# should we really run the server?
if start_server:
flag = threading.Event()
server.start(flag)
# wait for it to start
flag.wait()
else:
server.sock.close()
# try to connect
if client_protocol is None:
client_protocol = protocol
if client_ciphers is None:
client_ciphers = ciphers
if client_curves is None:
client_curves = curves
if client_sigalgs is None:
client_sigalgs = sigalgs
try:
s = DtlsSocket(socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
keyfile=client_certfile,
certfile=client_certfile,
cert_reqs=client_certreqs,
ssl_version=client_protocol,
ca_certs=client_cacertsfile,
ciphers=client_ciphers,
curves=client_curves,
sigalgs=client_sigalgs,
user_mtu=mtu)
s.connect((HOST, server.port))
if connectionchatty:
sys.stdout.write(" client: sending %s...\n" % (repr(indata)))
s.write(indata)
outdata = s.read()
if connectionchatty:
sys.stdout.write(" client: read %s\n" % repr(outdata))
if outdata != indata.lower():
raise AssertionError("bad data <<%s>> (%d) received; expected <<%s>> (%d)\n"
% (outdata[:min(len(outdata), 20)], len(outdata),
indata[:min(len(indata), 20)].lower(), len(indata)))
cert = s.getpeercert()
cipher = s.cipher()
if connectionchatty:
sys.stdout.write("cert:\n" + pprint.pformat(cert) + "\n")
sys.stdout.write("cipher:\n" + pprint.pformat(cipher) + "\n")
if connectionchatty:
sys.stdout.write(" client: closing connection.\n")
try:
s.close()
except Exception as e:
if connectionchatty:
sys.stdout.write(" client: error closing connection %s...\n" % (repr(e)))
pass
except Exception as e:
if connectionchatty:
sys.stdout.write(" client: aborting with exception %s...\n" % (repr(e)))
return False, e
finally:
if start_server:
server.stop()
return True, None
class TestSequenceMeta(type):
def __new__(mcs, name, bases, dict):
def gen_test(_case, _input, _result):
def test(self):
try:
if CHATTY or CHATTY_CLIENT:
sys.stdout.write("\nTestcase: %s\n" % _case['name'])
ret, e = params_test(_case['start_server'], chatty=CHATTY, connectionchatty=CHATTY_CLIENT, **_input)
if _result['ret_success']:
self.assertEqual(ret, _result['ret_success'])
else:
try:
last_error = e.errqueue[-1][0]
except:
try:
last_error = e.errno
except:
last_error = None
self.assertEqual(last_error, _result['error_code'])
except Exception as e:
raise
return test
for testcase in tests:
_case, _input, _result = testcase.itervalues()
test_name = "test_%s" % _case['name'].lower().replace(' ', '_')
dict[test_name] = gen_test(_case, _input, _result)
return type.__new__(mcs, name, bases, dict)
class WrapperTests(unittest.TestCase):
__metaclass__ = TestSequenceMeta
def setUp(self):
super(WrapperTests, self).setUp()
do_patch()
def test_build_cert_chain(self):
steps = [SSL_BUILD_CHAIN_FLAG_NONE, SSL_BUILD_CHAIN_FLAG_NO_ROOT]
chatty, connectionchatty = CHATTY, CHATTY_CLIENT
indata = 'FOO'
certs = dict()
if chatty or connectionchatty:
sys.stdout.write("\nTestcase: test_build_cert_chain\n")
for step in steps:
server = ThreadedEchoServer(certificate=CERTFILE,
ssl_version=ssl.PROTOCOL_DTLSv1_2,
certreqs=ssl.CERT_NONE,
cacerts=ISSUER_CERTFILE,
ciphers=None,
curves=None,
sigalgs=None,
mtu=1500,
server_key_exchange_curve=None,
server_cert_options=step,
chatty=chatty)
flag = threading.Event()
server.start(flag)
# wait for it to start
flag.wait()
try:
s = DtlsSocket(socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
keyfile=None,
certfile=None,
cert_reqs=ssl.CERT_REQUIRED,
ssl_version=ssl.PROTOCOL_DTLSv1_2,
ca_certs=ISSUER_CERTFILE,
ciphers=None,
curves=None,
sigalgs=None,
user_mtu=1500)
s.connect((HOST, server.port))
if connectionchatty:
sys.stdout.write(" client: sending %s...\n" % (repr(indata)))
s.write(indata)
outdata = s.read()
if connectionchatty:
sys.stdout.write(" client: read %s\n" % repr(outdata))
if outdata != indata.lower():
raise AssertionError("bad data <<%s>> (%d) received; expected <<%s>> (%d)\n"
% (outdata[:min(len(outdata), 20)], len(outdata),
indata[:min(len(indata), 20)].lower(), len(indata)))
# cert = s.getpeercert()
# cipher = s.cipher()
# if connectionchatty:
# sys.stdout.write("cert:\n" + pprint.pformat(cert) + "\n")
# sys.stdout.write("cipher:\n" + pprint.pformat(cipher) + "\n")
certs[step] = s.getpeercertchain()
if connectionchatty:
sys.stdout.write(" client: closing connection.\n")
try:
s.close()
except Exception as e:
if connectionchatty:
sys.stdout.write(" client: error closing connection %s...\n" % (repr(e)))
pass
except Exception as e:
if connectionchatty:
sys.stdout.write(" client: aborting with exception %s...\n" % (repr(e)))
raise
finally:
server.stop()
if chatty:
sys.stdout.write("certs:\n")
for step in steps:
sys.stdout.write("SSL_CTX_build_cert_chain: %s\n%s\n" % (step, pprint.pformat(certs[step])))
self.assertNotEqual(certs[steps[0]], certs[steps[1]])
self.assertEqual(len(certs[steps[0]]) - len(certs[steps[1]]), 1)
def test_set_ecdh_curve(self):
steps = {
# server, client, result
'all auto': (None, None, True), # Auto
'client restricted': (None, "secp256k1:prime256v1", True), # client can handle key curve
'client too restricted': (None, "secp256k1", False), # client _cannot_ handle key curve
'client minimum': (None, "prime256v1", True), # client can only handle key curve
'server restricted': ("secp384r1", None, True), # client can handle key curve
'server one, client two': ("secp384r1", "prime256v1:secp384r1", True), # client can handle key curve
'server one, client one': ("secp384r1", "secp384r1", False), # client _cannot_ handle key curve
}
chatty, connectionchatty = CHATTY, CHATTY_CLIENT
indata = 'FOO'
certs = dict()
if chatty or connectionchatty:
sys.stdout.write("\nTestcase: test_ecdh_curve\n")
for step, tmp in steps.iteritems():
if chatty or connectionchatty:
sys.stdout.write("\n Subcase: %s\n" % step)
server_curve, client_curve, result = tmp
server = ThreadedEchoServer(certificate=CERTFILE_EC,
ssl_version=ssl.PROTOCOL_DTLSv1_2,
certreqs=ssl.CERT_NONE,
cacerts=ISSUER_CERTFILE_EC,
ciphers=None,
curves=None,
sigalgs=None,
mtu=1500,
server_key_exchange_curve=server_curve,
server_cert_options=None,
chatty=chatty)
flag = threading.Event()
server.start(flag)
# wait for it to start
flag.wait()
try:
s = DtlsSocket(socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
keyfile=None,
certfile=None,
cert_reqs=ssl.CERT_REQUIRED,
ssl_version=ssl.PROTOCOL_DTLSv1_2,
ca_certs=ISSUER_CERTFILE_EC,
ciphers=None,
curves=client_curve,
sigalgs=None,
user_mtu=1500)
s.connect((HOST, server.port))
if connectionchatty:
sys.stdout.write(" client: sending %s...\n" % (repr(indata)))
s.write(indata)
outdata = s.read()
if connectionchatty:
sys.stdout.write(" client: read %s\n" % repr(outdata))
if outdata != indata.lower():
raise AssertionError("bad data <<%s>> (%d) received; expected <<%s>> (%d)\n"
% (outdata[:min(len(outdata), 20)], len(outdata),
indata[:min(len(indata), 20)].lower(), len(indata)))
if connectionchatty:
sys.stdout.write(" client: closing connection.\n")
try:
s.close()
except Exception as e:
if connectionchatty:
sys.stdout.write(" client: error closing connection %s...\n" % (repr(e)))
pass
except Exception as e:
if connectionchatty:
sys.stdout.write(" client: aborting with exception %s...\n" % (repr(e)))
if result:
raise
finally:
server.stop()
pass
if __name__ == '__main__':
unittest.main()

View File

@ -57,3 +57,15 @@ class _BIO(_Rsrc):
BIO_free(self._value)
self.owned = False
self._value = None
class _EC_KEY(_Rsrc):
"""EC KEY wrapper"""
def __init__(self, value):
super(_EC_KEY, self).__init__(value)
def __del__(self):
_logger.debug("Freeing EC_KEY: %d", self.raw)
from openssl import EC_KEY_free
EC_KEY_free(self._value)
self._value = None

361
dtls/wrapper.py 100644
View File

@ -0,0 +1,361 @@
# -*- encoding: utf-8 -*-
# DTLS Socket: A wrapper for a server and client using a DTLS connection.
# Copyright 2017 Björn Freise
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# The License is also distributed with this work in the file named "LICENSE."
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""DTLS Socket
This wrapper encapsulates the state and behavior associated with the connection
between the OpenSSL library and an individual peer when using the DTLS
protocol.
Classes:
DtlsSocket -- DTLS Socket wrapper for use as a client or server
"""
import select
from logging import getLogger
import ssl
import socket
from patch import do_patch
do_patch()
from sslconnection import SSLContext, SSL
from sslconnection import SSL_BUILD_CHAIN_FLAG_NONE, SSL_BUILD_CHAIN_FLAG_UNTRUSTED, \
SSL_BUILD_CHAIN_FLAG_NO_ROOT, SSL_BUILD_CHAIN_FLAG_CHECK
_logger = getLogger(__name__)
class DtlsSocket(object):
class _ClientSession(object):
def __init__(self, host, port, handshake_done=False):
self.host = host
self.port = int(port)
self.handshake_done = handshake_done
def getAddr(self):
return self.host, self.port
def __init__(self,
peerOrSock,
keyfile=None,
certfile=None,
server_side=False,
cert_reqs=ssl.CERT_NONE,
ssl_version=ssl.PROTOCOL_DTLSv1_2,
ca_certs=None,
do_handshake_on_connect=False,
suppress_ragged_eofs=True,
ciphers=None,
curves=None,
sigalgs=None,
user_mtu=None,
server_key_exchange_curve=None,
server_cert_options=SSL_BUILD_CHAIN_FLAG_NONE):
if server_cert_options is None:
server_cert_options = SSL_BUILD_CHAIN_FLAG_NONE
self._ssl_logging = False
self._peer = None
self._server_side = server_side
self._ciphers = ciphers
self._curves = curves
self._sigalgs = sigalgs
self._user_mtu = user_mtu
self._server_key_exchange_curve = server_key_exchange_curve
self._server_cert_options = server_cert_options
# Default socket creation
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if isinstance(peerOrSock, tuple):
# Address tuple
self._peer = peerOrSock
else:
# Socket, use given
sock = peerOrSock
self._sock = ssl.wrap_socket(sock,
keyfile=keyfile,
certfile=certfile,
server_side=self._server_side,
cert_reqs=cert_reqs,
ssl_version=ssl_version,
ca_certs=ca_certs,
do_handshake_on_connect=do_handshake_on_connect,
suppress_ragged_eofs=suppress_ragged_eofs,
ciphers=self._ciphers,
cb_user_config_ssl_ctx=self.user_config_ssl_ctx,
cb_user_config_ssl=self.user_config_ssl)
if self._server_side:
self._clients = {}
self._timeout = None
if self._peer:
self._sock.bind(self._peer)
self._sock.listen(0)
else:
if self._peer:
self._sock.connect(self._peer)
def __getattr__(self, item):
if hasattr(self, "_sock") and hasattr(self._sock, item):
return getattr(self._sock, item)
raise AttributeError
def user_config_ssl_ctx(self, _ctx):
"""
:param SSLContext _ctx:
"""
_ctx.set_ssl_logging(self._ssl_logging)
if self._ciphers:
_ctx.set_ciphers(self._ciphers)
if self._curves:
_ctx.set_curves(self._curves)
if self._sigalgs:
_ctx.set_sigalgs(self._sigalgs)
if self._server_side:
_ctx.build_cert_chain(flags=self._server_cert_options)
_ctx.set_ecdh_curve(curve_name=self._server_key_exchange_curve)
def user_config_ssl(self, _ssl):
"""
:param SSL _ssl:
"""
if self._user_mtu:
_ssl.set_link_mtu(self._user_mtu)
def settimeout(self, t):
if self._server_side:
self._timeout = t
else:
self._sock.settimeout(t)
def close(self):
if self._server_side:
for cli in self._clients.keys():
cli.close()
else:
self._sock.unwrap()
self._sock.close()
def write(self, data):
# return self._sock.write(data)
return self.sendto(data, self._peer)
def read(self, len=1024):
# return self._sock.read(len=len)
return self.recvfrom(len)[0]
def recvfrom(self, bufsize, flags=0):
if self._server_side:
return self._recvfrom_on_server_side(bufsize, flags=flags)
else:
return self._recvfrom_on_client_side(bufsize, flags=flags)
def _recvfrom_on_server_side(self, bufsize, flags):
try:
r, _, _ = select.select(self._getAllReadingSockets(), [], [], self._timeout)
except socket.timeout as e_timeout:
raise e_timeout
try:
for conn in r: # type: ssl.SSLSocket
if self._sockIsServerSock(conn):
# Connect
self._clientAccept(conn)
else:
# Handshake
if not self._clientHandshakeDone(conn):
self._clientDoHandshake(conn)
# Normal read
else:
buf = self._clientRead(conn, bufsize)
if buf and conn in self._clients:
return buf, self._clients[conn].getAddr()
except Exception as e:
raise e
try:
for conn in self._getClientReadingSockets():
if conn.get_timeout():
conn.handle_timeout()
except Exception as e:
raise e
raise socket.timeout
def _recvfrom_on_client_side(self, bufsize, flags):
try:
buf = self._sock.recv(bufsize, flags)
except ssl.SSLError as e_ssl:
if e_ssl.args[0] == ssl.SSL_ERROR_ZERO_RETURN:
return '', self._peer
elif e_ssl.args[0] in [ssl.SSL_ERROR_SSL, ssl.SSL_ERROR_SYSCALL]:
raise e_ssl
else: # like in [ssl.SSL_ERROR_WANT_READ, ...]
pass
else:
if buf:
return buf, self._peer
raise socket.timeout
def sendto(self, buf, address):
if self._server_side:
return self._sendto_from_server_side(buf, address)
else:
return self._sendto_from_client_side(buf, address)
def _sendto_from_server_side(self, buf, address):
for conn, client in self._clients.iteritems():
if client.getAddr() == address:
return self._clientWrite(conn, buf)
return 0
def _sendto_from_client_side(self, buf, address):
while True:
try:
bytes_sent = self._sock.send(buf)
except ssl.SSLError as e_ssl:
if str(e_ssl).startswith("503:"):
# The write operation timed out
continue
raise e_ssl
else:
if bytes_sent:
break
return bytes_sent
def _getClientReadingSockets(self):
return [x for x in self._clients.keys()]
def _getAllReadingSockets(self):
return [self._sock] + self._getClientReadingSockets()
def _sockIsServerSock(self, conn):
return conn is self._sock
def _clientHandshakeDone(self, conn):
return conn in self._clients and self._clients[conn].handshake_done is True
def _clientAccept(self, conn):
_logger.debug('+' * 60)
ret = None
try:
ret = conn.accept()
_logger.debug('Accept returned with ... %s' % (str(ret)))
except Exception as e_accept:
raise e_accept
else:
if ret:
client, addr = ret
host, port = addr
if client in self._clients:
raise ValueError
self._clients[client] = self._ClientSession(host=host, port=port)
self._clientDoHandshake(client)
def _clientDoHandshake(self, conn):
_logger.debug('-' * 60)
conn.setblocking(False)
try:
conn.do_handshake()
_logger.debug('Connection from %s succesful' % (str(self._clients[conn].getAddr())))
self._clients[conn].handshake_done = True
except ssl.SSLError as e_handshake:
if str(e_handshake).startswith("504:"):
pass
elif e_handshake.args[0] == ssl.SSL_ERROR_WANT_READ:
pass
else:
raise e_handshake
def _clientRead(self, conn, bufsize=4096):
_logger.debug('*' * 60)
ret = None
try:
ret = conn.recv(bufsize)
_logger.debug('From client %s ... bytes received %s' % (str(self._clients[conn].getAddr()), str(len(ret))))
except ssl.SSLError as e_read:
if e_read.args[0] == ssl.SSL_ERROR_ZERO_RETURN:
self._clientDrop(conn)
elif e_read.args[0] in [ssl.SSL_ERROR_SSL, ssl.SSL_ERROR_SYSCALL]:
self._clientDrop(conn, error=e_read)
else: # like in [ssl.SSL_ERROR_WANT_READ, ...]
pass
return ret
def _clientWrite(self, conn, data):
_logger.debug('#' * 60)
ret = None
try:
_data = data
if False:
_data = data.raw
ret = conn.send(_data)
_logger.debug('To client %s ... bytes sent %s' % (str(self._clients[conn].getAddr()), str(ret)))
except Exception as e_write:
raise e_write
return ret
def _clientDrop(self, conn, error=None):
_logger.debug('$' * 60)
try:
if error:
_logger.debug('Drop client %s ... with error: %s' % (self._clients[conn].getAddr(), error))
else:
_logger.debug('Drop client %s' % str(self._clients[conn].getAddr()))
if conn in self._clients:
del self._clients[conn]
conn.unwrap()
conn.close()
except Exception as e_drop:
pass

View File

@ -43,7 +43,7 @@ class _X509(_Rsrc):
super(_X509, self).__init__(value)
def __del__(self):
_logger.debug("Freeing X509: %d", self._value._as_parameter)
_logger.debug("Freeing X509: %d", self.raw)
X509_free(self._value)
self._value = None
@ -54,11 +54,10 @@ class _STACK(_Rsrc):
super(_STACK, self).__init__(value)
def __del__(self):
_logger.debug("Freeing stack: %d", self._value._as_parameter)
_logger.debug("Freeing stack: %d", self.raw)
sk_pop_free(self._value)
self._value = None
def decode_cert(cert):
"""Convert an X509 certificate into a Python dictionary

View File

@ -33,7 +33,7 @@ for scheme in INSTALL_SCHEMES.values():
scheme['data'] = scheme['purelib']
NAME = "Dtls"
VERSION = "1.0.1"
VERSION = "1.0.2"
DIST_DIR = "dist"
FORMAT_TO_SUFFIX = { "zip": ".zip",