tis-audit-cert-my icon

tis-audit-cert-my

Silent install package for tis-audit-cert-my

2-10

  • package: tis-audit-cert-my
  • version: 2-10
  • maintainer: sfonteneau
  • licence: ©
  • target_os: windows
  • architecture: all
  • signature_date:
  • size: 10.81 Ko

package           : tis-audit-cert-my
version           : 2-10
architecture      : all
section           : base
priority          : optional
name              : 
categories        : 
maintainer        : sfonteneau
description       : Audit cert in "My" Store
depends           : 
conflicts         : 
maturity          : PROD
locale            : 
target_os         : windows
min_wapt_version  : 
sources           : 
installed_size    : 
impacted_process  : 
description_fr    : 
description_pl    : 
description_de    : 
description_es    : 
description_pt    : 
description_it    : 
description_nl    : 
description_ru    : 
audit_schedule    : 1d
editor            : 
keywords          : 
licence           : ©
homepage          : 
package_uuid      : 0e5e9e29-08cc-403f-be3d-44095304ce4a
valid_from        : 
valid_until       : 
forced_install_on : 
changelog         : 
min_os_version    : 
max_os_version    : 
icon_sha256sum    : 7f00fc99ce8f9f34ed57f77501e4e635afaef7809c99755f6f108cdf53c5f955
signer            : Tranquil IT
signer_fingerprint: 8c5127a75392be9cc9afd0dbae1222a673072c308c14d88ab246e23832e8c6bb
signature_date    : 2025-10-04T16:01:29.000000
signed_attributes : package,version,architecture,section,priority,name,categories,maintainer,description,depends,conflicts,maturity,locale,target_os,min_wapt_version,sources,installed_size,impacted_process,description_fr,description_pl,description_de,description_es,description_pt,description_it,description_nl,description_ru,audit_schedule,editor,keywords,licence,homepage,package_uuid,valid_from,valid_until,forced_install_on,changelog,min_os_version,max_os_version,icon_sha256sum,signer,signer_fingerprint,signature_date,signed_attributes
signature         : hoP2iDfn9E7wAmziWuG9kCyCzIFQQ5D8doZM0vOXu5zvI/bPbndCgDwJgDDp6NeEyMjOM4zfliLK+23l2p/hSO7AJEGTud/Ea8WvSVSC3aKaj0JEW3KadCTolSR5A2Vm1iOYPtq2wWQIdtJLN+DFoaB/b6sE9UZoJ/AHe0BVzMK4M4JUpNEOHtB4EQGtMfX6IU7Rjsn9rxok1H4ndKRtpuZAxVw0CAdrPNgSz+Z7Ip0ugSXrFP4Au2ROgxlUpkButHmhICrFxjAXmG9nJoUt3APLwn/Kq/djTMzTPrLcQzVUkzUci0cJ34wSeto8Hacs3i42oTwQYTNc2s9BjozBaw==

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import argparse
from ctypes import (
    windll, wintypes, byref, POINTER, Structure, c_void_p,
    create_unicode_buffer, create_string_buffer, cast, wstring_at
)
from datetime import datetime, timedelta, timezone


EXPIRY_THRESHOLD_DAYS = 90


def install():
    pass


