tls.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. # -*- coding: utf-8 -*-
  2. """
  3. hyper/tls
  4. ~~~~~~~~~
  5. Contains the TLS/SSL logic for use in hyper.
  6. """
  7. import os.path as path
  8. from .common.exceptions import MissingCertFile
  9. from .compat import ignore_missing, ssl
  10. NPN_PROTOCOL = 'h2'
  11. H2_NPN_PROTOCOLS = [NPN_PROTOCOL, 'h2-16', 'h2-15', 'h2-14']
  12. SUPPORTED_NPN_PROTOCOLS = H2_NPN_PROTOCOLS + ['http/1.1']
  13. H2C_PROTOCOL = 'h2c'
  14. # We have a singleton SSLContext object. There's no reason to be creating one
  15. # per connection.
  16. _context = None
  17. # Work out where our certificates are.
  18. cert_loc = path.join(path.dirname(__file__), 'certs.pem')
  19. def wrap_socket(sock, server_hostname, ssl_context=None, force_proto=None):
  20. """
  21. A vastly simplified SSL wrapping function. We'll probably extend this to
  22. do more things later.
  23. """
  24. global _context
  25. if ssl_context:
  26. # if an SSLContext is provided then use it instead of default context
  27. _ssl_context = ssl_context
  28. else:
  29. # create the singleton SSLContext we use
  30. if _context is None: # pragma: no cover
  31. _context = init_context()
  32. _ssl_context = _context
  33. # the spec requires SNI support
  34. ssl_sock = _ssl_context.wrap_socket(sock, server_hostname=server_hostname)
  35. # Setting SSLContext.check_hostname to True only verifies that the
  36. # post-handshake servername matches that of the certificate. We also need
  37. # to check that it matches the requested one.
  38. if _ssl_context.check_hostname: # pragma: no cover
  39. try:
  40. ssl.match_hostname(ssl_sock.getpeercert(), server_hostname)
  41. except AttributeError:
  42. ssl.verify_hostname(ssl_sock, server_hostname) # pyopenssl
  43. # Allow for the protocol to be forced externally.
  44. proto = force_proto
  45. # ALPN is newer, so we prefer it over NPN. The odds of us getting
  46. # different answers is pretty low, but let's be sure.
  47. with ignore_missing():
  48. if proto is None:
  49. proto = ssl_sock.selected_alpn_protocol()
  50. with ignore_missing():
  51. if proto is None:
  52. proto = ssl_sock.selected_npn_protocol()
  53. return (ssl_sock, proto)
  54. def init_context(cert_path=None, cert=None, cert_password=None):
  55. """
  56. Create a new ``SSLContext`` that is correctly set up for an HTTP/2
  57. connection. This SSL context object can be customized and passed as a
  58. parameter to the :class:`HTTPConnection <hyper.HTTPConnection>` class.
  59. Provide your own certificate file in case you don’t want to use hyper’s
  60. default certificate. The path to the certificate can be absolute or
  61. relative to your working directory.
  62. :param cert_path: (optional) The path to the certificate file of
  63. “certification authority” (CA) certificates
  64. :param cert: (optional) if string, path to ssl client cert file (.pem).
  65. If tuple, ('cert', 'key') pair.
  66. The certfile string must be the path to a single file in PEM format
  67. containing the certificate as well as any number of CA certificates
  68. needed to establish the certificate’s authenticity. The keyfile string,
  69. if present, must point to a file containing the private key in.
  70. Otherwise the private key will be taken from certfile as well.
  71. :param cert_password: (optional) The password argument may be a function to
  72. call to get the password for decrypting the private key. It will only
  73. be called if the private key is encrypted and a password is necessary.
  74. It will be called with no arguments, and it should return a string,
  75. bytes, or bytearray. If the return value is a string it will be
  76. encoded as UTF-8 before using it to decrypt the key. Alternatively a
  77. string, bytes, or bytearray value may be supplied directly as the
  78. password argument. It will be ignored if the private key is not
  79. encrypted and no password is needed.
  80. :returns: An ``SSLContext`` correctly set up for HTTP/2.
  81. """
  82. cafile = cert_path or cert_loc
  83. if not cafile or not path.exists(cafile):
  84. err_msg = ("No certificate found at " + str(cafile) + ". Either " +
  85. "ensure the default cert.pem file is included in the " +
  86. "distribution or provide a custom certificate when " +
  87. "creating the connection.")
  88. raise MissingCertFile(err_msg)
  89. context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
  90. context.set_default_verify_paths()
  91. context.load_verify_locations(cafile=cafile)
  92. context.verify_mode = ssl.CERT_REQUIRED
  93. context.check_hostname = True
  94. with ignore_missing():
  95. context.set_npn_protocols(SUPPORTED_NPN_PROTOCOLS)
  96. with ignore_missing():
  97. context.set_alpn_protocols(SUPPORTED_NPN_PROTOCOLS)
  98. # required by the spec
  99. context.options |= ssl.OP_NO_COMPRESSION
  100. if cert is not None:
  101. try:
  102. basestring
  103. except NameError:
  104. basestring = (str, bytes)
  105. if not isinstance(cert, basestring):
  106. context.load_cert_chain(cert[0], cert[1], cert_password)
  107. else:
  108. context.load_cert_chain(cert, password=cert_password)
  109. return context