Audit BitLocker
Paquet d'installation silencieuse pour Audit BitLocker
Fonctionnement de tis-audit-bitlocker
-
Le paquet permet de remonter la clé de chiffrement bitlocker en audit sur le serveur Wapt.
-
C'est un complément ou un remplacement à la fonctionnalité Microsoft de remontée de clé dans l'Active Directory. L'avantage de ce paquet Wapt est qu'il fonctionne aussi sur les postes hors domaine.
-
La clé est chiffrée avec chaque certificat qui est déployé sur la machine. C'est à dire que chaque administrateur ayant la possibilité de faire l'administration de la machine pourra déchiffrer la clé.
-
Les clés étant en audit sur les machines, si la machine est supprimée, les audits sont supprimés en même temps et il n'y a pas de "corbeille" pour restaurer la machine. Il faut donc être bien sûr que l'on n'a plus besoin des clés de chiffrements (ou tout autres données d'audit) avant de supprimer la machine. Sinon il faudra récupérer la clé depuis une sauvegarde.
-
Les clés sont chiffrées et signées avec les certificats déployés sur la machine. Si l'administrateur perd sa clé ou bien si il part et que la clé est détruite, la clé bitlocker qui a été signée par cette clé ne pourra pas être récupérée. Il est donc important d'avoir plusieurs clés de signature sur le poste pour garantir la continuité.
-
Lorsqu'un nouvel adminsys aura sa clé déployée sur la machine, l'audit fera à nouveau le chiffrement de la clé bitlocker avec ce nouveau certificat.
-
Pour le recouvrement, il peut être pertinent de déployer une clé "de service" destinée à cet effet sur les postes, et qui n'est utilisé que dans ces cas de figure.
-
Les clés bitlocker sont stockées sous forme chiffrée par les certificats administrateurs dans la base de données SQL. Seule une personne ayant la clée privée correspondant au certificat personnel ayant été utilisé pour le chiffrement pourra déchiffrer la clé.
-
Les clés privées des administrateurs ne sont pas stockées sur le serveur Wapt.
-
Pour la séquestre, il peut être pertinent de faire une extraction de la base de données SQL des clés sur un support externe au serveur Wapt dédié à la séquestre.
- package: tis-audit-bitlocker
- name: Audit BitLocker
- version: 10.6.0-45
- categories: Security
- maintainer: WAPT Team,Tranquil IT,Joffrey Le Piquet
- licence: wapt_public
- target_os: windows
- architecture: all
- signature_date:
- size: 10.77 Ko
package : tis-audit-bitlocker
version : 10.6.0-45
architecture : all
section : base
priority : optional
name : Audit BitLocker
categories : Security
maintainer : WAPT Team,Tranquil IT,Joffrey Le Piquet
description : Audit BitLocker hard disk encryption
depends :
conflicts :
maturity : PROD
locale :
target_os : windows
min_wapt_version : 2.3
sources :
installed_size :
impacted_process :
description_fr : Audit du chiffrement BitLocker des disques durs
description_pl : Audyt szyfrowania dysków twardych BitLocker
description_de : Audit der BitLocker-Verschlüsselung von Festplatten
description_es : Auditoría del cifrado de discos duros BitLocker
description_pt : Auditoria da encriptação do disco rígido BitLocker
description_it : Controllo della crittografia del disco rigido BitLocker
description_nl : Controle van BitLocker-encryptie van harde schijven
description_ru : Аудит шифрования жестких дисков BitLocker
audit_schedule : 1d
editor :
keywords :
licence : wapt_public
homepage :
package_uuid : 78f695cf-3976-4767-a130-66dbdb0ab7b9
valid_from :
valid_until :
forced_install_on :
changelog :
min_os_version : 10.0
max_os_version :
icon_sha256sum : b895508a66d6cea028c2a7781604a358298ba9c8082602ab633e8bff7f2b844c
signer : Tranquil IT
signer_fingerprint: 8c5127a75392be9cc9afd0dbae1222a673072c308c14d88ab246e23832e8c6bb
signature_date : 2026-05-07T15:00:43.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 : xRmu3pwKb60gIeCdsgdtL48nvKK6nmGiC5vdHPzYQiWPmbq0MtUGIRRzZfZRPN5Dgb6zOOgDqsIDbdQU71ALjEqa2/Y7mb3eRV0zz3t0l8xZ6mKpS/BUnmvgw8W8AabFtzaycCd/NWk9HCHQLuVehPsIy5/eK+WL8gaEHfJkaUFb+KA7HZ7/+VJaL84G3mAsc+N4OeNBR+hG1hSpLRbUfhO/Qmw+x82D3Gwmd9u0IZ4NyWwZ/SxYNAIzu395R+/IpladKkTLD3VH+sIEQsL/8hPUOXZtbkxahxPymVI2MNYqFnJjiT38zBa/d3iuyqw9AJg3byewn8HGrtzav8i6rA==
# -*- coding: utf-8 -*-
from setuphelpers import *
from waptutils import sha256_for_data
import json
try:
import waptcrypto
from waptcrypto import SSLCertificate
if "encrypted_data_str" in dir(waptcrypto):
from waptcrypto import encrypted_data_str as rsa_encrypted_data_str
except:
pass
r"""
Sources:
https://learn.microsoft.com/windows/security/operating-system-security/data-protection/bitlocker/faq
https://learn.microsoft.com/windows/security/operating-system-security/data-protection/bitlocker/bitlocker-basic-deployment
https://learn.microsoft.com/powershell/module/bitlocker/get-bitlockervolume
https://learn.microsoft.com/en-us/windows/win32/secprov/getconversionstatus-win32-encryptablevolume#parameters
BitLocker Powershell informations:
PS C:\waptdev\enable-bitlocker\windows> Get-BitLockerVolume | Get-Member -Name *
TypeName: Microsoft.BitLocker.Structures.BitLockerVolume
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
AutoUnlockEnabled Property System.Nullable[bool] AutoUnlockEnabled {get;}
AutoUnlockKeyStored Property System.Nullable[bool] AutoUnlockKeyStored {get;}
CapacityGB Property float CapacityGB {get;}
ComputerName Property string ComputerName {get;}
EncryptionMethod Property Microsoft.BitLocker.Structures.BitLockerVolumeEncryptionMethodOnGet EncryptionMethod {…
EncryptionPercentage Property System.Nullable[float] EncryptionPercentage {get;}
KeyProtector Property System.Collections.ObjectModel.ReadOnlyCollection[Microsoft.BitLocker.Structures.BitLo…
LockStatus Property Microsoft.BitLocker.Structures.BitLockerVolumeLockStatus LockStatus {get;}
MetadataVersion Property int MetadataVersion {get;}
MountPoint Property string MountPoint {get;}
ProtectionStatus Property Microsoft.BitLocker.Structures.BitLockerVolumeProtectionStatus ProtectionStatus {get;}
VolumeStatus Property System.Nullable[Microsoft.BitLocker.Structures.BitLockerVolumeStatus] VolumeStatus {ge…
VolumeType Property Microsoft.BitLocker.Structures.BitLockerVolumeType VolumeType {get;}
WipePercentage Property System.Nullable[float] WipePercentage {get;}
"""
volumestatus_dict = {
0: "FullyDecrypted",
1: "FullyEncrypted",
2: "EncryptionInProgress",
3: "DecryptionInProgress",
4: "EncryptionPaused",
5: "DecryptionPaused",
}
# https://learn.microsoft.com/en-us/windows/win32/secprov/getencryptionmethod-win32-encryptablevolume#parameters
# EncryptionMethod: XtsAes128 = 6; XtsAes256 = 7 #
encryptionmethod_dict = {
0: "None",
1: "AES_128_WITH_DIFFUSER",
2: "AES_256_WITH_DIFFUSER",
3: "Aes128",
4: "Aes256",
5: "HARDWARE_ENCRYPTION",
6: "XtsAes128",
7: "XtsAes256",
-1: "(uint32)",
}
target_encryption_method = 7
allow_swap_encryption_method = False # Not implemented yet
decrypt_cert_list = []
def install():
# Adding certificates allowed to decrypt in WAPT
for cert in decrypt_cert_list:
cert_path = makepath(WAPT.wapt_base_dir, "ssl", cert)
if not isfile(cert_path):
print("Copying: %s" % cert_path)
filecopyto(cert, cert_path)
def audit():
audit_status = "OK"
# audit_status = check_tpm_state()
certs_path = []
for cert in decrypt_cert_list:
certs_path.append(makepath(WAPT.wapt_base_dir, "ssl", cert))
# Add all ssl cert that has code signing by default
certs_path.extend(x for x in get_code_signing_cert() if x not in certs_path)
mountpoint_list = ensure_list(run_powershell("(Get-BitLockerVolume).MountPoint"))
# Cleaning mountpoints (unpartitionned devices and removal devices)
for mountpoint in mountpoint_list:
is_ignored = False
if "?" in mountpoint:
print("INFO: An unknow volume has been detected and will be skipped (%s)" % mountpoint)
is_ignored = True
if run_powershell("Get-Volume -DriveLetter %s | Where-Object DriveType -EQ Removable" % mountpoint.replace(":", "")):
is_ignored = True
if run_powershell("(Get-PhysicalDisk | Where-Object BusType -EQ USB | ForEach-Object { Get-Disk -Number $_.DeviceId | Get-Partition | Get-Volume } | Where-Object DriveLetter -EQ '%s').DriveLetter -ne $null" % mountpoint.replace(":", "")):
is_ignored = True
if is_ignored:
mountpoint_list.remove(mountpoint)
for mountpoint in mountpoint_list:
bitlockervolume = run_powershell(f"Get-BitLockerVolume -MountPoint {mountpoint}")
protection_status = bitlockervolume.get("ProtectionStatus", 0) # ProtectionStatus: Off = 0; On = 1
volumetype = "OperatingSystem" if bitlockervolume.get("VolumeType", 1) == 0 else "Data" # VolumeType: OperatingSystem = 0; Data = 1
volumestatus = volumestatus_dict[bitlockervolume.get("VolumeStatus", 0)]
lockstatus = bitlockervolume.get("LockStatus", 0) #LockStatus : Unlocked = 0; Locked = 1
if lockstatus == 1:
print(f"WARNING: Drive {mountpoint} {volumetype} is locked with Bitlocker, cannot retrieve the informations")
audit_status = set_audit_status(audit_status, "WARNING")
continue
elif protection_status == 1 and volumestatus =="FullyEncrypted":
if volumetype == "OperatingSystem":
print(f"OK: {mountpoint} {volumetype} drive is encrypted and protection status is ON with BitLocker")
audit_status = set_audit_status(audit_status, "OK")
else:
print(f"OK: {mountpoint} {volumetype} drive is encrypted and protection status is ON with BitLocker")
audit_status = set_audit_status(audit_status, "OK") # Warning for external drives?
try:
keyprotector_list = ensure_list(run_powershell(f'(Get-BitLockerVolume -MountPoint "{mountpoint}").KeyProtector'))
except:
print(f"Unable to get KeyProtector informations for: {mountpoint} drive.")
for keyprotector in keyprotector_list:
keyprotectorid = keyprotector["KeyProtectorId"]
if keyprotector["KeyProtectorType"] in [1,2,4]:
# WAPT.delete_audit_data(
# "enable-bitlocker", f"RecoveryPassword_{keyprotectorid}"
# ) # not possible from package, please delete from WAPT Console
# print("INFO: Skipping Backup-BitLockerKeyProtector for KeyProtectorType: Tpm")
continue
try:
# Backuping RecoveryPassword to the AD
if registry_readstring(HKEY_LOCAL_MACHINE, r'SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History', 'MachineDomain', None):
print(f"Backuping: RecoveryPassword {keyprotectorid} to the AD")
run_powershell(f'Backup-BitLockerKeyProtector -MountPoint "{mountpoint}" -KeyProtectorId "{keyprotectorid}"')
except Exception as e:
print(e)
print(f"WARNING: Failed to backup RecoveryPassword {keyprotectorid} to the AD")
audit_status = set_audit_status(audit_status, "WARNING")
if certs_path:
try:
# Backuping RecoveryPassword to the WAPT Console (encrypted)
print(f"Backuping: RecoveryPassword {keyprotectorid} to the WAPT Console")
sha256_data = sha256_for_data(json.dumps([certs_path,keyprotector["RecoveryPassword"]]))
last_data = ""
last_data_file = makepath(persistent_dir,keyprotectorid)
if isfile(last_data_file) :
with open(last_data_file,'r') as f:
last_data = f.read()
if last_data != sha256_data:
WAPT.write_audit_data_if_changed(
"enable-bitlocker",
f"RecoveryPassword_{keyprotectorid}",
rsa_encrypted_data_str(keyprotector["RecoveryPassword"] , certs_path),
)
with open(last_data_file,'w') as f:
f.write(sha256_data)
else:
print("Data already write")
except Exception as e:
print(e)
print(f"WARNING: Failed to backup RecoveryPassword {keyprotectorid} to the WAPT Console")
audit_status = set_audit_status(audit_status, "WARNING")
else:
print(
f'IMPORTANT: There is no certificate in certs_path, so the RecoveryPassword {keyprotectorid} cannot be encrypted, so it will not be saved to the WAPT Console'
)
keyprotectortype_list = ",".join(
run('powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -OutputFormat text -Command "&{(Get-BitLockerVolume -MountPoint "{mountpoint}").KeyProtector.KeyProtectorType}"').splitlines()
)
WAPT.write_audit_data_if_changed(
"enable-bitlocker",
"keyprotectortype_list_%s_drive" % mountpoint.split(":")[0],
keyprotectortype_list,
)
continue
elif volumestatus == "EncryptionInProgress":
encryptpercent = bitlockervolume.get("EncryptionPercentage", 0)
if volumetype == "OperatingSystem":
print(f"WARNING: Encryption in progress : {encryptpercent}% on {mountpoint} {volumetype} drive")
audit_status = set_audit_status(audit_status, "WARNING")
else:
print(f"WARNING: Encryption in progress : {encryptpercent}% on {mountpoint} {volumetype} drive")
audit_status = set_audit_status(audit_status, "WARNING") # Warning for external drives?
continue
elif volumestatus == "DecryptionInProgress":
encryptpercent = bitlockervolume.get("EncryptionPercentage", 0)
if volumetype == "OperatingSystem":
print(f"WARNING: Decryption in progress : {encryptpercent}% on {mountpoint} {volumetype} drive")
audit_status = set_audit_status(audit_status, "WARNING")
else:
print(f"WARNING: Decryption in progress : {encryptpercent}% on {mountpoint} {volumetype} drive")
audit_status = set_audit_status(audit_status, "WARNING") # Warning for external drives?
continue
elif protection_status == 0 and volumestatus =="FullyEncrypted":
if volumetype == "OperatingSystem":
print(f"ERROR: {mountpoint} {volumetype} drive is encrypted but protection status is OFF with BitLocker")
audit_status = set_audit_status(audit_status, "ERROR")
else:
print(f"WARNING: {mountpoint} {volumetype} drive is encrypted but protection status is OFF with BitLocker")
audit_status = set_audit_status(audit_status, "WARNING") # Warning for external drives?
continue
else:
print(f"ERROR: {mountpoint} {volumetype} drive is not encrypted with BitLocker")
audit_status = set_audit_status(audit_status, "ERROR")
# Checking EncryptionMethod
drive_encryption_method = bitlockervolume.get("EncryptionMethod", 0)
print(f"INFO: {mountpoint} {volumetype} drive BitLocker EncryptionMethod is: {encryptionmethod_dict[drive_encryption_method]}")
WAPT.write_audit_data_if_changed(
"enable-bitlocker",
"encryptionmethod_%s_drive" % mountpoint.split(":")[0],
encryptionmethod_dict[drive_encryption_method],
)
return audit_status
def get_code_signing_cert():
dir_cert_wapt = makepath(WAPT.wapt_base_dir, "ssl")
all_cert = glob.glob(f'{dir_cert_wapt}/*.crt')
try:
code_signing_cert = [ cert for cert in all_cert if SSLCertificate(cert).as_dict()['is_code_signing'] ]
return code_signing_cert
except:
return []
def set_audit_status(old_audit_status, new_audit_status):
"""Maintain higher criticality for audit status."""
audit_level = {"OK": 0, "WARNING": 1, "ERROR": 2}
old_status = old_audit_status.upper().strip()
new_status = new_audit_status.upper().strip()
if audit_level.get(new_status, -1) > audit_level.get(old_status, -1):
return new_status
else:
return old_audit_status
def check_tpm_state():
audit_status = "OK"
get_tpm = run_powershell("Get-Tpm")
if get_tpm["TpmPresent"] == False:
print("ERROR: No TPM chip found on this system")
audit_status = "ERROR"
else:
print("OK: TPM chip found on this system")
if get_tpm["TpmReady"] == False:
print("WARNING: TPM chip not ready")
audit_status = "WARNING"
else:
print("OK: TPM chip ready")
WAPT.write_audit_data_if_changed("enable-bitlocker", "TpmPresent", get_tpm["TpmPresent"])
WAPT.write_audit_data_if_changed("enable-bitlocker", "TpmReady", get_tpm["TpmReady"])
if get_tpm["ManufacturerVersion"]:
WAPT.write_audit_data_if_changed("enable-bitlocker", "ManufacturerVersion", get_tpm["ManufacturerVersion"].split("\x00")[0].strip())
return audit_status
138bc3646473acfd1c9437129f41358f6673962f9cc74df922dc1f3aa082e399 : WAPT/README.md
38d056ab130f7bf7c481c12636a4e9959de36561d3dfcbe54c6e3571bc0c1dc3 : WAPT/certificate.crt
c24821c6051f7ce2a9215b556e63baf275fa5a0dc40e95e1a756e28a15a1a708 : WAPT/control
b895508a66d6cea028c2a7781604a358298ba9c8082602ab633e8bff7f2b844c : WAPT/icon.png
ad547f9a42ab5724f95cca786daeb4d6e88f7a7b12253e7d3978105135976de6 : luti.json
a078922168ef9dd87e52ee6d58cb558963c5f2ef52563a178a8da2307ed68732 : setup.py