Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions httpie/ssl_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import re
from typing import Optional

_SSL_ERROR_MESSAGES = {
r'CERTIFICATE_VERIFY_FAILED.*unable to get local issuer': (
'SSL Error: The server certificate could not be verified because the\n'
'issuing Certificate Authority (CA) is not trusted by your system.\n'
'Possible fixes:\n'
' 1. Specify a custom CA bundle: --verify=/path/to/ca-bundle.crt\n'
' 2. If using a corporate proxy, install your organization CA cert.\n'
' WARNING: Do NOT use --verify=no in production environments.'
),
r'CERTIFICATE_VERIFY_FAILED.*certificate has expired': (
'SSL Error: The server certificate has EXPIRED.\n'
'The remote host must renew its SSL certificate.\n'
'Contact the server administrator if this is unexpected.'
),
r'CERTIFICATE_VERIFY_FAILED.*self.signed': (
'SSL Error: The server is using a SELF-SIGNED certificate.\n'
'If you trust this server, you may provide the cert:\n'
' --verify=/path/to/server.crt'
),
r'hostname.*does not match': (
'SSL Error: HOSTNAME MISMATCH — the certificate is valid, but\n'
'it was issued for a different hostname than the one you requested.'
),
}


def get_ssl_error_message(exc: Exception) -> Optional[str]:
"""
Inspect *exc* and return a human-readable description.
Returns None if the error is unrecognised.
"""
msg = str(exc)
for pattern, friendly in _SSL_ERROR_MESSAGES.items():
if re.search(pattern, msg, re.IGNORECASE):
return friendly
return None
45 changes: 45 additions & 0 deletions tests/test_ssl_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import pytest
from httpie.ssl_errors import get_ssl_error_message


class TestGetSslErrorMessage:

def test_local_issuer_certificate(self):
exc = Exception(
'[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: '
'unable to get local issuer certificate (_ssl.c:1000)'
)
result = get_ssl_error_message(exc)
assert result is not None
assert 'Certificate Authority' in result
assert '--verify=' in result

def test_expired_certificate(self):
exc = Exception(
'[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: '
'certificate has expired'
)
result = get_ssl_error_message(exc)
assert result is not None
assert 'EXPIRED' in result

def test_self_signed_certificate(self):
exc = Exception(
'[SSL: CERTIFICATE_VERIFY_FAILED] self signed certificate'
)
result = get_ssl_error_message(exc)
assert result is not None
assert 'SELF-SIGNED' in result

def test_hostname_mismatch(self):
exc = Exception(
"hostname 'example.com' does not match 'other.com'"
)
result = get_ssl_error_message(exc)
assert result is not None
assert 'HOSTNAME MISMATCH' in result

def test_unrecognised_error_returns_none(self):
exc = Exception('Some completely unknown SSL error')
result = get_ssl_error_message(exc)
assert result is None