Commit 22dea724 authored by Stephane Bortzmeyer's avatar Stephane Bortzmeyer
Browse files

* [DoT] Authenticates with SPKI. Closes #15

* Option to disable SNI. Addresses #18.
parent 6f6987bf
......@@ -53,7 +53,10 @@ Possible options, besides `--dot`:
* -6: Uses only IPv6
* --dnssec: requests DNSSEC data (signatures)
* --noedns: no EDNS (default is to indicate EDNS support)
* --ecs: send ECS, my subnet to auth. servers (default is to refuse it)
* --ecs: send ECS, my subnet to auth. servers (default is to refuse
it)
* --key KEYINBASE64: authentifies a DoT resolver with its public
key. Example: `homer.py --key "62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4=" --dot 145.100.185.15 IN NS`
* --check: Run a set of tests (see below)
### Check
......@@ -136,6 +139,7 @@ monitoring plugins conventions:
* -P: uses the HTTP method POST
* -h: uses the HTTP method HEAD
* -i: insecure (do not check the certificate)
* -k: authenticated the DoT server with this public key
For Icinga, the following definition enables the plugin:
......@@ -168,7 +172,8 @@ object CheckCommand "dot_monitor" {
"-t" = "$dot_type$",
"-P" = "$dot_post$",
"-i" = "$dot_insecure$",
"-h" = "$dot_head$"
"-h" = "$dot_head$",
"-k" = "$dot_key$"
}
}
......
......@@ -42,9 +42,11 @@ head = False
dnssec = False
edns = True
no_ecs = True
sni = True
rtype = 'AAAA'
vhostname = None
tests = 1 # Number of repeated tests
key = None # SPKI
ifile = None # Input file
delay = None
forceIPv4 = False
......@@ -351,7 +353,8 @@ class ConnectionDoT(Connection):
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)
if sni:
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
......@@ -360,23 +363,29 @@ class ConnectionDoT(Connection):
if self.verbose:
print("Timeout")
return False
self.cert = self.session.get_peer_certificate()
# RFC 7858, section 4.2 and appendix A
self.cert = self.session.get_peer_certificate()
self.publickey = self.cert.get_pubkey()
if verbose or key is not None:
self.hasher.update(OpenSSL.crypto.dump_publickey(OpenSSL.crypto.FILETYPE_ASN1,
self.publickey))
self.digest = self.hasher.digest()
key_string = base64.standard_b64encode(self.digest).decode()
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())
key_string)
if not insecure:
valid = validate_hostname(self.check, self.cert)
if not valid:
error("Certificate error: \"%s\" is not in the certificate" % (self.check))
if key is None:
valid = validate_hostname(self.check, self.cert)
if not valid:
error("Certificate error: \"%s\" is not in the certificate" % (self.check))
else:
if key_string != key:
error("Key error: expected \"%s\", got \"%s\"" % (key, key_string))
signal.alarm(0)
return True
......@@ -705,6 +714,7 @@ if not monitoring:
["help", "verbose", "dot", "head",
"insecure", "POST", "vhost=",
"dnssec", "noedns", "ecs", "repeat=", "file=", "delay=",
"key=", "nosni",
"v4only", "v6only",
"check", "mandatory-level="])
for option, value in optlist:
......@@ -725,6 +735,8 @@ if not monitoring:
insecure = True
elif option == "--dnssec":
dnssec = True
elif option == "--nosni":
sni = False
elif option == "--noedns": # Warning: it will mean the
# resolver may send ECS
# information to the
......@@ -742,6 +754,8 @@ if not monitoring:
error("--delay needs a value > 0")
elif option == "--file" or option == "-f":
ifile = value
elif option == "--key":
key = value
elif option == "-4" or option == "v4only":
forceIPv4 = True
elif option == "-6" or option == "v6only":
......@@ -791,7 +805,7 @@ else: # Monitoring plugin
dot = (me == "check_dot")
name = None
try:
optlist, args = getopt.getopt (sys.argv[1:], "H:n:p:V:t:e:Pih46")
optlist, args = getopt.getopt (sys.argv[1:], "H:n:p:V:t:e:Pih46k:x")
for option, value in optlist:
if option == "-H":
host = value
......@@ -811,10 +825,14 @@ else: # Monitoring plugin
head = True
elif option == "-i":
insecure = True
elif option == "-x":
sni = False
elif option == "-4":
forceIPv4 = True
elif option == "-6":
forceIPv6 = True
elif option == "-k":
key = value
else:
# Should never occur, it is trapped by getopt
print("Unknown option %s" % option)
......
......@@ -264,6 +264,34 @@ tests:
partstderr: 'not connect to'
stdout: ''
- exe: './homer.py'
name: '[dot] Authenticates with the key (SPKI)'
markers:
- 'dot'
timeout: 5
args:
- '--dot'
- '--key'
- '62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4='
- '145.100.185.15'
- 'sinodun.com'
retcode: 0
stderr: ''
- exe: './homer.py'
name: '[dot] Authenticates with the WRONG key (SPKI)'
markers:
- 'dot'
timeout: 5
args:
- '--dot'
- '--key'
- '62pKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL5='
- '145.100.185.15'
- 'sinodun.com'
retcode: 1
partstderr: 'Key error'
################################################################################
# check_dot
......
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