ssl_support.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. # Copyright 2014-2015 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. HAVE_SSL = True
  19. try:
  20. import ssl
  21. except ImportError:
  22. HAVE_SSL = False
  23. HAVE_CERTIFI = False
  24. try:
  25. import certifi
  26. HAVE_CERTIFI = True
  27. except ImportError:
  28. pass
  29. HAVE_WINCERTSTORE = False
  30. try:
  31. from wincertstore import CertFile
  32. HAVE_WINCERTSTORE = True
  33. except ImportError:
  34. pass
  35. from bson.py3compat import string_type
  36. from pymongo.errors import ConfigurationError
  37. _WINCERTSLOCK = threading.Lock()
  38. _WINCERTS = None
  39. if HAVE_SSL:
  40. try:
  41. # Python 3.2 and above.
  42. from ssl import SSLContext
  43. except ImportError:
  44. from pymongo.ssl_context import SSLContext
  45. def validate_cert_reqs(option, value):
  46. """Validate the cert reqs are valid. It must be None or one of the
  47. three values ``ssl.CERT_NONE``, ``ssl.CERT_OPTIONAL`` or
  48. ``ssl.CERT_REQUIRED``.
  49. """
  50. if value is None:
  51. return value
  52. elif isinstance(value, string_type) and hasattr(ssl, value):
  53. value = getattr(ssl, value)
  54. if value in (ssl.CERT_NONE, ssl.CERT_OPTIONAL, ssl.CERT_REQUIRED):
  55. return value
  56. raise ValueError("The value of %s must be one of: "
  57. "`ssl.CERT_NONE`, `ssl.CERT_OPTIONAL` or "
  58. "`ssl.CERT_REQUIRED" % (option,))
  59. def _load_wincerts():
  60. """Set _WINCERTS to an instance of wincertstore.Certfile."""
  61. global _WINCERTS
  62. certfile = CertFile()
  63. certfile.addstore("CA")
  64. certfile.addstore("ROOT")
  65. atexit.register(certfile.close)
  66. _WINCERTS = certfile
  67. # XXX: Possible future work.
  68. # - Support CRL files? Only supported by CPython >= 2.7.9 and >= 3.4
  69. # http://bugs.python.org/issue8813
  70. # - OCSP? Not supported by python at all.
  71. # http://bugs.python.org/issue17123
  72. # - Setting OP_NO_COMPRESSION? The server doesn't yet.
  73. # - Adding an ssl_context keyword argument to MongoClient? This might
  74. # be useful for sites that have unusual requirements rather than
  75. # trying to expose every SSLContext option through a keyword/uri
  76. # parameter.
  77. def get_ssl_context(*args):
  78. """Create and return an SSLContext object."""
  79. certfile, keyfile, ca_certs, cert_reqs = args
  80. # Note PROTOCOL_SSLv23 is about the most misleading name imaginable.
  81. # This configures the server and client to negotiate the
  82. # highest protocol version they both support. A very good thing.
  83. ctx = SSLContext(ssl.PROTOCOL_SSLv23)
  84. if hasattr(ctx, "options"):
  85. # Explicitly disable SSLv2 and SSLv3. Note that up to
  86. # date versions of MongoDB 2.4 and above already do this,
  87. # python disables SSLv2 by default in >= 2.7.7 and >= 3.3.4
  88. # and SSLv3 in >= 3.4.3. There is no way for us to do this
  89. # explicitly for python 2.6 or 2.7 before 2.7.9.
  90. ctx.options |= getattr(ssl, "OP_NO_SSLv2", 0)
  91. ctx.options |= getattr(ssl, "OP_NO_SSLv3", 0)
  92. if certfile is not None:
  93. ctx.load_cert_chain(certfile, keyfile)
  94. if ca_certs is not None:
  95. ctx.load_verify_locations(ca_certs)
  96. elif cert_reqs != ssl.CERT_NONE:
  97. # CPython >= 2.7.9 or >= 3.4.0, pypy >= 2.5.1
  98. if hasattr(ctx, "load_default_certs"):
  99. ctx.load_default_certs()
  100. # Python >= 3.2.0, useless on Windows.
  101. elif (sys.platform != "win32" and
  102. hasattr(ctx, "set_default_verify_paths")):
  103. ctx.set_default_verify_paths()
  104. elif sys.platform == "win32" and HAVE_WINCERTSTORE:
  105. with _WINCERTSLOCK:
  106. if _WINCERTS is None:
  107. _load_wincerts()
  108. ctx.load_verify_locations(_WINCERTS.name)
  109. elif HAVE_CERTIFI:
  110. ctx.load_verify_locations(certifi.where())
  111. else:
  112. raise ConfigurationError(
  113. "`ssl_cert_reqs` is not ssl.CERT_NONE and no system "
  114. "CA certificates could be loaded. `ssl_ca_certs` is "
  115. "required.")
  116. ctx.verify_mode = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
  117. return ctx
  118. else:
  119. def validate_cert_reqs(option, dummy):
  120. """No ssl module, raise ConfigurationError."""
  121. raise ConfigurationError("The value of %s is set but can't be "
  122. "validated. The ssl module is not available"
  123. % (option,))
  124. def get_ssl_context(*dummy):
  125. """No ssl module, raise ConfigurationError."""
  126. raise ConfigurationError("The ssl module is not available.")