Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
DNS testing tools
Remoh
Commits
861563f4
Commit
861563f4
authored
Dec 01, 2019
by
Stephane Bortzmeyer
Browse files
[DoT] New, RFC6215-compliant host name validation. Closes #11
parent
9f053941
Changes
1
Hide whitespace changes
Inline
Side-by-side
homer.py
View file @
861563f4
...
...
@@ -74,7 +74,14 @@ def usage(msg=None):
def
is_valid_hostname
(
name
):
name
=
str
(
name
.
encode
(
'idna'
).
lower
())
return
re_host
.
search
(
name
)
def
is_valid_ip_address
(
addr
):
try
:
baddr
=
netaddr
.
IPAddress
(
addr
)
except
netaddr
.
core
.
AddrFormatError
:
return
False
return
True
def
is_valid_url
(
url
):
try
:
result
=
urllib
.
parse
.
urlparse
(
url
)
# A very poor validation, many
...
...
@@ -93,27 +100,44 @@ def get_certificate_san(x509cert):
san
=
str
(
ext
)
return
san
def
validate_hostname
(
hostname
,
cert
):
hostname
=
hostname
.
lower
()
cn
=
cert
.
get_subject
().
commonName
.
lower
()
if
cn
.
startswith
(
"*."
):
# Wildcard
(
start
,
base
)
=
cn
.
split
(
"*."
)
if
hostname
.
endswith
(
base
):
# Try one possible name. Names must be already canonicalized.
def
match_hostname
(
hostname
,
possibleMatch
):
print
(
"Testing %s against %s"
%
(
hostname
,
possibleMatch
))
if
possibleMatch
.
startswith
(
"*."
):
# Wildcard
base
=
possibleMatch
[
1
:]
# Skip the star
# RFC 6125 says that we MAY accept left-most labels with
# wildcards included (foo*bar). We don't do it here.
try
:
(
first
,
rest
)
=
hostname
.
split
(
"."
,
maxsplit
=
1
)
except
ValueError
:
# One-label name
rest
=
hostname
if
rest
==
base
[
1
:]:
return
True
else
:
if
hostname
==
cn
:
if
hostname
==
base
[
1
:]:
return
True
return
False
else
:
return
hostname
==
possibleMatch
# Try all the names in the certificate
def
validate_hostname
(
hostname
,
cert
):
# Complete specification is in RFC 6125. It is long and
# complicated and I'm not sure we do it perfectly.
is_addr
=
is_valid_ip_address
(
hostname
)
hostname
=
hostname
.
lower
()
hostname
=
hostname
.
encode
(
'idna'
).
decode
()
for
alt_name
in
get_certificate_san
(
cert
).
split
(
", "
):
if
alt_name
.
startswith
(
"DNS:"
):
if
alt_name
.
startswith
(
"DNS:"
)
and
not
is_addr
:
(
start
,
base
)
=
alt_name
.
split
(
"DNS:"
)
base
=
base
.
lower
()
if
hostname
==
base
:
# We assume the certificate contains only
# A-labels. Otherwise, we would need to: "base =
# str(base.encode('idna'))"
found
=
match_hostname
(
hostname
,
base
)
if
found
:
return
True
elif
alt_name
.
startswith
(
"IP Address:"
):
try
:
host_i
=
netaddr
.
IPAddress
(
hostname
)
except
netaddr
.
core
.
AddrFormatError
:
continue
# If hostname is not an IP address, we cannot use it for comparison
elif
alt_name
.
startswith
(
"IP Address:"
)
and
is_addr
:
host_i
=
netaddr
.
IPAddress
(
hostname
)
(
start
,
base
)
=
alt_name
.
split
(
"IP Address:"
)
if
base
.
endswith
(
"
\n
"
):
base
=
base
[:
-
1
]
...
...
@@ -121,16 +145,23 @@ def validate_hostname(hostname, cert):
base_i
=
netaddr
.
IPAddress
(
base
)
except
netaddr
.
core
.
AddrFormatError
:
continue
# Ignore broken IP addresses in certificates. Are we too liberal?
print
(
"Testing %s against %s"
%
(
hostname
,
base
))
if
host_i
==
base_i
:
return
True
else
:
pass
# Ignore unknown alternative name types
pass
# Ignore unknown alternative name types. May be
# accept URI alternative names for DoH,
# According to RFC 6125, we MUST NOT try the Common Name before the Subject Alternative Names.
cn
=
cert
.
get_subject
().
commonName
.
lower
()
found
=
match_hostname
(
hostname
,
cn
)
if
found
:
return
True
return
False
class
Connection
:
def
__init__
(
self
,
server
,
servername
=
None
,
dot
=
False
,
verbose
=
verbose
,
insecure
=
insecure
,
post
=
post
,
head
=
head
):
if
dot
and
not
is_valid_hostname
(
server
):
error
(
"DoT requires a host name, not
\"
%s
\"
"
%
server
)
error
(
"DoT requires a host name
or IP address
, not
\"
%s
\"
"
%
server
)
if
not
dot
and
not
is_valid_url
(
url
):
error
(
"DoH requires a valid HTTPS URL, not
\"
%s
\"
"
%
server
)
self
.
server
=
server
...
...
@@ -165,7 +196,8 @@ class Connection:
lambda
conn
,
cert
,
errno
,
depth
,
preverify_ok
:
preverify_ok
)
self
.
session
=
OpenSSL
.
SSL
.
Connection
(
self
.
context
,
self
.
sock
)
self
.
session
.
set_tlsext_host_name
(
check
.
encode
())
# Server Name Indication (SNI)
self
.
session
.
connect
((
self
.
server
,
853
))
self
.
session
.
connect
((
self
.
server
,
853
))
# TODO We may here have exceptions such as OpenSSL.SSL.ZeroReturnError
self
.
session
.
do_handshake
()
self
.
cert
=
self
.
session
.
get_peer_certificate
()
if
not
insecure
:
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment