ssl_support.py 6.8 KB

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