2 # Licensed to the Apache Software Foundation (ASF) under one
3 # or more contributor license agreements. See the NOTICE file
4 # distributed with this work for additional information
5 # regarding copyright ownership. The ASF licenses this file
6 # to you under the Apache License, Version 2.0 (the
7 # "License"); you may not use this file except in compliance
8 # with the License. You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing,
13 # software distributed under the License is distributed on an
14 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 # KIND, either express or implied. See the License for the
16 # specific language governing permissions and limitations
24 from thrift.transport import TSocket
25 from thrift.transport.TTransport import TTransportException
28 class TSSLSocket(TSocket.TSocket):
30 SSL implementation of client-side TSocket
32 This class creates outbound sockets wrapped using the
33 python standard ssl module for encrypted connections.
35 The protocol used is set using the class variable
36 SSL_VERSION, which must be one of ssl.PROTOCOL_* and
37 defaults to ssl.PROTOCOL_TLSv1 for greatest security.
39 SSL_VERSION = ssl.PROTOCOL_TLSv1
52 @param validate: Set to False to disable SSL certificate validation
54 @param ca_certs: Filename to the Certificate Authority pem file, possibly a
55 file downloaded from: http://curl.haxx.se/ca/cacert.pem This is passed to
56 the ssl_wrap function as the 'ca_certs' parameter.
58 @param keyfile: The private key
60 @param certfile: The cert file
62 @param ciphers: The cipher suites to allow. This is passed to
63 the ssl_wrap function as the 'ciphers' parameter.
66 Raises an IOError exception if validate is True and the ca_certs file is
67 None, not present or unreadable.
69 self.validate = validate
73 self.cert_reqs = ssl.CERT_NONE
75 self.cert_reqs = ssl.CERT_REQUIRED
76 self.ca_certs = ca_certs
77 self.keyfile = keyfile
78 self.certfile = certfile
79 self.ciphers = ciphers
81 if ca_certs is None or not os.access(ca_certs, os.R_OK):
82 raise IOError('Certificate Authority ca_certs file "%s" '
83 'is not readable, cannot validate SSL '
84 'certificates.' % (ca_certs))
85 TSocket.TSocket.__init__(self, host, port, unix_socket)
89 res0 = self._resolveAddr()
91 sock_family, sock_type = res[0:2]
93 plain_sock = socket.socket(sock_family, sock_type)
94 self.handle = ssl.wrap_socket(plain_sock,
95 ssl_version=self.SSL_VERSION,
96 do_handshake_on_connect=True,
97 ca_certs=self.ca_certs,
99 certfile=self.certfile,
100 cert_reqs=self.cert_reqs,
101 ciphers=self.ciphers)
102 self.handle.settimeout(self._timeout)
104 self.handle.connect(ip_port)
105 except socket.error as e:
106 if res is not res0[-1]:
111 except socket.error as e:
112 if self._unix_socket:
113 message = 'Could not connect to secure socket %s: %s' \
114 % (self._unix_socket, e)
116 message = 'Could not connect to %s:%d: %s' % (self.host, self.port, e)
117 raise TTransportException(type=TTransportException.NOT_OPEN,
120 self._validate_cert()
122 def _validate_cert(self):
123 """internal method to validate the peer's SSL certificate, and to check the
124 commonName of the certificate to ensure it matches the hostname we
125 used to make this connection. Does not support subjectAltName records
128 raises TTransportException if the certificate fails validation.
130 cert = self.handle.getpeercert()
132 if 'subject' not in cert:
133 raise TTransportException(
134 type=TTransportException.NOT_OPEN,
135 message='No SSL certificate found from %s:%s' % (self.host, self.port))
136 fields = cert['subject']
138 # ensure structure we get back is what we expect
139 if not isinstance(field, tuple):
142 if len(cert_pair) < 2:
144 cert_key, cert_value = cert_pair[0:2]
145 if cert_key != 'commonName':
147 certhost = cert_value
148 # this check should be performed by some sort of Access Manager
149 if certhost == self.host:
150 # success, cert commonName matches desired hostname
154 raise TTransportException(
155 type=TTransportException.UNKNOWN,
156 message='Hostname we connected to "%s" doesn\'t match certificate '
157 'provided commonName "%s"' % (self.host, certhost))
158 raise TTransportException(
159 type=TTransportException.UNKNOWN,
160 message='Could not validate SSL certificate from '
161 'host "%s". Cert=%s' % (self.host, cert))
164 class TSSLServerSocket(TSocket.TServerSocket):
165 """SSL implementation of TServerSocket
167 This uses the ssl module's wrap_socket() method to provide SSL
168 negotiated encryption.
170 SSL_VERSION = ssl.PROTOCOL_TLSv1
178 """Initialize a TSSLServerSocket
180 @param certfile: filename of the server certificate, defaults to cert.pem
182 @param host: The hostname or IP to bind the listen socket to,
183 i.e. 'localhost' for only allowing local network connections.
184 Pass None to bind to all interfaces.
186 @param port: The port to listen on for inbound connections.
188 @param ciphers: The cipher suites to allow. This is passed to
189 the ssl_wrap function as the 'ciphers' parameter.
193 self.setCertfile(certfile)
194 TSocket.TServerSocket.__init__(self, host, port)
195 self.ciphers = ciphers
197 def setCertfile(self, certfile):
198 """Set or change the server certificate file used to wrap new connections.
200 @param certfile: The filename of the server certificate,
201 i.e. '/etc/certs/server.pem'
204 Raises an IOError exception if the certfile is not present or unreadable.
206 if not os.access(certfile, os.R_OK):
207 raise IOError('No such certfile found: %s' % (certfile))
208 self.certfile = certfile
211 plain_client, addr = self.handle.accept()
213 client = ssl.wrap_socket(plain_client, certfile=self.certfile,
214 server_side=True, ssl_version=self.SSL_VERSION,
215 ciphers=self.ciphers)
216 except ssl.SSLError as ssl_exc:
217 # failed handshake/ssl wrap, close socket to client
220 # We can't raise the exception, because it kills most TServer derived
222 # Instead, return None, and let the TServer instance deal with it in
223 # other exception handling. (but TSimpleServer dies anyway)
225 result = TSocket.TSocket()
226 result.setHandle(client)