forked from extern/httpie-cli
Add support for --ciphers (#870)
This commit is contained in:
parent
0a81facccf
commit
d62d6a77d1
@ -9,6 +9,7 @@ This project adheres to `Semantic Versioning <https://semver.org/>`_.
|
||||
`2.2.0-dev`_ (unreleased)
|
||||
-------------------------
|
||||
|
||||
* Added support for ``--ciphers`` (`#870`_).
|
||||
* Added support for ``$XDG_CONFIG_HOME`` (`#920`_).
|
||||
|
||||
|
||||
@ -433,5 +434,6 @@ This project adheres to `Semantic Versioning <https://semver.org/>`_.
|
||||
|
||||
.. _#488: https://github.com/jakubroztocil/httpie/issues/488
|
||||
.. _#840: https://github.com/jakubroztocil/httpie/issues/840
|
||||
.. _#870: https://github.com/jakubroztocil/httpie/issues/870
|
||||
.. _#895: https://github.com/jakubroztocil/httpie/issues/895
|
||||
.. _#920: https://github.com/jakubroztocil/httpie/issues/920
|
||||
|
32
README.rst
32
README.rst
@ -1092,11 +1092,13 @@ path of the key file with ``--cert-key``:
|
||||
SSL version
|
||||
-----------
|
||||
|
||||
Use the ``--ssl=<PROTOCOL>`` to specify the desired protocol version to use.
|
||||
This will default to SSL v2.3 which will negotiate the highest protocol that both
|
||||
the server and your installation of OpenSSL support. The available protocols
|
||||
are ``ssl2.3``, ``ssl3``, ``tls1``, ``tls1.1``, ``tls1.2``, ``tls1.3``. (The actually
|
||||
available set of protocols may vary depending on your OpenSSL installation.)
|
||||
Use the ``--ssl=<PROTOCOL>`` option to specify the desired protocol version to
|
||||
use. This will default to SSL v2.3 which will negotiate the highest protocol
|
||||
that both the server and your installation of OpenSSL support. The available
|
||||
protocols are
|
||||
``ssl2.3``, ``ssl3``, ``tls1``, ``tls1.1``, ``tls1.2``, ``tls1.3``.
|
||||
(The actually available set of protocols may vary depending on your OpenSSL
|
||||
installation.)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@ -1104,6 +1106,26 @@ available set of protocols may vary depending on your OpenSSL installation.)
|
||||
$ http --ssl=ssl3 https://vulnerable.example.org
|
||||
|
||||
|
||||
|
||||
SSL ciphers
|
||||
-----------
|
||||
|
||||
You can specify the available ciphers with ``--ciphers``.
|
||||
It should be a string in the
|
||||
`OpenSSL cipher list format <https://www.openssl.org/docs/man1.1.0/man1/ciphers.html>`_.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ http --ciphers=ECDHE-RSA-AES128-GCM-SHA256 https://httpbin.org/get
|
||||
|
||||
Note: these cipher strings do not change the negotiated version of SSL or TLS,
|
||||
they only affect the list of available cipher suites.
|
||||
|
||||
To see the default cipher string, run ``http --help`` and see
|
||||
the ``--ciphers`` section under SSL.
|
||||
|
||||
|
||||
|
||||
Output options
|
||||
==============
|
||||
|
||||
|
@ -87,17 +87,3 @@ PRETTY_STDOUT_TTY_ONLY = object()
|
||||
OUTPUT_OPTIONS_DEFAULT = OUT_RESP_HEAD + OUT_RESP_BODY
|
||||
OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED = OUT_RESP_BODY
|
||||
OUTPUT_OPTIONS_DEFAULT_OFFLINE = OUT_REQ_HEAD + OUT_REQ_BODY
|
||||
|
||||
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',
|
||||
}
|
||||
SSL_VERSION_ARG_MAPPING = {
|
||||
cli_arg: getattr(ssl, ssl_constant)
|
||||
for cli_arg, ssl_constant in SSL_VERSION_ARG_MAPPING.items()
|
||||
if hasattr(ssl, ssl_constant)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ from httpie.cli.argtypes import (
|
||||
from httpie.cli.constants import (
|
||||
OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT, OUT_REQ_BODY, OUT_REQ_HEAD,
|
||||
OUT_RESP_BODY, OUT_RESP_HEAD, PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY,
|
||||
SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_PROXY, SSL_VERSION_ARG_MAPPING,
|
||||
SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_PROXY,
|
||||
)
|
||||
from httpie.output.formatters.colors import (
|
||||
AUTO_STYLE, AVAILABLE_STYLES, DEFAULT_STYLE,
|
||||
@ -21,6 +21,7 @@ from httpie.output.formatters.colors import (
|
||||
from httpie.plugins import plugin_manager
|
||||
from httpie.plugins.builtin import BuiltinAuthPlugin
|
||||
from httpie.sessions import DEFAULT_SESSIONS_DIR
|
||||
from httpie.ssl import DEFAULT_SSL_CIPHERS, AVAILABLE_SSL_VERSION_ARG_MAPPING
|
||||
|
||||
|
||||
parser = HTTPieArgumentParser(
|
||||
@ -580,7 +581,7 @@ ssl.add_argument(
|
||||
ssl.add_argument(
|
||||
'--ssl', # TODO: Maybe something more general, such as --secure-protocol?
|
||||
dest='ssl_version',
|
||||
choices=list(sorted(SSL_VERSION_ARG_MAPPING.keys())),
|
||||
choices=list(sorted(AVAILABLE_SSL_VERSION_ARG_MAPPING.keys())),
|
||||
help="""
|
||||
The desired protocol version to use. This will default to
|
||||
SSL v2.3 which will negotiate the highest protocol that both
|
||||
@ -590,6 +591,17 @@ ssl.add_argument(
|
||||
|
||||
"""
|
||||
)
|
||||
ssl.add_argument(
|
||||
'--ciphers',
|
||||
help=f"""
|
||||
|
||||
A string in the OpenSSL cipher list format. By default, the following
|
||||
is used:
|
||||
|
||||
{DEFAULT_SSL_CIPHERS}
|
||||
|
||||
"""
|
||||
)
|
||||
ssl.add_argument(
|
||||
'--cert',
|
||||
default=None,
|
||||
|
@ -9,13 +9,12 @@ from typing import Iterable, Union
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
|
||||
from httpie import __version__
|
||||
from httpie.cli.constants import SSL_VERSION_ARG_MAPPING
|
||||
from httpie.cli.dicts import RequestHeadersDict
|
||||
from httpie.plugins import plugin_manager
|
||||
from httpie.sessions import get_httpie_session
|
||||
from httpie.ssl import HTTPieHTTPSAdapter, AVAILABLE_SSL_VERSION_ARG_MAPPING
|
||||
from httpie.utils import repr_dict
|
||||
|
||||
|
||||
@ -57,6 +56,7 @@ def collect_messages(
|
||||
send_kwargs_mergeable_from_env = make_send_kwargs_mergeable_from_env(args)
|
||||
requests_session = build_requests_session(
|
||||
ssl_version=args.ssl_version,
|
||||
ciphers=args.ciphers,
|
||||
)
|
||||
|
||||
if httpie_session:
|
||||
@ -121,6 +121,7 @@ def collect_messages(
|
||||
@contextmanager
|
||||
def max_headers(limit):
|
||||
# <https://github.com/jakubroztocil/httpie/issues/802>
|
||||
# noinspection PyUnresolvedReferences
|
||||
orig = http.client._MAXHEADERS
|
||||
http.client._MAXHEADERS = limit or float('Inf')
|
||||
try:
|
||||
@ -145,26 +146,17 @@ def compress_body(request: requests.PreparedRequest, always: bool):
|
||||
request.headers['Content-Length'] = str(len(deflated_data))
|
||||
|
||||
|
||||
class HTTPieHTTPSAdapter(HTTPAdapter):
|
||||
|
||||
def __init__(self, ssl_version=None, **kwargs):
|
||||
self._ssl_version = ssl_version
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def init_poolmanager(self, *args, **kwargs):
|
||||
kwargs['ssl_version'] = self._ssl_version
|
||||
super().init_poolmanager(*args, **kwargs)
|
||||
|
||||
|
||||
def build_requests_session(
|
||||
ssl_version: str = None,
|
||||
ciphers: str = None,
|
||||
) -> requests.Session:
|
||||
requests_session = requests.Session()
|
||||
|
||||
# Install our adapter.
|
||||
requests_session.mount('https://', HTTPieHTTPSAdapter(
|
||||
ciphers=ciphers,
|
||||
ssl_version=(
|
||||
SSL_VERSION_ARG_MAPPING[ssl_version]
|
||||
AVAILABLE_SSL_VERSION_ARG_MAPPING[ssl_version]
|
||||
if ssl_version else None
|
||||
)
|
||||
))
|
||||
|
38
httpie/ssl.py
Normal file
38
httpie/ssl.py
Normal file
@ -0,0 +1,38 @@
|
||||
import ssl
|
||||
|
||||
from requests.adapters import HTTPAdapter
|
||||
# noinspection PyPackageRequirements
|
||||
from urllib3.util.ssl_ import DEFAULT_CIPHERS, create_urllib3_context
|
||||
|
||||
|
||||
DEFAULT_SSL_CIPHERS = DEFAULT_CIPHERS
|
||||
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 HTTPieHTTPSAdapter(HTTPAdapter):
|
||||
def __init__(self, ssl_version: str = None, ciphers: str = None, **kwargs):
|
||||
self._ssl_context: ssl.SSLContext = create_urllib3_context(
|
||||
ciphers=ciphers,
|
||||
ssl_version=ssl_version
|
||||
)
|
||||
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)
|
@ -1,11 +1,9 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_httpbin.certs
|
||||
import requests.exceptions
|
||||
|
||||
from httpie.ssl import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS
|
||||
from httpie.status import ExitStatus
|
||||
from httpie.cli.constants import SSL_VERSION_ARG_MAPPING
|
||||
from utils import HTTP_OK, TESTS_ROOT, http
|
||||
|
||||
|
||||
@ -23,10 +21,12 @@ except ImportError:
|
||||
requests.exceptions.SSLError,
|
||||
)
|
||||
|
||||
CERTS_ROOT = TESTS_ROOT / 'client_certs'
|
||||
CLIENT_CERT = str(CERTS_ROOT / 'client.crt')
|
||||
CLIENT_KEY = str(CERTS_ROOT / 'client.key')
|
||||
CLIENT_PEM = str(CERTS_ROOT / 'client.pem')
|
||||
|
||||
|
||||
CLIENT_CERT = os.path.join(TESTS_ROOT, 'client_certs', 'client.crt')
|
||||
CLIENT_KEY = os.path.join(TESTS_ROOT, 'client_certs', 'client.key')
|
||||
CLIENT_PEM = os.path.join(TESTS_ROOT, 'client_certs', 'client.pem')
|
||||
# FIXME:
|
||||
# We test against a local httpbin instance which uses a self-signed cert.
|
||||
# Requests without --verify=<CA_BUNDLE> will fail with a verification error.
|
||||
@ -34,7 +34,8 @@ CLIENT_PEM = os.path.join(TESTS_ROOT, 'client_certs', 'client.pem')
|
||||
CA_BUNDLE = pytest_httpbin.certs.where()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ssl_version', SSL_VERSION_ARG_MAPPING.keys())
|
||||
@pytest.mark.parametrize('ssl_version',
|
||||
AVAILABLE_SSL_VERSION_ARG_MAPPING.keys())
|
||||
def test_ssl_version(httpbin_secure, ssl_version):
|
||||
try:
|
||||
r = http(
|
||||
@ -113,3 +114,23 @@ class TestServerCert:
|
||||
def test_verify_custom_ca_bundle_invalid_bundle(self, httpbin_secure):
|
||||
with pytest.raises(ssl_errors):
|
||||
http(httpbin_secure.url + '/get', '--verify', __file__)
|
||||
|
||||
|
||||
def test_ciphers(httpbin_secure):
|
||||
r = http(
|
||||
httpbin_secure.url + '/get',
|
||||
'--ciphers',
|
||||
DEFAULT_SSL_CIPHERS,
|
||||
)
|
||||
assert HTTP_OK in r
|
||||
|
||||
|
||||
def test_ciphers_none_can_be_selected(httpbin_secure):
|
||||
r = http(
|
||||
httpbin_secure.url + '/get',
|
||||
'--ciphers',
|
||||
'__FOO__',
|
||||
tolerate_error_exit_status=True,
|
||||
)
|
||||
assert r.exit_status == ExitStatus.ERROR
|
||||
assert 'No cipher can be selected.' in r.stderr
|
||||
|
Loading…
Reference in New Issue
Block a user