Imported Upstream version 2.6.1
[platform/upstream/cryptsetup.git] / lib / bitlk / bitlk.c
index 8846660..de7bcea 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * BITLK (BitLocker-compatible) volume handling
  *
- * Copyright (C) 2019-2021 Red Hat, Inc. All rights reserved.
- * Copyright (C) 2019-2021 Milan Broz
- * Copyright (C) 2019-2021 Vojtech Trefny
+ * Copyright (C) 2019-2023 Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2019-2023 Milan Broz
+ * Copyright (C) 2019-2023 Vojtech Trefny
  *
  * This file is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -24,7 +24,6 @@
 #include <string.h>
 #include <uuid/uuid.h>
 #include <time.h>
-#include <iconv.h>
 #include <limits.h>
 
 #include "bitlk.h"
@@ -234,86 +233,11 @@ static const char* get_bitlk_type_string(BITLKEncryptionType type)
        }
 }
 
-/* TODO -- move to some utils file */
-static void hexprint(struct crypt_device *cd, const char *d, int n, const char *sep)
-{
-       int i;
-       for(i = 0; i < n; i++)
-               log_std(cd, "%02hhx%s", (const char)d[i], sep);
-}
-
 static uint64_t filetime_to_unixtime(uint64_t time)
 {
        return (time - EPOCH_AS_FILETIME) / HUNDREDS_OF_NANOSECONDS;
 }
 
