Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move ssl_match_hostname to urllib3.utils. #2198

Merged
merged 3 commits into from
Apr 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 1 addition & 3 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@
"src/urllib3/_collections.py",
"src/urllib3/fields.py",
"src/urllib3/filepost.py",
"src/urllib3/packages/__init__.py",
"src/urllib3/packages/ssl_match_hostname/__init__.py",
"src/urllib3/packages/ssl_match_hostname/_implementation.py",
sethmlarson marked this conversation as resolved.
Show resolved Hide resolved
"src/urllib3/util/connection.py",
"src/urllib3/util/proxy.py",
"src/urllib3/util/queue.py",
"src/urllib3/util/response.py",
"src/urllib3/util/ssl_.py",
"src/urllib3/util/ssl_match_hostname.py",
"src/urllib3/util/ssltransport.py",
"src/urllib3/util/url.py",
"src/urllib3/util/wait.py",
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[flake8]
ignore = E501, E203, W503, W504
exclude=./docs/conf.py,./src/urllib3/packages/*
exclude=./docs/conf.py
max-line-length=99

[metadata]
Expand Down
2 changes: 0 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@
license="MIT",
packages=[
"urllib3",
"urllib3.packages",
"urllib3.packages.ssl_match_hostname",
"urllib3.contrib",
"urllib3.contrib._securetransport",
"urllib3.util",
Expand Down
2 changes: 1 addition & 1 deletion src/urllib3/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ class BaseSSLError(BaseException): # type: ignore
NewConnectionError,
SystemTimeWarning,
)
from .packages.ssl_match_hostname import CertificateError, match_hostname
from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection, ssl_
from .util.ssl_ import (
PeerCertRetType,
Expand All @@ -57,6 +56,7 @@ class BaseSSLError(BaseException): # type: ignore
resolve_ssl_version,
ssl_wrap_socket,
)
from .util.ssl_match_hostname import CertificateError, match_hostname

# Not a no-op, we're adding this to the namespace so it can be imported.
ConnectionError = ConnectionError
Expand Down
2 changes: 1 addition & 1 deletion src/urllib3/connectionpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@
SSLError,
TimeoutError,
)
from .packages.ssl_match_hostname import CertificateError
from .request import RequestMethods
from .response import BaseHTTPResponse, HTTPResponse
from .util.connection import is_connection_dropped
from .util.proxy import connection_requires_http_tunnel
from .util.request import set_file_position
from .util.response import assert_header_parsing
from .util.retry import Retry
from .util.ssl_match_hostname import CertificateError
from .util.timeout import Timeout
from .util.url import Url, _encode_target
from .util.url import _normalize_host as normalize_host
Expand Down
3 changes: 0 additions & 3 deletions src/urllib3/packages/__init__.py

This file was deleted.

Empty file removed src/urllib3/packages/__init__.pyi
Empty file.
3 changes: 0 additions & 3 deletions src/urllib3/packages/ssl_match_hostname/__init__.py

This file was deleted.

11 changes: 0 additions & 11 deletions src/urllib3/packages/ssl_match_hostname/_implementation.pyi

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

import ipaddress
import re
import sys
from typing import Any, Match, Optional, Union

from .ssl_ import PeerCertRetType

__version__ = "3.5.0.1"

Expand All @@ -14,7 +16,9 @@ class CertificateError(ValueError):
pass


def _dnsname_match(dn, hostname, max_wildcards=1):
def _dnsname_match(
dn: Any, hostname: str, max_wildcards: int = 1
) -> Union[Optional[Match[str]], bool]:
"""Matching according to RFC 6125, section 6.4.3

http://tools.ietf.org/html/rfc6125#section-6.4.3
Expand All @@ -41,7 +45,7 @@ def _dnsname_match(dn, hostname, max_wildcards=1):

# speed up common case w/o wildcards
if not wildcards:
return dn.lower() == hostname.lower()
return bool(dn.lower() == hostname.lower())
sethmlarson marked this conversation as resolved.
Show resolved Hide resolved

# RFC 6125, section 6.4.3, subitem 1.
# The client SHOULD NOT attempt to match a presented identifier in which
Expand All @@ -68,7 +72,7 @@ def _dnsname_match(dn, hostname, max_wildcards=1):
return pat.match(hostname)


def _ipaddress_match(ipname, host_ip):
def _ipaddress_match(ipname: Any, host_ip: str) -> bool:
"""Exact matching of IP addresses.

RFC 6125 explicitly doesn't define an algorithm for this
Expand All @@ -77,10 +81,10 @@ def _ipaddress_match(ipname, host_ip):
# OpenSSL may add a trailing newline to a subjectAltName's IP address
# Divergence from upstream: ipaddress can't handle byte str
ip = ipaddress.ip_address(ipname.rstrip())
return ip == host_ip
return bool(ip == host_ip)


def match_hostname(cert, hostname):
def match_hostname(cert: PeerCertRetType, hostname: str) -> None:
"""Verify that *cert* (in decoded format as returned by
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
rules are followed, but IP addresses are not accepted for *hostname*.
Expand All @@ -101,8 +105,8 @@ def match_hostname(cert, hostname):
# Not an IP address (common case)
host_ip = None
dnsnames = []
san = cert.get("subjectAltName", ())
for key, value in san:
san = cert.get("subjectAltName", ()) # type: ignore
for key, value in san: # type: ignore
if key == "DNS":
if host_ip is None and _dnsname_match(value, hostname):
return
Expand Down
49 changes: 47 additions & 2 deletions test/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import pytest

from urllib3.connection import RECENT_DATE, CertificateError, _match_hostname
from urllib3.packages.ssl_match_hostname._implementation import (
from urllib3.util.ssl_match_hostname import (
CertificateError as ImplementationCertificateError,
)
from urllib3.packages.ssl_match_hostname._implementation import match_hostname
from urllib3.util.ssl_match_hostname import match_hostname


class TestConnection:
Expand Down Expand Up @@ -47,6 +47,36 @@ def test_match_hostname_mismatch(self):
)
assert e._peer_cert == cert

def test_match_hostname_no_dns(self):
cert = {"subjectAltName": [("DNS", "")]}
asserted_hostname = "bar"
try:
with mock.patch("urllib3.connection.log.warning") as mock_log:
_match_hostname(cert, asserted_hostname)
except CertificateError as e:
assert "hostname 'bar' doesn't match ''" in str(e)
mock_log.assert_called_once_with(
"Certificate did not match expected hostname: %s. Certificate: %s",
"bar",
{"subjectAltName": [("DNS", "")]},
)
assert e._peer_cert == cert

def test_match_hostname_startwith_wildcard(self):
cert = {"subjectAltName": [("DNS", "*")]}
asserted_hostname = "foo"
_match_hostname(cert, asserted_hostname)

def test_match_hostname_dnsname(self):
cert = {"subjectAltName": [("DNS", "xn--p1b6ci4b4b3a*.xn--11b5bs8d")]}
asserted_hostname = "xn--p1b6ci4b4b3a*.xn--11b5bs8d"
_match_hostname(cert, asserted_hostname)

def test_match_hostname_include_wildcard(self):
cert = {"subjectAltName": [("DNS", "foo*")]}
asserted_hostname = "foobar"
_match_hostname(cert, asserted_hostname)

def test_match_hostname_ignore_common_name(self):
cert = {"subject": [("commonName", "foo")]}
asserted_hostname = "foo"
Expand All @@ -56,6 +86,21 @@ def test_match_hostname_ignore_common_name(self):
):
match_hostname(cert, asserted_hostname)

def test_match_hostname_ip_address(self):
cert = {"subjectAltName": [("IP Address", "1.1.1.1")]}
asserted_hostname = "1.1.1.2"
try:
with mock.patch("urllib3.connection.log.warning") as mock_log:
_match_hostname(cert, asserted_hostname)
except CertificateError as e:
assert "hostname '1.1.1.2' doesn't match '1.1.1.1'" in str(e)
mock_log.assert_called_once_with(
"Certificate did not match expected hostname: %s. Certificate: %s",
"1.1.1.2",
{"subjectAltName": [("IP Address", "1.1.1.1")]},
)
assert e._peer_cert == cert

def test_recent_date(self):
# This test is to make sure that the RECENT_DATE value
# doesn't get too far behind what the current date is.
Expand Down
2 changes: 1 addition & 1 deletion test/test_connectionpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
SSLError,
TimeoutError,
)
from urllib3.packages.ssl_match_hostname import CertificateError
from urllib3.response import HTTPResponse
from urllib3.util.ssl_match_hostname import CertificateError
from urllib3.util.timeout import Timeout

from .test_response import MockChunkedEncodingResponse, MockSock
Expand Down