Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(379)

Side by Side Diff: third_party/cherrypy/wsgiserver/ssl_pyopenssl.py

Issue 9368042: Add CherryPy to third_party. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build/
Patch Set: '' Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 """A library for integrating pyOpenSSL with CherryPy.
2
3 The OpenSSL module must be importable for SSL functionality.
4 You can obtain it from http://pyopenssl.sourceforge.net/
5
6 To use this module, set CherryPyWSGIServer.ssl_adapter to an instance of
7 SSLAdapter. There are two ways to use SSL:
8
9 Method One
10 ----------
11
12 * ``ssl_adapter.context``: an instance of SSL.Context.
13
14 If this is not None, it is assumed to be an SSL.Context instance,
15 and will be passed to SSL.Connection on bind(). The developer is
16 responsible for forming a valid Context object. This approach is
17 to be preferred for more flexibility, e.g. if the cert and key are
18 streams instead of files, or need decryption, or SSL.SSLv3_METHOD
19 is desired instead of the default SSL.SSLv23_METHOD, etc. Consult
20 the pyOpenSSL documentation for complete options.
21
22 Method Two (shortcut)
23 ---------------------
24
25 * ``ssl_adapter.certificate``: the filename of the server SSL certificate.
26 * ``ssl_adapter.private_key``: the filename of the server's private key file.
27
28 Both are None by default. If ssl_adapter.context is None, but .private_key
29 and .certificate are both given and valid, they will be read, and the
30 context will be automatically created from them.
31 """
32
33 import socket
34 import threading
35 import time
36
37 from cherrypy import wsgiserver
38
39 try:
40 from OpenSSL import SSL
41 from OpenSSL import crypto
42 except ImportError:
43 SSL = None
44
45
46 class SSL_fileobject(wsgiserver.CP_fileobject):
47 """SSL file object attached to a socket object."""
48
49 ssl_timeout = 3
50 ssl_retry = .01
51
52 def _safe_call(self, is_reader, call, *args, **kwargs):
53 """Wrap the given call with SSL error-trapping.
54
55 is_reader: if False EOF errors will be raised. If True, EOF errors
56 will return "" (to emulate normal sockets).
57 """
58 start = time.time()
59 while True:
60 try:
61 return call(*args, **kwargs)
62 except SSL.WantReadError:
63 # Sleep and try again. This is dangerous, because it means
64 # the rest of the stack has no way of differentiating
65 # between a "new handshake" error and "client dropped".
66 # Note this isn't an endless loop: there's a timeout below.
67 time.sleep(self.ssl_retry)
68 except SSL.WantWriteError:
69 time.sleep(self.ssl_retry)
70 except SSL.SysCallError, e:
71 if is_reader and e.args == (-1, 'Unexpected EOF'):
72 return ""
73
74 errnum = e.args[0]
75 if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
76 return ""
77 raise socket.error(errnum)
78 except SSL.Error, e:
79 if is_reader and e.args == (-1, 'Unexpected EOF'):
80 return ""
81
82 thirdarg = None
83 try:
84 thirdarg = e.args[0][0][2]
85 except IndexError:
86 pass
87
88 if thirdarg == 'http request':
89 # The client is talking HTTP to an HTTPS server.
90 raise wsgiserver.NoSSLError()
91
92 raise wsgiserver.FatalSSLAlert(*e.args)
93 except:
94 raise
95
96 if time.time() - start > self.ssl_timeout:
97 raise socket.timeout("timed out")
98
99 def recv(self, *args, **kwargs):
100 buf = []
101 r = super(SSL_fileobject, self).recv
102 while True:
103 data = self._safe_call(True, r, *args, **kwargs)
104 buf.append(data)
105 p = self._sock.pending()
106 if not p:
107 return "".join(buf)
108
109 def sendall(self, *args, **kwargs):
110 return self._safe_call(False, super(SSL_fileobject, self).sendall,
111 *args, **kwargs)
112
113 def send(self, *args, **kwargs):
114 return self._safe_call(False, super(SSL_fileobject, self).send,
115 *args, **kwargs)
116
117
118 class SSLConnection:
119 """A thread-safe wrapper for an SSL.Connection.
120
121 ``*args``: the arguments to create the wrapped ``SSL.Connection(*args)``.
122 """
123
124 def __init__(self, *args):
125 self._ssl_conn = SSL.Connection(*args)
126 self._lock = threading.RLock()
127
128 for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
129 'renegotiate', 'bind', 'listen', 'connect', 'accept',
130 'setblocking', 'fileno', 'close', 'get_cipher_list',
131 'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
132 'makefile', 'get_app_data', 'set_app_data', 'state_string',
133 'sock_shutdown', 'get_peer_certificate', 'want_read',
134 'want_write', 'set_connect_state', 'set_accept_state',
135 'connect_ex', 'sendall', 'settimeout', 'gettimeout'):
136 exec("""def %s(self, *args):
137 self._lock.acquire()
138 try:
139 return self._ssl_conn.%s(*args)
140 finally:
141 self._lock.release()
142 """ % (f, f))
143
144 def shutdown(self, *args):
145 self._lock.acquire()
146 try:
147 # pyOpenSSL.socket.shutdown takes no args
148 return self._ssl_conn.shutdown()
149 finally:
150 self._lock.release()
151
152
153 class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
154 """A wrapper for integrating pyOpenSSL with CherryPy."""
155
156 context = None
157 """An instance of SSL.Context."""
158
159 certificate = None
160 """The filename of the server SSL certificate."""
161
162 private_key = None
163 """The filename of the server's private key file."""
164
165 certificate_chain = None
166 """Optional. The filename of CA's intermediate certificate bundle.
167
168 This is needed for cheaper "chained root" SSL certificates, and should be
169 left as None if not required."""
170
171 def __init__(self, certificate, private_key, certificate_chain=None):
172 if SSL is None:
173 raise ImportError("You must install pyOpenSSL to use HTTPS.")
174
175 self.context = None
176 self.certificate = certificate
177 self.private_key = private_key
178 self.certificate_chain = certificate_chain
179 self._environ = None
180
181 def bind(self, sock):
182 """Wrap and return the given socket."""
183 if self.context is None:
184 self.context = self.get_context()
185 conn = SSLConnection(self.context, sock)
186 self._environ = self.get_environ()
187 return conn
188
189 def wrap(self, sock):
190 """Wrap and return the given socket, plus WSGI environ entries."""
191 return sock, self._environ.copy()
192
193 def get_context(self):
194 """Return an SSL.Context from self attributes."""
195 # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
196 c = SSL.Context(SSL.SSLv23_METHOD)
197 c.use_privatekey_file(self.private_key)
198 if self.certificate_chain:
199 c.load_verify_locations(self.certificate_chain)
200 c.use_certificate_file(self.certificate)
201 return c
202
203 def get_environ(self):
204 """Return WSGI environ entries to be merged into each request."""
205 ssl_environ = {
206 "HTTPS": "on",
207 # pyOpenSSL doesn't provide access to any of these AFAICT
208 ## 'SSL_PROTOCOL': 'SSLv2',
209 ## SSL_CIPHER string The cipher specification name
210 ## SSL_VERSION_INTERFACE string The mod_ssl program version
211 ## SSL_VERSION_LIBRARY string The OpenSSL program version
212 }
213
214 if self.certificate:
215 # Server certificate attributes
216 cert = open(self.certificate, 'rb').read()
217 cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
218 ssl_environ.update({
219 'SSL_SERVER_M_VERSION': cert.get_version(),
220 'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
221 ## 'SSL_SERVER_V_START': Validity of server's certificate (start time),
222 ## 'SSL_SERVER_V_END': Validity of server's certificate (end time ),
223 })
224
225 for prefix, dn in [("I", cert.get_issuer()),
226 ("S", cert.get_subject())]:
227 # X509Name objects don't seem to have a way to get the
228 # complete DN string. Use str() and slice it instead,
229 # because str(dn) == "<X509Name object '/C=US/ST=...'>"
230 dnstr = str(dn)[18:-2]
231
232 wsgikey = 'SSL_SERVER_%s_DN' % prefix
233 ssl_environ[wsgikey] = dnstr
234
235 # The DN should be of the form: /k1=v1/k2=v2, but we must allow
236 # for any value to contain slashes itself (in a URL).
237 while dnstr:
238 pos = dnstr.rfind("=")
239 dnstr, value = dnstr[:pos], dnstr[pos + 1:]
240 pos = dnstr.rfind("/")
241 dnstr, key = dnstr[:pos], dnstr[pos + 1:]
242 if key and value:
243 wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
244 ssl_environ[wsgikey] = value
245
246 return ssl_environ
247
248 def makefile(self, sock, mode='r', bufsize=-1):
249 if SSL and isinstance(sock, SSL.ConnectionType):
250 timeout = sock.gettimeout()
251 f = SSL_fileobject(sock, mode, bufsize)
252 f.ssl_timeout = timeout
253 return f
254 else:
255 return wsgiserver.CP_fileobject(sock, mode, bufsize)
256
OLDNEW
« no previous file with comments | « third_party/cherrypy/wsgiserver/ssl_builtin.py ('k') | third_party/cherrypy/wsgiserver/wsgiserver2.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698