diff --git a/include/dislocker/accesses/user_pass/user_pass.h b/include/dislocker/accesses/user_pass/user_pass.h index f98f6d8..522dfb8 100644 --- a/include/dislocker/accesses/user_pass/user_pass.h +++ b/include/dislocker/accesses/user_pass/user_pass.h @@ -39,6 +39,8 @@ int get_vmk_from_user_pass(dis_metadata_t dis_meta, dis_config_t* cfg, void** vmk_datum); int get_vmk_from_user_pass2(dis_metadata_t dis_meta, uint8_t** user_password, void** vmk_datum); +int get_vmk_from_tpm_pin(dis_metadata_t dis_meta, dis_config_t* cfg, void** vmk_datum); + int user_key(const uint8_t *user_password, const uint8_t *salt, uint8_t *result_key); int prompt_up(uint8_t** up); diff --git a/include/dislocker/config.h b/include/dislocker/config.h index d5ef91d..e5fc4cc 100644 --- a/include/dislocker/config.h +++ b/include/dislocker/config.h @@ -44,6 +44,8 @@ typedef enum { DIS_OPT_SET_FVEK_FILE_PATH, DIS_OPT_USE_VMK_FILE, DIS_OPT_SET_VMK_FILE_PATH, + DIS_OPT_USE_TPM_PIN, + DIS_OPT_SET_TPM_DATUM_FILE_PATH, DIS_OPT_VERBOSITY, DIS_OPT_LOG_FILE_PATH, DIS_OPT_FORCE_BLOCK, diff --git a/include/dislocker/config.priv.h b/include/dislocker/config.priv.h index 8b9eaa0..01fb11b 100644 --- a/include/dislocker/config.priv.h +++ b/include/dislocker/config.priv.h @@ -35,11 +35,12 @@ typedef enum { DIS_USE_RECOVERY_PASSWORD = (1 << 2), DIS_USE_BEKFILE = (1 << 3), DIS_USE_FVEKFILE = (1 << 4), + DIS_USE_TPM_PIN = (1 << 5), DIS_USE_VMKFILE = (1 << 8) } DIS_DECRYPT_MEAN; /* Don't use this as a decryption mean, but as the last one */ -#define LAST_MEAN (1 << 5) +#define LAST_MEAN (1 << 6) /** @@ -80,6 +81,8 @@ typedef struct _dis_cfg { char* fvek_file; /* Use directly the VMK file DECRYPT_MEAN */ char* vmk_file; + /* Path to the TPM datum file for TPM+PIN DECRYPT_MEAN */ + char* tpm_datum_file; /* Output verbosity */ DIS_LOGS verbosity; diff --git a/src/accesses/accesses.c b/src/accesses/accesses.c index 1e2cacc..24f0905 100644 --- a/src/accesses/accesses.c +++ b/src/accesses/accesses.c @@ -60,6 +60,29 @@ int dis_get_access(dis_context_t dis_ctx) break; } } + else if(dis_ctx->cfg.decryption_mean & DIS_USE_TPM_PIN) + { + if(!get_vmk_from_tpm_pin(dis_ctx->metadata, &dis_ctx->cfg, &vmk_datum)) + { + dis_ctx->cfg.decryption_mean &= (unsigned) ~DIS_USE_TPM_PIN; + } + else + { + dis_printf(L_INFO, "Used TPM+PIN decryption method\n"); + dis_ctx->cfg.decryption_mean = DIS_USE_TPM_PIN; + + /* We don't need the user password anymore */ + if(dis_ctx->cfg.user_password) + { + memclean( + (char*) dis_ctx->cfg.user_password, + strlen((char*) dis_ctx->cfg.user_password) + ); + dis_ctx->cfg.user_password = NULL; + } + break; + } + } else if(dis_ctx->cfg.decryption_mean & DIS_USE_USER_PASSWORD) { if(!get_vmk_from_user_pass(dis_ctx->metadata, &dis_ctx->cfg, &vmk_datum)) diff --git a/src/accesses/user_pass/user_pass.c b/src/accesses/user_pass/user_pass.c index 21c45bf..3d2d5e4 100644 --- a/src/accesses/user_pass/user_pass.c +++ b/src/accesses/user_pass/user_pass.c @@ -30,6 +30,9 @@ #include #include #include +#include +#include +#include /** @@ -178,6 +181,236 @@ int get_vmk_from_user_pass2(dis_metadata_t dis_meta, } +/** + * Get the VMK datum using a TPM datum file and a user PIN + * + * The TPM datum file contains a raw AES-CCM datum (as obtained from the TPM). + * The PIN/user password is used together with the salt from the volume metadata + * to derive an intermediate key that decrypts the TPM datum. The result is then + * used to decrypt the actual VMK from the volume metadata. + * + * @param dis_meta The metadata structure + * @param cfg The configuration structure (provides tpm_datum_file and user_password) + * @param vmk_datum The datum_key_t found, containing the unencrypted VMK + * @return TRUE if result can be trusted, FALSE otherwise + */ +int get_vmk_from_tpm_pin(dis_metadata_t dis_meta, + dis_config_t* cfg, + void** vmk_datum) +{ + if(!dis_meta || !cfg || !cfg->tpm_datum_file) + return FALSE; + + uint8_t user_hash[32] = {0,}; + uint8_t salt[16] = {0,}; + void* vmk_key = NULL; + size_t vmk_key_size = 0; + int file_fd = -1; + off_t file_size; + ssize_t rs; + void* tpm_aesccm = NULL; + + /* If the user password/PIN wasn't provided, ask for it */ + if(!cfg->user_password) + if(!prompt_up(&cfg->user_password)) + { + dis_printf(L_ERROR, "Cannot get valid user PIN. Abort.\n"); + return FALSE; + } + + dis_printf( + L_DEBUG, + "Using TPM+PIN method with datum file '%s'.\n", + cfg->tpm_datum_file + ); + + /* Read the TPM datum file */ + file_fd = dis_open(cfg->tpm_datum_file, O_RDONLY); + if(file_fd == -1) + { + dis_printf(L_ERROR, "Cannot open TPM datum file (%s)\n", cfg->tpm_datum_file); + return FALSE; + } + + file_size = dis_lseek(file_fd, 0, SEEK_END); + if(file_size < (off_t)sizeof(datum_aes_ccm_t) || file_size > 65536) + { + dis_printf( + L_ERROR, + "Invalid TPM datum file size: %d (expected at least %lu bytes)\n", + (int)file_size, + (unsigned long)sizeof(datum_aes_ccm_t) + ); + dis_close(file_fd); + return FALSE; + } + + tpm_aesccm = dis_malloc((size_t)file_size); + dis_lseek(file_fd, 0, SEEK_SET); + rs = dis_read(file_fd, tpm_aesccm, (size_t)file_size); + dis_close(file_fd); + + if(rs != file_size) + { + dis_printf(L_ERROR, "Cannot read TPM datum file completely\n"); + dis_free(tpm_aesccm); + return FALSE; + } + + dis_printf(L_DEBUG, "TPM datum file read (%d bytes):\n", (int)file_size); + hexdump(L_DEBUG, tpm_aesccm, (size_t)file_size); + + /* + * Iterate over all VMK datums in the metadata, looking for one that + * has both a STRETCH_KEY and an AES_CCM nested datum. For each candidate, + * try to decrypt the TPM blob with the PIN-derived key, then use the + * result to decrypt the actual VMK. + */ + void* current_vmk = NULL; + + while(get_next_datum( + dis_meta, + DATUMS_ENTRY_VMK, + DATUMS_VALUE_VMK, + current_vmk, + ¤t_vmk + )) + { + /* Look for a STRETCH_KEY nested datum (contains the salt) */ + void* stretch_datum = NULL; + if(!get_nested_datumvaluetype( + current_vmk, + DATUMS_VALUE_STRETCH_KEY, + &stretch_datum + ) || + !stretch_datum) + { + continue; + } + + 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); + + /* + * Derive the intermediate key from the user PIN and the salt + */ + if(!user_key(cfg->user_password, salt, user_hash)) + { + dis_printf(L_DEBUG, "Cannot stretch user PIN with this salt, trying next VMK.\n"); + continue; + } + + dis_printf(L_DEBUG, "Derived user hash:\n"); + hexdump(L_DEBUG, user_hash, 32); + + /* + * Step 1: Decrypt the TPM datum with the PIN-derived key. + * This yields an intermediate VMK key. + */ + /* + * Initialize to tpm_aesccm so get_vmk's debug print has a + * valid datum to display before decryption overwrites it. + */ + datum_key_t* intermediate_key = (datum_key_t*) tpm_aesccm; + if(!get_vmk( + (datum_aes_ccm_t*) tpm_aesccm, + user_hash, + 32, + &intermediate_key + )) + { + dis_printf(L_DEBUG, "Cannot decrypt TPM datum with this VMK's salt, trying next.\n"); + continue; + } + + /* Extract the raw key bytes from the intermediate key datum */ + if(!get_payload_safe(intermediate_key, &vmk_key, &vmk_key_size)) + { + dis_printf( + L_DEBUG, + "Cannot extract payload from intermediate key, trying next.\n" + ); + dis_free(intermediate_key); + continue; + } + + dis_printf(L_DEBUG, "Intermediate VMK key (%lu bytes):\n", + (unsigned long)vmk_key_size); + hexdump(L_DEBUG, vmk_key, vmk_key_size); + + /* + * Step 2: Get the AES_CCM nested datum from the metadata VMK + * and decrypt it with the intermediate key to get the real VMK. + */ + void* aesccm_vmk_datum = NULL; + if(!get_nested_datumvaluetype( + current_vmk, + DATUMS_VALUE_AES_CCM, + &aesccm_vmk_datum + ) || + !aesccm_vmk_datum) + { + dis_printf( + L_DEBUG, + "No AES_CCM datum in this VMK entry, trying next.\n" + ); + dis_free(intermediate_key); + dis_free(vmk_key); + vmk_key = NULL; + continue; + } + + /* Make a copy since get_vmk may modify the pointer */ + size_t aesccm_size = ((datum_aes_ccm_t*)aesccm_vmk_datum)->header.datum_size; + void* aesccm_copy = dis_malloc(aesccm_size); + memcpy(aesccm_copy, aesccm_vmk_datum, aesccm_size); + + dis_printf(L_DEBUG, "AES_CCM VMK datum (%lu bytes):\n", + (unsigned long)aesccm_size); + hexdump(L_DEBUG, aesccm_copy, aesccm_size); + + datum_key_t* final_vmk = (datum_key_t*) aesccm_copy; + if(!get_vmk( + (datum_aes_ccm_t*) aesccm_copy, + vmk_key, + (vmk_key_size > 32) ? 32 : vmk_key_size, + &final_vmk + )) + { + dis_printf( + L_DEBUG, + "Cannot decrypt VMK with intermediate key, trying next.\n" + ); + dis_free(intermediate_key); + dis_free(vmk_key); + dis_free(aesccm_copy); + vmk_key = NULL; + continue; + } + + /* Success! */ + dis_printf(L_INFO, "Successfully decrypted VMK using TPM+PIN method.\n"); + *vmk_datum = final_vmk; + + dis_free(intermediate_key); + dis_free(vmk_key); + dis_free(aesccm_copy); + dis_free(tpm_aesccm); + return TRUE; + } + + dis_printf( + L_ERROR, + "Failed to decrypt VMK using TPM+PIN method. " + "No matching VMK datum found or wrong PIN.\n" + ); + dis_free(tpm_aesccm); + return FALSE; +} + + /** * Get the user's pass without displaying it. * diff --git a/src/config.c b/src/config.c index 9577799..344b092 100644 --- a/src/config.c +++ b/src/config.c @@ -86,6 +86,12 @@ static void setvmk(dis_context_t dis_ctx, char* optarg) dis_setopt(dis_ctx, DIS_OPT_USE_VMK_FILE, &trueval); dis_setopt(dis_ctx, DIS_OPT_SET_VMK_FILE_PATH, optarg); } +static void settpmdatum(dis_context_t dis_ctx, char* optarg) +{ + int trueval = TRUE; + dis_setopt(dis_ctx, DIS_OPT_USE_TPM_PIN, &trueval); + dis_setopt(dis_ctx, DIS_OPT_SET_TPM_DATUM_FILE_PATH, optarg); +} static void setlogfile(dis_context_t dis_ctx, char* optarg) { dis_setopt(dis_ctx, DIS_OPT_LOG_FILE_PATH, optarg); @@ -154,6 +160,7 @@ static struct _dis_options dis_opt[] = { { {"readonly", no_argument, NULL, 'r'}, setro }, { {"ro", no_argument, NULL, 'r'}, setro }, { {"stateok", no_argument, NULL, 's'}, setstateok }, + { {"tpm-datum", required_argument, NULL, 't'}, settpmdatum }, { {"user-password", optional_argument, NULL, 'u'}, setuserpassword }, { {"verbosity", no_argument, NULL, 'v'}, setverbosity }, { {"volume", required_argument, NULL, 'V'}, NULL } @@ -173,7 +180,7 @@ PROGNAME " by " AUTHOR ", v" VERSION " (compiled for " __OS "/" __ARCH ")\n" #endif "\n" "Usage: " PROGNAME " [-hqrsv] [-l LOG_FILE] [-O OFFSET] [-V VOLUME DECRYPTMETHOD -F[N]] [-- ARGS...]\n" -" with DECRYPTMETHOD = -p[RECOVERY_PASSWORD]|-f BEK_FILE|-u[USER_PASSWORD]|-k FVEK_FILE|-K VMK_FILE|-c\n" +" with DECRYPTMETHOD = -p[RECOVERY_PASSWORD]|-f BEK_FILE|-u[USER_PASSWORD]|-k FVEK_FILE|-K VMK_FILE|-t TPM_DATUM_FILE [-u[PIN]]|-c\n" "\n" "Options:\n" " -c, --clearkey decrypt volume using a clear key (default)\n" @@ -191,6 +198,8 @@ PROGNAME " by " AUTHOR ", v" VERSION " (compiled for " __OS "/" __ARCH ")\n" " -q, --quiet do NOT display anything\n" " -r, --readonly do not allow one to write on the BitLocker volume\n" " -s, --stateok do not check the volume's state, assume it's ok to mount it\n" +" -t, --tpm-datum TPM_DATUM_FILE\n" +" decrypt volume using the TPM+PIN method (use with -u for PIN)\n" " -u, --user-password=[USER_PASSWORD]\n" " decrypt volume using the user password method\n" " -v, --verbosity increase verbosity (CRITICAL errors are displayed by default)\n" @@ -259,7 +268,7 @@ int dis_getopts(dis_context_t dis_ctx, int argc, char** argv) /* Options which could be passed as argument */ - const char short_opts[] = "cf:F::hk:K:l:O:o:p::qrsu::vV:"; + const char short_opts[] = "cf:F::hk:K:l:O:o:p::qrst:u::vV:"; struct option* long_opts; if(!dis_ctx || !argv) @@ -361,6 +370,12 @@ int dis_getopts(dis_context_t dis_ctx, int argc, char** argv) dis_setopt(dis_ctx, DIS_OPT_DONT_CHECK_VOLUME_STATE, &trueval); break; } + case 't': + { + dis_setopt(dis_ctx, DIS_OPT_USE_TPM_PIN, &trueval); + dis_setopt(dis_ctx, DIS_OPT_SET_TPM_DATUM_FILE_PATH, optarg); + break; + } case 'u': { dis_setopt(dis_ctx, DIS_OPT_USE_USER_PASSWORD, &trueval); @@ -484,6 +499,15 @@ int dis_getopt(dis_context_t dis_ctx, dis_opt_e opt_name, void** opt_value) case DIS_OPT_SET_VMK_FILE_PATH: *opt_value = cfg->vmk_file; break; + case DIS_OPT_USE_TPM_PIN: + if(cfg->decryption_mean & DIS_USE_TPM_PIN) + *opt_value = (void*) TRUE; + else + *opt_value = (void*) FALSE; + break; + case DIS_OPT_SET_TPM_DATUM_FILE_PATH: + *opt_value = cfg->tpm_datum_file; + break; case DIS_OPT_VERBOSITY: *opt_value = (void*) cfg->verbosity; break; @@ -633,6 +657,20 @@ int dis_setopt(dis_context_t dis_ctx, dis_opt_e opt_name, const void* opt_value) else cfg->vmk_file = strdup((const char*) opt_value); break; + case DIS_OPT_USE_TPM_PIN: + if(opt_value == NULL) + cfg->decryption_mean &= (unsigned) ~DIS_USE_TPM_PIN; + else + set_decryption_mean(cfg, *(int*) opt_value, DIS_USE_TPM_PIN); + break; + case DIS_OPT_SET_TPM_DATUM_FILE_PATH: + if(cfg->tpm_datum_file != NULL) + free(cfg->tpm_datum_file); + if(opt_value == NULL) + cfg->tpm_datum_file = NULL; + else + cfg->tpm_datum_file = strdup((const char*) opt_value); + break; case DIS_OPT_VERBOSITY: if(opt_value == NULL) cfg->verbosity = 0; @@ -739,6 +777,9 @@ void dis_free_args(dis_context_t dis_ctx) if(cfg->vmk_file) memclean(cfg->vmk_file, strlen(cfg->vmk_file) + sizeof(char)); + if(cfg->tpm_datum_file) + memclean(cfg->tpm_datum_file, strlen(cfg->tpm_datum_file) + sizeof(char)); + if(cfg->volume_path) dis_free(cfg->volume_path); @@ -783,6 +824,10 @@ void dis_print_args(dis_context_t dis_ctx) { dis_printf(L_DEBUG, " \tusing the FVEK file at '%s'\n", cfg->fvek_file); } + else if(cfg->decryption_mean & DIS_USE_TPM_PIN) + { + dis_printf(L_DEBUG, " \tusing the TPM+PIN method with datum file '%s'\n", cfg->tpm_datum_file); + } else if(cfg->decryption_mean & DIS_USE_VMKFILE) { dis_printf(L_DEBUG, " \tusing the VMK file at '%s'\n", cfg->vmk_file);