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)
|
`2.2.0-dev`_ (unreleased)
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
* Added support for ``--ciphers`` (`#870`_).
|
||||||
* Added support for ``$XDG_CONFIG_HOME`` (`#920`_).
|
* 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
|
.. _#488: https://github.com/jakubroztocil/httpie/issues/488
|
||||||
.. _#840: https://github.com/jakubroztocil/httpie/issues/840
|
.. _#840: https://github.com/jakubroztocil/httpie/issues/840
|
||||||
|
.. _#870: https://github.com/jakubroztocil/httpie/issues/870
|
||||||
.. _#895: https://github.com/jakubroztocil/httpie/issues/895
|
.. _#895: https://github.com/jakubroztocil/httpie/issues/895
|
||||||
.. _#920: https://github.com/jakubroztocil/httpie/issues/920
|
.. _#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
|
SSL version
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
Use the ``--ssl=<PROTOCOL>`` to specify the desired protocol version to use.
|
Use the ``--ssl=<PROTOCOL>`` option to specify the desired protocol version to
|
||||||
This will default to SSL v2.3 which will negotiate the highest protocol that both
|
use. This will default to SSL v2.3 which will negotiate the highest protocol
|
||||||
the server and your installation of OpenSSL support. The available protocols
|
that both the server and your installation of OpenSSL support. The available
|
||||||
are ``ssl2.3``, ``ssl3``, ``tls1``, ``tls1.1``, ``tls1.2``, ``tls1.3``. (The actually
|
protocols are
|
||||||
available set of protocols may vary depending on your OpenSSL installation.)
|
``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
|
.. 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
|
$ 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
|
Output options
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
@ -87,17 +87,3 @@ PRETTY_STDOUT_TTY_ONLY = object()
|
|||||||
OUTPUT_OPTIONS_DEFAULT = OUT_RESP_HEAD + OUT_RESP_BODY
|
OUTPUT_OPTIONS_DEFAULT = OUT_RESP_HEAD + OUT_RESP_BODY
|
||||||
OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED = OUT_RESP_BODY
|
OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED = OUT_RESP_BODY
|
||||||
OUTPUT_OPTIONS_DEFAULT_OFFLINE = OUT_REQ_HEAD + OUT_REQ_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 (
|
from httpie.cli.constants import (
|
||||||
OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT, OUT_REQ_BODY, OUT_REQ_HEAD,
|
OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT, OUT_REQ_BODY, OUT_REQ_HEAD,
|
||||||
OUT_RESP_BODY, OUT_RESP_HEAD, PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY,
|
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 (
|
from httpie.output.formatters.colors import (
|
||||||
AUTO_STYLE, AVAILABLE_STYLES, DEFAULT_STYLE,
|
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 import plugin_manager
|
||||||
from httpie.plugins.builtin import BuiltinAuthPlugin
|
from httpie.plugins.builtin import BuiltinAuthPlugin
|
||||||
from httpie.sessions import DEFAULT_SESSIONS_DIR
|
from httpie.sessions import DEFAULT_SESSIONS_DIR
|
||||||
|
from httpie.ssl import DEFAULT_SSL_CIPHERS, AVAILABLE_SSL_VERSION_ARG_MAPPING
|
||||||
|
|
||||||
|
|
||||||
parser = HTTPieArgumentParser(
|
parser = HTTPieArgumentParser(
|
||||||
@ -580,7 +581,7 @@ ssl.add_argument(
|
|||||||
ssl.add_argument(
|
ssl.add_argument(
|
||||||
'--ssl', # TODO: Maybe something more general, such as --secure-protocol?
|
'--ssl', # TODO: Maybe something more general, such as --secure-protocol?
|
||||||
dest='ssl_version',
|
dest='ssl_version',
|
||||||
choices=list(sorted(SSL_VERSION_ARG_MAPPING.keys())),
|
choices=list(sorted(AVAILABLE_SSL_VERSION_ARG_MAPPING.keys())),
|
||||||
help="""
|
help="""
|
||||||
The desired protocol version to use. This will default to
|
The desired protocol version to use. This will default to
|
||||||
SSL v2.3 which will negotiate the highest protocol that both
|
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(
|
ssl.add_argument(
|
||||||
'--cert',
|
'--cert',
|
||||||
default=None,
|
default=None,
|
||||||
|
@ -9,13 +9,12 @@ from typing import Iterable, Union
|
|||||||
from urllib.parse import urlparse, urlunparse
|
from urllib.parse import urlparse, urlunparse
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from requests.adapters import HTTPAdapter
|
|
||||||
|
|
||||||
from httpie import __version__
|
from httpie import __version__
|
||||||
from httpie.cli.constants import SSL_VERSION_ARG_MAPPING
|
|
||||||
from httpie.cli.dicts import RequestHeadersDict
|
from httpie.cli.dicts import RequestHeadersDict
|
||||||
from httpie.plugins import plugin_manager
|
from httpie.plugins import plugin_manager
|
||||||
from httpie.sessions import get_httpie_session
|
from httpie.sessions import get_httpie_session
|
||||||
|
from httpie.ssl import HTTPieHTTPSAdapter, AVAILABLE_SSL_VERSION_ARG_MAPPING
|
||||||
from httpie.utils import repr_dict
|
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)
|
send_kwargs_mergeable_from_env = make_send_kwargs_mergeable_from_env(args)
|
||||||
requests_session = build_requests_session(
|
requests_session = build_requests_session(
|
||||||
ssl_version=args.ssl_version,
|
ssl_version=args.ssl_version,
|
||||||
|
ciphers=args.ciphers,
|
||||||
)
|
)
|
||||||
|
|
||||||
if httpie_session:
|
if httpie_session:
|
||||||
@ -121,6 +121,7 @@ def collect_messages(
|
|||||||
@contextmanager
|
@contextmanager
|
||||||
def max_headers(limit):
|
def max_headers(limit):
|
||||||
# <https://github.com/jakubroztocil/httpie/issues/802>
|
# <https://github.com/jakubroztocil/httpie/issues/802>
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
orig = http.client._MAXHEADERS
|
orig = http.client._MAXHEADERS
|
||||||
http.client._MAXHEADERS = limit or float('Inf')
|
http.client._MAXHEADERS = limit or float('Inf')
|
||||||
try:
|
try:
|
||||||
@ -145,26 +146,17 @@ def compress_body(request: requests.PreparedRequest, always: bool):
|
|||||||
request.headers['Content-Length'] = str(len(deflated_data))
|
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(
|
def build_requests_session(
|
||||||
ssl_version: str = None,
|
ssl_version: str = None,
|
||||||
|
ciphers: str = None,
|
||||||
) -> requests.Session:
|
) -> requests.Session:
|
||||||
requests_session = requests.Session()
|
requests_session = requests.Session()
|
||||||
|
|
||||||
# Install our adapter.
|
# Install our adapter.
|
||||||
requests_session.mount('https://', HTTPieHTTPSAdapter(
|
requests_session.mount('https://', HTTPieHTTPSAdapter(
|
||||||
|
ciphers=ciphers,
|
||||||
ssl_version=(
|
ssl_version=(
|
||||||
SSL_VERSION_ARG_MAPPING[ssl_version]
|
AVAILABLE_SSL_VERSION_ARG_MAPPING[ssl_version]
|
||||||
if ssl_version else None
|
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
|
||||||
import pytest_httpbin.certs
|
import pytest_httpbin.certs
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
|
|
||||||
|
from httpie.ssl import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS
|
||||||
from httpie.status import ExitStatus
|
from httpie.status import ExitStatus
|
||||||
from httpie.cli.constants import SSL_VERSION_ARG_MAPPING
|
|
||||||
from utils import HTTP_OK, TESTS_ROOT, http
|
from utils import HTTP_OK, TESTS_ROOT, http
|
||||||
|
|
||||||
|
|
||||||
@ -23,10 +21,12 @@ except ImportError:
|
|||||||
requests.exceptions.SSLError,
|
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:
|
# FIXME:
|
||||||
# We test against a local httpbin instance which uses a self-signed cert.
|
# We test against a local httpbin instance which uses a self-signed cert.
|
||||||
# Requests without --verify=<CA_BUNDLE> will fail with a verification error.
|
# 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()
|
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):
|
def test_ssl_version(httpbin_secure, ssl_version):
|
||||||
try:
|
try:
|
||||||
r = http(
|
r = http(
|
||||||
@ -113,3 +114,23 @@ class TestServerCert:
|
|||||||
def test_verify_custom_ca_bundle_invalid_bundle(self, httpbin_secure):
|
def test_verify_custom_ca_bundle_invalid_bundle(self, httpbin_secure):
|
||||||
with pytest.raises(ssl_errors):
|
with pytest.raises(ssl_errors):
|
||||||
http(httpbin_secure.url + '/get', '--verify', __file__)
|
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