Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 2459

Show
Ignore:
Timestamp:
06/16/09 11:23:50
Author:
fumanchu
Message:

python3: Fix for #774 (Migrate from pyOpenSSL to the ssl module) -- mostly equal to justin's patch.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/python3/cherrypy/_cpwsgi_server.py

    r2158 r2459  
    6262        self.protocol = self.server_adapter.protocol_version 
    6363        self.nodelay = self.server_adapter.nodelay 
    64         self.ssl_context = self.server_adapter.ssl_context 
    6564        self.ssl_certificate = self.server_adapter.ssl_certificate 
    66         self.ssl_certificate_chain = self.server_adapter.ssl_certificate_chain 
    6765        self.ssl_private_key = self.server_adapter.ssl_private_key 
    6866 
  • branches/python3/cherrypy/wsgiserver/__init__.py

    r2433 r2459  
    100100 
    101101try: 
    102     from OpenSSL import SSL 
    103     from OpenSSL import crypto 
     102    import ssl 
    104103except ImportError: 
    105     SSL = None 
     104    ssl = None 
    106105 
    107106import errno 
     
    132131    "EHOSTDOWN", "EHOSTUNREACH", 
    133132    ) 
     133 
    134134socket_errors_to_ignore.append("timed out") 
     135socket_errors_to_ignore.append("The read operation timed out") 
    135136 
    136137socket_errors_nonblocking = plat_specific_errors( 
     
    319320        # with FIN_WAIT_2). 
    320321        request_line = self.rfile.readline() 
     322 
    321323        # Set started_request to True so communicate() knows to send 408 
    322324        # from here on out. 
     
    789791 
    790792 
    791 class NoSSLError(Exception): 
    792     """Exception raised when a client speaks HTTP to an HTTPS socket.""" 
    793     pass 
    794  
    795  
    796 class FatalSSLAlert(Exception): 
    797     """Exception raised when the SSL implementation signals a fatal alert.""" 
    798     pass 
    799  
    800793class CP_BufferedWriter(io.BufferedWriter): 
    801794    """Faux file object attached to a socket object.""" 
    802      
     795 
    803796    def write(self, b): 
    804          
    805797        self._checkClosed() 
    806798        if isinstance(b, str): 
     
    816808        while self._write_buf: 
    817809            try: 
    818                 n = self.raw.write(self._write_buf) 
    819             except BlockingIOError as e: 
     810                # ssl sockets only except 'bytes', not bytearrays 
     811                # so perhaps we should conditionally wrap this for perf? 
     812                n = self.raw.write(bytes(self._write_buf)) 
     813            except io.BlockingIOError as e: 
    820814                n = e.characters_written 
    821815            del self._write_buf[:n] 
    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) 
    890816 
    891817 
     
    919845        self.environ.update(environ) 
    920846         
    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 
    933850         
    934851        # Wrap wsgi.input but not HTTPConnection.rfile itself. 
     
    964881        except socket.error as e: 
    965882            errnum = e.args[0] 
    966             if errnum == 'timed out': 
     883            # sadly SSL sockets return a different (longer) time out string 
     884            if errnum == 'timed out' or errnum == 'The read operation timed out': 
    967885                # Don't error if we're between requests; only error 
    968886                # if 1) no request has been started at all, or 2) we're 
     
    981899        except (KeyboardInterrupt, SystemExit): 
    982900            raise 
    983         except FatalSSLAlert as e: 
    984             # Close the connection. 
    985             return 
    986         except NoSSLError: 
    987             if req and not req.sent_headers: 
    988                 # Unwrap our wfile 
    989                 req.wfile = CP_fileobject(self.socket._sock, "wb", -1) 
    990                 req.simple_response("400 Bad Request", 
    991                     "The client sent a plain HTTP request, but " 
    992                     "this server only speaks HTTPS on this port.") 
    993                 self.linger = True 
    994901        except Exception as e: 
    995902            if req and not req.sent_headers: 
     
    11461053                            c = worker.conn 
    11471054                            if c and not c.rfile.closed: 
    1148                                 if SSL and isinstance(c.socket, SSL.ConnectionType): 
    1149                                     # pyOpenSSL.socket.shutdown takes no args 
    1150                                     c.socket.shutdown() 
    1151                                 else: 
    1152                                     c.socket.shutdown(socket.SHUT_RD) 
     1055                                c.socket.shutdown(socket.SHUT_RD) 
    11531056                            worker.join() 
    11541057                except (AssertionError, 
     
    11581061                    pass 
    11591062 
    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)) 
    11871063 
    11881064 
     
    12411117    SSL/HTTPS 
    12421118    --------- 
    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 
    12601122        ssl_certificate: the filename of the server SSL certificate. 
    12611123        ssl_privatekey: the filename of the server's private key file. 
    12621124         
    1263         Both are None by default. If ssl_context is None, but ssl_privatekey 
    1264         and ssl_certificate are both given and valid, they will be read on 
    1265         server start, and self.ssl_context will be automatically created 
    1266         from them. 
    1267          
    1268         ssl_certificate_chain: (optional) the filename of CA's intermediate 
    1269             certificate bundle. This is needed for cheaper "chained root" SSL 
    1270             certificates, and should be left as None if not required. 
    12711125    """ 
    12721126     
     
    12821136    environ = {} 
    12831137     
    1284     # An SSL.Context instance... 
    1285     ssl_context = None 
    1286      
    12871138    # ...or paths to certificate and private key files 
    12881139    ssl_certificate = None 
    1289     ssl_certificate_chain = None 
    12901140    ssl_private_key = None 
    12911141     
     
    14181268            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 
    14191269         
    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.") 
    14351273         
    14361274        # If listening on the IPV6 any address ('::' = IN6ADDR_ANY), 
     
    14511289        try: 
    14521290            s, addr = self.socket.accept() 
     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             
    14531318            prevent_socket_inheritance(s) 
    14541319            if not self.ready: 
     
    14781343                environ["REMOTE_ADDR"] = addr[0] 
    14791344                environ["REMOTE_PORT"] = str(addr[1]) 
     1345             
     1346            if (self.ssl_certificate and self.ssl_private_key): 
     1347                self.populate_ssl_environ(environ) 
    14801348             
    14811349            conn = self.ConnectionClass(s, self.wsgi_app, environ) 
     
    15531421        self.requests.stop(self.shutdown_timeout) 
    15541422     
    1555     def populate_ssl_environ(self): 
     1423    # TODO: fill this out more with mod ssl env 
     1424    def populate_ssl_environ(self, environ): 
    15561425        """Create WSGI environ entries to be merged into each request.""" 
    15571426        ssl_environ = { 
    15581427            "wsgi.url_scheme": "https", 
    15591428            "HTTPS": "on", 
    1560             # pyOpenSSL doesn't provide access to any of these AFAICT 
    1561 ##            'SSL_PROTOCOL': 'SSLv2', 
    1562 ##            SSL_CIPHER        string  The cipher specification name 
     1429            'SSL_PROTOCOL': self.ssl_cipher[1], 
     1430            'SSL_CIPHER': self.ssl_cipher[0] 
    15631431##            SSL_VERSION_INTERFACE     string  The mod_ssl program version 
    15641432##            SSL_VERSION_LIBRARY       string  The OpenSSL program version 
    15651433            } 
    15661434         
     1435        """ 
    15671436        if self.ssl_certificate: 
    15681437            # Server certificate attributes 
     
    15961465                        wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key) 
    15971466                        ssl_environ[wsgikey] = value 
    1598          
    1599         self.environ.update(ssl_environ) 
    1600  
     1467        """ 
     1468         
     1469        environ.update(ssl_environ) 
     1470 

Hosted by WebFaction

Log in as guest/cpguest to create tickets