Skip to content

Commit f32bae7

Browse files
committed
THRIFT-3651 Make backports.match_hostname and ipaddress optional
Client: Python Patch: Nobuaki Sukegawa This closes apache#880
1 parent a72ffbe commit f32bae7

File tree

2 files changed

+83
-42
lines changed

2 files changed

+83
-42
lines changed

lib/py/src/transport/TSSLSocket.py

Lines changed: 6 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import ssl
2424
import sys
2525
import warnings
26-
from backports.ssl_match_hostname import match_hostname
2726

27+
from .sslcompat import _match_hostname, _match_has_ipaddress
2828
from thrift.transport import TSocket
2929
from thrift.transport.TTransport import TTransportException
3030

@@ -259,7 +259,7 @@ def __init__(self, host='localhost', port=9090, *args, **kwargs):
259259
kwargs['cert_reqs'] = ssl.CERT_REQUIRED if validate else ssl.CERT_NONE
260260

261261
unix_socket = kwargs.pop('unix_socket', None)
262-
self._validate_callback = kwargs.pop('validate_callback', match_hostname)
262+
self._validate_callback = kwargs.pop('validate_callback', _match_hostname)
263263
TSSLBase.__init__(self, False, host, kwargs)
264264
TSocket.TSocket.__init__(self, host, port, unix_socket)
265265

@@ -297,45 +297,6 @@ def open(self):
297297
except Exception as ex:
298298
raise TTransportException(TTransportException.UNKNOWN, str(ex))
299299

300-
@staticmethod
301-
def legacy_validate_callback(self, cert, hostname):
302-
"""legacy method to validate the peer's SSL certificate, and to check
303-
the commonName of the certificate to ensure it matches the hostname we
304-
used to make this connection. Does not support subjectAltName records
305-
in certificates.
306-
307-
raises TTransportException if the certificate fails validation.
308-
"""
309-
if 'subject' not in cert:
310-
raise TTransportException(
311-
TTransportException.NOT_OPEN,
312-
'No SSL certificate found from %s:%s' % (self.host, self.port))
313-
fields = cert['subject']
314-
for field in fields:
315-
# ensure structure we get back is what we expect
316-
if not isinstance(field, tuple):
317-
continue
318-
cert_pair = field[0]
319-
if len(cert_pair) < 2:
320-
continue
321-
cert_key, cert_value = cert_pair[0:2]
322-
if cert_key != 'commonName':
323-
continue
324-
certhost = cert_value
325-
# this check should be performed by some sort of Access Manager
326-
if certhost == hostname:
327-
# success, cert commonName matches desired hostname
328-
return
329-
else:
330-
raise TTransportException(
331-
TTransportException.UNKNOWN,
332-
'Hostname we connected to "%s" doesn\'t match certificate '
333-
'provided commonName "%s"' % (self.host, certhost))
334-
raise TTransportException(
335-
TTransportException.UNKNOWN,
336-
'Could not validate SSL certificate from host "%s". Cert=%s'
337-
% (hostname, cert))
338-
339300

340301
class TSSLServerSocket(TSocket.TServerSocket, TSSLBase):
341302
"""SSL implementation of TServerSocket
@@ -381,9 +342,12 @@ def __init__(self, host=None, port=9090, *args, **kwargs):
381342

382343
unix_socket = kwargs.pop('unix_socket', None)
383344
self._validate_callback = \
384-
kwargs.pop('validate_callback', match_hostname)
345+
kwargs.pop('validate_callback', _match_hostname)
385346
TSSLBase.__init__(self, True, None, kwargs)
386347
TSocket.TServerSocket.__init__(self, host, port, unix_socket)
348+
if self._should_verify and not _match_has_ipaddress:
349+
raise ValueError('Need ipaddress and backports.ssl_match_hostname'
350+
'module to verify client certificate')
387351

388352
def setCertfile(self, certfile):
389353
"""Set or change the server certificate file used to wrap new

lib/py/src/transport/sslcompat.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
from thrift.transport.TTransport import TTransportException
21+
22+
23+
def legacy_validate_callback(self, cert, hostname):
24+
"""legacy method to validate the peer's SSL certificate, and to check
25+
the commonName of the certificate to ensure it matches the hostname we
26+
used to make this connection. Does not support subjectAltName records
27+
in certificates.
28+
29+
raises TTransportException if the certificate fails validation.
30+
"""
31+
if 'subject' not in cert:
32+
raise TTransportException(
33+
TTransportException.NOT_OPEN,
34+
'No SSL certificate found from %s:%s' % (self.host, self.port))
35+
fields = cert['subject']
36+
for field in fields:
37+
# ensure structure we get back is what we expect
38+
if not isinstance(field, tuple):
39+
continue
40+
cert_pair = field[0]
41+
if len(cert_pair) < 2:
42+
continue
43+
cert_key, cert_value = cert_pair[0:2]
44+
if cert_key != 'commonName':
45+
continue
46+
certhost = cert_value
47+
# this check should be performed by some sort of Access Manager
48+
if certhost == hostname:
49+
# success, cert commonName matches desired hostname
50+
return
51+
else:
52+
raise TTransportException(
53+
TTransportException.UNKNOWN,
54+
'Hostname we connected to "%s" doesn\'t match certificate '
55+
'provided commonName "%s"' % (self.host, certhost))
56+
raise TTransportException(
57+
TTransportException.UNKNOWN,
58+
'Could not validate SSL certificate from host "%s". Cert=%s'
59+
% (hostname, cert))
60+
61+
62+
try:
63+
import ipaddress # noqa
64+
_match_has_ipaddress = True
65+
except ImportError:
66+
_match_has_ipaddress = False
67+
68+
try:
69+
from backports.ssl_match_hostname import match_hostname
70+
_match_hostname = match_hostname
71+
except ImportError:
72+
try:
73+
from ssl import match_hostname
74+
_match_hostname = match_hostname
75+
except ImportError:
76+
_match_hostname = legacy_validate_callback
77+
_match_has_ipaddress = False

0 commit comments

Comments
 (0)