|
26 | 26 |
|
27 | 27 | #include "dislocker/accesses/rp/recovery_password.h" |
28 | 28 | #include "dislocker/metadata/vmk.h" |
| 29 | +#include "dislocker/metadata/datums.h" |
| 30 | +#include "dislocker/encryption/decrypt.h" |
29 | 31 | #include "dislocker/xstd/xsys_select.h" |
30 | 32 |
|
31 | 33 |
|
@@ -594,3 +596,159 @@ void print_intermediate_key(uint8_t *result_key) |
594 | 596 |
|
595 | 597 | dis_printf(L_INFO, "Intermediate recovery key:\n\t%s\n", s); |
596 | 598 | } |
| 599 | + |
| 600 | + |
| 601 | +#define VMK_SIZE 32 |
| 602 | +#define RECOVERY_KEY_SIZE 16 |
| 603 | + |
| 604 | +/** |
| 605 | + * Convert 16-byte recovery key material to recovery password string |
| 606 | + * |
| 607 | + * @param key_material 16 bytes of recovery key material |
| 608 | + * @param password Output buffer (must be at least 56 bytes: 8*6 digits + 7 hyphens + null) |
| 609 | + * @return TRUE on success, FALSE on failure |
| 610 | + */ |
| 611 | +static int recovery_key_to_password(const uint8_t* key_material, char* password) |
| 612 | +{ |
| 613 | + int i; |
| 614 | + char* p = password; |
| 615 | + |
| 616 | + for (i = 0; i < NB_RP_BLOCS; i++) |
| 617 | + { |
| 618 | + /* Extract 16-bit little-endian value */ |
| 619 | + uint16_t value = (uint16_t)(key_material[i * 2] | (key_material[i * 2 + 1] << 8)); |
| 620 | + |
| 621 | + /* Multiply by 11 to get the 6-digit recovery password group */ |
| 622 | + uint32_t digit_group = (uint32_t)value * 11; |
| 623 | + |
| 624 | + /* Format as 6-digit group */ |
| 625 | + int written = snprintf(p, 7, "%06u", digit_group); |
| 626 | + if (written != 6) |
| 627 | + { |
| 628 | + dis_printf(L_ERROR, "Error formatting recovery password block %d\n", i + 1); |
| 629 | + return FALSE; |
| 630 | + } |
| 631 | + p += 6; |
| 632 | + |
| 633 | + /* Add hyphen separator (except after last block) */ |
| 634 | + if (i < NB_RP_BLOCS - 1) |
| 635 | + { |
| 636 | + *p++ = '-'; |
| 637 | + } |
| 638 | + } |
| 639 | + |
| 640 | + *p = '\0'; |
| 641 | + return TRUE; |
| 642 | +} |
| 643 | + |
| 644 | + |
| 645 | +/** |
| 646 | + * Extract recovery password from VMK |
| 647 | + * |
| 648 | + * Given a decrypted VMK, this function finds the recovery password protector |
| 649 | + * datum, decrypts it using the VMK, and converts the result to the standard |
| 650 | + * 8x6-digit recovery password format. |
| 651 | + * |
| 652 | + * @param dis_meta The metadata structure |
| 653 | + * @param vmk The 32-byte Volume Master Key |
| 654 | + * @param password Output buffer for recovery password (at least 56 bytes) |
| 655 | + * @return TRUE on success, FALSE on failure |
| 656 | + */ |
| 657 | +int extract_recovery_password_from_vmk(dis_metadata_t dis_meta, uint8_t* vmk, char* password) |
| 658 | +{ |
| 659 | + void* vmk_datum = NULL; |
| 660 | + void* stretch_datum = NULL; |
| 661 | + void* aesccm_datum = NULL; |
| 662 | + datum_aes_ccm_t* aesccm = NULL; |
| 663 | + void* decrypted = NULL; |
| 664 | + uint8_t* key_material = NULL; |
| 665 | + unsigned int header_size; |
| 666 | + unsigned int payload_size; |
| 667 | + |
| 668 | + if (!dis_meta || !vmk || !password) |
| 669 | + return FALSE; |
| 670 | + |
| 671 | + /* |
| 672 | + * Find VMK datum for recovery password protector |
| 673 | + * Recovery password protectors have priority range 0x800-0xfff |
| 674 | + */ |
| 675 | + if (!get_vmk_datum_from_range(dis_meta, 0x800, 0xfff, &vmk_datum, NULL)) |
| 676 | + { |
| 677 | + dis_printf(L_DEBUG, "No recovery password protector found in metadata\n"); |
| 678 | + return FALSE; |
| 679 | + } |
| 680 | + |
| 681 | + dis_printf(L_DEBUG, "Found VMK datum for recovery password protector\n"); |
| 682 | + |
| 683 | + /* |
| 684 | + * Get the nested STRETCH_KEY datum |
| 685 | + * This contains the salt and nested AES-CCM data |
| 686 | + */ |
| 687 | + if (!get_nested_datumvaluetype(vmk_datum, DATUMS_VALUE_STRETCH_KEY, &stretch_datum) || |
| 688 | + !stretch_datum) |
| 689 | + { |
| 690 | + dis_printf(L_DEBUG, "Cannot find STRETCH_KEY datum in VMK datum\n"); |
| 691 | + return FALSE; |
| 692 | + } |
| 693 | + |
| 694 | + /* |
| 695 | + * Get the nested AES-CCM datum inside the STRETCH_KEY |
| 696 | + * This contains the recovery key material encrypted by the VMK |
| 697 | + */ |
| 698 | + if (!get_nested_datumvaluetype(stretch_datum, DATUMS_VALUE_AES_CCM, &aesccm_datum) || |
| 699 | + !aesccm_datum) |
| 700 | + { |
| 701 | + dis_printf(L_DEBUG, "Cannot find AES-CCM datum in STRETCH_KEY datum\n"); |
| 702 | + return FALSE; |
| 703 | + } |
| 704 | + |
| 705 | + aesccm = (datum_aes_ccm_t*)aesccm_datum; |
| 706 | + |
| 707 | + /* Calculate payload size */ |
| 708 | + header_size = datum_value_types_prop[aesccm->header.value_type].size_header; |
| 709 | + payload_size = aesccm->header.datum_size - header_size; |
| 710 | + |
| 711 | + dis_printf(L_DEBUG, "AES-CCM payload size: %u bytes\n", payload_size); |
| 712 | + |
| 713 | + /* Decrypt the recovery key material using the VMK */ |
| 714 | + if (!decrypt_key( |
| 715 | + (unsigned char*)aesccm_datum + header_size, |
| 716 | + payload_size, |
| 717 | + aesccm->mac, |
| 718 | + aesccm->nonce, |
| 719 | + vmk, |
| 720 | + VMK_SIZE * 8, /* key size in bits */ |
| 721 | + &decrypted)) |
| 722 | + { |
| 723 | + dis_printf(L_DEBUG, "Failed to decrypt recovery key material\n"); |
| 724 | + return FALSE; |
| 725 | + } |
| 726 | + |
| 727 | + /* |
| 728 | + * The decrypted data has the following structure: |
| 729 | + * - 4 bytes: size |
| 730 | + * - 4 bytes: type |
| 731 | + * - 4 bytes: algorithm |
| 732 | + * - 16 bytes: recovery key material |
| 733 | + */ |
| 734 | + if (payload_size < 12 + RECOVERY_KEY_SIZE) |
| 735 | + { |
| 736 | + dis_printf(L_DEBUG, "Decrypted data too small (%u bytes)\n", payload_size); |
| 737 | + dis_free(decrypted); |
| 738 | + return FALSE; |
| 739 | + } |
| 740 | + |
| 741 | + /* Skip the 12-byte header to get to the recovery key material */ |
| 742 | + key_material = (uint8_t*)decrypted + 12; |
| 743 | + |
| 744 | + /* Convert to recovery password format */ |
| 745 | + if (!recovery_key_to_password(key_material, password)) |
| 746 | + { |
| 747 | + dis_printf(L_ERROR, "Failed to convert recovery key to password format\n"); |
| 748 | + dis_free(decrypted); |
| 749 | + return FALSE; |
| 750 | + } |
| 751 | + |
| 752 | + dis_free(decrypted); |
| 753 | + return TRUE; |
| 754 | +} |
0 commit comments