Skip to content

Add TPM+PIN decryption support (-t/--tpm-datum option)#355

Open
ludovictyack wants to merge 1 commit intoAorimn:masterfrom
ludovictyack:feature/tpm-pin-support
Open

Add TPM+PIN decryption support (-t/--tpm-datum option)#355
ludovictyack wants to merge 1 commit intoAorimn:masterfrom
ludovictyack:feature/tpm-pin-support

Conversation

@ludovictyack
Copy link
Copy Markdown

Allow decrypting BitLocker volumes protected with TPM+PIN by providing a TPM datum file (the AES-CCM blob manually extracted from the TPM) along with the user PIN. Usage: dislocker -t vmk_datum.bin -u PIN -V /dev/sdX

The implementation iterates all VMK datums in the volume metadata, derives an intermediate key from the PIN and the salt found in each VMK's stretch key datum, decrypts the TPM blob to obtain an intermediate key, then uses it to decrypt the actual VMK.

Allow decrypting BitLocker volumes protected with TPM+PIN by providing
a TPM datum file (the AES-CCM blob manually extracted from the TPM)
along with the user PIN. Usage: dislocker -t vmk_datum.bin -u PIN -V /dev/sdX

The implementation iterates all VMK datums in the volume metadata,
derives an intermediate key from the PIN and the salt found in each
VMK's stretch key datum, decrypts the TPM blob to obtain an
intermediate key, then uses it to decrypt the actual VMK.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@billatarm billatarm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a maintainer, but I also have patches staged for dislocker, as a TPM maintainer for Linux, I was interested in your patch. My 2 cents in the review, for what it's worth, take it or leave it. Nice patch.


/* If the user password/PIN wasn't provided, ask for it */
if(!cfg->user_password)
if(!prompt_up(&cfg->user_password))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, I think this is more clear to read and avoids the unbracketed if followed by a bracketed if.

if(!cfg->user_password) && !prompt_up(@cfg->user_password)() {
			dis_printf(L_ERROR, "Cannot get valid user PIN. Abort.\n");
			return FALSE;
}

}

file_size = dis_lseek(file_fd, 0, SEEK_END);
if(file_size < (off_t)sizeof(datum_aes_ccm_t) || file_size > 65536)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 65536? Is this the max an unsigned 16 bit can hold, which is 0xFFFF or 65535? Should we use the UINT16_MAX define or create a new one here to make the size restriction more clear.

L_ERROR,
"Invalid TPM datum file size: %d (expected at least %lu bytes)\n",
(int)file_size,
(unsigned long)sizeof(datum_aes_ccm_t)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

%jd for off_t and %zu for size_t. I am not sure if there some weird thing where this has to be c89 or some anchient compiler support needed, so I won't mention these again. But there are lots of spots where %zu or other format specifiers could be used vs casting. Then you wont have any truncation or other issues.

!stretch_datum)
{
continue;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bracket consistency, looks like single line conditions don't get brackets.

continue;
}

memcpy(salt, ((datum_stretch_key_t*) stretch_datum)->salt, 16);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use sizeof(stretch_datum->salt) instead of repeating the constant 16.

))
{
/* Look for a STRETCH_KEY nested datum (contains the salt) */
void* stretch_datum = NULL;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change this to type datum_stretch_key_t and then pass that to get_nested_datumvaluetype, then you don't need to cast it again later. You can pass any pointer as a void pointer too.

memcpy(salt, ((datum_stretch_key_t*) stretch_datum)->salt, 16);

dis_printf(L_DEBUG, "Found VMK with stretch key, salt:\n");
hexdump(L_DEBUG, salt, 16);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, sizeof(salt) here.

}

dis_printf(L_DEBUG, "Derived user hash:\n");
hexdump(L_DEBUG, user_hash, 32);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sizeof(user_hash) here as well.

if(!get_vmk(
(datum_aes_ccm_t*) tpm_aesccm,
user_hash,
32,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, sizeof(user_hash)

Comment thread src/config.c
if(opt_value == NULL)
cfg->tpm_datum_file = NULL;
else
cfg->tpm_datum_file = strdup((const char*) opt_value);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you're matching the code around you, and the other option handling code ignores this bug, but if this fails here will this get detected in some meaningful way? Maybe add a dis_strdup that dies like dis_malloc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants