def __init__(self, ip_address): if not isinstance(ip_address, text_type): raise TypeError("IPADDRESS-ID must be a unicode string.") ip_address = ip_address.strip() if not _is_ip_address(ip_address): raise ValueError("Invalid IPADDRESS-ID.") self.ip_address = ip_address
def test_no_ips(self): """ Return False for patterns and hosts that aren't IP addresses. """ for s in [ b"*.twistedmatrix.com", b"twistedmatrix.com", b"mail.google.com", b"omega7.de", b"omega7", ]: assert not _is_ip_address(s), "False positive {0!r}".format(s)
def test_ips(self): """ Returns True for patterns and hosts that could match IP addresses. """ for s in [ b"127.0.0.1", u"127.0.0.1", b"172.16.254.12", b"*.0.0.1", b"::1", b"*::1", b"2001:0db8:0000:0000:0000:ff00:0042:8329", b"2001:0db8::ff00:0042:8329", ]: assert _is_ip_address(s), "Not detected {0!r}".format(s)
def verifyHostname(connection, hostname): if _is_ip_address(hostname): hostname_id = IPADDRESS_ID(hostname) else: hostname_id = DNS_ID(hostname) cert = connection.get_peer_certificate() ids = [] # Basically the same as service_identity.pyopenssl.extract_ids, but with # subjectAltName support for iPAddress IDs and IP Addresses stuck in dNSName ids for i in range(cert.get_extension_count()): ext = cert.get_extension(i) if ext.get_short_name() == b"subjectAltName": names, _ = decode(ext.get_data(), asn1Spec=GeneralNames()) for n in names: name_string = n.getName() if name_string == "iPAddress": ids.append(IPADDRESSPattern(n.getComponent().asOctets())) elif name_string == "dNSName" and _is_ip_address( n.getComponent().asOctets().strip()): # If an IP Address is passed as a dNSName, it will # cause service_identity to throw an exception, as # it doesn't consider this valid. From reading the # RFCs, i think it's a gray area, so i'm going to # allow it, since we've seen it happen with a customer. try: ip_string = n.getComponent().asOctets().strip() if ":" in ip_string: value = inet_pton(AF_INET6, ip_string) else: value = inet_pton(AF_INET, ip_string) ids.append(IPADDRESSPattern(value)) except CertificateError as e: log.warning( "Ignoring invalid dNSName record in subjectAltName: %s", e) # Normal behavior below: elif name_string == "dNSName": ids.append(DNSPattern(n.getComponent().asOctets())) elif name_string == "uniformResourceIdentifier": ids.append(URIPattern(n.getComponent().asOctets())) elif name_string == "otherName": comp = n.getComponent() oid = comp.getComponentByPosition(0) if oid == ID_ON_DNS_SRV: srv, _ = decode(comp.getComponentByPosition(1)) if isinstance(srv, IA5String): ids.append(SRVPattern(srv.asOctets())) else: # pragma: nocover raise CertificateError( "Unexpected certificate content.") if not ids: # http://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. 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) ids = [ DNSPattern(c[1]) for c in cert.get_subject().get_components() if c[0] == b"CN" ] verify_service_identity( cert_patterns=ids, obligatory_ids=[hostname_id], optional_ids=[], )
def test_not_ips(self, not_ip): """ Return False for patterns and hosts that aren't IP addresses. """ assert _is_ip_address(not_ip) is False
def test_ips(self, ip): """ Returns True for patterns and hosts that could match IP addresses. """ assert _is_ip_address(ip) is True
def verifyHostname(connection, hostname): if _is_ip_address(hostname): hostname_id = IPADDRESS_ID(hostname) else: hostname_id = DNS_ID(hostname) cert = connection.get_peer_certificate() ids = [] # Basically the same as service_identity.pyopenssl.extract_ids, but with # subjectAltName support for iPAddress IDs and IP Addresses stuck in dNSName ids for i in range(cert.get_extension_count()): ext = cert.get_extension(i) if ext.get_short_name() == b"subjectAltName": names, _ = decode(ext.get_data(), asn1Spec=GeneralNames()) for n in names: name_string = n.getName() if name_string == "iPAddress": ids.append(IPADDRESSPattern(n.getComponent().asOctets())) elif name_string == "dNSName" and _is_ip_address(n.getComponent().asOctets().strip()): # If an IP Address is passed as a dNSName, it will # cause service_identity to throw an exception, as # it doesn't consider this valid. From reading the # RFCs, i think it's a gray area, so i'm going to # allow it, since we've seen it happen with a customer. try: ip_string = n.getComponent().asOctets().strip() if ":" in ip_string: value = inet_pton(AF_INET6, ip_string) else: value = inet_pton(AF_INET, ip_string) ids.append(IPADDRESSPattern(value)) except CertificateError as e: log.warning( "Ignoring invalid dNSName record in subjectAltName: %s", e) # Normal behavior below: elif name_string == "dNSName": ids.append(DNSPattern(n.getComponent().asOctets())) elif name_string == "uniformResourceIdentifier": ids.append(URIPattern(n.getComponent().asOctets())) elif name_string == "otherName": comp = n.getComponent() oid = comp.getComponentByPosition(0) if oid == ID_ON_DNS_SRV: srv, _ = decode(comp.getComponentByPosition(1)) if isinstance(srv, IA5String): ids.append(SRVPattern(srv.asOctets())) else: # pragma: nocover raise CertificateError( "Unexpected certificate content.") if not ids: # http://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. 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 ) ids = [DNSPattern(c[1]) for c in cert.get_subject().get_components() if c[0] == b"CN"] verify_service_identity( cert_patterns=ids, obligatory_ids=[hostname_id], optional_ids=[], )