| 822 | | |
|---|
| 823 | | ## class SSL_fileobject(CP_fileobject): |
|---|
| 824 | | ## """SSL file object attached to a socket object.""" |
|---|
| 825 | | |
|---|
| 826 | | ## ssl_timeout = 3 |
|---|
| 827 | | ## ssl_retry = .01 |
|---|
| 828 | | |
|---|
| 829 | | ## def _safe_call(self, is_reader, call, *args, **kwargs): |
|---|
| 830 | | ## """Wrap the given call with SSL error-trapping. |
|---|
| 831 | | |
|---|
| 832 | | ## is_reader: if False EOF errors will be raised. If True, EOF errors |
|---|
| 833 | | ## will return "" (to emulate normal sockets). |
|---|
| 834 | | ## """ |
|---|
| 835 | | ## start = time.time() |
|---|
| 836 | | ## while True: |
|---|
| 837 | | ## try: |
|---|
| 838 | | ## return call(*args, **kwargs) |
|---|
| 839 | | ## except SSL.WantReadError: |
|---|
| 840 | | ## # Sleep and try again. This is dangerous, because it means |
|---|
| 841 | | ## # the rest of the stack has no way of differentiating |
|---|
| 842 | | ## # between a "new handshake" error and "client dropped". |
|---|
| 843 | | ## # Note this isn't an endless loop: there's a timeout below. |
|---|
| 844 | | ## time.sleep(self.ssl_retry) |
|---|
| 845 | | ## except SSL.WantWriteError: |
|---|
| 846 | | ## time.sleep(self.ssl_retry) |
|---|
| 847 | | ## except SSL.SysCallError as e: |
|---|
| 848 | | ## if is_reader and e.args == (-1, 'Unexpected EOF'): |
|---|
| 849 | | ## return "" |
|---|
| 850 | | |
|---|
| 851 | | ## errnum = e.args[0] |
|---|
| 852 | | ## if is_reader and errnum in socket_errors_to_ignore: |
|---|
| 853 | | ## return "" |
|---|
| 854 | | ## raise socket.error(errnum) |
|---|
| 855 | | ## except SSL.Error as e: |
|---|
| 856 | | ## if is_reader and e.args == (-1, 'Unexpected EOF'): |
|---|
| 857 | | ## return "" |
|---|
| 858 | | |
|---|
| 859 | | ## thirdarg = None |
|---|
| 860 | | ## try: |
|---|
| 861 | | ## thirdarg = e.args[0][0][2] |
|---|
| 862 | | ## except IndexError: |
|---|
| 863 | | ## pass |
|---|
| 864 | | |
|---|
| 865 | | ## if thirdarg == 'http request': |
|---|
| 866 | | ## # The client is talking HTTP to an HTTPS server. |
|---|
| 867 | | ## raise NoSSLError() |
|---|
| 868 | | ## raise FatalSSLAlert(*e.args) |
|---|
| 869 | | ## except: |
|---|
| 870 | | ## raise |
|---|
| 871 | | |
|---|
| 872 | | ## if time.time() - start > self.ssl_timeout: |
|---|
| 873 | | ## raise socket.timeout("timed out") |
|---|
| 874 | | |
|---|
| 875 | | ## def recv(self, *args, **kwargs): |
|---|
| 876 | | ## buf = [] |
|---|
| 877 | | ## r = super(SSL_fileobject, self).recv |
|---|
| 878 | | ## while True: |
|---|
| 879 | | ## data = self._safe_call(True, r, *args, **kwargs) |
|---|
| 880 | | ## buf.append(data) |
|---|
| 881 | | ## p = self._sock.pending() |
|---|
| 882 | | ## if not p: |
|---|
| 883 | | ## return "".join(buf) |
|---|
| 884 | | |
|---|
| 885 | | ## def sendall(self, *args, **kwargs): |
|---|
| 886 | | ## return self._safe_call(False, super(SSL_fileobject, self).sendall, *args, **kwargs) |
|---|
| 887 | | |
|---|
| 888 | | ## def send(self, *args, **kwargs): |
|---|
| 889 | | ## return self._safe_call(False, super(SSL_fileobject, self).send, *args, **kwargs) |
|---|
| 921 | | if SSL and isinstance(sock, SSL.ConnectionType): |
|---|
| 922 | | #TODO: will need to override SocketIO for ssl |
|---|
| 923 | | timeout = sock.gettimeout() |
|---|
| 924 | | self.rfile = SSL_fileobject(sock, "rb", self.rbufsize) |
|---|
| 925 | | self.rfile.ssl_timeout = timeout |
|---|
| 926 | | self.wfile = SSL_fileobject(sock, "wb", -1) |
|---|
| 927 | | self.wfile.ssl_timeout = timeout |
|---|
| 928 | | else: |
|---|
| 929 | | raw = socket.SocketIO(sock, "r") |
|---|
| 930 | | self.rfile = io.BufferedReader(raw) |
|---|
| 931 | | raw = socket.SocketIO(sock, "w") |
|---|
| 932 | | self.wfile = CP_BufferedWriter(raw) |
|---|
| | 847 | self.rfile = io.BufferedReader(socket.SocketIO(sock, "r")) |
|---|
| | 848 | self.wfile = CP_BufferedWriter(socket.SocketIO(sock, "w")) |
|---|
| | 849 | |
|---|
| 1160 | | |
|---|
| 1161 | | |
|---|
| 1162 | | class SSLConnection: |
|---|
| 1163 | | """A thread-safe wrapper for an SSL.Connection. |
|---|
| 1164 | | |
|---|
| 1165 | | *args: the arguments to create the wrapped SSL.Connection(*args). |
|---|
| 1166 | | """ |
|---|
| 1167 | | |
|---|
| 1168 | | def __init__(self, *args): |
|---|
| 1169 | | self._ssl_conn = SSL.Connection(*args) |
|---|
| 1170 | | self._lock = threading.RLock() |
|---|
| 1171 | | |
|---|
| 1172 | | for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read', |
|---|
| 1173 | | 'renegotiate', 'bind', 'listen', 'connect', 'accept', |
|---|
| 1174 | | 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list', |
|---|
| 1175 | | 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', |
|---|
| 1176 | | 'makefile', 'get_app_data', 'set_app_data', 'state_string', |
|---|
| 1177 | | 'sock_shutdown', 'get_peer_certificate', 'want_read', |
|---|
| 1178 | | 'want_write', 'set_connect_state', 'set_accept_state', |
|---|
| 1179 | | 'connect_ex', 'sendall', 'settimeout'): |
|---|
| 1180 | | exec("""def %s(self, *args): |
|---|
| 1181 | | self._lock.acquire() |
|---|
| 1182 | | try: |
|---|
| 1183 | | return self._ssl_conn.%s(*args) |
|---|
| 1184 | | finally: |
|---|
| 1185 | | self._lock.release() |
|---|
| 1186 | | """ % (f, f)) |
|---|
| 1243 | | The OpenSSL module must be importable for SSL functionality. |
|---|
| 1244 | | You can obtain it from http://pyopenssl.sourceforge.net/ |
|---|
| 1245 | | |
|---|
| 1246 | | There are two ways to use SSL: |
|---|
| 1247 | | |
|---|
| 1248 | | Method One: |
|---|
| 1249 | | ssl_context: an instance of SSL.Context. |
|---|
| 1250 | | |
|---|
| 1251 | | If this is not None, it is assumed to be an SSL.Context instance, |
|---|
| 1252 | | and will be passed to SSL.Connection on bind(). The developer is |
|---|
| 1253 | | responsible for forming a valid Context object. This approach is |
|---|
| 1254 | | to be preferred for more flexibility, e.g. if the cert and key are |
|---|
| 1255 | | streams instead of files, or need decryption, or SSL.SSLv3_METHOD |
|---|
| 1256 | | is desired instead of the default SSL.SSLv23_METHOD, etc. Consult |
|---|
| 1257 | | the pyOpenSSL documentation for complete options. |
|---|
| 1258 | | |
|---|
| 1259 | | Method Two (shortcut): |
|---|
| | 1119 | The builtin ssl module must be importable for SSL functionality. |
|---|
| | 1120 | |
|---|
| | 1121 | To enable, set the following config params to valid cert/key file names |
|---|
| 1420 | | if (self.ssl_context is None and |
|---|
| 1421 | | self.ssl_certificate and self.ssl_private_key): |
|---|
| 1422 | | if SSL is None: |
|---|
| 1423 | | raise ImportError("You must install pyOpenSSL to use HTTPS.") |
|---|
| 1424 | | |
|---|
| 1425 | | # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473 |
|---|
| 1426 | | self.ssl_context = SSL.Context(SSL.SSLv23_METHOD) |
|---|
| 1427 | | self.ssl_context.use_privatekey_file(self.ssl_private_key) |
|---|
| 1428 | | if self.ssl_certificate_chain: |
|---|
| 1429 | | self.ssl_context.load_verify_locations(self.ssl_certificate_chain) |
|---|
| 1430 | | self.ssl_context.use_certificate_file(self.ssl_certificate) |
|---|
| 1431 | | |
|---|
| 1432 | | if self.ssl_context is not None: |
|---|
| 1433 | | self.socket = SSLConnection(self.ssl_context, self.socket) |
|---|
| 1434 | | self.populate_ssl_environ() |
|---|
| | 1270 | if self.ssl_certificate and self.ssl_private_key: |
|---|
| | 1271 | if ssl is None: |
|---|
| | 1272 | raise ImportError("You must install the ssl module to use HTTPS.") |
|---|
| | 1291 | |
|---|
| | 1292 | # if ssl cert and key are set, we try to be a secure HTTP server |
|---|
| | 1293 | if self.ssl_certificate and self.ssl_private_key: |
|---|
| | 1294 | try: |
|---|
| | 1295 | s = ssl.wrap_socket(s, do_handshake_on_connect=True, |
|---|
| | 1296 | server_side=True, certfile=self.ssl_certificate, |
|---|
| | 1297 | keyfile=self.ssl_private_key, |
|---|
| | 1298 | ssl_version=ssl.PROTOCOL_SSLv23) |
|---|
| | 1299 | self.ssl_cipher = s.cipher() |
|---|
| | 1300 | except socket.timeout: |
|---|
| | 1301 | return |
|---|
| | 1302 | except ssl.SSLError as e: |
|---|
| | 1303 | if e.errno == ssl.SSL_ERROR_EOF: |
|---|
| | 1304 | # This is almost certainly due to the cherrypy engine |
|---|
| | 1305 | # 'pinging' the socket to assert it's connectable; |
|---|
| | 1306 | # the 'ping' isn't SSL. |
|---|
| | 1307 | return |
|---|
| | 1308 | elif e.errno == ssl.SSL_ERROR_SSL: |
|---|
| | 1309 | if e.args[1].endswith('http request'): |
|---|
| | 1310 | # The client is speaking HTTP to an HTTPS server. |
|---|
| | 1311 | # We used to respond with a 'please use HTTPS' |
|---|
| | 1312 | # message with pyOpenSSL, but I can't figure out |
|---|
| | 1313 | # how to do that with the builtin ssl module. TODO. |
|---|
| | 1314 | return |
|---|
| | 1315 | print("unexpected", e) |
|---|
| | 1316 | raise |
|---|
| | 1317 | |
|---|