tis-audit-cert-my icon

tis-audit-cert-my

Silent install package for tis-audit-cert-my

0-10

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

package           : tis-audit-cert-my
version           : 0-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    : 
editor            : 
keywords          : 
licence           : ©
homepage          : 
package_uuid      : 2657ba65-9c85-435c-83c8-3d1084721506
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:06:51.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         : lrgUqGaAGS8h3NyciHbcGnaN3n9l3einfN2R/zgRfTztV/NkVz4ALlr/t7bELoYVKRe+ln9C8cJCljMoEg+9hGQ4x11ExjqUx346gw1AGsODpGFKjkVllGe9efI0bzQlFpXphF/NzlVzBOikyo0W4X4Tc4fBUs0j8aZVo+GUbw1ZpVUPFUx3mo4NqCdX/gQuElPp5DsWK+u3F2FqYIHJcbfj5jjE41aL/kzFCWBUOl4gW0j0+Dh7PP/fGwWlJaro/+lTac0G/gObY2QM7XdR5oLReMzQoNvGQx9zzYgzCSeMLncxnUFDsQtdIzSUFnt6nUACuueiO3zOSIrTE8Audg==

#!/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


def install():
    pass


def audit():

    certs = list_certs(current_user=False) or []

    dict_cert = {}
    for c in certs:
        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", ""),
        }


    WAPT.write_audit_data_if_changed(
        "audit-cert-my", "audit-cert-my", dict_cert, keep_days=365, max_count=1
    )
    
    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

# ---------------- Point d’entrée audit ----------------


38d056ab130f7bf7c481c12636a4e9959de36561d3dfcbe54c6e3571bc0c1dc3 : WAPT/certificate.crt
ac31d606a4193723326e5f93d2be1b4c94ae5d934fdd7d7a2d0cffa419558988 : WAPT/control
7f00fc99ce8f9f34ed57f77501e4e635afaef7809c99755f6f108cdf53c5f955 : WAPT/icon.png
dbacc82b1aa9a945d24415ec45aa34be516bfb91818be116e069b342b292cc46 : luti.json
4e736994967f1e8c143c1844e96cdfebf2082fef8165f062b532d0a989c05f85 : setup.py