blob: b252c849b0a442392ee9129e286df17afeaddd14 [file] [log] [blame]
Bryan Duxbury50409112011-03-21 17:59:49 +00001#
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
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
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
17# under the License.
18#
Bryan Duxbury69720412012-01-03 17:32:30 +000019
Bryan Duxbury50409112011-03-21 17:59:49 +000020import os
21import socket
22import ssl
Bryan Duxbury2b969ad2011-02-22 18:20:53 +000023
24from thrift.transport import TSocket
Bryan Duxbury50409112011-03-21 17:59:49 +000025from thrift.transport.TTransport import TTransportException
Bryan Duxbury2b969ad2011-02-22 18:20:53 +000026
Bryan Duxbury69720412012-01-03 17:32:30 +000027
Bryan Duxbury2b969ad2011-02-22 18:20:53 +000028class TSSLSocket(TSocket.TSocket):
Bryan Duxbury50409112011-03-21 17:59:49 +000029 """
30 SSL implementation of client-side TSocket
31
32 This class creates outbound sockets wrapped using the
33 python standard ssl module for encrypted connections.
Bryan Duxbury69720412012-01-03 17:32:30 +000034
Bryan Duxbury50409112011-03-21 17:59:49 +000035 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.
38 """
39 SSL_VERSION = ssl.PROTOCOL_TLSv1
40
Bryan Duxbury69720412012-01-03 17:32:30 +000041 def __init__(self,
42 host='localhost',
43 port=9090,
44 validate=True,
45 ca_certs=None,
Jake Farrell877125c2013-06-07 23:47:22 -040046 keyfile=None,
47 certfile=None,
Roger Meierfc1303e2014-12-02 00:11:54 +010048 unix_socket=None,
49 ciphers=None):
Bryan Duxbury69720412012-01-03 17:32:30 +000050 """Create SSL TSocket
51
52 @param validate: Set to False to disable SSL certificate validation
Bryan Duxbury50409112011-03-21 17:59:49 +000053 @type validate: bool
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.
57 @type ca_certs: str
Jake Farrell877125c2013-06-07 23:47:22 -040058 @param keyfile: The private key
59 @type keyfile: str
60 @param certfile: The cert file
61 @type certfile: str
Roger Meierfc1303e2014-12-02 00:11:54 +010062 @param ciphers: The cipher suites to allow. This is passed to
63 the ssl_wrap function as the 'ciphers' parameter.
64 @type ciphers: str
Jake Farrell877125c2013-06-07 23:47:22 -040065
Bryan Duxbury50409112011-03-21 17:59:49 +000066 Raises an IOError exception if validate is True and the ca_certs file is
67 None, not present or unreadable.
68 """
69 self.validate = validate
70 self.is_valid = False
71 self.peercert = None
72 if not validate:
73 self.cert_reqs = ssl.CERT_NONE
74 else:
75 self.cert_reqs = ssl.CERT_REQUIRED
76 self.ca_certs = ca_certs
Jake Farrell877125c2013-06-07 23:47:22 -040077 self.keyfile = keyfile
78 self.certfile = certfile
Roger Meierfc1303e2014-12-02 00:11:54 +010079 self.ciphers = ciphers
Bryan Duxbury16066592011-03-22 18:06:04 +000080 if validate:
81 if ca_certs is None or not os.access(ca_certs, os.R_OK):
Bryan Duxbury69720412012-01-03 17:32:30 +000082 raise IOError('Certificate Authority ca_certs file "%s" '
83 'is not readable, cannot validate SSL '
84 'certificates.' % (ca_certs))
Bryan Duxbury16066592011-03-22 18:06:04 +000085 TSocket.TSocket.__init__(self, host, port, unix_socket)
Bryan Duxbury50409112011-03-21 17:59:49 +000086
87 def open(self):
88 try:
89 res0 = self._resolveAddr()
90 for res in res0:
Bryan Duxbury69720412012-01-03 17:32:30 +000091 sock_family, sock_type = res[0:2]
Bryan Duxbury50409112011-03-21 17:59:49 +000092 ip_port = res[4]
93 plain_sock = socket.socket(sock_family, sock_type)
Bryan Duxbury69720412012-01-03 17:32:30 +000094 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,
Jake Farrell877125c2013-06-07 23:47:22 -040098 keyfile=self.keyfile,
99 certfile=self.certfile,
Roger Meierfc1303e2014-12-02 00:11:54 +0100100 cert_reqs=self.cert_reqs
101 ciphers=self.ciphers)
Bryan Duxbury50409112011-03-21 17:59:49 +0000102 self.handle.settimeout(self._timeout)
Bryan Duxbury2b969ad2011-02-22 18:20:53 +0000103 try:
Bryan Duxbury50409112011-03-21 17:59:49 +0000104 self.handle.connect(ip_port)
Todd Lipcon2b2560e2012-12-10 14:29:59 -0800105 except socket.error, e:
Bryan Duxbury50409112011-03-21 17:59:49 +0000106 if res is not res0[-1]:
107 continue
Bryan Duxbury2b969ad2011-02-22 18:20:53 +0000108 else:
Bryan Duxbury50409112011-03-21 17:59:49 +0000109 raise e
110 break
Todd Lipcon2b2560e2012-12-10 14:29:59 -0800111 except socket.error, e:
Bryan Duxbury50409112011-03-21 17:59:49 +0000112 if self._unix_socket:
Roger Meier52820d02012-11-08 23:11:14 +0000113 message = 'Could not connect to secure socket %s: %s' \
114 % (self._unix_socket, e)
Bryan Duxbury50409112011-03-21 17:59:49 +0000115 else:
Roger Meier52820d02012-11-08 23:11:14 +0000116 message = 'Could not connect to %s:%d: %s' % (self.host, self.port, e)
Bryan Duxbury69720412012-01-03 17:32:30 +0000117 raise TTransportException(type=TTransportException.NOT_OPEN,
118 message=message)
Bryan Duxbury50409112011-03-21 17:59:49 +0000119 if self.validate:
120 self._validate_cert()
121
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
126 in certificates.
Bryan Duxbury69720412012-01-03 17:32:30 +0000127
128 raises TTransportException if the certificate fails validation.
129 """
Bryan Duxbury50409112011-03-21 17:59:49 +0000130 cert = self.handle.getpeercert()
131 self.peercert = cert
132 if 'subject' not in cert:
Bryan Duxbury69720412012-01-03 17:32:30 +0000133 raise TTransportException(
134 type=TTransportException.NOT_OPEN,
135 message='No SSL certificate found from %s:%s' % (self.host, self.port))
Bryan Duxbury50409112011-03-21 17:59:49 +0000136 fields = cert['subject']
137 for field in fields:
138 # ensure structure we get back is what we expect
139 if not isinstance(field, tuple):
140 continue
141 cert_pair = field[0]
142 if len(cert_pair) < 2:
143 continue
144 cert_key, cert_value = cert_pair[0:2]
145 if cert_key != 'commonName':
146 continue
147 certhost = cert_value
Jake Farrell877125c2013-06-07 23:47:22 -0400148 # this check should be performed by some sort of Access Manager
Bryan Duxbury50409112011-03-21 17:59:49 +0000149 if certhost == self.host:
150 # success, cert commonName matches desired hostname
151 self.is_valid = True
Bryan Duxbury69720412012-01-03 17:32:30 +0000152 return
Bryan Duxbury50409112011-03-21 17:59:49 +0000153 else:
Bryan Duxbury69720412012-01-03 17:32:30 +0000154 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))
162
Bryan Duxbury2b969ad2011-02-22 18:20:53 +0000163
164class TSSLServerSocket(TSocket.TServerSocket):
Bryan Duxbury69720412012-01-03 17:32:30 +0000165 """SSL implementation of TServerSocket
Bryan Duxbury50409112011-03-21 17:59:49 +0000166
167 This uses the ssl module's wrap_socket() method to provide SSL
168 negotiated encryption.
169 """
170 SSL_VERSION = ssl.PROTOCOL_TLSv1
171
Bryan Duxbury69720412012-01-03 17:32:30 +0000172 def __init__(self,
173 host=None,
174 port=9090,
175 certfile='cert.pem',
Roger Meierfc1303e2014-12-02 00:11:54 +0100176 unix_socket=None,
177 ciphers=None):
Bryan Duxbury50409112011-03-21 17:59:49 +0000178 """Initialize a TSSLServerSocket
Bryan Duxbury69720412012-01-03 17:32:30 +0000179
180 @param certfile: filename of the server certificate, defaults to cert.pem
Bryan Duxbury50409112011-03-21 17:59:49 +0000181 @type certfile: str
Bryan Duxbury69720412012-01-03 17:32:30 +0000182 @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.
Bryan Duxbury50409112011-03-21 17:59:49 +0000185 @type host: str
186 @param port: The port to listen on for inbound connections.
187 @type port: int
Roger Meierfc1303e2014-12-02 00:11:54 +0100188 @param ciphers: The cipher suites to allow. This is passed to
189 the ssl_wrap function as the 'ciphers' parameter.
190 @type ciphers: str
191
Bryan Duxbury50409112011-03-21 17:59:49 +0000192 """
193 self.setCertfile(certfile)
Bryan Duxbury16066592011-03-22 18:06:04 +0000194 TSocket.TServerSocket.__init__(self, host, port)
Roger Meierfc1303e2014-12-02 00:11:54 +0100195 self.ciphers = ciphers
Bryan Duxbury50409112011-03-21 17:59:49 +0000196
197 def setCertfile(self, certfile):
198 """Set or change the server certificate file used to wrap new connections.
Bryan Duxbury69720412012-01-03 17:32:30 +0000199
200 @param certfile: The filename of the server certificate,
201 i.e. '/etc/certs/server.pem'
Bryan Duxbury50409112011-03-21 17:59:49 +0000202 @type certfile: str
Bryan Duxbury69720412012-01-03 17:32:30 +0000203
Bryan Duxbury50409112011-03-21 17:59:49 +0000204 Raises an IOError exception if the certfile is not present or unreadable.
205 """
206 if not os.access(certfile, os.R_OK):
207 raise IOError('No such certfile found: %s' % (certfile))
208 self.certfile = certfile
209
210 def accept(self):
211 plain_client, addr = self.handle.accept()
Bryan Duxbury16066592011-03-22 18:06:04 +0000212 try:
213 client = ssl.wrap_socket(plain_client, certfile=self.certfile,
Roger Meierfc1303e2014-12-02 00:11:54 +0100214 server_side=True, ssl_version=self.SSL_VERSION,
215 ciphers=self.ciphers)
Todd Lipcon2b2560e2012-12-10 14:29:59 -0800216 except ssl.SSLError, ssl_exc:
Bryan Duxbury16066592011-03-22 18:06:04 +0000217 # failed handshake/ssl wrap, close socket to client
218 plain_client.close()
219 # raise ssl_exc
Bryan Duxbury69720412012-01-03 17:32:30 +0000220 # We can't raise the exception, because it kills most TServer derived
221 # serve() methods.
Bryan Duxbury16066592011-03-22 18:06:04 +0000222 # Instead, return None, and let the TServer instance deal with it in
223 # other exception handling. (but TSimpleServer dies anyway)
Bryan Duxbury69720412012-01-03 17:32:30 +0000224 return None
Bryan Duxbury50409112011-03-21 17:59:49 +0000225 result = TSocket.TSocket()
Bryan Duxbury50409112011-03-21 17:59:49 +0000226 result.setHandle(client)
227 return result