tls.py 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import logging
  2. from OpenSSL import SSL
  3. from service_identity.exceptions import CertificateError
  4. from twisted.internet._sslverify import ClientTLSOptions, verifyHostname, VerificationError
  5. from twisted.internet.ssl import AcceptableCiphers
  6. from scrapy import twisted_version
  7. from scrapy.utils.ssl import x509name_to_string, get_temp_key_info
  8. logger = logging.getLogger(__name__)
  9. METHOD_SSLv3 = 'SSLv3'
  10. METHOD_TLS = 'TLS'
  11. METHOD_TLSv10 = 'TLSv1.0'
  12. METHOD_TLSv11 = 'TLSv1.1'
  13. METHOD_TLSv12 = 'TLSv1.2'
  14. openssl_methods = {
  15. METHOD_TLS: SSL.SSLv23_METHOD, # protocol negotiation (recommended)
  16. METHOD_SSLv3: SSL.SSLv3_METHOD, # SSL 3 (NOT recommended)
  17. METHOD_TLSv10: SSL.TLSv1_METHOD, # TLS 1.0 only
  18. METHOD_TLSv11: getattr(SSL, 'TLSv1_1_METHOD', 5), # TLS 1.1 only
  19. METHOD_TLSv12: getattr(SSL, 'TLSv1_2_METHOD', 6), # TLS 1.2 only
  20. }
  21. if twisted_version < (17, 0, 0):
  22. from twisted.internet._sslverify import _maybeSetHostNameIndication as set_tlsext_host_name
  23. else:
  24. def set_tlsext_host_name(connection, hostNameBytes):
  25. connection.set_tlsext_host_name(hostNameBytes)
  26. class ScrapyClientTLSOptions(ClientTLSOptions):
  27. """
  28. SSL Client connection creator ignoring certificate verification errors
  29. (for genuinely invalid certificates or bugs in verification code).
  30. Same as Twisted's private _sslverify.ClientTLSOptions,
  31. except that VerificationError, CertificateError and ValueError
  32. exceptions are caught, so that the connection is not closed, only
  33. logging warnings. Also, HTTPS connection parameters logging is added.
  34. """
  35. def __init__(self, hostname, ctx, verbose_logging=False):
  36. super(ScrapyClientTLSOptions, self).__init__(hostname, ctx)
  37. self.verbose_logging = verbose_logging
  38. def _identityVerifyingInfoCallback(self, connection, where, ret):
  39. if where & SSL.SSL_CB_HANDSHAKE_START:
  40. set_tlsext_host_name(connection, self._hostnameBytes)
  41. elif where & SSL.SSL_CB_HANDSHAKE_DONE:
  42. if self.verbose_logging:
  43. if hasattr(connection, 'get_cipher_name'): # requires pyOPenSSL 0.15
  44. if hasattr(connection, 'get_protocol_version_name'): # requires pyOPenSSL 16.0.0
  45. logger.debug('SSL connection to %s using protocol %s, cipher %s',
  46. self._hostnameASCII,
  47. connection.get_protocol_version_name(),
  48. connection.get_cipher_name(),
  49. )
  50. else:
  51. logger.debug('SSL connection to %s using cipher %s',
  52. self._hostnameASCII,
  53. connection.get_cipher_name(),
  54. )
  55. server_cert = connection.get_peer_certificate()
  56. logger.debug('SSL connection certificate: issuer "%s", subject "%s"',
  57. x509name_to_string(server_cert.get_issuer()),
  58. x509name_to_string(server_cert.get_subject()),
  59. )
  60. key_info = get_temp_key_info(connection._ssl)
  61. if key_info:
  62. logger.debug('SSL temp key: %s', key_info)
  63. try:
  64. verifyHostname(connection, self._hostnameASCII)
  65. except (CertificateError, VerificationError) as e:
  66. logger.warning(
  67. 'Remote certificate is not valid for hostname "{}"; {}'.format(
  68. self._hostnameASCII, e))
  69. except ValueError as e:
  70. logger.warning(
  71. 'Ignoring error while verifying certificate '
  72. 'from host "{}" (exception: {})'.format(
  73. self._hostnameASCII, repr(e)))
  74. DEFAULT_CIPHERS = AcceptableCiphers.fromOpenSSLCipherString('DEFAULT')