Commit fcc367a2 authored by Alexandre's avatar Alexandre
Browse files

[DoT] Try another IP on connection timeout. Addresses #13

parent edc518f7
......@@ -31,6 +31,7 @@ import re
import os.path
import hashlib
import base64
import signal
# Values that can be changed from the command line
dot = False # DoH by default
......@@ -62,6 +63,8 @@ STATE_CRITICAL = 2
STATE_UNKNOWN = 3
STATE_DEPENDENT = 4
TIMEOUT_CONN = 2
def error(msg=None):
if msg is None:
msg = "Unknown error"
......@@ -168,6 +171,12 @@ def validate_hostname(hostname, cert):
return True
return False
def timeout_connection(signum, frame):
raise TimeoutConnectionError('Connection timeout')
class TimeoutConnectionError(Exception):
pass
class RequestDoT:
def __init__(self, qname, qtype=rtype, use_edns=edns, want_dnssec=dnssec):
......@@ -330,48 +339,59 @@ class ConnectionDoT(Connection):
forceIPv4=forceIPv4, forceIPv6=forceIPv6, dot=dot,
verbose=verbose, insecure=insecure, post=post, head=head)
self.check_ip_address(self.server)
self.hasher = hashlib.sha256()
addrinfo = socket.getaddrinfo(server, 853, self.family)
# May be loop over the results of getaddrinfo, to test all
# the IP addresses? See #13.
self.sock = socket.socket(addrinfo[0][0], socket.SOCK_STREAM)
self.addr = addrinfo[0][4]
if self.verbose:
print("Connecting to %s ..." % str(self.addr))
# With typical DoT servers, we *must* use TLS 1.2 (otherwise,
# do_handshake fails with "OpenSSL.SSL.SysCallError: (-1, 'Unexpected
# EOF')" Typical HTTP servers are more lax.
self.context = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD)
if self.insecure:
self.context.set_verify(OpenSSL.SSL.VERIFY_NONE, lambda *x: True)
else:
self.context.set_default_verify_paths()
self.context.set_verify_depth(4) # Seems ignored
self.context.set_verify(OpenSSL.SSL.VERIFY_PEER | OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT | \
OpenSSL.SSL.VERIFY_CLIENT_ONCE,
lambda conn, cert, errno, depth, preverify_ok: preverify_ok)
self.session = OpenSSL.SSL.Connection(self.context, self.sock)
self.session.set_tlsext_host_name(canonicalize(self.check).encode()) # Server Name Indication (SNI)
self.session.connect((self.addr))
# TODO We may here have exceptions such as OpenSSL.SSL.ZeroReturnError
self.session.do_handshake()
self.cert = self.session.get_peer_certificate()
# RFC 7858, section 4.2 and appendix A
self.publickey = self.cert.get_pubkey()
if verbose:
print("Certificate #%x for \"%s\", delivered by \"%s\"" % \
(self.cert.get_serial_number(),
self.cert.get_subject().commonName,
self.cert.get_issuer().commonName))
self.hasher.update(OpenSSL.crypto.dump_publickey(OpenSSL.crypto.FILETYPE_ASN1,
self.publickey))
self.digest = self.hasher.digest()
print("Public key is pin-sha256=\"%s\"" % \
base64.standard_b64encode(self.digest).decode())
if not insecure:
valid = validate_hostname(self.check, self.cert)
if not valid:
error("Certificate error: \"%s\" is not in the certificate" % (self.check))
addrinfo_list = socket.getaddrinfo(server, 853, self.family)
addrinfo_set = { (addrinfo[4], addrinfo[0]) for addrinfo in addrinfo_list }
signal.signal(signal.SIGALRM, timeout_connection)
connection_success = False
for addrinfo in addrinfo_set:
signal.alarm(TIMEOUT_CONN)
self.hasher = hashlib.sha256()
self.sock = socket.socket(addrinfo[1], socket.SOCK_STREAM)
self.addr = addrinfo[0]
if self.verbose:
print("Connecting to %s ..." % str(self.addr))
# With typical DoT servers, we *must* use TLS 1.2 (otherwise,
# do_handshake fails with "OpenSSL.SSL.SysCallError: (-1, 'Unexpected
# EOF')" Typical HTTP servers are more lax.
self.context = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD)
if self.insecure:
self.context.set_verify(OpenSSL.SSL.VERIFY_NONE, lambda *x: True)
else:
self.context.set_default_verify_paths()
self.context.set_verify_depth(4) # Seems ignored
self.context.set_verify(OpenSSL.SSL.VERIFY_PEER | OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT | \
OpenSSL.SSL.VERIFY_CLIENT_ONCE,
lambda conn, cert, errno, depth, preverify_ok: preverify_ok)
self.session = OpenSSL.SSL.Connection(self.context, self.sock)
self.session.set_tlsext_host_name(canonicalize(self.check).encode()) # Server Name Indication (SNI)
try:
self.session.connect((self.addr))
# TODO We may here have exceptions such as OpenSSL.SSL.ZeroReturnError
self.session.do_handshake()
except TimeoutConnectionError:
continue
self.cert = self.session.get_peer_certificate()
# RFC 7858, section 4.2 and appendix A
self.publickey = self.cert.get_pubkey()
if verbose:
print("Certificate #%x for \"%s\", delivered by \"%s\"" % \
(self.cert.get_serial_number(),
self.cert.get_subject().commonName,
self.cert.get_issuer().commonName))
self.hasher.update(OpenSSL.crypto.dump_publickey(OpenSSL.crypto.FILETYPE_ASN1,
self.publickey))
self.digest = self.hasher.digest()
print("Public key is pin-sha256=\"%s\"" % \
base64.standard_b64encode(self.digest).decode())
if not insecure:
valid = validate_hostname(self.check, self.cert)
if not valid:
error("Certificate error: \"%s\" is not in the certificate" % (self.check))
connection_success = True
break
if not connection_success:
error(f'Could not connect to "{server}"')
signal.alarm(0)
def end(self):
self.session.shutdown()
......@@ -584,6 +604,7 @@ else: # Monitoring plugin
if path.startswith("/"):
path = path[1:]
url += path
ok = True
start = time.time()
try:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment