From 5d071d27027fc427eefe815560ed1ce4e7175d74 Mon Sep 17 00:00:00 2001 From: Adrian Ehrsam Date: Fri, 17 Mar 2023 09:05:36 +0100 Subject: [PATCH 1/5] Fixes #https://github.com/denisenkom/pytds/issues/148 --- src/pytds/tls.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/pytds/tls.py b/src/pytds/tls.py index 81c40ae..c9f610c 100644 --- a/src/pytds/tls.py +++ b/src/pytds/tls.py @@ -100,14 +100,22 @@ def validate_host(cert, name): return True # checking SAN - s_name = name.decode('ascii') + s_name = name.decode("ascii") for i in range(cert.get_extension_count()): ext = cert.get_extension(i) - if ext.get_short_name() == b'subjectAltName': + if ext.get_short_name() == b"subjectAltName": s = str(ext) - # SANs are usually have form like: DNS:hostname - if s.startswith('DNS:') and s[4:] == s_name: - return True + items = s.split(",") + for item in items: + dnsentry = item.strip() + # SANs are usually have form like: DNS:hostname + if dnsentry.startswith("DNS:") and s[4:] == s_name: + return True + if dnsentry.startswith("DNS:*."): + afterstar_parts = dnsentry[6:] + afterstar_parts_sname = ".".join(s_name.split(".")[1:]) + if afterstar_parts == afterstar_parts_sname: + return True # TODO handle wildcards return False From 1bfca1ef82f550889d82d9e237a99c48b317efb3 Mon Sep 17 00:00:00 2001 From: Adrian Ehrsam Date: Fri, 17 Mar 2023 09:07:42 +0100 Subject: [PATCH 2/5] respect ' instead of " --- src/pytds/tls.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pytds/tls.py b/src/pytds/tls.py index c9f610c..5e94720 100644 --- a/src/pytds/tls.py +++ b/src/pytds/tls.py @@ -100,24 +100,24 @@ def validate_host(cert, name): return True # checking SAN - s_name = name.decode("ascii") + s_name = name.decode('ascii') for i in range(cert.get_extension_count()): ext = cert.get_extension(i) - if ext.get_short_name() == b"subjectAltName": + if ext.get_short_name() == b'subjectAltName': s = str(ext) - items = s.split(",") + items = s.split(',') for item in items: dnsentry = item.strip() # SANs are usually have form like: DNS:hostname - if dnsentry.startswith("DNS:") and s[4:] == s_name: + if dnsentry.startswith('DNS:') and s[4:] == s_name: return True - if dnsentry.startswith("DNS:*."): + if dnsentry.startswith('DNS:*.'): # support for wildcards, but only at the first position afterstar_parts = dnsentry[6:] - afterstar_parts_sname = ".".join(s_name.split(".")[1:]) + afterstar_parts_sname = '.'.join(s_name.split('.')[1:]) # remove first part of dns name if afterstar_parts == afterstar_parts_sname: return True - # TODO handle wildcards + # TODO check if wildcard is needed in CN as well return False From 76b8a4d417e2c12f75c44565a7af27fccbb16d6a Mon Sep 17 00:00:00 2001 From: Adrian Ehrsam Date: Tue, 21 Mar 2023 08:20:24 +0100 Subject: [PATCH 3/5] add tests --- src/pytds/tls.py | 25 ++++++++++++++----------- tests/tls_san_test.py | 10 ++++++++++ 2 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 tests/tls_san_test.py diff --git a/src/pytds/tls.py b/src/pytds/tls.py index 5e94720..84d08dd 100644 --- a/src/pytds/tls.py +++ b/src/pytds/tls.py @@ -81,6 +81,18 @@ def shutdown(self): def verify_cb(conn, cert, err_num, err_depth, ret_code): return ret_code == 1 +def is_san_matching(san: str, host_name: str) -> bool: + for item in san.split(','): + dnsentry = item.lstrip('DNS:').strip() + # SANs are usually have form like: DNS:hostname + if dnsentry == s_name: + return True + if dnsentry[0:2] == "*.": # support for wildcards, but only at the first position + afterstar_parts = dnsentry[2:] + afterstar_parts_sname = '.'.join(s_name.split('.')[1:]) # remove first part of dns name + if afterstar_parts == afterstar_parts_sname: + return True + return False def validate_host(cert, name): """ @@ -105,17 +117,8 @@ def validate_host(cert, name): ext = cert.get_extension(i) if ext.get_short_name() == b'subjectAltName': s = str(ext) - items = s.split(',') - for item in items: - dnsentry = item.strip() - # SANs are usually have form like: DNS:hostname - if dnsentry.startswith('DNS:') and s[4:] == s_name: - return True - if dnsentry.startswith('DNS:*.'): # support for wildcards, but only at the first position - afterstar_parts = dnsentry[6:] - afterstar_parts_sname = '.'.join(s_name.split('.')[1:]) # remove first part of dns name - if afterstar_parts == afterstar_parts_sname: - return True + if is_san_matching(s, s_name): + return True # TODO check if wildcard is needed in CN as well return False diff --git a/tests/tls_san_test.py b/tests/tls_san_test.py new file mode 100644 index 0000000..c7b9f4e --- /dev/null +++ b/tests/tls_san_test.py @@ -0,0 +1,10 @@ +from pytds.tls import is_san_matching + +def test_san(): + assert not is_san_matching("", "host.com") + assert is_san_matching("database.com", "database.com") + assert not is_san_matching("notdatabase.com", "database.com") + assert is_san_matching("*.database.com", "database.com") + assert not is_san_matching("*.database.com", "*.database.com") + assert not is_san_matching("database.com", "*.database.com") + assert not is_san_matching("test.*.database.com", "test.subdomain.database.com") # That star should be at first position \ No newline at end of file From acf0822742a3ec91c1d19d6c3f8165cd06a19406 Mon Sep 17 00:00:00 2001 From: Adrian Ehrsam Date: Tue, 21 Mar 2023 08:27:33 +0100 Subject: [PATCH 4/5] bug fix --- src/pytds/tls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pytds/tls.py b/src/pytds/tls.py index 84d08dd..b4db564 100644 --- a/src/pytds/tls.py +++ b/src/pytds/tls.py @@ -85,11 +85,11 @@ def is_san_matching(san: str, host_name: str) -> bool: for item in san.split(','): dnsentry = item.lstrip('DNS:').strip() # SANs are usually have form like: DNS:hostname - if dnsentry == s_name: + if dnsentry == host_name: return True if dnsentry[0:2] == "*.": # support for wildcards, but only at the first position afterstar_parts = dnsentry[2:] - afterstar_parts_sname = '.'.join(s_name.split('.')[1:]) # remove first part of dns name + afterstar_parts_sname = '.'.join(host_name.split('.')[1:]) # remove first part of dns name if afterstar_parts == afterstar_parts_sname: return True return False From c9b929fe9199145d5fafff6f6a4494ad9c5f91f8 Mon Sep 17 00:00:00 2001 From: Adrian Ehrsam Date: Tue, 21 Mar 2023 08:33:49 +0100 Subject: [PATCH 5/5] fix tests --- tests/tls_san_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tls_san_test.py b/tests/tls_san_test.py index c7b9f4e..e44345a 100644 --- a/tests/tls_san_test.py +++ b/tests/tls_san_test.py @@ -4,7 +4,7 @@ def test_san(): assert not is_san_matching("", "host.com") assert is_san_matching("database.com", "database.com") assert not is_san_matching("notdatabase.com", "database.com") - assert is_san_matching("*.database.com", "database.com") - assert not is_san_matching("*.database.com", "*.database.com") + assert not is_san_matching("*.database.com", "database.com") + assert is_san_matching("*.database.com", "test.database.com") assert not is_san_matching("database.com", "*.database.com") assert not is_san_matching("test.*.database.com", "test.subdomain.database.com") # That star should be at first position \ No newline at end of file