-static int convert_to_utf8(struct crypt_device *cd, uint8_t *input, size_t inlen, char **out)
-{
-       char *outbuf = NULL;
-       iconv_t ic;
-       size_t ic_inlen = inlen;
-       size_t ic_outlen = inlen;
-       char *ic_outbuf = NULL;
-       size_t r = 0;
-
-       outbuf = malloc(inlen);
-       if (outbuf == NULL)
-               return -ENOMEM;
-
-       memset(outbuf, 0, inlen);
-       ic_outbuf = outbuf;
-
-       ic = iconv_open("UTF-8", "UTF-16LE");
-       r = iconv(ic, (char **) &input, &ic_inlen, &ic_outbuf, &ic_outlen);
-       iconv_close(ic);
-
-       if (r == 0)
-               *out = strdup(outbuf);
-       else {
-               *out = NULL;
-               log_dbg(cd, "Failed to convert volume description: %s", strerror(errno));
-               r = 0;
-       }
-
-       free(outbuf);
-       return r;
-}
-
-static int passphrase_to_utf16(struct crypt_device *cd, char *input, size_t inlen, char **out)
-{
-       char *outbuf = NULL;
-       iconv_t ic;
-       size_t ic_inlen = inlen;
-       size_t ic_outlen = inlen * 2;
-       char *ic_outbuf = NULL;
-       size_t r = 0;
-
-       if (inlen == 0)
-               return r;
-
-       outbuf = crypt_safe_alloc(inlen * 2);
-       if (outbuf == NULL)
-               return -ENOMEM;
-
-       memset(outbuf, 0, inlen * 2);
-       ic_outbuf = outbuf;
-
-       ic = iconv_open("UTF-16LE", "UTF-8");
-       r = iconv(ic, &input, &ic_inlen, &ic_outbuf, &ic_outlen);
-       iconv_close(ic);
-
-       if (r == 0) {
-               *out = outbuf;
-       } else {
-               *out = NULL;
-               crypt_safe_free(outbuf);
-               log_dbg(cd, "Failed to convert passphrase: %s", strerror(errno));
-               r = -errno;
-       }
-
-       return r;
-}
-
 static int parse_vmk_entry(struct crypt_device *cd, uint8_t *data, int start, int end, struct bitlk_vmk **vmk)
 {
        uint16_t key_entry_size = 0;
@@ -324,19 +248,23 @@ static int parse_vmk_entry(struct crypt_device *cd, uint8_t *data, int start, in
        const char *key = NULL;
        struct volume_key *vk = NULL;
        bool supported = false;
+       int r = 0;
 
        /* only passphrase or recovery passphrase vmks are supported (can be used to activate) */
        supported = (*vmk)->protection == BITLK_PROTECTION_PASSPHRASE ||
                    (*vmk)->protection == BITLK_PROTECTION_RECOVERY_PASSPHRASE ||
                    (*vmk)->protection == BITLK_PROTECTION_STARTUP_KEY;
 
-       while (end - start > 2) {
+       while ((end - start) >= (ssize_t)(sizeof(key_entry_size) + sizeof(key_entry_type) + sizeof(key_entry_value))) {
                /* size of this entry */
                memcpy(&key_entry_size, data + start, sizeof(key_entry_size));
                key_entry_size = le16_to_cpu(key_entry_size);
                if (key_entry_size == 0)
                        break;
 
+               if (key_entry_size > (end - start))
+                       return -EINVAL;
+
                /* type and value of this entry */
                memcpy(&key_entry_type, data + start + sizeof(key_entry_size), sizeof(key_entry_type));
                memcpy(&key_entry_value,
@@ -355,20 +283,24 @@ static int parse_vmk_entry(struct crypt_device *cd, uint8_t *data, int start, in
                }
 
                /* stretch key with salt, skip 4 B (encryption method of the stretch key) */
-               if (key_entry_value == BITLK_ENTRY_VALUE_STRETCH_KEY)
+               if (key_entry_value == BITLK_ENTRY_VALUE_STRETCH_KEY) {
+                       if ((end - start) < (BITLK_ENTRY_HEADER_LEN + BITLK_SALT_SIZE + 4))
+                               return -EINVAL;
                        memcpy((*vmk)->salt,
                               data + start + BITLK_ENTRY_HEADER_LEN + 4,
-                              sizeof((*vmk)->salt));
+                              BITLK_SALT_SIZE);
                /* AES-CCM encrypted key */
-               else if (key_entry_value == BITLK_ENTRY_VALUE_ENCRYPTED_KEY) {
+               } else if (key_entry_value == BITLK_ENTRY_VALUE_ENCRYPTED_KEY) {
+                       if (key_entry_size < (BITLK_ENTRY_HEADER_LEN + BITLK_NONCE_SIZE + BITLK_VMK_MAC_TAG_SIZE))
+                               return -EINVAL;
                        /* nonce */
                        memcpy((*vmk)->nonce,
                               data + start + BITLK_ENTRY_HEADER_LEN,
-                              sizeof((*vmk)->nonce));
+                              BITLK_NONCE_SIZE);
                        /* MAC tag */
                        memcpy((*vmk)->mac_tag,
                               data + start + BITLK_ENTRY_HEADER_LEN + BITLK_NONCE_SIZE,
-                              sizeof((*vmk)->mac_tag));
+                              BITLK_VMK_MAC_TAG_SIZE);
                        /* AES-CCM encrypted key */
                        key_size = key_entry_size - (BITLK_ENTRY_HEADER_LEN + BITLK_NONCE_SIZE + BITLK_VMK_MAC_TAG_SIZE);
                        key = (const char *) data + start + BITLK_ENTRY_HEADER_LEN + BITLK_NONCE_SIZE + BITLK_VMK_MAC_TAG_SIZE;
@@ -393,9 +325,16 @@ static int parse_vmk_entry(struct crypt_device *cd, uint8_t *data, int start, in
                } else if (key_entry_value == BITLK_ENTRY_VALUE_RECOVERY_TIME) {
                        ;
                } else if (key_entry_value == BITLK_ENTRY_VALUE_STRING) {
-                       if (convert_to_utf8(cd, data + start + BITLK_ENTRY_HEADER_LEN, key_entry_size - BITLK_ENTRY_HEADER_LEN, &string) < 0) {
-                               log_err(cd, _("Invalid string found when parsing Volume Master Key."));
+                       if (key_entry_size < BITLK_ENTRY_HEADER_LEN)
+                               return -EINVAL;
+                       string = malloc((key_entry_size - BITLK_ENTRY_HEADER_LEN) * 2 + 1);
+                       if (!string)
+                               return -ENOMEM;
+                       r = crypt_utf16_to_utf8(&string, CONST_CAST(char16_t *)(data + start + BITLK_ENTRY_HEADER_LEN),
+                                                    key_entry_size - BITLK_ENTRY_HEADER_LEN);
+                       if (r < 0 || !string) {
                                free(string);
+                               log_err(cd, _("Invalid string found when parsing Volume Master Key."));
                                return -EINVAL;
                        } else if ((*vmk)->name != NULL) {
                                if (supported) {
@@ -475,6 +414,7 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
        struct bitlk_fve_metadata fve = {};
        struct bitlk_entry_vmk entry_vmk = {};
        uint8_t *fve_entries = NULL;
+       size_t fve_entries_size = 0;
        uint32_t fve_metadata_size = 0;
        int fve_offset = 0;
        char guid_buf[UUID_STR_LEN] = {0};
@@ -483,9 +423,9 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
        int i = 0;
        int r = 0;
        int start = 0;
-       int end = 0;
        size_t key_size = 0;
        const char *key = NULL;
+       char *description = NULL;
 
        struct bitlk_vmk *vmk = NULL;
        struct bitlk_vmk *vmk_p = params->vmks;
@@ -499,8 +439,8 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
        /* read and check the signature */
        if (read_lseek_blockwise(devfd, device_block_size(cd, device),
                device_alignment(device), &sig, sizeof(sig), 0) != sizeof(sig)) {
-               log_err(cd, _("Failed to read BITLK signature from %s."), device_path(device));
-               r = -EINVAL;
+               log_dbg(cd, "Failed to read BITLK signature from %s.", device_path(device));
+               r = -EIO;
                goto out;
        }
 
@@ -511,7 +451,7 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
                params->togo = true;
                fve_offset = BITLK_HEADER_METADATA_OFFSET_TOGO;
        } else {
-               log_err(cd, _("Invalid or unknown signature for BITLK device."));
+               log_dbg(cd, "Invalid or unknown signature for BITLK device.");
                r = -EINVAL;
                goto out;
        }
@@ -581,8 +521,8 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
                        le16_to_cpu(fve.curr_state), le16_to_cpu(fve.next_state));
        }
 
+       params->volume_size = le64_to_cpu(fve.volume_size);
        params->metadata_version = le16_to_cpu(fve.fve_version);
-       fve_metadata_size = le32_to_cpu(fve.metadata_size);
 
        switch (le16_to_cpu(fve.encryption)) {
        /* AES-CBC with Elephant difuser */
@@ -637,40 +577,56 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
 
        params->creation_time = filetime_to_unixtime(le64_to_cpu(fve.creation_time));
 
+       fve_metadata_size = le32_to_cpu(fve.metadata_size);
+       if (fve_metadata_size < (BITLK_FVE_METADATA_HEADER_LEN + sizeof(entry_size) + sizeof(entry_type)) ||
+           fve_metadata_size > BITLK_FVE_METADATA_SIZE) {
+               r = -EINVAL;
+               goto out;
+       }
+       fve_entries_size = fve_metadata_size - BITLK_FVE_METADATA_HEADER_LEN;
+
        /* read and parse all FVE metadata entries */
-       fve_entries = malloc(fve_metadata_size - BITLK_FVE_METADATA_HEADER_LEN);
+       fve_entries = malloc(fve_entries_size);
        if (!fve_entries) {
                r = -ENOMEM;
                goto out;
        }
-       memset(fve_entries, 0, (fve_metadata_size - BITLK_FVE_METADATA_HEADER_LEN));
+       memset(fve_entries, 0, fve_entries_size);
 
-       log_dbg(cd, "Reading BITLK FVE metadata entries of size %" PRIu32 " on device %s, offset %" PRIu64 ".",
-               fve_metadata_size - BITLK_FVE_METADATA_HEADER_LEN, device_path(device),
-               params->metadata_offset[0] + BITLK_FVE_METADATA_HEADERS_LEN);
+       log_dbg(cd, "Reading BITLK FVE metadata entries of size %zu on device %s, offset %" PRIu64 ".",
+               fve_entries_size, device_path(device), params->metadata_offset[0] + BITLK_FVE_METADATA_HEADERS_LEN);
 
        if (read_lseek_blockwise(devfd, device_block_size(cd, device),
-               device_alignment(device), fve_entries, fve_metadata_size - BITLK_FVE_METADATA_HEADER_LEN,
-               params->metadata_offset[0] + BITLK_FVE_METADATA_HEADERS_LEN) != (ssize_t)(fve_metadata_size - BITLK_FVE_METADATA_HEADER_LEN)) {
+               device_alignment(device), fve_entries, fve_entries_size,
+               params->metadata_offset[0] + BITLK_FVE_METADATA_HEADERS_LEN) != (ssize_t)fve_entries_size) {
                log_err(cd, _("Failed to read BITLK metadata entries from %s."), device_path(device));
                r = -EINVAL;
                goto out;
        }
 
-       end = fve_metadata_size - BITLK_FVE_METADATA_HEADER_LEN;
-       while (end - start > 2) {
+       while ((fve_entries_size - start) >= (sizeof(entry_size) + sizeof(entry_type))) {
+
                /* size of this entry */
                memcpy(&entry_size, fve_entries + start, sizeof(entry_size));
                entry_size = le16_to_cpu(entry_size);
                if (entry_size == 0)
                        break;
 
+               if (entry_size > (fve_entries_size - start)) {
+                       r = -EINVAL;
+                       goto out;
+               }
+
                /* type of this entry */
                memcpy(&entry_type, fve_entries + start + sizeof(entry_size), sizeof(entry_type));
                entry_type = le16_to_cpu(entry_type);
 
                /* VMK */
                if (entry_type == BITLK_ENTRY_TYPE_VMK) {
+                       if (entry_size < (BITLK_ENTRY_HEADER_LEN + sizeof(entry_vmk))) {
+                               r = -EINVAL;
+                               goto out;
+                       }
                        /* skip first four variables in the entry (entry size, type, value and version) */
                        memcpy(&entry_vmk,
                               fve_entries + start + BITLK_ENTRY_HEADER_LEN,
@@ -707,7 +663,11 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
                        vmk_p = vmk;
                        vmk = vmk->next;
                /* FVEK */
-               } else if (entry_type == BITLK_ENTRY_TYPE_FVEK) {
+               } else if (entry_type == BITLK_ENTRY_TYPE_FVEK && !params->fvek) {
+                       if (entry_size < (BITLK_ENTRY_HEADER_LEN + BITLK_NONCE_SIZE + BITLK_VMK_MAC_TAG_SIZE)) {
+                               r = -EINVAL;
+                               goto out;
+                       }
                        params->fvek = malloc(sizeof(struct bitlk_fvek));
                        if (!params->fvek) {
                                r = -ENOMEM;
@@ -715,11 +675,11 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
                        }
                        memcpy(params->fvek->nonce,
                               fve_entries + start + BITLK_ENTRY_HEADER_LEN,
-                              sizeof(params->fvek->nonce));
+                              BITLK_NONCE_SIZE);
                        /* MAC tag */
                        memcpy(params->fvek->mac_tag,
                               fve_entries + start + BITLK_ENTRY_HEADER_LEN + BITLK_NONCE_SIZE,
-                              sizeof(params->fvek->mac_tag));
+                              BITLK_VMK_MAC_TAG_SIZE);
                        /* AES-CCM encrypted key */
                        key_size = entry_size - (BITLK_ENTRY_HEADER_LEN + BITLK_NONCE_SIZE + BITLK_VMK_MAC_TAG_SIZE);
                        key = (const char *) fve_entries + start + BITLK_ENTRY_HEADER_LEN + BITLK_NONCE_SIZE + BITLK_VMK_MAC_TAG_SIZE;
@@ -731,20 +691,35 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
                /* volume header info (location and size) */
                } else if (entry_type == BITLK_ENTRY_TYPE_VOLUME_HEADER) {
                        struct bitlk_entry_header_block entry_header;
+                       if ((fve_entries_size - start) < (BITLK_ENTRY_HEADER_LEN + sizeof(entry_header))) {
+                               r = -EINVAL;
+                               goto out;
+                       }
                        memcpy(&entry_header,
                               fve_entries + start + BITLK_ENTRY_HEADER_LEN,
                               sizeof(entry_header));
                        params->volume_header_offset = le64_to_cpu(entry_header.offset);
                        params->volume_header_size = le64_to_cpu(entry_header.size);
                /* volume description (utf-16 string) */
-               } else if (entry_type == BITLK_ENTRY_TYPE_DESCRIPTION) {
-                       r = convert_to_utf8(cd, fve_entries + start + BITLK_ENTRY_HEADER_LEN,
-                                           entry_size - BITLK_ENTRY_HEADER_LEN,
-                                           &(params->description));
+               } else if (entry_type == BITLK_ENTRY_TYPE_DESCRIPTION && !params->description) {
+                       if (entry_size < BITLK_ENTRY_HEADER_LEN) {
+                               r = -EINVAL;
+                               goto out;
+                       }
+                       description = malloc((entry_size - BITLK_ENTRY_HEADER_LEN) * 2 + 1);
+                       if (!description) {
+                               r = -ENOMEM;
+                               goto out;
+                       }
+                       r = crypt_utf16_to_utf8(&description, CONST_CAST(char16_t *)(fve_entries + start + BITLK_ENTRY_HEADER_LEN),
+                                                         entry_size - BITLK_ENTRY_HEADER_LEN);
                        if (r < 0) {
+                               free(description);
                                BITLK_bitlk_vmk_free(vmk);
+                               log_err(cd, _("Failed to convert BITLK volume description"));
                                goto out;
                        }
+                       params->description = description;
                }
 
                start += entry_size;
@@ -767,6 +742,7 @@ int BITLK_dump(struct crypt_device *cd, struct device *device, struct bitlk_meta
        log_std(cd, "Version:      \t%u\n", params->metadata_version);
        log_std(cd, "GUID:         \t%s\n", params->guid);
        log_std(cd, "Sector size:  \t%u [bytes]\n", params->sector_size);
+       log_std(cd, "Volume size:  \t%" PRIu64 " [bytes]\n", params->volume_size);
        log_std(cd, "Created:      \t%s", ctime((time_t *)&(params->creation_time)));
        log_std(cd, "Description:  \t%s\n", params->description);
        log_std(cd, "Cipher name:  \t%s\n", params->cipher);
@@ -785,7 +761,7 @@ int BITLK_dump(struct crypt_device *cd, struct device *device, struct bitlk_meta
                log_std(cd, "\tGUID:       \t%s\n", vmk_p->guid);
                log_std(cd, "\tProtection: \t%s\n", get_vmk_protection_string (vmk_p->protection));
                log_std(cd, "\tSalt:       \t");
-               hexprint(cd, (const char *) vmk_p->salt, 16, "");
+               crypt_log_hex(cd, (const char *) vmk_p->salt, 16, "", 0, NULL);
                log_std(cd, "\n");
 
                vk_p = vmk_p->vk;
@@ -835,13 +811,13 @@ static int get_recovery_key(struct crypt_device *cd,
            - each part is a number dividable by 11
        */
        if (passwordLen != BITLK_RECOVERY_KEY_LEN) {
-                if (passwordLen == BITLK_RECOVERY_KEY_LEN + 1 && password[passwordLen - 1] == '\n') {
-                        /* looks like a recovery key with an extra newline, possibly from a key file */
-                        passwordLen--;
-                        log_dbg(cd, "Possible extra EOL stripped from the recovery key.");
-                } else
-                        return 0;
-        }
+               if (passwordLen == BITLK_RECOVERY_KEY_LEN + 1 && password[passwordLen - 1] == '\n') {
+                       /* looks like a recovery key with an extra newline, possibly from a key file */
+                       passwordLen--;
+                       log_dbg(cd, "Possible extra EOL stripped from the recovery key.");
+               } else
+                       return 0;
+       }
 
        for (i = BITLK_RECOVERY_PART_LEN; i < passwordLen; i += BITLK_RECOVERY_PART_LEN + 1) {
                if (password[i] != '-')
@@ -869,21 +845,31 @@ static int get_recovery_key(struct crypt_device *cd,
        return 0;
 }
 
-static int parse_external_key_entry(struct crypt_device *cd, const char *data, int start, int end, struct volume_key **vk)
+static int parse_external_key_entry(struct crypt_device *cd,
+                                   const char *data,
+                                   int start,
+                                   int end,
+                                   struct volume_key **vk,
+                                   const struct bitlk_metadata *params)
 {
        uint16_t key_entry_size = 0;
        uint16_t key_entry_type = 0;
        uint16_t key_entry_value = 0;
        size_t key_size = 0;
        const char *key = NULL;
+       struct bitlk_guid guid;
+       char guid_buf[UUID_STR_LEN] = {0};
 
-       while (end - start > 2) {
+       while ((end - start) >= (ssize_t)(sizeof(key_entry_size) + sizeof(key_entry_type) + sizeof(key_entry_value))) {
                /* size of this entry */
                memcpy(&key_entry_size, data + start, sizeof(key_entry_size));
                key_entry_size = le16_to_cpu(key_entry_size);
                if (key_entry_size == 0)
                        break;
 
+               if (key_entry_size > (end - start))
+                       return -EINVAL;
+
                /* type and value of this entry */
                memcpy(&key_entry_type, data + start + sizeof(key_entry_size), sizeof(key_entry_type));
                memcpy(&key_entry_value,
@@ -892,13 +878,14 @@ static int parse_external_key_entry(struct crypt_device *cd, const char *data, i
                key_entry_type = le16_to_cpu(key_entry_type);
                key_entry_value = le16_to_cpu(key_entry_value);
 
-               /* only properties should be in this entry */
-               if (key_entry_type != BITLK_ENTRY_TYPE_PROPERTY) {
+               if (key_entry_type != BITLK_ENTRY_TYPE_PROPERTY && key_entry_type != BITLK_ENTRY_TYPE_VOLUME_GUID) {
                        log_err(cd, _("Unexpected metadata entry type '%u' found when parsing external key."), key_entry_type);
                        return -EINVAL;
                }
 
                if (key_entry_value == BITLK_ENTRY_VALUE_KEY) {
+                       if (key_entry_size < (BITLK_ENTRY_HEADER_LEN + 4))
+                               return -EINVAL;
                        key_size = key_entry_size - (BITLK_ENTRY_HEADER_LEN + 4);
                        key = (const char *) data + start + BITLK_ENTRY_HEADER_LEN + 4;
                        *vk = crypt_alloc_volume_key(key_size, key);
@@ -908,7 +895,17 @@ static int parse_external_key_entry(struct crypt_device *cd, const char *data, i
                /* optional "ExternalKey" string, we can safely ignore it */
                } else if (key_entry_value == BITLK_ENTRY_VALUE_STRING)
                        ;
-               else {
+               /* GUID of the BitLocker device we are trying to open with this key */
+               else if (key_entry_value == BITLK_ENTRY_VALUE_GUID) {
+                       if ((end - start) < (ssize_t)(BITLK_ENTRY_HEADER_LEN + sizeof(struct bitlk_guid)))
+                               return -EINVAL;
+                       memcpy(&guid, data + start + BITLK_ENTRY_HEADER_LEN, sizeof(struct bitlk_guid));
+                       guid_to_string(&guid, guid_buf);
+                       if (strcmp(guid_buf, params->guid) != 0) {
+                               log_err(cd, _("BEK file GUID '%s' does not match GUID of the volume."), guid_buf);
+                               return -EINVAL;
+                       }
+               } else {
                        log_err(cd, _("Unexpected metadata entry value '%u' found when parsing external key."), key_entry_value);
                        return -EINVAL;
                }
@@ -925,7 +922,8 @@ static int get_startup_key(struct crypt_device *cd,
                           const char *password,
                           size_t passwordLen,
                           const struct bitlk_vmk *vmk,
-                          struct volume_key **su_key)
+                          struct volume_key **su_key,
+                          const struct bitlk_metadata *params)
 {
        struct bitlk_bek_header bek_header = {0};
        char guid_buf[UUID_STR_LEN] = {0};
@@ -934,7 +932,7 @@ static int get_startup_key(struct crypt_device *cd,
        uint16_t key_entry_type = 0;
        uint16_t key_entry_value = 0;
 
-       if (passwordLen < BITLK_BEK_FILE_HEADER_LEN)
+       if (passwordLen < (BITLK_BEK_FILE_HEADER_LEN + sizeof(key_entry_size) + sizeof(key_entry_type) + sizeof(key_entry_value)))
                return -EPERM;
 
        memcpy(&bek_header, password, BITLK_BEK_FILE_HEADER_LEN);
@@ -946,13 +944,14 @@ static int get_startup_key(struct crypt_device *cd,
        else
                return -EPERM;
 
-       if (bek_header.metadata_version != 1) {
-               log_err(cd, "Unsupported BEK metadata version %" PRIu32 "", bek_header.metadata_version);
+       if (le32_to_cpu(bek_header.metadata_version) != 1) {
+               log_err(cd, _("Unsupported BEK metadata version %" PRIu32), le32_to_cpu(bek_header.metadata_version));
                return -ENOTSUP;
        }
 
-       if (bek_header.metadata_size != passwordLen) {
-               log_err(cd, "Unexpected BEK metadata size  %" PRIu32 " does not match BEK file length", bek_header.metadata_size);
+       if (le32_to_cpu(bek_header.metadata_size) != passwordLen) {
+               log_err(cd, _("Unexpected BEK metadata size %" PRIu32 " does not match BEK file length"),
+                       le32_to_cpu(bek_header.metadata_size));
                return -EINVAL;
        }
 
@@ -975,7 +974,7 @@ static int get_startup_key(struct crypt_device *cd,
        if (key_entry_type == BITLK_ENTRY_TYPE_STARTUP_KEY && key_entry_value == BITLK_ENTRY_VALUE_EXTERNAL_KEY) {
                return parse_external_key_entry(cd, password,
                                                BITLK_BEK_FILE_HEADER_LEN + BITLK_ENTRY_HEADER_LEN + BITLK_STARTUP_KEY_HEADER_LEN,
-                                               passwordLen, su_key);
+                                               passwordLen, su_key, params);
        } else {
                log_err(cd, _("Unexpected metadata entry found when parsing startup key."));
                log_dbg(cd, "Entry type: %u, entry value: %u", key_entry_type, key_entry_value);
@@ -993,7 +992,7 @@ static int bitlk_kdf(struct crypt_device *cd,
        struct bitlk_kdf_data kdf = {};
        struct crypt_hash *hd = NULL;
        int len = 0;
-       char *utf16Password = NULL;
+       char16_t *utf16Password = NULL;
        int i = 0;
        int r = 0;
 
@@ -1010,11 +1009,16 @@ static int bitlk_kdf(struct crypt_device *cd,
 
        if (!recovery) {
                /* passphrase: convert to UTF-16 first, then sha256(sha256(pw)) */
-               r = passphrase_to_utf16(cd, CONST_CAST(char*)password, passwordLen, &utf16Password);
+               utf16Password = crypt_safe_alloc(sizeof(char16_t) * (passwordLen + 1));
+               if (!utf16Password) {
+                       r = -ENOMEM;
+                       goto out;
+               }
+               r = crypt_utf8_to_utf16(&utf16Password, CONST_CAST(char*)password, passwordLen);
                if (r < 0)
                        goto out;
 
-               crypt_hash_write(hd, utf16Password, passwordLen * 2);
+               crypt_hash_write(hd, (char*)utf16Password, passwordLen * 2);
                r = crypt_hash_final(hd, kdf.initial_sha256, len);
                if (r < 0)
                        goto out;
@@ -1101,45 +1105,17 @@ out:
        return r;
 }
 
-int BITLK_activate(struct crypt_device *cd,
-                  const char *name,
-                  const char *password,
-                  size_t passwordLen,
-                  const struct bitlk_metadata *params,
-                  uint32_t flags)
+int BITLK_get_volume_key(struct crypt_device *cd,
+                        const char *password,
+                        size_t passwordLen,
+                        const struct bitlk_metadata *params,
+                        struct volume_key **open_fvek_key)
 {
        int r = 0;
-       int i = 0;
-       int j = 0;
-       int min = 0;
-       int num_segments = 0;
-       struct crypt_dm_active_device dmd = {
-               .flags = flags,
-       };
-       struct dm_target *next_segment = NULL;
        struct volume_key *open_vmk_key = NULL;
-       struct volume_key *open_fvek_key = NULL;
        struct volume_key *vmk_dec_key = NULL;
        struct volume_key *recovery_key = NULL;
        const struct bitlk_vmk *next_vmk = NULL;
-       struct segment segments[MAX_BITLK_SEGMENTS] = {};
-       struct segment temp;
-       uint64_t next_start = 0;
-       uint64_t next_end = 0;
-       uint64_t last_segment = 0;
-       uint32_t dmt_flags;
-
-       if (!params->state) {
-               log_err(cd, _("This BITLK device is in an unsupported state and cannot be activated."));
-               r = -ENOTSUP;
-               goto out;
-       }
-
-       if (params->type != BITLK_ENCRYPTION_TYPE_NORMAL) {
-               log_err(cd, _("BITLK devices with type '%s' cannot be activated."), get_bitlk_type_string(params->type));
-               r = -ENOTSUP;
-               goto out;
-       }
 
        next_vmk = params->vmks;
        while (next_vmk) {
@@ -1170,7 +1146,7 @@ int BITLK_activate(struct crypt_device *cd,
                        if (r)
                                return r;
                } else if (next_vmk->protection == BITLK_PROTECTION_STARTUP_KEY) {
-                       r = get_startup_key(cd, password, passwordLen, next_vmk, &vmk_dec_key);
+                       r = get_startup_key(cd, password, passwordLen, next_vmk, &vmk_dec_key, params);
                        if (r) {
                                next_vmk = next_vmk->next;
                                continue;
@@ -1200,7 +1176,7 @@ int BITLK_activate(struct crypt_device *cd,
                }
                crypt_free_volume_key(vmk_dec_key);
 
-               r = decrypt_key(cd, &open_fvek_key, params->fvek->vk, open_vmk_key,
+               r = decrypt_key(cd, open_fvek_key, params->fvek->vk, open_vmk_key,
                                params->fvek->mac_tag, BITLK_VMK_MAC_TAG_SIZE,
                                params->fvek->nonce, BITLK_NONCE_SIZE, true);
                if (r < 0) {
@@ -1221,28 +1197,71 @@ int BITLK_activate(struct crypt_device *cd,
                return r;
        }
 
-       /* Password verify only */
-       if (!name) {
-               crypt_free_volume_key(open_fvek_key);
-               return r;
+       return 0;
+}
+
+static int _activate_check(struct crypt_device *cd,
+                          const struct bitlk_metadata *params)
+{
+       const struct bitlk_vmk *next_vmk = NULL;
+
+       if (!params->state) {
+               log_err(cd, _("This BITLK device is in an unsupported state and cannot be activated."));
+               return -ENOTSUP;
+       }
+
+       if (params->type != BITLK_ENCRYPTION_TYPE_NORMAL) {
+               log_err(cd, _("BITLK devices with type '%s' cannot be activated."), get_bitlk_type_string(params->type));
+               return -ENOTSUP;
        }
 
        next_vmk = params->vmks;
        while (next_vmk) {
                if (next_vmk->protection == BITLK_PROTECTION_CLEAR_KEY) {
-                       crypt_free_volume_key(open_fvek_key);
                        log_err(cd, _("Activation of partially decrypted BITLK device is not supported."));
                        return -ENOTSUP;
                }
                next_vmk = next_vmk->next;
        }
 
+       return 0;
+}
+
+static int _activate(struct crypt_device *cd,
+                    const char *name,
+                    struct volume_key *open_fvek_key,
+                    const struct bitlk_metadata *params,
+                    uint32_t flags)
+{
+       int r = 0;
+       int i = 0;
+       int j = 0;
+       int min = 0;
+       int num_segments = 0;
+       struct crypt_dm_active_device dmd = {
+               .flags = flags,
+       };
+       struct dm_target *next_segment = NULL;
+       struct segment segments[MAX_BITLK_SEGMENTS] = {};
+       struct segment temp;
+       uint64_t next_start = 0;
+       uint64_t next_end = 0;
+       uint64_t last_segment = 0;
+       uint32_t dmt_flags = 0;
+
+       r = _activate_check(cd, params);
+       if (r)
+               return r;
+
        r = device_block_adjust(cd, crypt_data_device(cd), DEV_EXCL,
                                0, &dmd.size, &dmd.flags);
-       if (r) {
-               crypt_free_volume_key(open_fvek_key);
+       if (r)
                return r;
-       }
+
+       if (dmd.size * SECTOR_SIZE != params->volume_size)
+               log_std(cd, _("WARNING: BitLocker volume size %" PRIu64 " does not match the underlying device size %" PRIu64 ""),
+                       params->volume_size,
+                       dmd.size * SECTOR_SIZE);
 
        /* there will be always 4 dm-zero segments: 3x metadata, 1x FS header */
        for (i = 0; i < 3; i++) {
@@ -1374,9 +1393,68 @@ int BITLK_activate(struct crypt_device *cd,
                        log_err(cd, _("Cannot activate device, kernel dm-crypt is missing support for BITLK Elephant diffuser."));
                        r = -ENOTSUP;
                }
+               if ((dmd.flags & CRYPT_ACTIVATE_IV_LARGE_SECTORS) && !(dmt_flags & DM_SECTOR_SIZE_SUPPORTED)) {
+                       log_err(cd, _("Cannot activate device, kernel dm-crypt is missing support for large sector size."));
+                       r = -ENOTSUP;
+               }
+               if (dm_flags(cd, DM_ZERO, &dmt_flags) < 0) {
+                       log_err(cd, _("Cannot activate device, kernel dm-zero module is missing."));
+                       r = -ENOTSUP;
+               }
        }
 out:
        dm_targets_free(cd, &dmd);
+       return r;
+}
+
+int BITLK_activate_by_passphrase(struct crypt_device *cd,
+                                const char *name,
+                                const char *password,
+                                size_t passwordLen,
+                                const struct bitlk_metadata *params,
+                                uint32_t flags)
+{
+       int r = 0;
+       struct volume_key *open_fvek_key = NULL;
+
+       r = _activate_check(cd, params);
+       if (r)
+               return r;
+
+       r = BITLK_get_volume_key(cd, password, passwordLen, params, &open_fvek_key);
+       if (r < 0)
+               goto out;
+
+       /* Password verify only */
+       if (!name)
+               goto out;
+
+       r = _activate(cd, name, open_fvek_key, params, flags);
+out:
+       crypt_free_volume_key(open_fvek_key);
+       return r;
+}
+
+int BITLK_activate_by_volume_key(struct crypt_device *cd,
+                                const char *name,
+                                const char *volume_key,
+                                size_t volume_key_size,
+                                const struct bitlk_metadata *params,
+                                uint32_t flags)
+{
+       int r = 0;
+       struct volume_key *open_fvek_key = NULL;
+
+       r = _activate_check(cd, params);
+       if (r)
+               return r;
+
+       open_fvek_key = crypt_alloc_volume_key(volume_key_size, volume_key);
+       if (!open_fvek_key)
+               return -ENOMEM;
+
+       r = _activate(cd, name, open_fvek_key, params, flags);
+
        crypt_free_volume_key(open_fvek_key);
        return r;
 }