~martijnbraam/certtool

fee82a3cc26dc9c50d89b3e7cc80ada8afb42ebc — Martijn Braam 1 year, 10 months ago f09e800 master
Implement verbose certificate output
1 files changed, 51 insertions(+), 10 deletions(-)

M certtool/__main__.py
M certtool/__main__.py => certtool/__main__.py +51 -10
@@ 86,7 86,7 @@ def check_generic(domain, port, timeout=5, starttls=False):
    try:
        chain = get_tls_certificate(domain, port, timeout=timeout, starttls=starttls)
    except TimeoutError:
        return False, "Timeout"
        return False, "Timeout", None
    except OpenSSL.SSL.Error as e:
        return False, str(e)
    x509 = chain[0]


@@ 96,7 96,7 @@ def check_generic(domain, port, timeout=5, starttls=False):
            cn = component[1].decode()
            break
    else:
        return False, "Certificate had no common name"
        return False, "Certificate had no common name", chain

    names = [cn]



@@ 122,7 122,7 @@ def check_generic(domain, port, timeout=5, starttls=False):
        if re.match(name_regex, domain):
            break
    else:
        return False, f"Wrong CN, certificate is for {cn}"
        return False, f"Wrong CN, certificate is for {cn}", chain

    # TODO: If ocsp is True, verify ocsp



@@ 132,13 132,13 @@ def check_generic(domain, port, timeout=5, starttls=False):
    time_left = not_after - now

    if now < not_before:
        return False, f"Certificate is not yet valid (valid after {not_before})"
        return False, f"Certificate is not yet valid (valid after {not_before})", chain

    if now > not_after:
        return False, f"Certificate is expired (valid until {not_after})"
        return False, f"Certificate is expired (valid until {not_after})", chain

    if (not_after - now).days < 20:
        return None, f"Certificate almost expired ({time_left})"
        return None, f"Certificate almost expired ({time_left})", chain

    # Check the chain
    store = get_store()


@@ 148,12 148,43 @@ def check_generic(domain, port, timeout=5, starttls=False):
    except OpenSSL.crypto.X509StoreContextError as e:
        errnum = e.args[0][0]
        if errnum == 18:
            return False, "Certificate is self-signed"
            return False, "Certificate is self-signed", chain
        elif errnum == 19:
            return False, "Self-signed certificate in the certificate chain"
            return False, "Self-signed certificate in the certificate chain", chain
        return False, str(e)

    return True, f"Ok, {time_left.days} days remaining"
    return True, f"Ok, {time_left.days} days remaining", chain


def print_detail(chain):
    result = []
    x509 = chain[0]
    subject = []
    components = x509.get_subject().get_components()
    for c in components:
        subject.append(f'{c[0].decode()}={c[1].decode()}')
    result.append(colorama.Fore.WHITE + 'subject:   ' + colorama.Style.RESET_ALL + ' '.join(subject))

    names = []
    for i in range(0, x509.get_extension_count()):
        ext = x509.get_extension(i)
        if ext.get_short_name() == b'subjectAltName':
            san = str(ext)
            sans = san.split(', ')
            for s in sans:
                s_type, s_name = s.split(":", maxsplit=1)
                if s_type == 'DNS':
                    names.append(s_name)
    result.append(colorama.Fore.WHITE + 'alt names: ' + colorama.Style.RESET_ALL + ', '.join(names))
    result.append(colorama.Fore.WHITE + f'serial:  {colorama.Style.RESET_ALL}  {x509.get_serial_number():x}')

    issuer_name = x509.get_issuer()
    issuer = []
    for c in issuer_name.get_components():
        issuer.append(f'{c[0].decode()}={c[1].decode()}')
    result.append(colorama.Fore.WHITE + 'issuer:    ' + colorama.Style.RESET_ALL + ' '.join(issuer))

    return result


def main():


@@ 163,10 194,12 @@ def main():
    parser.add_argument('--imaps', action=argparse.BooleanOptionalAction, default=False, help='Check imap (993)')
    parser.add_argument('--pop3s', action=argparse.BooleanOptionalAction, default=False, help='Check pop3s (995)')
    parser.add_argument('--smtps', action=argparse.BooleanOptionalAction, default=False, help='Check smtp (465)')
    parser.add_argument('--submission', action=argparse.BooleanOptionalAction, default=False, help='Check smtp with starttls (587)')
    parser.add_argument('--submission', action=argparse.BooleanOptionalAction, default=False,
                        help='Check smtp with starttls (587)')
    parser.add_argument('--port', action="append", help="Check a specific port", default=[], type=int)
    parser.add_argument('--timeout', '-t', default=5, help='Set the timeout in seconds for the TCP connection',
                        type=int)
    parser.add_argument('--verbose', '-v', action='store_true', help='Output detailed cert info')
    args = parser.parse_args()

    domain_len = len(max(args.domain, key=len))


@@ 220,6 253,14 @@ def main():
            else:
                print(colorama.Fore.YELLOW, result[1], colorama.Style.RESET_ALL)

            if args.verbose:
                lines = print_detail(result[2])
                offset = domain_len + 3
                if len(protolist) > 1:
                    offset += proto_len + 1
                for line in lines:
                    print(' ' * offset + line)

    exit(returncode)