Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tls.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. """
  2. """
  3. # Created on 2013.08.05
  4. #
  5. # Author: Giovanni Cannata
  6. #
  7. # Copyright 2013 - 2018 Giovanni Cannata
  8. #
  9. # This file is part of ldap3.
  10. #
  11. # ldap3 is free software: you can redistribute it and/or modify
  12. # it under the terms of the GNU Lesser General Public License as published
  13. # by the Free Software Foundation, either version 3 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # ldap3 is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU Lesser General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU Lesser General Public License
  22. # along with ldap3 in the COPYING and COPYING.LESSER files.
  23. # If not, see <http://www.gnu.org/licenses/>.
  24. from .exceptions import LDAPSSLNotSupportedError, LDAPSSLConfigurationError, LDAPStartTLSError, LDAPCertificateError, start_tls_exception_factory
  25. from .. import SEQUENCE_TYPES
  26. from ..utils.log import log, log_enabled, ERROR, BASIC, NETWORK
  27. try:
  28. # noinspection PyUnresolvedReferences
  29. import ssl
  30. except ImportError:
  31. if log_enabled(ERROR):
  32. log(ERROR, 'SSL not supported in this Python interpreter')
  33. raise LDAPSSLNotSupportedError('SSL not supported in this Python interpreter')
  34. try:
  35. from ssl import match_hostname, CertificateError # backport for python2 missing ssl functionalities
  36. except ImportError:
  37. from ..utils.tls_backport import CertificateError
  38. from ..utils.tls_backport import match_hostname
  39. if log_enabled(BASIC):
  40. log(BASIC, 'using tls_backport')
  41. try: # try to use SSLContext
  42. # noinspection PyUnresolvedReferences
  43. from ssl import create_default_context, Purpose # defined in Python 3.4 and Python 2.7.9
  44. use_ssl_context = True
  45. except ImportError:
  46. use_ssl_context = False
  47. if log_enabled(BASIC):
  48. log(BASIC, 'SSLContext unavailable')
  49. from os import path
  50. # noinspection PyProtectedMember
  51. class Tls(object):
  52. """
  53. tls/ssl configuration for Server object
  54. Starting from python 2.7.9 and python 3.4 uses the SSLContext object
  55. that tries to read the CAs defined at system level
  56. ca_certs_path and ca_certs_data are valid only when using SSLContext
  57. local_private_key_password is valid only when using SSLContext
  58. sni is the server name for Server Name Indication (when available)
  59. """
  60. def __init__(self,
  61. local_private_key_file=None,
  62. local_certificate_file=None,
  63. validate=ssl.CERT_NONE,
  64. version=None,
  65. ca_certs_file=None,
  66. valid_names=None,
  67. ca_certs_path=None,
  68. ca_certs_data=None,
  69. local_private_key_password=None,
  70. ciphers=None,
  71. sni=None):
  72. if validate in [ssl.CERT_NONE, ssl.CERT_OPTIONAL, ssl.CERT_REQUIRED]:
  73. self.validate = validate
  74. elif validate:
  75. if log_enabled(ERROR):
  76. log(ERROR, 'invalid validate parameter <%s>', validate)
  77. raise LDAPSSLConfigurationError('invalid validate parameter')
  78. if ca_certs_file and path.exists(ca_certs_file):
  79. self.ca_certs_file = ca_certs_file
  80. elif ca_certs_file:
  81. if log_enabled(ERROR):
  82. log(ERROR, 'invalid CA public key file <%s>', ca_certs_file)
  83. raise LDAPSSLConfigurationError('invalid CA public key file')
  84. else:
  85. self.ca_certs_file = None
  86. if ca_certs_path and use_ssl_context and path.exists(ca_certs_path):
  87. self.ca_certs_path = ca_certs_path
  88. elif ca_certs_path and not use_ssl_context:
  89. if log_enabled(ERROR):
  90. log(ERROR, 'cannot use CA public keys path, SSLContext not available')
  91. raise LDAPSSLNotSupportedError('cannot use CA public keys path, SSLContext not available')
  92. elif ca_certs_path:
  93. if log_enabled(ERROR):
  94. log(ERROR, 'invalid CA public keys path <%s>', ca_certs_path)
  95. raise LDAPSSLConfigurationError('invalid CA public keys path')
  96. else:
  97. self.ca_certs_path = None
  98. if ca_certs_data and use_ssl_context:
  99. self.ca_certs_data = ca_certs_data
  100. elif ca_certs_data:
  101. if log_enabled(ERROR):
  102. log(ERROR, 'cannot use CA data, SSLContext not available')
  103. raise LDAPSSLNotSupportedError('cannot use CA data, SSLContext not available')
  104. else:
  105. self.ca_certs_data = None
  106. if local_private_key_password and use_ssl_context:
  107. self.private_key_password = local_private_key_password
  108. elif local_private_key_password:
  109. if log_enabled(ERROR):
  110. log(ERROR, 'cannot use local private key password, SSLContext not available')
  111. raise LDAPSSLNotSupportedError('cannot use local private key password, SSLContext is not available')
  112. else:
  113. self.private_key_password = None
  114. self.version = version
  115. self.private_key_file = local_private_key_file
  116. self.certificate_file = local_certificate_file
  117. self.valid_names = valid_names
  118. self.ciphers = ciphers
  119. self.sni = sni
  120. if log_enabled(BASIC):
  121. log(BASIC, 'instantiated Tls: <%r>' % self)
  122. def __str__(self):
  123. s = [
  124. 'protocol: ' + str(self.version),
  125. 'client private key: ' + ('present ' if self.private_key_file else 'not present'),
  126. 'client certificate: ' + ('present ' if self.certificate_file else 'not present'),
  127. 'private key password: ' + ('present ' if self.private_key_password else 'not present'),
  128. 'CA certificates file: ' + ('present ' if self.ca_certs_file else 'not present'),
  129. 'CA certificates path: ' + ('present ' if self.ca_certs_path else 'not present'),
  130. 'CA certificates data: ' + ('present ' if self.ca_certs_data else 'not present'),
  131. 'verify mode: ' + str(self.validate),
  132. 'valid names: ' + str(self.valid_names),
  133. 'ciphers: ' + str(self.ciphers),
  134. 'sni: ' + str(self.sni)
  135. ]
  136. return ' - '.join(s)
  137. def __repr__(self):
  138. r = '' if self.private_key_file is None else ', local_private_key_file={0.private_key_file!r}'.format(self)
  139. r += '' if self.certificate_file is None else ', local_certificate_file={0.certificate_file!r}'.format(self)
  140. r += '' if self.validate is None else ', validate={0.validate!r}'.format(self)
  141. r += '' if self.version is None else ', version={0.version!r}'.format(self)
  142. r += '' if self.ca_certs_file is None else ', ca_certs_file={0.ca_certs_file!r}'.format(self)
  143. r += '' if self.ca_certs_path is None else ', ca_certs_path={0.ca_certs_path!r}'.format(self)
  144. r += '' if self.ca_certs_data is None else ', ca_certs_data={0.ca_certs_data!r}'.format(self)
  145. r += '' if self.ciphers is None else ', ciphers={0.ciphers!r}'.format(self)
  146. r += '' if self.sni is None else ', sni={0.sni!r}'.format(self)
  147. r = 'Tls(' + r[2:] + ')'
  148. return r
  149. def wrap_socket(self, connection, do_handshake=False):
  150. """
  151. Adds TLS to the connection socket
  152. """
  153. if use_ssl_context:
  154. if self.version is None: # uses the default ssl context for reasonable security
  155. ssl_context = create_default_context(purpose=Purpose.SERVER_AUTH,
  156. cafile=self.ca_certs_file,
  157. capath=self.ca_certs_path,
  158. cadata=self.ca_certs_data)
  159. else: # code from create_default_context in the Python standard library 3.5.1, creates a ssl context with the specificd protocol version
  160. ssl_context = ssl.SSLContext(self.version)
  161. if self.ca_certs_file or self.ca_certs_path or self.ca_certs_data:
  162. ssl_context.load_verify_locations(self.ca_certs_file, self.ca_certs_path, self.ca_certs_data)
  163. elif self.validate != ssl.CERT_NONE:
  164. ssl_context.load_default_certs(Purpose.SERVER_AUTH)
  165. if self.certificate_file:
  166. ssl_context.load_cert_chain(self.certificate_file, keyfile=self.private_key_file, password=self.private_key_password)
  167. ssl_context.check_hostname = False
  168. ssl_context.verify_mode = self.validate
  169. if self.ciphers:
  170. try:
  171. ssl_context.set_ciphers(self.ciphers)
  172. except ssl.SSLError:
  173. pass
  174. if self.sni:
  175. wrapped_socket = ssl_context.wrap_socket(connection.socket, server_side=False, do_handshake_on_connect=do_handshake, server_hostname=self.sni)
  176. else:
  177. wrapped_socket = ssl_context.wrap_socket(connection.socket, server_side=False, do_handshake_on_connect=do_handshake)
  178. if log_enabled(NETWORK):
  179. log(NETWORK, 'socket wrapped with SSL using SSLContext for <%s>', connection)
  180. else:
  181. if self.version is None and hasattr(ssl, 'PROTOCOL_SSLv23'):
  182. self.version = ssl.PROTOCOL_SSLv23
  183. if self.ciphers:
  184. try:
  185. wrapped_socket = ssl.wrap_socket(connection.socket,
  186. keyfile=self.private_key_file,
  187. certfile=self.certificate_file,
  188. server_side=False,
  189. cert_reqs=self.validate,
  190. ssl_version=self.version,
  191. ca_certs=self.ca_certs_file,
  192. do_handshake_on_connect=do_handshake,
  193. ciphers=self.ciphers)
  194. except ssl.SSLError:
  195. raise
  196. except TypeError: # in python2.6 no ciphers argument is present, failback to self.ciphers=None
  197. self.ciphers = None
  198. if not self.ciphers:
  199. wrapped_socket = ssl.wrap_socket(connection.socket,
  200. keyfile=self.private_key_file,
  201. certfile=self.certificate_file,
  202. server_side=False,
  203. cert_reqs=self.validate,
  204. ssl_version=self.version,
  205. ca_certs=self.ca_certs_file,
  206. do_handshake_on_connect=do_handshake)
  207. if log_enabled(NETWORK):
  208. log(NETWORK, 'socket wrapped with SSL for <%s>', connection)
  209. if do_handshake and (self.validate == ssl.CERT_REQUIRED or self.validate == ssl.CERT_OPTIONAL):
  210. check_hostname(wrapped_socket, connection.server.host, self.valid_names)
  211. connection.socket = wrapped_socket
  212. return
  213. def start_tls(self, connection):
  214. if connection.server.ssl: # ssl already established at server level
  215. return False
  216. if (connection.tls_started and not connection._executing_deferred) or connection.strategy._outstanding or connection.sasl_in_progress:
  217. # Per RFC 4513 (3.1.1)
  218. if log_enabled(ERROR):
  219. log(ERROR, "can't start tls because operations are in progress for <%s>", self)
  220. return False
  221. connection.starting_tls = True
  222. if log_enabled(BASIC):
  223. log(BASIC, 'starting tls for <%s>', connection)
  224. if not connection.strategy.sync:
  225. connection._awaiting_for_async_start_tls = True # some flaky servers (OpenLDAP) doesn't return the extended response name in response
  226. result = connection.extended('1.3.6.1.4.1.1466.20037')
  227. if not connection.strategy.sync:
  228. # asynchronous - _start_tls must be executed by the strategy
  229. response = connection.get_response(result)
  230. if response != (None, None):
  231. if log_enabled(BASIC):
  232. log(BASIC, 'tls started for <%s>', connection)
  233. return True
  234. else:
  235. if log_enabled(BASIC):
  236. log(BASIC, 'tls not started for <%s>', connection)
  237. return False
  238. else:
  239. if connection.result['description'] not in ['success']:
  240. # startTLS failed
  241. connection.last_error = 'startTLS failed - ' + str(connection.result['description'])
  242. if log_enabled(ERROR):
  243. log(ERROR, '%s for <%s>', connection.last_error, connection)
  244. raise LDAPStartTLSError(connection.last_error)
  245. if log_enabled(BASIC):
  246. log(BASIC, 'tls started for <%s>', connection)
  247. return self._start_tls(connection)
  248. def _start_tls(self, connection):
  249. exc = None
  250. try:
  251. self.wrap_socket(connection, do_handshake=True)
  252. except Exception as e:
  253. connection.last_error = 'wrap socket error: ' + str(e)
  254. exc = e
  255. connection.starting_tls = False
  256. if exc:
  257. if log_enabled(ERROR):
  258. log(ERROR, 'error <%s> wrapping socket for TLS in <%s>', connection.last_error, connection)
  259. raise start_tls_exception_factory(LDAPStartTLSError, exc)(connection.last_error)
  260. if connection.usage:
  261. connection._usage.wrapped_sockets += 1
  262. connection.tls_started = True
  263. return True
  264. def check_hostname(sock, server_name, additional_names):
  265. server_certificate = sock.getpeercert()
  266. if log_enabled(NETWORK):
  267. log(NETWORK, 'certificate found for %s: %s', sock, server_certificate)
  268. if additional_names:
  269. host_names = [server_name] + (additional_names if isinstance(additional_names, SEQUENCE_TYPES) else [additional_names])
  270. else:
  271. host_names = [server_name]
  272. for host_name in host_names:
  273. if not host_name:
  274. continue
  275. elif host_name == '*':
  276. if log_enabled(NETWORK):
  277. log(NETWORK, 'certificate matches * wildcard')
  278. return # valid
  279. try:
  280. match_hostname(server_certificate, host_name) # raise CertificateError if certificate doesn't match server name
  281. if log_enabled(NETWORK):
  282. log(NETWORK, 'certificate matches host name <%s>', host_name)
  283. return # valid
  284. except CertificateError as e:
  285. if log_enabled(NETWORK):
  286. log(NETWORK, str(e))
  287. if log_enabled(ERROR):
  288. log(ERROR, "hostname doesn't match certificate")
  289. raise LDAPCertificateError("certificate %s doesn't match any name in %s " % (server_certificate, str(host_names)))