added sqlite3 support at Domino Server for label subscriptions and client registrations
[domino.git] / lib / thrift / transport / TSSLSocket.py
1 #
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 #
19
20 import os
21 import socket
22 import ssl
23
24 from thrift.transport import TSocket
25 from thrift.transport.TTransport import TTransportException
26
27
28 class TSSLSocket(TSocket.TSocket):
29   """
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.
34
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.
38   """
39   SSL_VERSION = ssl.PROTOCOL_TLSv1
40
41   def __init__(self,
42                host='localhost',
43                port=9090,
44                validate=True,
45                ca_certs=None,
46                keyfile=None,
47                certfile=None,
48                unix_socket=None,
49                ciphers=None):
50     """Create SSL TSocket
51
52     @param validate: Set to False to disable SSL certificate validation
53     @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
58     @param keyfile: The private key
59     @type keyfile: str
60     @param certfile: The cert file
61     @type certfile: str
62     @param ciphers: The cipher suites to allow. This is passed to
63                     the ssl_wrap function as the 'ciphers' parameter.
64     @type ciphers: str
65     
66     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
77     self.keyfile = keyfile
78     self.certfile = certfile
79     self.ciphers = ciphers
80     if validate:
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)
86
87   def open(self):
88     try:
89       res0 = self._resolveAddr()
90       for res in res0:
91         sock_family, sock_type = res[0:2]
92         ip_port = res[4]
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,
98                                       keyfile=self.keyfile,
99                                       certfile=self.certfile,
100                                       cert_reqs=self.cert_reqs,
101                                       ciphers=self.ciphers)
102         self.handle.settimeout(self._timeout)
103         try:
104           self.handle.connect(ip_port)
105         except socket.error as e:
106           if res is not res0[-1]:
107             continue
108           else:
109             raise e
110         break
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)
115       else:
116         message = 'Could not connect to %s:%d: %s' % (self.host, self.port, e)
117       raise TTransportException(type=TTransportException.NOT_OPEN,
118                                 message=message)
119     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.
127
128     raises TTransportException if the certificate fails validation.
129     """
130     cert = self.handle.getpeercert()
131     self.peercert = cert
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']
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
148       # this check should be performed by some sort of Access Manager
149       if certhost == self.host:
150         # success, cert commonName matches desired hostname
151         self.is_valid = True
152         return
153       else:
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))
162
163
164 class TSSLServerSocket(TSocket.TServerSocket):
165   """SSL implementation of TServerSocket
166
167   This uses the ssl module's wrap_socket() method to provide SSL
168   negotiated encryption.
169   """
170   SSL_VERSION = ssl.PROTOCOL_TLSv1
171
172   def __init__(self,
173                host=None,
174                port=9090,
175                certfile='cert.pem',
176                unix_socket=None,
177                ciphers=None):
178     """Initialize a TSSLServerSocket
179
180     @param certfile: filename of the server certificate, defaults to cert.pem
181     @type certfile: str
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.
185     @type host: str
186     @param port: The port to listen on for inbound connections.
187     @type port: int
188     @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
192     """
193     self.setCertfile(certfile)
194     TSocket.TServerSocket.__init__(self, host, port)
195     self.ciphers = ciphers
196
197   def setCertfile(self, certfile):
198     """Set or change the server certificate file used to wrap new connections.
199
200     @param certfile: The filename of the server certificate,
201                      i.e. '/etc/certs/server.pem'
202     @type certfile: str
203
204     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()
212     try:
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
218       plain_client.close()
219       # raise ssl_exc
220       # We can't raise the exception, because it kills most TServer derived
221       # serve() methods.
222       # Instead, return None, and let the TServer instance deal with it in
223       # other exception handling.  (but TSimpleServer dies anyway)
224       return None
225     result = TSocket.TSocket()
226     result.setHandle(client)
227     return result