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 ``--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 ``--max-redirects`` (default 30)
* 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
-----------
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)
----------------------------

View File

@ -20,7 +20,7 @@ from httpie.input import (HTTPieArgumentParser,
OUT_RESP_BODY, OUTPUT_OPTIONS,
OUTPUT_OPTIONS_DEFAULT, PRETTY_MAP,
PRETTY_STDOUT_TTY_ONLY, SessionNameValidator,
readable_file_arg)
readable_file_arg, SSL_VERSION_ARG_MAPPING)
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(
'--timeout',
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
#######################################################################

View File

@ -3,11 +3,13 @@ import sys
from pprint import pformat
import requests
from requests.adapters import HTTPAdapter
from requests.packages import urllib3
from httpie import sessions
from httpie import __version__
from httpie.compat import str
from httpie.input import SSL_VERSION_ARG_MAPPING
from httpie.plugins import plugin_manager
@ -27,8 +29,23 @@ JSON = 'application/json'
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.mount(
'https://',
HTTPieHTTPAdapter(ssl_version=ssl_version)
)
for cls in plugin_manager.get_transport_plugins():
transport_plugin = cls()
requests_session.mount(prefix=transport_plugin.prefix,
@ -38,7 +55,12 @@ def get_requests_session():
def get_response(args, config_dir):
"""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
if not args.session and not args.session_read_only:

View File

@ -2,6 +2,7 @@
"""
import os
import ssl
import sys
import re
import errno
@ -103,6 +104,20 @@ OUTPUT_OPTIONS_DEFAULT = OUT_RESP_HEAD + 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):
"""Adds additional logic to `argparse.ArgumentParser`.

View File

@ -5,6 +5,7 @@ import pytest_httpbin.certs
from requests.exceptions import SSLError
from httpie import ExitStatus
from httpie.input import SSL_VERSION_ARG_MAPPING
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()
@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:
def test_cert_and_key(self, httpbin_secure):