handle openssl errors if installed see https github comhttpiecliissues729 noinspection pyunresolvedreferences in case of a regeneration use the following commands in the pwdtestsroot openssl genrsa aes128 passout pass password 3072 client pem openssl req new x509 nodes days 10000 key client pem client pem we test against a local httpbin instance which uses a selfsigned cert requests without verifycabundle will fail with a verification error see https github comkevin1024pytesthttpbinhttpssupport pytesthttpbin doesn t support ssl3 avoid warnings when explicitly testing insecure requests since 2 14 0 requests raises ioerror an oserror subclass linuxmacos http error sslerror no cipher can be selected openbsd https marc info lopenbsdportsm159251948515635w2 http error error ssl routines unknownsslinternal no cipher match handle openssl errors if installed see https github com httpie cli issues 729 noinspection pyunresolvedreferences todo remove with oss 65 in case of a regeneration use the following commands in the pwd_tests_root openssl genrsa aes128 passout pass password 3072 client pem openssl req new x509 nodes days 10000 key client pem client pem we test against a local httpbin instance which uses a self signed cert requests without verify ca_bundle will fail with a verification error see https github com kevin1024 pytest httpbin https support pytest httpbin doesn t support ssl3 check if root cause was an unsupported tls version todo remove with oss 65 avoid warnings when explicitly testing insecure requests since 2 14 0 requests raises ioerror an oserror subclass linux macos http error sslerror no cipher can be selected openbsd https marc info l openbsd ports m 159251948515635 w 2 http error error ssl routines unknown ssl_internal no cipher match
import ssl

import pytest
import pytest_httpbin.certs
import requests.exceptions
import urllib3

from unittest import mock

from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS_STRING
from httpie.status import ExitStatus

from .utils import HTTP_OK, TESTS_ROOT, IS_PYOPENSSL, http


try:
    
    
    
    import OpenSSL.SSL
    ssl_errors = (
        requests.exceptions.SSLError,
        OpenSSL.SSL.Error,
        ValueError,  
    )
except ImportError:
    ssl_errors = (
        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')





PWD_TESTS_ROOT = CERTS_ROOT / 'password_protected'
PWD_CLIENT_PEM = str(PWD_TESTS_ROOT / 'client.pem')
PWD_CLIENT_KEY = str(PWD_TESTS_ROOT / 'client.key')
PWD_CLIENT_PASS = 'password'
PWD_CLIENT_INVALID_PASS = PWD_CLIENT_PASS + 'invalid'




CA_BUNDLE = pytest_httpbin.certs.where()


@pytest.mark.parametrize('ssl_version',
                         AVAILABLE_SSL_VERSION_ARG_MAPPING.keys())
def test_ssl_version(httpbin_secure, ssl_version):
    try:
        r = http(
            '--ssl', ssl_version,
            httpbin_secure + '/get'
        )
        assert HTTP_OK in r
    except ssl_errors as e:
        if ssl_version == 'ssl3':
            
            pass
        elif e.__context__ is not None:  
            root = e.__context__
            while root.__context__ is not None:
                root = root.__context__
            if isinstance(root, ssl.SSLError) and root.reason == "TLSV1_ALERT_PROTOCOL_VERSION":
                pytest.skip(f'Unsupported TLS version: {ssl_version}')
        elif 'No such protocol' in str(e):  
            pytest.skip(f'Unsupported TLS version: {ssl_version}')
        else:
            raise


class TestClientCert:

    def test_cert_and_key(self, httpbin_secure):
        r = http(httpbin_secure + '/get',
                 '--cert', CLIENT_CERT,
                 '--cert-key', CLIENT_KEY)
        assert HTTP_OK in r

    def test_cert_pem(self, httpbin_secure):
        r = http(httpbin_secure + '/get',
                 '--cert', CLIENT_PEM)
        assert HTTP_OK in r

    def test_cert_file_not_found(self, httpbin_secure):
        r = http(httpbin_secure + '/get',
                 '--cert', '/__not_found__',
                 tolerate_error_exit_status=True)
        assert r.exit_status == ExitStatus.ERROR
        assert '/__not_found__: No such file or directory' in r.stderr

    def test_cert_file_invalid(self, httpbin_secure):
        with pytest.raises(ssl_errors):
            http(httpbin_secure + '/get',
                 '--cert', __file__)

    def test_cert_ok_but_missing_key(self, httpbin_secure):
        with pytest.raises(ssl_errors):
            http(httpbin_secure + '/get',
                 '--cert', CLIENT_CERT)


class TestServerCert:

    def test_verify_no_OK(self, httpbin_secure):
        
        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
        r = http(httpbin_secure.url + '/get', '--verify=no')
        assert HTTP_OK in r

    @pytest.mark.parametrize('verify_value', ['false', 'fALse'])
    def test_verify_false_OK(self, httpbin_secure, verify_value):
        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
        r = http(httpbin_secure.url + '/get', '--verify', verify_value)
        assert HTTP_OK in r

    def test_verify_custom_ca_bundle_path(
        self, httpbin_secure_untrusted
    ):
        r = http(httpbin_secure_untrusted + '/get', '--verify', CA_BUNDLE)
        assert HTTP_OK in r

    def test_self_signed_server_cert_by_default_raises_ssl_error(
        self,
        httpbin_secure_untrusted
    ):
        with pytest.raises(ssl_errors):
            http(httpbin_secure_untrusted.url + '/get')

    def test_verify_custom_ca_bundle_invalid_path(self, httpbin_secure):
        
        with pytest.raises(ssl_errors + (OSError,)):
            http(httpbin_secure.url + '/get', '--verify', '/__not_found__')

    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_STRING,
    )
    assert HTTP_OK in r


@pytest.mark.skipif(IS_PYOPENSSL, reason='pyOpenSSL uses a different message format.')
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 'cipher' in r.stderr


def test_pyopenssl_presence():
    if not IS_PYOPENSSL:
        assert not urllib3.util.ssl_.IS_PYOPENSSL
        assert not urllib3.util.IS_PYOPENSSL
    else:
        assert urllib3.util.ssl_.IS_PYOPENSSL
        assert urllib3.util.IS_PYOPENSSL


@mock.patch('httpie.cli.argtypes.SSLCredentials._prompt_password',
            new=lambda self, prompt: PWD_CLIENT_PASS)
def test_password_protected_cert_prompt(httpbin_secure):
    r = http(httpbin_secure + '/get',
             '--cert', PWD_CLIENT_PEM,
             '--cert-key', PWD_CLIENT_KEY)
    assert HTTP_OK in r


@mock.patch('httpie.cli.argtypes.SSLCredentials._prompt_password',
            new=lambda self, prompt: PWD_CLIENT_INVALID_PASS)
def test_password_protected_cert_prompt_invalid(httpbin_secure):
    with pytest.raises(ssl_errors):
        http(httpbin_secure + '/get',
             '--cert', PWD_CLIENT_PEM,
             '--cert-key', PWD_CLIENT_KEY)


def test_password_protected_cert_cli_arg(httpbin_secure):
    r = http(httpbin_secure + '/get',
             '--cert', PWD_CLIENT_PEM,
             '--cert-key', PWD_CLIENT_KEY,
             '--cert-key-pass', PWD_CLIENT_PASS)
    assert HTTP_OK in r


def test_password_protected_cert_cli_arg_invalid(httpbin_secure):
    with pytest.raises(ssl_errors):
        http(httpbin_secure + '/get',
             '--cert', PWD_CLIENT_PEM,
             '--cert-key', PWD_CLIENT_KEY,
             '--cert-key-pass', PWD_CLIENT_INVALID_PASS)