Skip to content

Commit 0b4df48

Browse files
committed
Change error on empty subjectAltNames
1 parent 66b9847 commit 0b4df48

File tree

5 files changed

+107
-46
lines changed

5 files changed

+107
-46
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ You can find out backwards-compatibility policy [here](https://github.com/pyca/s
1515

1616
## [Unreleased](https://github.com/pyca/service-identity/compare/23.1.0...HEAD)
1717

18+
### Changed
19+
20+
- If a certificate doesn't contain any `subjectAltName`s, we now raise `service_identity.exceptions.CertificateError` instead of `service_identity.exceptions.VerificationError` to make the problem easier to debug.
21+
22+
1823
## [23.1.0](https://github.com/pyca/service-identity/compare/21.1.0...23.1.0) - 2023-06-14
1924

2025
### Removed

src/service_identity/cryptography.py

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,31 @@
4040
def verify_certificate_hostname(
4141
certificate: Certificate, hostname: str
4242
) -> None:
43-
"""
43+
r"""
4444
Verify whether *certificate* is valid for *hostname*.
4545
46-
.. note:: Nothing is verified about the *authority* of the certificate;
47-
the caller must verify that the certificate chains to an appropriate
48-
trust root themselves.
46+
.. note::
47+
Nothing is verified about the *authority* of the certificate;
48+
the caller must verify that the certificate chains to an appropriate
49+
trust root themselves.
50+
51+
Args:
52+
certificate: A *cryptography* X509 certificate object.
53+
54+
hostname: The hostname that *certificate* should be valid for.
4955
50-
:param certificate: A *cryptography* X509 certificate object.
51-
:param hostname: The hostname that *certificate* should be valid for.
56+
Raises:
57+
service_identity.VerificationError:
58+
If *certificate* is not valid for *hostname*.
5259
53-
:raises service_identity.VerificationError: If *certificate* is not valid
54-
for *hostname*.
55-
:raises service_identity.CertificateError: If *certificate* contains
56-
invalid / unexpected data.
60+
service_identity.CertificateError:
61+
If *certificate* contains invalid / unexpected data. This includes
62+
the case where the certificate contains no `subjectAltName`\ s.
5763
58-
:returns: ``None``
64+
.. versionchanged:: 24.1.0
65+
:exc:`~service_identity.CertificateError` is raised if the certificate
66+
contains no ``subjectAltName``\ s instead of
67+
:exc:`~service_identity.VerificationError`.
5968
"""
6069
verify_service_identity(
6170
cert_patterns=extract_patterns(certificate),
@@ -67,25 +76,35 @@ def verify_certificate_hostname(
6776
def verify_certificate_ip_address(
6877
certificate: Certificate, ip_address: str
6978
) -> None:
70-
"""
79+
r"""
7180
Verify whether *certificate* is valid for *ip_address*.
7281
73-
.. note:: Nothing is verified about the *authority* of the certificate;
74-
the caller must verify that the certificate chains to an appropriate
75-
trust root themselves.
82+
.. note::
83+
Nothing is verified about the *authority* of the certificate;
84+
the caller must verify that the certificate chains to an appropriate
85+
trust root themselves.
86+
87+
Args:
88+
certificate: A *cryptography* X509 certificate object.
7689
77-
:param certificate: A *cryptography* X509 certificate object.
78-
:param ip_address: The IP address that *connection* should be valid
79-
for. Can be an IPv4 or IPv6 address.
90+
ip_address:
91+
The IP address that *connection* should be valid for. Can be an
92+
IPv4 or IPv6 address.
8093
81-
:raises service_identity.VerificationError: If *certificate* is not valid
82-
for *ip_address*.
83-
:raises service_identity.CertificateError: If *certificate* contains
84-
invalid / unexpected data.
94+
Raises:
95+
service_identity.VerificationError:
96+
If *certificate* is not valid for *ip_address*.
8597
86-
:returns: ``None``
98+
service_identity.CertificateError:
99+
If *certificate* contains invalid / unexpected data. This includes
100+
the case where the certificate contains no ``subjectAltName``\ s.
87101
88102
.. versionadded:: 18.1.0
103+
104+
.. versionchanged:: 24.1.0
105+
:exc:`~service_identity.CertificateError` is raised if the certificate
106+
contains no ``subjectAltName``\ s instead of
107+
:exc:`~service_identity.VerificationError`.
89108
"""
90109
verify_service_identity(
91110
cert_patterns=extract_patterns(certificate),
@@ -101,9 +120,11 @@ def extract_patterns(cert: Certificate) -> Sequence[CertificatePattern]:
101120
"""
102121
Extract all valid ID patterns from a certificate for service verification.
103122
104-
:param cert: The certificate to be dissected.
123+
Args:
124+
cert: The certificate to be dissected.
105125
106-
:return: List of IDs.
126+
Returns:
127+
List of IDs.
107128
108129
.. versionchanged:: 23.1.0
109130
``commonName`` is not used as a fallback anymore.

src/service_identity/hazmat.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ def verify_service_identity(
5050
*obligatory_ids* must be both present and match. *optional_ids* must match
5151
if a pattern of the respective type is present.
5252
"""
53+
if not cert_patterns:
54+
raise CertificateError(
55+
"Certificate does not contain any `subjectAltName`s."
56+
)
57+
5358
errors = []
5459
matches = _find_matches(cert_patterns, obligatory_ids) + _find_matches(
5560
cert_patterns, optional_ids

src/service_identity/pyopenssl.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,28 @@
3737

3838

3939
def verify_hostname(connection: Connection, hostname: str) -> None:
40-
"""
40+
r"""
4141
Verify whether the certificate of *connection* is valid for *hostname*.
4242
43-
:param connection: A pyOpenSSL connection object.
44-
:param hostname: The hostname that *connection* should be connected to.
43+
Args:
44+
connection: A pyOpenSSL connection object.
45+
46+
hostname: The hostname that *connection* should be connected to.
47+
48+
Raises:
49+
service_identity.VerificationError:
50+
If *connection* does not provide a certificate that is valid for
51+
*hostname*.
4552
46-
:raises service_identity.VerificationError: If *connection* does not
47-
provide a certificate that is valid for *hostname*.
48-
:raises service_identity.CertificateError: If the certificate chain of
49-
*connection* contains a certificate that contains invalid/unexpected
50-
data.
53+
service_identity.CertificateError:
54+
If certificate provided by *connection* contains invalid /
55+
unexpected data. This includes the case where the certificate
56+
contains no ``subjectAltName``\ s.
5157
52-
:returns: ``None``
58+
.. versionchanged:: 24.1.0
59+
:exc:`~service_identity.CertificateError` is raised if the certificate
60+
contains no ``subjectAltName``\ s instead of
61+
:exc:`~service_identity.VerificationError`.
5362
"""
5463
verify_service_identity(
5564
cert_patterns=extract_patterns(
@@ -61,22 +70,31 @@ def verify_hostname(connection: Connection, hostname: str) -> None:
6170

6271

6372
def verify_ip_address(connection: Connection, ip_address: str) -> None:
64-
"""
73+
r"""
6574
Verify whether the certificate of *connection* is valid for *ip_address*.
6675
67-
:param connection: A pyOpenSSL connection object.
68-
:param ip_address: The IP address that *connection* should be connected to.
69-
Can be an IPv4 or IPv6 address.
76+
Args:
77+
connection: A pyOpenSSL connection object.
78+
79+
ip_address:
80+
The IP address that *connection* should be connected to. Can be an
81+
IPv4 or IPv6 address.
7082
71-
:raises service_identity.VerificationError: If *connection* does not
72-
provide a certificate that is valid for *ip_address*.
73-
:raises service_identity.CertificateError: If the certificate chain of
74-
*connection* contains a certificate that contains invalid/unexpected
75-
data.
83+
Raises:
84+
service_identity.VerificationError:
85+
If *connection* does not provide a certificate that is valid for
86+
*ip_address*.
7687
77-
:returns: ``None``
88+
service_identity.CertificateError:
89+
If the certificate chain of *connection* contains a certificate
90+
that contains invalid/unexpected data.
7891
7992
.. versionadded:: 18.1.0
93+
94+
.. versionchanged:: 24.1.0
95+
:exc:`~service_identity.CertificateError` is raised if the certificate
96+
contains no ``subjectAltName``\ s instead of
97+
:exc:`~service_identity.VerificationError`.
8098
"""
8199
verify_service_identity(
82100
cert_patterns=extract_patterns(
@@ -94,9 +112,11 @@ def extract_patterns(cert: X509) -> Sequence[CertificatePattern]:
94112
"""
95113
Extract all valid ID patterns from a certificate for service verification.
96114
97-
:param cert: The certificate to be dissected.
115+
Args:
116+
cert: The certificate to be dissected.
98117
99-
:return: List of IDs.
118+
Returns:
119+
List of IDs.
100120
101121
.. versionchanged:: 23.1.0
102122
``commonName`` is not used as a fallback anymore.

tests/test_hazmat.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ class TestVerifyServiceIdentity:
4545
Simple integration tests for verify_service_identity.
4646
"""
4747

48+
def test_no_cert_patterns(self):
49+
"""
50+
Empty cert patterns raise a helpful CertificateError.
51+
"""
52+
with pytest.raises(
53+
CertificateError,
54+
match="Certificate does not contain any `subjectAltName`s.",
55+
):
56+
verify_service_identity([], [], [])
57+
4858
def test_dns_id_success(self):
4959
"""
5060
Return pairs of certificate ids and service ids on matches.

0 commit comments

Comments
 (0)