tis-audit-cert-my
Silent install package for tis-audit-cert-my
1-10
- package: tis-audit-cert-my
- version: 1-10
- maintainer: sfonteneau
- licence: ©
- target_os: windows
- architecture: all
- signature_date:
- size: 10.81 Ko
package : tis-audit-cert-my
version : 1-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 : ece79385-8a53-4cd8-a9f7-6b0c6e6ad5fa
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:04:02.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 : kZoSEZBeXRU3jjdMC2F/Rpq4nmvXg55f9u4K6bFvz1V9GmryjG6x51AO1nXWGOzq8/osjMBaqCAUBVp2JxrkYhJheW2AKbtHuEXfF93IdjOg5ig8lwv5qjfHxUjdSqy8UwFNJTXMGDClYbZepBO7aXgbeS07mozXxN4dsyuWpnliTMtHxSIe63wSCQmDTOJO0Gnev1qPIJoddEH284/SLbbVqlwu136cTTLpYDXZdx6+fwvj37FShst8AmRDA5hogryQd817+MU/Vo2EE0fYsllLb5leJlGi/XP+IzzzUk7vz5CEeEg2nLxc6a/sKBVkK9FgbaURi9h7STWdjQsniQ==
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
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 []
now = datetime.now(timezone.utc)
dict_cert = {}
min_days_left = None
for c in certs:
days_left = int((c["not_after"] - now).total_seconds() // 86400)
if min_days_left == None :
min_days_left = days_left
else:
if min_days_left > days_left:
min_days_left = days_left
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", ""),
"days_left": days_left, # utile pour le tri/alerting
}
audit_data = {"dict_cert":dict_cert,"days_left" : min_days_left}
WAPT.write_audit_data_if_changed(
"audit-cert-my", "audit-cert-my", audit_data, keep_days=365, max_count=1
)
if min_days_left == None :
return "OK"
if min_days_left < 0 :
return "ERROR"
if min_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
# ---------------- Point d’entrée audit ----------------
38d056ab130f7bf7c481c12636a4e9959de36561d3dfcbe54c6e3571bc0c1dc3 : WAPT/certificate.crt
c33c63cbf055ba322e09e79dd6d38df495a7841c40d8fd1322e195f4fe783376 : WAPT/control
7f00fc99ce8f9f34ed57f77501e4e635afaef7809c99755f6f108cdf53c5f955 : WAPT/icon.png
696c3a3cafdfe2a089f11e561513d1a41cf13043bf8cb41739baea294fe7b74b : luti.json
622bce74be5c915019b813d78e1d72161760c3ccc1aa0cae14f3e8d74dc511b7 : setup.py