tis-audit-bitlocker icon

Audit BitLocker

Paquet d'installation silencieuse pour Audit BitLocker

10.6.0-45
Security
Security

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