1# Copyright 2014-present MongoDB, Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you 4# may not use this file except in compliance with the License. You 5# may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12# implied. See the License for the specific language governing 13# permissions and limitations under the License. 14 15"""Support for SSL in PyMongo.""" 16 17import atexit 18import sys 19import threading 20 21from bson.py3compat import string_type 22from pymongo.errors import ConfigurationError 23 24HAVE_SSL = True 25 26try: 27 import pymongo.pyopenssl_context as _ssl 28except ImportError: 29 try: 30 import pymongo.ssl_context as _ssl 31 except ImportError: 32 HAVE_SSL = False 33 34HAVE_CERTIFI = False 35try: 36 import certifi 37 HAVE_CERTIFI = True 38except ImportError: 39 pass 40 41HAVE_WINCERTSTORE = False 42try: 43 from wincertstore import CertFile 44 HAVE_WINCERTSTORE = True 45except ImportError: 46 pass 47 48_WINCERTSLOCK = threading.Lock() 49_WINCERTS = None 50 51if HAVE_SSL: 52 # Note: The validate* functions below deal with users passing 53 # CPython ssl module constants to configure certificate verification 54 # at a high level. This is legacy behavior, but requires us to 55 # import the ssl module even if we're only using it for this purpose. 56 import ssl as _stdlibssl 57 from ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED 58 HAS_SNI = _ssl.HAS_SNI 59 IPADDR_SAFE = _ssl.IS_PYOPENSSL or sys.version_info[:2] >= (3, 7) 60 SSLError = _ssl.SSLError 61 def validate_cert_reqs(option, value): 62 """Validate the cert reqs are valid. It must be None or one of the 63 three values ``ssl.CERT_NONE``, ``ssl.CERT_OPTIONAL`` or 64 ``ssl.CERT_REQUIRED``. 65 """ 66 if value is None: 67 return value 68 if isinstance(value, string_type) and hasattr(_stdlibssl, value): 69 value = getattr(_stdlibssl, value) 70 71 if value in (CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED): 72 return value 73 raise ValueError("The value of %s must be one of: " 74 "`ssl.CERT_NONE`, `ssl.CERT_OPTIONAL` or " 75 "`ssl.CERT_REQUIRED`" % (option,)) 76 77 def validate_allow_invalid_certs(option, value): 78 """Validate the option to allow invalid certificates is valid.""" 79 # Avoid circular import. 80 from pymongo.common import validate_boolean_or_string 81 boolean_cert_reqs = validate_boolean_or_string(option, value) 82 if boolean_cert_reqs: 83 return CERT_NONE 84 return CERT_REQUIRED 85 86 def _load_wincerts(): 87 """Set _WINCERTS to an instance of wincertstore.Certfile.""" 88 global _WINCERTS 89 90 certfile = CertFile() 91 certfile.addstore("CA") 92 certfile.addstore("ROOT") 93 atexit.register(certfile.close) 94 95 _WINCERTS = certfile 96 97 def get_ssl_context(*args): 98 """Create and return an SSLContext object.""" 99 (certfile, 100 keyfile, 101 passphrase, 102 ca_certs, 103 cert_reqs, 104 crlfile, 105 match_hostname, 106 check_ocsp_endpoint) = args 107 verify_mode = CERT_REQUIRED if cert_reqs is None else cert_reqs 108 ctx = _ssl.SSLContext(_ssl.PROTOCOL_SSLv23) 109 # SSLContext.check_hostname was added in CPython 2.7.9 and 3.4. 110 if hasattr(ctx, "check_hostname"): 111 if _ssl.CHECK_HOSTNAME_SAFE and verify_mode != CERT_NONE: 112 ctx.check_hostname = match_hostname 113 else: 114 ctx.check_hostname = False 115 if hasattr(ctx, "check_ocsp_endpoint"): 116 ctx.check_ocsp_endpoint = check_ocsp_endpoint 117 if hasattr(ctx, "options"): 118 # Explicitly disable SSLv2, SSLv3 and TLS compression. Note that 119 # up to date versions of MongoDB 2.4 and above already disable 120 # SSLv2 and SSLv3, python disables SSLv2 by default in >= 2.7.7 121 # and >= 3.3.4 and SSLv3 in >= 3.4.3. 122 ctx.options |= _ssl.OP_NO_SSLv2 123 ctx.options |= _ssl.OP_NO_SSLv3 124 ctx.options |= _ssl.OP_NO_COMPRESSION 125 ctx.options |= _ssl.OP_NO_RENEGOTIATION 126 if certfile is not None: 127 try: 128 ctx.load_cert_chain(certfile, keyfile, passphrase) 129 except _ssl.SSLError as exc: 130 raise ConfigurationError( 131 "Private key doesn't match certificate: %s" % (exc,)) 132 if crlfile is not None: 133 if _ssl.IS_PYOPENSSL: 134 raise ConfigurationError( 135 "ssl_crlfile cannot be used with PyOpenSSL") 136 if not hasattr(ctx, "verify_flags"): 137 raise ConfigurationError( 138 "Support for ssl_crlfile requires " 139 "python 2.7.9+ (pypy 2.5.1+) or 3.4+") 140 # Match the server's behavior. 141 ctx.verify_flags = getattr(_ssl, "VERIFY_CRL_CHECK_LEAF", 0) 142 ctx.load_verify_locations(crlfile) 143 if ca_certs is not None: 144 ctx.load_verify_locations(ca_certs) 145 elif cert_reqs != CERT_NONE: 146 # CPython >= 2.7.9 or >= 3.4.0, pypy >= 2.5.1 147 if hasattr(ctx, "load_default_certs"): 148 ctx.load_default_certs() 149 # Python >= 3.2.0, useless on Windows. 150 elif (sys.platform != "win32" and 151 hasattr(ctx, "set_default_verify_paths")): 152 ctx.set_default_verify_paths() 153 elif sys.platform == "win32" and HAVE_WINCERTSTORE: 154 with _WINCERTSLOCK: 155 if _WINCERTS is None: 156 _load_wincerts() 157 ctx.load_verify_locations(_WINCERTS.name) 158 elif HAVE_CERTIFI: 159 ctx.load_verify_locations(certifi.where()) 160 else: 161 raise ConfigurationError( 162 "`ssl_cert_reqs` is not ssl.CERT_NONE and no system " 163 "CA certificates could be loaded. `ssl_ca_certs` is " 164 "required.") 165 ctx.verify_mode = verify_mode 166 return ctx 167else: 168 class SSLError(Exception): 169 pass 170 HAS_SNI = False 171 IPADDR_SAFE = False 172 def validate_cert_reqs(option, dummy): 173 """No ssl module, raise ConfigurationError.""" 174 raise ConfigurationError("The value of %s is set but can't be " 175 "validated. The ssl module is not available" 176 % (option,)) 177 178 def validate_allow_invalid_certs(option, dummy): 179 """No ssl module, raise ConfigurationError.""" 180 return validate_cert_reqs(option, dummy) 181 182 def get_ssl_context(*dummy): 183 """No ssl module, raise ConfigurationError.""" 184 raise ConfigurationError("The ssl module is not available.") 185