mirror of
https://github.com/httpie/cli.git
synced 2024-11-21 15:23:11 +01:00
110 lines
3.3 KiB
Python
110 lines
3.3 KiB
Python
import ssl
|
||
from typing import NamedTuple, Optional
|
||
|
||
# noinspection PyPackageRequirements
|
||
from urllib3.util.ssl_ import (
|
||
create_urllib3_context,
|
||
resolve_ssl_version,
|
||
)
|
||
|
||
from .adapters import HTTPAdapter
|
||
from .compat import ensure_default_certs_loaded
|
||
|
||
|
||
SSL_VERSION_ARG_MAPPING = {
|
||
'ssl2.3': 'PROTOCOL_SSLv23',
|
||
'ssl3': 'PROTOCOL_SSLv3',
|
||
'tls1': 'PROTOCOL_TLSv1',
|
||
'tls1.1': 'PROTOCOL_TLSv1_1',
|
||
'tls1.2': 'PROTOCOL_TLSv1_2',
|
||
'tls1.3': 'PROTOCOL_TLSv1_3',
|
||
}
|
||
AVAILABLE_SSL_VERSION_ARG_MAPPING = {
|
||
arg: getattr(ssl, constant_name)
|
||
for arg, constant_name in SSL_VERSION_ARG_MAPPING.items()
|
||
if hasattr(ssl, constant_name)
|
||
}
|
||
|
||
|
||
class HTTPieCertificate(NamedTuple):
|
||
cert_file: Optional[str] = None
|
||
key_file: Optional[str] = None
|
||
key_password: Optional[str] = None
|
||
|
||
def to_raw_cert(self):
|
||
"""Synthesize a requests-compatible (2-item tuple of cert and key file)
|
||
object from HTTPie's internal representation of a certificate."""
|
||
return self.cert_file, self.key_file
|
||
|
||
|
||
class HTTPieHTTPSAdapter(HTTPAdapter):
|
||
def __init__(
|
||
self,
|
||
verify: bool,
|
||
ssl_version: str = None,
|
||
ciphers: str = None,
|
||
**kwargs
|
||
):
|
||
self._ssl_context = self._create_ssl_context(
|
||
verify=verify,
|
||
ssl_version=ssl_version,
|
||
ciphers=ciphers,
|
||
)
|
||
super().__init__(**kwargs)
|
||
|
||
def init_poolmanager(self, *args, **kwargs):
|
||
kwargs['ssl_context'] = self._ssl_context
|
||
return super().init_poolmanager(*args, **kwargs)
|
||
|
||
def proxy_manager_for(self, *args, **kwargs):
|
||
kwargs['ssl_context'] = self._ssl_context
|
||
return super().proxy_manager_for(*args, **kwargs)
|
||
|
||
def cert_verify(self, conn, url, verify, cert):
|
||
if isinstance(cert, HTTPieCertificate):
|
||
conn.key_password = cert.key_password
|
||
cert = cert.to_raw_cert()
|
||
|
||
return super().cert_verify(conn, url, verify, cert)
|
||
|
||
@staticmethod
|
||
def _create_ssl_context(
|
||
verify: bool,
|
||
ssl_version: str = None,
|
||
ciphers: str = None,
|
||
) -> 'ssl.SSLContext':
|
||
ssl_context = create_urllib3_context(
|
||
ciphers=ciphers,
|
||
ssl_version=resolve_ssl_version(ssl_version),
|
||
# Since we are using a custom SSL context, we need to pass this
|
||
# here manually, even though it’s also passed to the connection
|
||
# in `super().cert_verify()`.
|
||
cert_reqs=ssl.CERT_REQUIRED if verify else ssl.CERT_NONE
|
||
)
|
||
ensure_default_certs_loaded(ssl_context)
|
||
return ssl_context
|
||
|
||
@classmethod
|
||
def get_default_ciphers_names(cls):
|
||
return [cipher['name'] for cipher in cls._create_ssl_context(verify=False).get_ciphers()]
|
||
|
||
|
||
def _is_key_file_encrypted(key_file):
|
||
"""Detects if a key file is encrypted or not.
|
||
|
||
Copy of the internal urllib function (urllib3.util.ssl_)"""
|
||
|
||
with open(key_file, "r") as f:
|
||
for line in f:
|
||
# Look for Proc-Type: 4,ENCRYPTED
|
||
if "ENCRYPTED" in line:
|
||
return True
|
||
|
||
return False
|
||
|
||
|
||
# We used to import the default set of TLS ciphers from urllib3, but they removed it.
|
||
# Instead, now urllib3 uses the list of ciphers configured by the system.
|
||
# <https://github.com/httpie/cli/pull/1501>
|
||
DEFAULT_SSL_CIPHERS_STRING = ':'.join(HTTPieHTTPSAdapter.get_default_ciphers_names())
|