"""
05_3_1_certificate_reader.py

A diagnostic script: connect to a host, retrieve its TLS certificate
WITHOUT verifying it, and decode the issuer and expiry date into
plain, readable text.

Run this instead of guessing whenever a certificate error like
"certificate has expired" or "certificate verify failed" shows up.
The decoded issuer name tells you which side actually has the problem:

  - A normal public certificate authority (Let's Encrypt, DigiCert,
    Google Trust Services, Sectigo, and similar) means the certificate
    itself is fine, and the error is a local trust-store problem --
    see Section 5.3's certifi-based fix.
  - An unfamiliar issuer name -- often an antivirus product or a
    corporate network security tool -- means something on the network
    path between you and the site is substituting its own certificate.
    That's a different problem with a different fix: check that
    software's SSL-inspection / HTTPS-scanning settings, not your
    Python trust store.

This script deliberately disables certificate verification so it can
inspect certificates that are currently FAILING verification. That is
the only legitimate reason ssl.CERT_NONE appears anywhere in this
book. Never use an unverified context to actually fetch data -- only
to diagnose why verification is failing. Once you know the fix, go
back to a verified connection (05_03_certificate.py) to do real work.

Requires the third-party "cryptography" package:
    pip install cryptography

Usage:
    python 05_3_1_certificate_reader.py [hostname]

    If no hostname is given, it checks en.wikipedia.org.
"""

import socket
import ssl
import sys
from datetime import datetime, timezone

from cryptography import x509
from cryptography.hazmat.backends import default_backend


def fetch_certificate(host, port=443, timeout=10):
    """Connect to host:port and return the peer's certificate as raw
    DER bytes, without verifying it. Diagnostic use only -- never use
    this connection to actually retrieve data."""
    ctx = ssl._create_unverified_context()  # diagnostic only -- never use this to fetch real data

    with socket.create_connection((host, port), timeout=timeout) as sock:
        with ctx.wrap_socket(sock, server_hostname=host) as ssock:
            return ssock.getpeercert(binary_form=True)


def describe_certificate(der_cert):
    """Decode raw DER certificate bytes into a plain-text summary:
    who it was issued to, who issued it, and when it expires."""
    cert = x509.load_der_x509_certificate(der_cert, default_backend())

    subject = cert.subject.rfc4514_string()
    issuer = cert.issuer.rfc4514_string()

    # cryptography 42+ exposes a UTC-aware property under this name;
    # older versions only have the naive not_valid_after. Try the
    # modern name first and fall back, so this keeps working either way.
    try:
        expires = cert.not_valid_after_utc
    except AttributeError:
        expires = cert.not_valid_after.replace(tzinfo=timezone.utc)

    days_left = (expires - datetime.now(timezone.utc)).days

    return {
        "subject": subject,
        "issuer": issuer,
        "expires": expires,
        "days_until_expiry": days_left,
        "is_expired": days_left < 0,
    }


def main(host):
    print(f"Connecting to {host} (verification disabled -- diagnostic only)...")
    der_cert = fetch_certificate(host)
    info = describe_certificate(der_cert)

    print()
    print(f"Subject : {info['subject']}")
    print(f"Issuer  : {info['issuer']}")
    print(f"Expires : {info['expires'].strftime('%Y-%m-%d %H:%M:%S UTC')}")

    if info["is_expired"]:
        print(f"Status  : EXPIRED {abs(info['days_until_expiry'])} days ago")
    else:
        print(f"Status  : valid, expires in {info['days_until_expiry']} days")

    print()
    print("Read the issuer line above. A recognizable public certificate")
    print("authority means the certificate is fine and the problem is on")
    print("this machine (see Section 5.3's certifi fix). An unfamiliar")
    print("issuer means something on the network is substituting its own")
    print("certificate -- check that software's settings instead.")


if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else "en.wikipedia.org"
    main(target)
