summaryrefslogtreecommitdiff
path: root/src/service_identity/pyopenssl.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/service_identity/pyopenssl.py')
-rw-r--r--src/service_identity/pyopenssl.py88
1 files changed, 63 insertions, 25 deletions
diff --git a/src/service_identity/pyopenssl.py b/src/service_identity/pyopenssl.py
index fb39954..7fc090d 100644
--- a/src/service_identity/pyopenssl.py
+++ b/src/service_identity/pyopenssl.py
@@ -6,31 +6,36 @@ from __future__ import absolute_import, division, print_function
import warnings
+import six
+
from pyasn1.codec.der.decoder import decode
from pyasn1.type.char import IA5String
from pyasn1.type.univ import ObjectIdentifier
from pyasn1_modules.rfc2459 import GeneralNames
-from .exceptions import SubjectAltNameWarning
from ._common import (
+ DNS_ID,
CertificateError,
DNSPattern,
- DNS_ID,
+ IPAddress_ID,
+ IPAddressPattern,
SRVPattern,
URIPattern,
verify_service_identity,
)
+from .exceptions import SubjectAltNameWarning
+
+
+__all__ = ["verify_hostname"]
def verify_hostname(connection, hostname):
"""
Verify whether the certificate of *connection* is valid for *hostname*.
- :param connection: A pyOpenSSL connection object.
- :type connection: :class:`OpenSSL.SSL.Connection`
-
- :param hostname: The hostname that *connection* should be connected to.
- :type hostname: :class:`unicode`
+ :param OpenSSL.SSL.Connection connection: A pyOpenSSL connection object.
+ :param unicode hostname: The hostname that *connection* should be connected
+ to.
:raises service_identity.VerificationError: If *connection* does not
provide a certificate that is valid for *hostname*.
@@ -47,7 +52,32 @@ def verify_hostname(connection, hostname):
)
-ID_ON_DNS_SRV = ObjectIdentifier('1.3.6.1.5.5.7.8.7') # id_on_dnsSRV
+def verify_ip_address(connection, ip_address):
+ """
+ Verify whether the certificate of *connection* is valid for *ip_address*.
+
+ :param OpenSSL.SSL.Connection connection: A pyOpenSSL connection object.
+ :param unicode ip_address: The IP address that *connection* should be
+ connected to. Can be an IPv4 or IPv6 address.
+
+ :raises service_identity.VerificationError: If *connection* does not
+ provide a certificate that is valid for *ip_address*.
+ :raises service_identity.CertificateError: If the certificate chain of
+ *connection* contains a certificate that contains invalid/unexpected
+ data.
+
+ :returns: ``None``
+
+ .. versionadded:: 18.1.0
+ """
+ verify_service_identity(
+ cert_patterns=extract_ids(connection.get_peer_certificate()),
+ obligatory_ids=[IPAddress_ID(ip_address)],
+ optional_ids=[],
+ )
+
+
+ID_ON_DNS_SRV = ObjectIdentifier("1.3.6.1.5.5.7.8.7") # id_on_dnsSRV
def extract_ids(cert):
@@ -57,13 +87,12 @@ def extract_ids(cert):
If *cert* doesn't contain any identifiers, the ``CN``s are used as DNS-IDs
as fallback.
- :param cert: The certificate to be dissected.
- :type cert: :class:`OpenSSL.SSL.X509`
+ :param OpenSSL.SSL.X509 cert: The certificate to be dissected.
:return: List of IDs.
"""
ids = []
- for i in range(cert.get_extension_count()):
+ for i in six.moves.range(cert.get_extension_count()):
ext = cert.get_extension(i)
if ext.get_short_name() == b"subjectAltName":
names, _ = decode(ext.get_data(), asn1Spec=GeneralNames())
@@ -71,6 +100,12 @@ def extract_ids(cert):
name_string = n.getName()
if name_string == "dNSName":
ids.append(DNSPattern(n.getComponent().asOctets()))
+ elif name_string == "iPAddress":
+ ids.append(
+ IPAddressPattern.from_bytes(
+ n.getComponent().asOctets()
+ )
+ )
elif name_string == "uniformResourceIdentifier":
ids.append(URIPattern(n.getComponent().asOctets()))
elif name_string == "otherName":
@@ -84,25 +119,28 @@ def extract_ids(cert):
raise CertificateError(
"Unexpected certificate content."
)
+ else: # pragma: nocover
+ pass
+ else: # pragma: nocover
+ pass
if not ids:
- # http://tools.ietf.org/search/rfc6125#section-6.4.4
+ # https://tools.ietf.org/search/rfc6125#section-6.4.4
# A client MUST NOT seek a match for a reference identifier of CN-ID if
# the presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
# application-specific identifier types supported by the client.
+ components = [
+ c[1] for c in cert.get_subject().get_components() if c[0] == b"CN"
+ ]
+ cn = next(iter(components), b"<not given>")
+ ids = [DNSPattern(c) for c in components]
warnings.warn(
- "Certificate has no `subjectAltName`, falling back to check for a "
- "`commonName` for now. This feature is being removed by major "
- "browsers and deprecated by RFC 2818.",
- SubjectAltNameWarning
+ "Certificate with CN '%s' has no `subjectAltName`, falling back "
+ "to check for a `commonName` for now. This feature is being "
+ "removed by major browsers and deprecated by RFC 2818. "
+ "service_identity will remove the support for it in mid-2018."
+ % (cn.decode("utf-8"),),
+ SubjectAltNameWarning,
+ stacklevel=2,
)
- ids = [DNSPattern(c[1])
- for c
- in cert.get_subject().get_components()
- if c[0] == b"CN"]
return ids
-
-
-__all__ = [
- "verify_hostname",
-]