Completed
Branch master (477316)
by Michael
08:56
created

thrift.transport.TSSLSocket.TSSLSocket.open()   B

Complexity

Conditions 7

Size

Total Lines 30
Code Lines 28

Duplication

Lines 30
Ratio 100 %

Importance

Changes 0
Metric Value
eloc 28
dl 30
loc 30
rs 7.808
c 0
b 0
f 0
cc 7
nop 1
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
               unix_socket=None):
47
    """Create SSL TSocket
48
49
    @param validate: Set to False to disable SSL certificate validation
50
    @type validate: bool
51
    @param ca_certs: Filename to the Certificate Authority pem file, possibly a
52
    file downloaded from: http://curl.haxx.se/ca/cacert.pem  This is passed to
53
    the ssl_wrap function as the 'ca_certs' parameter.
54
    @type ca_certs: str
55
56
    Raises an IOError exception if validate is True and the ca_certs file is
57
    None, not present or unreadable.
58
    """
59
    self.validate = validate
60
    self.is_valid = False
61
    self.peercert = None
62
    if not validate:
63
      self.cert_reqs = ssl.CERT_NONE
64
    else:
65
      self.cert_reqs = ssl.CERT_REQUIRED
66
    self.ca_certs = ca_certs
67
    if validate:
68
      if ca_certs is None or not os.access(ca_certs, os.R_OK):
69
        raise IOError('Certificate Authority ca_certs file "%s" '
70
                      'is not readable, cannot validate SSL '
71
                      'certificates.' % (ca_certs))
72
    TSocket.TSocket.__init__(self, host, port, unix_socket)
73
74
  def open(self):
75
    try:
76
      res0 = self._resolveAddr()
77
      for res in res0:
78
        sock_family, sock_type = res[0:2]
79
        ip_port = res[4]
80
        plain_sock = socket.socket(sock_family, sock_type)
81
        self.handle = ssl.wrap_socket(plain_sock,
82
                                      ssl_version=self.SSL_VERSION,
83
                                      do_handshake_on_connect=True,
84
                                      ca_certs=self.ca_certs,
85
                                      cert_reqs=self.cert_reqs)
86
        self.handle.settimeout(self._timeout)
87
        try:
88
          self.handle.connect(ip_port)
89
        except socket.error as e:
90
          if res is not res0[-1]:
91
            continue
92
          else:
93
            raise e
94
        break
95
    except socket.error as e:
96
      if self._unix_socket:
97
        message = 'Could not connect to secure socket %s' % self._unix_socket
98
      else:
99
        message = 'Could not connect to %s:%d' % (self.host, self.port)
100
      raise TTransportException(type=TTransportException.NOT_OPEN,
101
                                message=message)
102
    if self.validate:
103
      self._validate_cert()
104
105
  def _validate_cert(self):
106
    """internal method to validate the peer's SSL certificate, and to check the
107
    commonName of the certificate to ensure it matches the hostname we
108
    used to make this connection.  Does not support subjectAltName records
109
    in certificates.
110
111
    raises TTransportException if the certificate fails validation.
112
    """
113
    cert = self.handle.getpeercert()
114
    self.peercert = cert
115
    if 'subject' not in cert:
116
      raise TTransportException(
117
        type=TTransportException.NOT_OPEN,
118
        message='No SSL certificate found from %s:%s' % (self.host, self.port))
119
    fields = cert['subject']
120
    for field in fields:
121
      # ensure structure we get back is what we expect
122
      if not isinstance(field, tuple):
123
        continue
124
      cert_pair = field[0]
125
      if len(cert_pair) < 2:
126
        continue
127
      cert_key, cert_value = cert_pair[0:2]
128
      if cert_key != 'commonName':
129
        continue
130
      certhost = cert_value
131
      if certhost == self.host:
132
        # success, cert commonName matches desired hostname
133
        self.is_valid = True
134
        return
135
      else:
136
        raise TTransportException(
137
          type=TTransportException.UNKNOWN,
138
          message='Hostname we connected to "%s" doesn\'t match certificate '
139
                  'provided commonName "%s"' % (self.host, certhost))
140
    raise TTransportException(
141
      type=TTransportException.UNKNOWN,
142
      message='Could not validate SSL certificate from '
143
              'host "%s".  Cert=%s' % (self.host, cert))
144
145
146
class TSSLServerSocket(TSocket.TServerSocket):
147
  """SSL implementation of TServerSocket
148
149
  This uses the ssl module's wrap_socket() method to provide SSL
150
  negotiated encryption.
151
  """
152
  SSL_VERSION = ssl.PROTOCOL_TLSv1
153
154
  def __init__(self,
155
               host=None,
156
               port=9090,
157
               certfile='cert.pem',
158
               unix_socket=None):
159
    """Initialize a TSSLServerSocket
160
161
    @param certfile: filename of the server certificate, defaults to cert.pem
162
    @type certfile: str
163
    @param host: The hostname or IP to bind the listen socket to,
164
                 i.e. 'localhost' for only allowing local network connections.
165
                 Pass None to bind to all interfaces.
166
    @type host: str
167
    @param port: The port to listen on for inbound connections.
168
    @type port: int
169
    """
170
    self.setCertfile(certfile)
171
    TSocket.TServerSocket.__init__(self, host, port)
172
173
  def setCertfile(self, certfile):
174
    """Set or change the server certificate file used to wrap new connections.
175
176
    @param certfile: The filename of the server certificate,
177
                     i.e. '/etc/certs/server.pem'
178
    @type certfile: str
179
180
    Raises an IOError exception if the certfile is not present or unreadable.
181
    """
182
    if not os.access(certfile, os.R_OK):
183
      raise IOError('No such certfile found: %s' % (certfile))
184
    self.certfile = certfile
185
186
  def accept(self):
187
    plain_client, addr = self.handle.accept()
188
    try:
189
      client = ssl.wrap_socket(plain_client, certfile=self.certfile,
190
                      server_side=True, ssl_version=self.SSL_VERSION)
191
    except ssl.SSLError as ssl_exc:
192
      # failed handshake/ssl wrap, close socket to client
193
      plain_client.close()
194
      # raise ssl_exc
195
      # We can't raise the exception, because it kills most TServer derived
196
      # serve() methods.
197
      # Instead, return None, and let the TServer instance deal with it in
198
      # other exception handling.  (but TSimpleServer dies anyway)
199
      return None
200
    result = TSocket.TSocket()
201
    result.setHandle(client)
202
    return result
203