BitLocker on Linux with Cryptsetup

I decided to try mounting my laptop's Windows 10 partition with the new experimental BitLocker support in Cryptsetup, but it failed with the following error:

$ blkid /dev/nvme0n1p3
/dev/nvme0n1p3: TYPE="BitLocker" PARTLABEL="Basic data partition" PARTUUID="<removed>"

$ ./cryptsetup open --debug --readonly --type bitlk /dev/nvme0n1p3 windows 
# cryptsetup 2.3.0-git processing "/home/jbit/src/cryptsetup-build/.libs/cryptsetup open --debug --readonly --type bitlk /dev/nvme0n1p3 windows"
[...]
# Reading BitLocker FVE metadata of size 112 on device /dev/nvme0n1p3, offset 101765120.
# Reading BitLocker FVE metadata entries of size 1057 on device /dev/nvme0n1p3, offset 101765232.
# Unknown metadata entry value '2' found when parsing VMK.
# Unknown metadata entry value '6' found when parsing VMK.
Unexpected metadata entry found when parsing VMK.
Device /dev/nvme0n1p3 is not a valid BITLK device.
[...]
Command failed with code -1 (wrong or missing parameters).

It looks like cryptsetup tried to open the BitLocker partition, but encountered some unknown metadata. My laptop runs up-to-date Windows 10, so maybe Microsoft extended the format recently?

After digging around the cryptsetup codebase I found out that key_entry_value being set to 2 means BITLK_ENTRY_VALUE_STRING. This suggests the unknown metadata is a string, so I dumped out the values and found:

Metadata string TPM Protection for a BITLK_PROTECTION_TPM VMK entry.
Metadata string DiskPassword for a BITLK_PROTECTION_RECOVERY_PASSPHRASE VMK entry.

So it seems the unknown metadata is just a string identifying each individual key slot! This could be an easy to fix! I dug around the Windows documentation to confirm my suspicions and found that WMI exposes a "friendly name" for each key protector, so I wrote a little PowerShell script to dump it out.

foreach ($volume in Get-WmiObject -namespace Root\cimv2\security\MicrosoftVolumeEncryption -classname Win32_EncryptableVolume -filter "DriveLetter='C:'") {
    Write-Output "Drive $($volume.DriveLetter)"
    Write-Output "  PersistentVolumeID=$($volume.PersistentVolumeID)"
    Write-Output "  Version=$($volume.GetVersion().Version)"
    Write-Output "  EncryptionMethod=$($volume.GetEncryptionMethod().EncryptionMethod)"
    Write-Output "  IdentificationField=$($volume.GetIdentificationField().IdentificationField)"
    Write-Output "  Protectors:"    foreach ($protector in $volume.GetKeyProtectors("0").volumekeyprotectorID) {
        Write-Output "    $protector Type=$($volume.GetKeyProtectorType($protector).KeyProtectorType) FriendlyName=$($volume.GetKeyProtectorFriendlyName($protector).FriendlyName)"
    }
}
PS C:\Users\jbit> .\dump-bitlk.ps1
    Drive C:
    PersistentVolumeID={<removed>}
    Version=2
    EncryptionMethod=6
    IdentificationField=
    Protectors:
        {<removed>} Type=1 FriendlyName=TPM Protection
        {<removed>} Type=3 FriendlyName=DiskPassword

The FriendlyNames from WMI match the metadata strings exactly, so I'm quite confident the unknown metadata is indeed just a (friendly) name field! And after modifying the cryptsetup source to understand these metadata strings, I can now open my Windows partition!

$ cryptsetup open --type bitlk /dev/nvme0n1p3 windows 
Enter passphrase for /dev/nvme0n1p3: <removed>
$ blkid /dev/mapper/windows 
/dev/mapper/windows: LABEL="Windows10" UUID="<removed>" TYPE="ntfs"
$ mount /dev/mapper/windows /mnt
$ file /mnt/Windows/notepad.exe
notepad.exe: PE32+ executable (GUI) x86-64, for MS Windows

The modification is now in cryptsetup (as of merge request !77)

Update: EXPERIMENTAL BitLocker support (including the above modification) is in Cryptsetup v2.3.0