From 51ee958a71b32d2dd88041612db5faee43cb69f3 Mon Sep 17 00:00:00 2001 From: Christoph Egger Date: Tue, 30 Aug 2016 18:54:43 +0200 Subject: [PATCH] Add simple https check --- check_dane_https | 115 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100755 check_dane_https diff --git a/check_dane_https b/check_dane_https new file mode 100755 index 0000000..3d758c2 --- /dev/null +++ b/check_dane_https @@ -0,0 +1,115 @@ +#!/usr/bin/python3 + +from __future__ import print_function + +import sys +import argparse +import logging + +from socket import socket, AF_INET6, AF_INET +from ssl import SSLContext, PROTOCOL_TLSv1_2, CERT_REQUIRED +from unbound import ub_ctx + +from check_dane.tlsa import verify_tlsa_record +from check_dane.cert import verify_certificate, add_certificate_options + +def init_connection(sslcontext, family, host, port): + connection = sslcontext.wrap_socket(socket(family), + server_hostname=host) + connection.connect((host, port)) + connection.send(b"HEAD / HTTP/1.1\r\nHost: %s\r\n\r\n" % host.encode()) + answer = connection.recv(512) + logging.debug(answer) + + return connection + + +def close_connection(connection): + connection.close() + + +def init(args): + sslcontext = SSLContext(PROTOCOL_TLSv1_2) + sslcontext.verify_mode = CERT_REQUIRED + sslcontext.load_verify_locations(args.castore) + + resolver = ub_ctx() + resolver.add_ta_file(args.ancor) + + return sslcontext, resolver + + +def main(): + logging.basicConfig(format='%(levelname)5s %(message)s') + parser = argparse.ArgumentParser() + parser.add_argument("Host") + + parser.add_argument("--verbose", action="store_true") + parser.add_argument("--quiet", action="store_true") + parser.add_argument("-p", "--port", + action="store", type=int, default=443, + help="HTTPS port") + parser.add_argument("--check-dane", + action="store_false", + help="Verify presented certificate via DANE (default: enabled)") + parser.add_argument("--check-ca", + action="store_false", + help="Verify presented certificate via the CA system (default: enabled)") + parser.add_argument("--check-expire", + action="store_false", + help="Verify presented certificate for expiration (default: enabled)") + + parser.add_argument("-a", "--ancor", + action="store", type=str, default="/etc/unbound/root.key", + help="DNSSEC root ancor") + parser.add_argument("--castore", action="store", type=str, + default="/etc/ssl/certs/ca-certificates.crt", + help="ca certificate bundle") + + group = parser.add_mutually_exclusive_group() + group.add_argument("-6", "--6", action="store_true", dest="use6", help="check via IPv6 only") + group.add_argument("-4", "--4", action="store_true", dest="use4", help="check via IPv4 only") + group.add_argument("--64", action="store_false", dest="use64", help="check via IPv4 and IPv6 (default)") + + add_certificate_options(parser) + + args = parser.parse_args() + + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + elif args.quiet: + logging.getLogger().setLevel(logging.WARNING) + else: + logging.getLogger().setLevel(logging.INFO) + + host = args.Host.encode('idna').decode() + sslcontext, resolver = init(args) + + if args.use6: + afamilies = [AF_INET6] + elif args.use4: + afamilies = [AF_INET6] + else: + afamilies = [AF_INET, AF_INET6] + + retval = 0 + for afamily in afamilies: + try: + connection = init_connection(sslcontext, afamily, host, args.port) + except ConnectionRefusedError: + logging.error("Connection refused") + return 2 + + nretval = verify_certificate(connection.getpeercert(), args) + retval = max(retval, nretval) + nretval = verify_tlsa_record(resolver, "_%d._tcp.%s" % (args.port, host), + connection.getpeercert(binary_form=True)) + retval = max(retval, nretval) + + close_connection(connection) + + return retval + + +if __name__ == '__main__': + sys.exit(main()) -- 2.39.5