Added --ssl=<PROTOCOL_VERSION>

Closes #98
This commit is contained in:
Jakub Roztocil 2016-03-02 12:12:05 +08:00
parent 38e8ef14ec
commit dc4da527db
6 changed files with 119 additions and 26 deletions

View File

@ -10,6 +10,8 @@ This project adheres to `Semantic Versioning <http://semver.org/>`_.
------------------------- -------------------------
* Added ``Content-Type`` of files uploaded in ``multipart/form-data`` requests * Added ``Content-Type`` of files uploaded in ``multipart/form-data`` requests
* Added ``--ssl=<PROTOCOL>`` to specify SSL/TLS the desired protocol version
to use for HTTPS requests.
* Added ``--show-redirects, -R`` to show intermediate responses with ``--follow`` * Added ``--show-redirects, -R`` to show intermediate responses with ``--follow``
* Added ``--max-redirects`` (default 30) * Added ``--max-redirects`` (default 30)
* Added ``-A`` as short name for ``--auth-type`` * Added ``-A`` as short name for ``--auth-type``

View File

@ -730,6 +730,22 @@ path of the key file with ``--cert-key``:
$ http --cert=client.crt --cert-key=client.key https://example.org $ http --cert=client.crt --cert-key=client.key https://example.org
-----------
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``. (The actually
available set of protocols may vary depending your on OpenSSL installation.)
.. code-block:: bash
# Specify the vulnerable SSL v3 protocol to talk to an outdated server:
$ http --ssl=ssl3 https://vulnerable.example.org
---------------------------- ----------------------------
SNI (Server Name Indication) SNI (Server Name Indication)
---------------------------- ----------------------------

View File