def audit():
    # False = LocalMachine\My
    certs = list_certs(current_user=False) or []


    dict_cert = {}
    min_not_after = None
    min_not_after_strftime = None
    for c in certs:
        not_after =  c["not_after"]
        if min_not_after == None :
            min_not_after = not_after
        else:
            if min_not_after > not_after:
                min_not_after = not_after


        dict_cert[c["thumbprint"]] = {
            "not_after": c["not_after"].strftime("%Y-%m-%d %H:%M:%S%z"),
            "subject": c["subject"],
            "issuer": c["issuer"],
            "has_private_key": c["has_private_key"],
            "key_provider_type": c.get("key_provider_type", ""),
            "key_provider_name": c.get("key_provider_name", ""),
            "keyspec": c.get("keyspec", "")
        }
    if min_not_after :
        min_not_after_strftime = min_not_after.strftime("%Y-%m-%d %H:%M:%S%z")
    audit_data =  {"dict_cert":dict_cert,"min_not_after" : min_not_after_strftime}

    WAPT.write_audit_data_if_changed(
        "audit-cert-my", "audit-cert-my", audit_data, keep_days=365, max_count=1
    )

    if not dict_cert :
        return "OK"

    now = datetime.now(timezone.utc)
    days_left = int((min_not_after - now).total_seconds() // 86400)


    if days_left < 0 :
        return "ERROR"

    if days_left < EXPIRY_THRESHOLD_DAYS :
        print('A certificate expires soon')
        return "WARNING"
    return "OK"





# ---------------- Constantes ----------------

CERT_NAME_SIMPLE_DISPLAY_TYPE = 4
CERT_NAME_ISSUER_FLAG = 0x1
CERT_SHA1_HASH_PROP_ID = 3
CERT_KEY_PROV_INFO_PROP_ID = 2

AT_KEYEXCHANGE = 1
AT_SIGNATURE = 2
NCRYPT_KEY_SPEC = 0xFFFFFFFF

CRYPT_ACQUIRE_SILENT_FLAG = 0x00000040

CERT_STORE_PROV_SYSTEM_W = 10
CERT_SYSTEM_STORE_CURRENT_USER = 0x00010000
CERT_SYSTEM_STORE_LOCAL_MACHINE = 0x00020000
# CERT_STORE_OPEN_EXISTING_FLAG = 0x00004000  # optionnel

# ---------------- Structures ----------------

class FILETIME(Structure):
    _fields_ = [
        ("dwLowDateTime", wintypes.DWORD),
        ("dwHighDateTime", wintypes.DWORD),
    ]

class CRYPT_INTEGER_BLOB(Structure):
    _fields_ = [
        ("cbData", wintypes.DWORD),
        ("pbData", c_void_p),
    ]

class CRYPT_ALGORITHM_IDENTIFIER(Structure):
    _fields_ = [
        ("pszObjId", c_void_p),
        ("Parameters", CRYPT_INTEGER_BLOB),
    ]

class CERT_NAME_BLOB(Structure):
    _fields_ = [
        ("cbData", wintypes.DWORD),
        ("pbData", c_void_p),
    ]

class CERT_PUBLIC_KEY_INFO(Structure):
    _fields_ = [
        ("Algorithm", CRYPT_ALGORITHM_IDENTIFIER),
        ("PublicKey", CRYPT_INTEGER_BLOB),
    ]

class CERT_INFO(Structure):
    _fields_ = [
        ("dwVersion", wintypes.DWORD),
        ("SerialNumber", CRYPT_INTEGER_BLOB),
        ("SignatureAlgorithm", CRYPT_ALGORITHM_IDENTIFIER),
        ("Issuer", CERT_NAME_BLOB),
        ("NotBefore", FILETIME),
        ("NotAfter", FILETIME),
        ("Subject", CERT_NAME_BLOB),
        ("SubjectPublicKeyInfo", CERT_PUBLIC_KEY_INFO),
        ("IssuerUniqueId", CRYPT_INTEGER_BLOB),
        ("SubjectUniqueId", CRYPT_INTEGER_BLOB),
        ("cExtension", wintypes.DWORD),
        ("rgExtension", c_void_p),
    ]

class CERT_CONTEXT(Structure):
    _fields_ = [
        ("dwCertEncodingType", wintypes.DWORD),
        ("pbCertEncoded", c_void_p),
        ("cbCertEncoded", wintypes.DWORD),
        ("pCertInfo", POINTER(CERT_INFO)),
        ("hCertStore", c_void_p),
    ]

# CERT_KEY_PROV_INFO pour lire la propriété 2
class CRYPT_KEY_PROV_INFO(Structure):
    _fields_ = [
        ("pwszContainerName", wintypes.LPWSTR),
        ("pwszProvName", wintypes.LPWSTR),
        ("dwProvType", wintypes.DWORD),
        ("dwFlags", wintypes.DWORD),
        ("cProvParam", wintypes.DWORD),
        ("rgProvParam", c_void_p),
        ("dwKeySpec", wintypes.DWORD),
    ]

PCCERT_CONTEXT = POINTER(CERT_CONTEXT)
HCERTSTORE = c_void_p

# ---------------- Liens API ----------------

crypt32 = windll.crypt32
advapi32 = windll.advapi32
try:
    ncrypt = windll.ncrypt  # Windows moderne (CNG)
except Exception:
    ncrypt = None

# Cert store
crypt32.CertCloseStore.argtypes = [HCERTSTORE, wintypes.DWORD]
crypt32.CertCloseStore.restype = wintypes.BOOL

crypt32.CertOpenSystemStoreW.argtypes = [c_void_p, wintypes.LPCWSTR]
crypt32.CertOpenSystemStoreW.restype = HCERTSTORE

crypt32.CertOpenStore.argtypes = [c_void_p, wintypes.DWORD, c_void_p, wintypes.DWORD, c_void_p]
crypt32.CertOpenStore.restype = HCERTSTORE

crypt32.CertEnumCertificatesInStore.argtypes = [HCERTSTORE, PCCERT_CONTEXT]
crypt32.CertEnumCertificatesInStore.restype = PCCERT_CONTEXT

crypt32.CertFreeCertificateContext.argtypes = [PCCERT_CONTEXT]
crypt32.CertFreeCertificateContext.restype = wintypes.BOOL

# Noms lisibles
crypt32.CertGetNameStringW.argtypes = [
    PCCERT_CONTEXT, wintypes.DWORD, wintypes.DWORD, c_void_p, wintypes.LPWSTR, wintypes.DWORD
]
crypt32.CertGetNameStringW.restype = wintypes.DWORD

# Propriétés de contexte
crypt32.CertGetCertificateContextProperty.argtypes = [PCCERT_CONTEXT, wintypes.DWORD, c_void_p, POINTER(wintypes.DWORD)]
crypt32.CertGetCertificateContextProperty.restype = wintypes.BOOL

# Acquisition de clé privée
crypt32.CryptAcquireCertificatePrivateKey.argtypes = [
    PCCERT_CONTEXT, wintypes.DWORD, c_void_p,
    POINTER(c_void_p), POINTER(wintypes.DWORD), POINTER(wintypes.BOOL)
]
crypt32.CryptAcquireCertificatePrivateKey.restype = wintypes.BOOL

# Release
advapi32.CryptReleaseContext.argtypes = [c_void_p, wintypes.DWORD]
advapi32.CryptReleaseContext.restype = wintypes.BOOL
if ncrypt:
    ncrypt.NCryptFreeObject.argtypes = [c_void_p]
    # ncrypt.NCryptFreeObject.restype = wintypes.ULONG  # inutile ici

# ---------------- Utilitaires ----------------

def filetime_to_datetime(ft: FILETIME) -> datetime:
    value = (ft.dwHighDateTime << 32) | ft.dwLowDateTime
    seconds, hundred_ns = divmod(value, 10_000_000)
    base = datetime(1601, 1, 1, tzinfo=timezone.utc)
    return base + timedelta(seconds=seconds, microseconds=hundred_ns // 10)

def _get_name_string(pctx: PCCERT_CONTEXT, flags: int = 0) -> str:
    size = crypt32.CertGetNameStringW(pctx, CERT_NAME_SIMPLE_DISPLAY_TYPE, flags, None, None, 0)
    if size <= 1:
        return "—"
    buf = create_unicode_buffer(size)
    got = crypt32.CertGetNameStringW(pctx, CERT_NAME_SIMPLE_DISPLAY_TYPE, flags, None, buf, size)
    return buf.value if got > 1 else "—"

def get_subject_display(pctx: PCCERT_CONTEXT) -> str:
    return _get_name_string(pctx, 0)

def get_issuer_display(pctx: PCCERT_CONTEXT) -> str:
    return _get_name_string(pctx, CERT_NAME_ISSUER_FLAG)

def get_sha1_thumbprint(pctx: PCCERT_CONTEXT) -> str:
    cb = wintypes.DWORD(0)
    ok = crypt32.CertGetCertificateContextProperty(pctx, CERT_SHA1_HASH_PROP_ID, None, byref(cb))
    if not ok or cb.value == 0:
        return "—"
    raw = create_string_buffer(cb.value)
    ok = crypt32.CertGetCertificateContextProperty(pctx, CERT_SHA1_HASH_PROP_ID, raw, byref(cb))
    if not ok:
        return "—"
    data = raw.raw[:cb.value]
    return "".join(f"{b:02X}" for b in data)

def get_key_prov_info(pctx: PCCERT_CONTEXT):
    """
    Lit CERT_KEY_PROV_INFO_PROP_ID (2) pour déterminer nom/type du provider.
    Retourne un dict ou None si absent.
    """
    cb = wintypes.DWORD(0)
    ok = crypt32.CertGetCertificateContextProperty(pctx, CERT_KEY_PROV_INFO_PROP_ID, None, byref(cb))
    if not ok or cb.value == 0:
        return None

    buf = create_string_buffer(cb.value)
    ok = crypt32.CertGetCertificateContextProperty(pctx, CERT_KEY_PROV_INFO_PROP_ID, buf, byref(cb))
    if not ok:
        return None

    info = cast(buf, POINTER(CRYPT_KEY_PROV_INFO)).contents
    prov_name = wstring_at(info.pwszProvName) if info.pwszProvName else ""
    container = wstring_at(info.pwszContainerName) if info.pwszContainerName else ""
    is_ksp = (info.dwProvType == 0) or (info.dwKeySpec == NCRYPT_KEY_SPEC)

    # Label lisible du keyspec
    if info.dwKeySpec == NCRYPT_KEY_SPEC:
        keyspec_label = "NCRYPT (CNG/KSP)"
    elif info.dwKeySpec == AT_KEYEXCHANGE:
        keyspec_label = "AT_KEYEXCHANGE (CSP)"
    elif info.dwKeySpec == AT_SIGNATURE:
        keyspec_label = "AT_SIGNATURE (CSP)"
    else:
        keyspec_label = f"{info.dwKeySpec}"

    return {
        "provider_name": prov_name or container or "",
        "provider_type": "KSP" if is_ksp else "CSP",
        "prov_type_code": int(info.dwProvType),
        "keyspec": int(info.dwKeySpec),
        "keyspec_label": keyspec_label,
    }

def has_private_key_and_kind(pctx: PCCERT_CONTEXT):
    """
    Tente d'acquérir la clé privée pour confirmer sa présence et distinguer KSP/CSP.
    """
    hkey = c_void_p()
    keyspec = wintypes.DWORD(0)
    must_free = wintypes.BOOL(False)

    ok = crypt32.CryptAcquireCertificatePrivateKey(
        pctx, CRYPT_ACQUIRE_SILENT_FLAG, None,
        byref(hkey), byref(keyspec), byref(must_free)
    )
    if not ok:
        return False, None

    try:
        if keyspec.value == NCRYPT_KEY_SPEC:
            kind = {"provider_type": "KSP", "keyspec": keyspec.value, "keyspec_label": "NCRYPT (CNG/KSP)"}
        elif keyspec.value == AT_KEYEXCHANGE:
            kind = {"provider_type": "CSP", "keyspec": keyspec.value, "keyspec_label": "AT_KEYEXCHANGE (CSP)"}
        elif keyspec.value == AT_SIGNATURE:
            kind = {"provider_type": "CSP", "keyspec": keyspec.value, "keyspec_label": "AT_SIGNATURE (CSP)"}
        else:
            kind = {"provider_type": "Unknown", "keyspec": keyspec.value, "keyspec_label": str(keyspec.value)}
        return True, kind
    finally:
        if must_free and hkey:
            try:
                if keyspec.value == NCRYPT_KEY_SPEC and ncrypt:
                    ncrypt.NCryptFreeObject(hkey)
                else:
                    advapi32.CryptReleaseContext(hkey, 0)
            except Exception:
                pass

def open_store_my(current_user: bool = True) -> HCERTSTORE:
    if current_user:
        h = crypt32.CertOpenSystemStoreW(None, "MY")
    else:
        h = crypt32.CertOpenStore(
            c_void_p(CERT_STORE_PROV_SYSTEM_W),
            0,
            None,
            CERT_SYSTEM_STORE_LOCAL_MACHINE,
            cast(create_unicode_buffer("MY"), c_void_p),
        )
    if not h:
        raise OSError("Impossible d’ouvrir le magasin de certificats 'My'.")
    return h

def list_certs(current_user: bool = True):
    hstore = open_store_my(current_user=current_user)
    results = []
    try:
        pctx = crypt32.CertEnumCertificatesInStore(hstore, None)
        while pctx:
            ctx = pctx.contents
            info = ctx.pCertInfo.contents

            not_after = filetime_to_datetime(info.NotAfter)
            subject = get_subject_display(pctx)
            issuer = get_issuer_display(pctx)
            thumb = get_sha1_thumbprint(pctx)

            prov = get_key_prov_info(pctx) or {}
            has_key, kind = has_private_key_and_kind(pctx)
            key_provider_type = prov.get("provider_type") or (kind and kind.get("provider_type")) or ""
            key_provider_name = prov.get("provider_name", "")

            keyspec_label = prov.get("keyspec_label")
            if not keyspec_label and kind:
                keyspec_label = kind.get("keyspec_label", "")

            results.append({
                "subject": subject,
                "issuer": issuer,
                "thumbprint": thumb,
                "not_after": not_after,
                "has_private_key": has_key,
                "key_provider_type": key_provider_type,  # "KSP" ou "CSP"
                "key_provider_name": key_provider_name,  # ex: "Microsoft Software Key Storage Provider"
                "keyspec": keyspec_label,                # ex: "NCRYPT (CNG/KSP)"
            })

            pctx = crypt32.CertEnumCertificatesInStore(hstore, pctx)
    finally:
        crypt32.CertCloseStore(hstore, 0)
    return results


38d056ab130f7bf7c481c12636a4e9959de36561d3dfcbe54c6e3571bc0c1dc3 : WAPT/certificate.crt
fa333c538e000829132caa472c8fed81fbe392404b2208e7d80f3a7003fb3680 : WAPT/control
7f00fc99ce8f9f34ed57f77501e4e635afaef7809c99755f6f108cdf53c5f955 : WAPT/icon.png
cbfb9ac0ba549560453a388e58858d13cb23baeefd782c19ad9ef1a81764d1b9 : luti.json
1d4dbf2cfb20ff9d96e7f286ccc9eb554ca3e5da66e8b7ac2c4c52f4ea2569c2 : setup.py