# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""SSL Callbacks

These callback routines are used in conjunction with the ssl module
to customize the behavior of the SSL connection.  Since the default
x509 verification handler provides only one error message for all
certificate validation errors, we have a custom handler that logs the
detailed error to the Sigma agent log.

"""
import ssl
import logging

from sigma.util import avalog

logger = logging.getLogger("protocol.ssl_callbacks")

# Map ssl ALERT_DESCRIPTION codes to human-readable messages
_error_messages = {
    "CERTIFICATE_VERIFY_FAILED": "Certificate verification failed",
    "TLSV1_ALERT_UNKNOWN_CA": "Unknown certificate authority",
    "CERTIFICATE_EXPIRED": "Certificate expired",
    "CERTIFICATE_REVOKED": "Certificate revoked",
    "CERTIFICATE_UNKNOWN": "Certificate unknown",
}

def verify_callback(ssl_context, cert_pem, errnum, errdepth, ok):
    """Compatibility shim for code that references verify_callback.

    In the stdlib ssl module, verification is configured on the
    SSLContext rather than via a callback. This function is retained
    for interface compatibility but is not directly used by the
    ssl module.
    """
    if not ok:
        logger.error("X509 Certificate verification failed at depth %d", errdepth)
        logger.error("Error number: %d", errnum)
    return ok

def create_ssl_context(certfile=None, keyfile=None, cafile=None,
                       verify=True):
    """Create an ssl.SSLContext with appropriate verification settings.

    Arguments:
        certfile -- path to the client certificate file (PEM)
        keyfile  -- path to the client private key file (PEM)
        cafile   -- path to the CA certificate file (PEM) for verification
        verify   -- whether to verify the server certificate (default True)

    Returns an ssl.SSLContext configured for TLS client connections.
    """
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

    # The original M2Crypto implementation set postConnectionCheck = None,
    # which disabled hostname verification while still validating the
    # certificate chain. We must preserve this behavior for wire compatibility
    # with existing ESP server deployments whose certs may not have matching
    # CN/SAN entries.
    context.check_hostname = False

    if not verify:
        context.verify_mode = ssl.CERT_NONE
    else:
        context.verify_mode = ssl.CERT_REQUIRED
        if cafile:
            context.load_verify_locations(cafile)

    if certfile:
        context.load_cert_chain(certfile, keyfile)

    return context

def err_to_string(errnum):
    """Converts an ssl error number into a string error message."""
    return "SSL error %d" % errnum