@ -20,7 +20,7 @@ from httpie.input import (HTTPieArgumentParser,
OUT_RESP_BODY, OUTPUT_OPTIONS, OUT_RESP_BODY, OUTPUT_OPTIONS,
OUTPUT_OPTIONS_DEFAULT, PRETTY_MAP, OUTPUT_OPTIONS_DEFAULT, PRETTY_MAP,
PRETTY_STDOUT_TTY_ONLY, SessionNameValidator, PRETTY_STDOUT_TTY_ONLY, SessionNameValidator,
readable_file_arg) readable_file_arg, SSL_VERSION_ARG_MAPPING)
class HTTPieHelpFormatter(RawDescriptionHelpFormatter): class HTTPieHelpFormatter(RawDescriptionHelpFormatter):
@ -485,29 +485,6 @@ network.add_argument(
""" """
) )
network.add_argument(
'--cert',
default=None,
type=readable_file_arg,
help="""
You can specify a local cert to use as client side SSL certificate.
This file may either contain both private key and certificate or you may
specify --cert-key separately.
"""
)
network.add_argument(
'--cert-key',
default=None,
type=readable_file_arg,
help="""
The private key to use with SSL. Only needed if --cert is given and the
certificate file does not contain the private key.
"""
)
network.add_argument( network.add_argument(
'--timeout', '--timeout',
type=float, type=float,
@ -537,6 +514,46 @@ network.add_argument(
) )
#######################################################################
# SSL
#######################################################################
ssl = parser.add_argument_group(title='SSL')
ssl.add_argument(
'--ssl', # TODO: Maybe something more general, such as --secure-protocol?
dest='ssl_version',
choices=list(sorted(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
the server and your installation of OpenSSL support. Available protocols
may vary depending on OpenSSL installation (only the supported ones
are shown here).
"""
)
ssl.add_argument(
'--cert',
default=None,
type=readable_file_arg,
help="""
You can specify a local cert to use as client side SSL certificate.
This file may either contain both private key and certificate or you may
specify --cert-key separately.
"""
)
ssl.add_argument(
'--cert-key',
default=None,
type=readable_file_arg,
help="""
The private key to use with SSL. Only needed if --cert is given and the
certificate file does not contain the private key.
"""
)
####################################################################### #######################################################################
# Troubleshooting # Troubleshooting
####################################################################### #######################################################################

View File

@ -3,11 +3,13 @@ import sys
from pprint import pformat from pprint import pformat
import requests import requests
from requests.adapters import HTTPAdapter
from requests.packages import urllib3 from requests.packages import urllib3
from httpie import sessions from httpie import sessions
from httpie import __version__ from httpie import __version__
from httpie.compat import str from httpie.compat import str
from httpie.input import SSL_VERSION_ARG_MAPPING
from httpie.plugins import plugin_manager from httpie.plugins import plugin_manager
@ -27,8 +29,23 @@ JSON = 'application/json'
DEFAULT_UA = 'HTTPie/%s' % __version__ DEFAULT_UA = 'HTTPie/%s' % __version__
def get_requests_session(): class HTTPieHTTPAdapter(HTTPAdapter):
def __init__(self, ssl_version=None, **kwargs):
self._ssl_version = ssl_version
super(HTTPieHTTPAdapter, self).__init__(**kwargs)
def init_poolmanager(self, *args, **kwargs):
kwargs['ssl_version'] = self._ssl_version
super(HTTPieHTTPAdapter, self).init_poolmanager(*args, **kwargs)
def get_requests_session(ssl_version):
requests_session = requests.Session() requests_session = requests.Session()
requests_session.mount(
'https://',
HTTPieHTTPAdapter(ssl_version=ssl_version)
)
for cls in plugin_manager.get_transport_plugins(): for cls in plugin_manager.get_transport_plugins():
transport_plugin = cls() transport_plugin = cls()
requests_session.mount(prefix=transport_plugin.prefix, requests_session.mount(prefix=transport_plugin.prefix,
@ -38,7 +55,12 @@ def get_requests_session():
def get_response(args, config_dir): def get_response(args, config_dir):
"""Send the request and return a `request.Response`.""" """Send the request and return a `request.Response`."""
requests_session = get_requests_session()
ssl_version = None
if args.ssl_version:
ssl_version = SSL_VERSION_ARG_MAPPING[args.ssl_version]
requests_session = get_requests_session(ssl_version)
requests_session.max_redirects = args.max_redirects requests_session.max_redirects = args.max_redirects
if not args.session and not args.session_read_only: if not args.session and not args.session_read_only:

View File

@ -2,6 +2,7 @@
""" """
import os import os
import ssl
import sys import sys
import re import re
import errno import errno
@ -103,6 +104,20 @@ 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
SSL_VERSION_ARG_MAPPING = {
'ssl2.3': 'PROTOCOL_SSLv23',
'ssl3': 'PROTOCOL_SSLv3',
'tls1': 'PROTOCOL_TLSv1',
'tls1.1': 'PROTOCOL_TLSv1_1',
'tls1.2': 'PROTOCOL_TLSv1_2',
}
SSL_VERSION_ARG_MAPPING = dict(
(cli_arg, getattr(ssl, ssl_constant))
for cli_arg, ssl_constant in SSL_VERSION_ARG_MAPPING.items()
if hasattr(ssl, ssl_constant)
)
class HTTPieArgumentParser(ArgumentParser): class HTTPieArgumentParser(ArgumentParser):
"""Adds additional logic to `argparse.ArgumentParser`. """Adds additional logic to `argparse.ArgumentParser`.

View File

@ -5,6 +5,7 @@ import pytest_httpbin.certs
from requests.exceptions import SSLError from requests.exceptions import SSLError
from httpie import ExitStatus from httpie import ExitStatus
from httpie.input import SSL_VERSION_ARG_MAPPING
from utils import http, HTTP_OK, TESTS_ROOT from utils import http, HTTP_OK, TESTS_ROOT
@ -18,6 +19,26 @@ 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(
argnames='ssl_version',
argvalues=SSL_VERSION_ARG_MAPPING.keys()
)
def test_ssl_version(httpbin_secure, ssl_version):
try:
r = http(
'--verify', CA_BUNDLE,
'--ssl', ssl_version,
httpbin_secure + '/get'
)
assert HTTP_OK in r
except SSLError as e:
if ssl_version == 'ssl3':
# pytest-httpbin doesn't support ssl3
assert 'SSLV3_ALERT_HANDSHAKE_FAILURE' in str(e)
else:
raise
class TestClientCert: class TestClientCert:
def test_cert_and_key(self, httpbin_secure): def test_cert_and_key(self, httpbin_secure):