THRIFT-3651 Make backports.match_hostname and ipaddress optional
Client: Python
Patch: Nobuaki Sukegawa
This closes #880
diff --git a/lib/py/src/transport/sslcompat.py b/lib/py/src/transport/sslcompat.py
new file mode 100644
index 0000000..2d778d2
--- /dev/null
+++ b/lib/py/src/transport/sslcompat.py
@@ -0,0 +1,77 @@
+#
+# licensed to the apache software foundation (asf) under one
+# or more contributor license agreements. see the notice file
+# distributed with this work for additional information
+# regarding copyright ownership. the asf licenses this file
+# to you under the apache license, version 2.0 (the
+# "license"); you may not use this file except in compliance
+# with the license. you may obtain a copy of the license at
+#
+# http://www.apache.org/licenses/license-2.0
+#
+# unless required by applicable law or agreed to in writing,
+# software distributed under the license is distributed on an
+# "as is" basis, without warranties or conditions of any
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from thrift.transport.TTransport import TTransportException
+
+
+def legacy_validate_callback(self, cert, hostname):
+ """legacy method to validate the peer's SSL certificate, and to check
+ the commonName of the certificate to ensure it matches the hostname we
+ used to make this connection. Does not support subjectAltName records
+ in certificates.
+
+ raises TTransportException if the certificate fails validation.
+ """
+ if 'subject' not in cert:
+ raise TTransportException(
+ TTransportException.NOT_OPEN,
+ 'No SSL certificate found from %s:%s' % (self.host, self.port))
+ fields = cert['subject']
+ for field in fields:
+ # ensure structure we get back is what we expect
+ if not isinstance(field, tuple):
+ continue
+ cert_pair = field[0]
+ if len(cert_pair) < 2:
+ continue
+ cert_key, cert_value = cert_pair[0:2]
+ if cert_key != 'commonName':
+ continue
+ certhost = cert_value
+ # this check should be performed by some sort of Access Manager
+ if certhost == hostname:
+ # success, cert commonName matches desired hostname
+ return
+ else:
+ raise TTransportException(
+ TTransportException.UNKNOWN,
+ 'Hostname we connected to "%s" doesn\'t match certificate '
+ 'provided commonName "%s"' % (self.host, certhost))
+ raise TTransportException(
+ TTransportException.UNKNOWN,
+ 'Could not validate SSL certificate from host "%s". Cert=%s'
+ % (hostname, cert))
+
+
+try:
+ import ipaddress # noqa
+ _match_has_ipaddress = True
+except ImportError:
+ _match_has_ipaddress = False
+
+try:
+ from backports.ssl_match_hostname import match_hostname
+ _match_hostname = match_hostname
+except ImportError:
+ try:
+ from ssl import match_hostname
+ _match_hostname = match_hostname
+ except ImportError:
+ _match_hostname = legacy_validate_callback
+ _match_has_ipaddress = False