diff --git a/grub-core/disk/luks2.c b/grub-core/disk/luks2.c index 4ee5aeaad..e3eca68ca 100644 --- a/grub-core/disk/luks2.c +++ b/grub-core/disk/luks2.c @@ -353,8 +353,16 @@ luks2_scan (grub_disk_t disk, grub_cryptomount_args_t cargs) { grub_cryptodisk_t cryptodisk; grub_luks2_header_t header; + grub_luks2_keyslot_t keyslot; + grub_luks2_digest_t digest; + grub_luks2_segment_t segment; + char cipher[32], *json_header = NULL, *ptr; + grub_size_t candidate_key_len = 0, json_idx, size; char uuid[sizeof (header.uuid) + 1]; grub_size_t i, j; + grub_err_t ret; + gcry_md_spec_t *hash = NULL; + grub_json_t *json = NULL, keyslots; if (cargs->check_boot) return NULL; @@ -364,6 +372,175 @@ luks2_scan (grub_disk_t disk, grub_cryptomount_args_t cargs) grub_errno = GRUB_ERR_NONE; return NULL; } + json_header = grub_zalloc (grub_be_to_cpu64 (header.hdr_size) - sizeof (header)); + if (!json_header) + return GRUB_ERR_OUT_OF_MEMORY; + + /* Read the JSON area. */ + ret = grub_disk_read (disk, 0, grub_be_to_cpu64 (header.hdr_offset) + sizeof (header), + grub_be_to_cpu64 (header.hdr_size) - sizeof (header), json_header); + if (ret) + goto err; + + ptr = grub_memchr (json_header, 0, grub_be_to_cpu64 (header.hdr_size) - sizeof (header)); + if (!ptr) + goto err; + + ret = grub_json_parse (&json, json_header, grub_be_to_cpu64 (header.hdr_size)); + if (ret) + { + ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid LUKS2 JSON header"); + goto err; + } + + if (grub_json_getvalue (&keyslots, json, "keyslots") || + grub_json_getsize (&size, &keyslots)) + { + ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get keyslots"); + goto err; + } + + if (grub_disk_native_sectors (disk) == GRUB_DISK_SIZE_UNKNOWN) + { + /* FIXME: Allow use of source disk, and maybe cause errors in read. */ + grub_dprintf ("luks2", "Source disk %s has an unknown size, " + "conservatively returning error\n", disk->name); + ret = grub_error (GRUB_ERR_BUG, "Unknown size of luks2 source device"); + goto err; + } + + cryptodisk = grub_zalloc (sizeof (*cryptodisk)); + if (!cryptodisk) + return NULL; + + + /* Try all keyslot */ + for (json_idx = 0; json_idx < size; json_idx++) + { + char indexstr[21]; /* log10(2^64) ~ 20, plus NUL character. */ + typeof (disk->total_sectors) max_crypt_sectors = 0; + + grub_errno = GRUB_ERR_NONE; + ret = luks2_get_keyslot (&keyslot, &digest, &segment, json, json_idx); + if (ret) + goto err; + if (grub_errno != GRUB_ERR_NONE) + grub_dprintf ("luks2", "Ignoring unhandled error %d from luks2_get_keyslot\n", grub_errno); + + if (keyslot.priority == 0) + { + grub_dprintf ("luks2", "Ignoring keyslot \"%" PRIuGRUB_UINT64_T "\" due to priority\n", keyslot.idx); + continue; + } + + grub_dprintf ("luks2", "Trying keyslot \"%" PRIuGRUB_UINT64_T "\"\n", keyslot.idx); + + /* Sector size should be one of 512, 1024, 2048, or 4096. */ + if (!(segment.sector_size == 512 || segment.sector_size == 1024 || + segment.sector_size == 2048 || segment.sector_size == 4096)) + { + grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" sector" + " size %" PRIuGRUB_UINT64_T " is not one of" + " 512, 1024, 2048, or 4096\n", + segment.idx, segment.sector_size); + continue; + } + + /* Set up disk according to keyslot's segment. */ + cryptodisk->offset_sectors = grub_divmod64 (segment.offset, segment.sector_size, NULL); + cryptodisk->log_sector_size = grub_log2ull (segment.sector_size); + /* Set to the source disk/partition size, which is the maximum we allow. */ + max_crypt_sectors = grub_disk_native_sectors (disk); + max_crypt_sectors = grub_convert_sector (max_crypt_sectors, GRUB_DISK_SECTOR_BITS, + cryptodisk->log_sector_size); + + if (max_crypt_sectors < cryptodisk->offset_sectors) + { + grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" has offset" + " %" PRIuGRUB_UINT64_T " which is greater than" + " source disk size %" PRIuGRUB_UINT64_T "," + " skipping\n", segment.idx, cryptodisk->offset_sectors, + max_crypt_sectors); + continue; + } + + if (grub_strcmp (segment.size, "dynamic") == 0) + cryptodisk->total_sectors = max_crypt_sectors - cryptodisk->offset_sectors; + else + { + grub_errno = GRUB_ERR_NONE; + + /* Convert segment.size to sectors, rounding up to nearest sector */ + cryptodisk->total_sectors = grub_strtoull (segment.size, NULL, 10); + + if (grub_errno == GRUB_ERR_NONE) + { + cryptodisk->total_sectors = ALIGN_UP (cryptodisk->total_sectors, + 1 << cryptodisk->log_sector_size); + cryptodisk->total_sectors >>= cryptodisk->log_sector_size; + } + else if (grub_errno == GRUB_ERR_BAD_NUMBER) + { + grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" size" + " \"%s\" is not a parsable number," + " skipping keyslot\n", + segment.idx, segment.size); + continue; + } + else if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + { + /* + * There was an overflow in parsing segment.size, so disk must + * be very large or the string is incorrect. + * + * TODO: Allow reading of at least up max_crypt_sectors. Really, + * its very unlikely one would be booting from such a large drive + * anyway. Use another smaller LUKS2 boot device. + */ + grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" size" + " %s overflowed 64-bit unsigned integer," + " skipping keyslot\n", segment.idx, segment.size); + continue; + } + } + + if (cryptodisk->total_sectors == 0) + { + grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" has zero" + " sectors, skipping\n", segment.idx); + continue; + } + else if (max_crypt_sectors < (cryptodisk->offset_sectors + cryptodisk->total_sectors)) + { + grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" has last" + " data position greater than source disk size," + " the end of the crypto device will be" + " inaccessible\n", segment.idx); + + /* Allow decryption up to the end of the source disk. */ + cryptodisk->total_sectors = max_crypt_sectors - cryptodisk->offset_sectors; + } + + /* Set up disk hash. */ + if (keyslot.kdf.type == LUKS2_KDF_TYPE_PBKDF2) + { + hash = grub_crypto_lookup_md_by_name (keyslot.kdf.u.pbkdf2.hash); + if (!hash) + { + ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash", + keyslot.kdf.u.pbkdf2.hash); + goto err; + } + if (cryptodisk->hash) + { + if (grub_strcmp(hash->name, cryptodisk->hash->name)) { + ret = grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "LUKS2 Module does not support using multiple SHA versions."); + goto err; + } + } else + cryptodisk->hash = hash; + } + } for (i = 0, j = 0; i < sizeof (header.uuid); i++) if (header.uuid[i] != '-') @@ -376,15 +553,39 @@ luks2_scan (grub_disk_t disk, grub_cryptomount_args_t cargs) return NULL; } - cryptodisk = grub_zalloc (sizeof (*cryptodisk)); - if (!cryptodisk) - return NULL; - COMPILE_TIME_ASSERT (sizeof (cryptodisk->uuid) >= sizeof (uuid)); grub_memcpy (cryptodisk->uuid, uuid, sizeof (uuid)); + hash = grub_crypto_lookup_md_by_name (digest.hash); + if (cryptodisk->hash) { + if (grub_strcmp(hash->name, cryptodisk->hash->name)) { + ret = grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "LUKS2 Module does not support using multiple SHA versions."); + goto err; + } + } else + cryptodisk->hash = hash; + + /* Set up disk cipher. */ + grub_strncpy (cipher, segment.encryption, sizeof (cipher)); + ptr = grub_memchr (cipher, '-', grub_strlen (cipher)); + if (!ptr) { + ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid encryption"); + goto err; + } + *ptr = '\0'; + + ret = grub_cryptodisk_setcipher (cryptodisk, cipher, ptr + 1); + if (ret) + goto err; + + cryptodisk->modname = "luks2"; return cryptodisk; +err: + grub_free (json_header); + grub_json_free (json); + grub_errno = ret; + return NULL; } static grub_err_t diff --git a/grub-core/osdep/devmapper/getroot.c b/grub-core/osdep/devmapper/getroot.c index 9ba5c9865..9ae1780c9 100644 --- a/grub-core/osdep/devmapper/getroot.c +++ b/grub-core/osdep/devmapper/getroot.c @@ -141,7 +141,12 @@ grub_util_get_dm_abstraction (const char *os_dev) if (strncmp (uuid, "CRYPT-LUKS1-", 12) == 0) { grub_free (uuid); - return GRUB_DEV_ABSTRACTION_LUKS; + return GRUB_DEV_ABSTRACTION_LUKS1; + } + if (strncmp (uuid, "CRYPT-LUKS2-", 12) == 0) + { + grub_free (uuid); + return GRUB_DEV_ABSTRACTION_LUKS2; } grub_free (uuid); @@ -179,7 +184,7 @@ grub_util_pull_devmapper (const char *os_dev) grub_util_pull_device (subdev); } } - if (uuid && strncmp (uuid, "CRYPT-LUKS1-", sizeof ("CRYPT-LUKS1-") - 1) == 0 + if (uuid && (strncmp (uuid, "CRYPT-LUKS1-", sizeof ("CRYPT-LUKS1-") - 1) == 0 || strncmp (uuid, "CRYPT-LUKS2-", sizeof ("CRYPT-LUKS2-") - 1) == 0) && lastsubdev) { char *grdev = grub_util_get_grub_dev (lastsubdev); @@ -249,7 +254,8 @@ grub_util_get_devmapper_grub_dev (const char *os_dev) return grub_dev; } - case GRUB_DEV_ABSTRACTION_LUKS: + case GRUB_DEV_ABSTRACTION_LUKS1: + case GRUB_DEV_ABSTRACTION_LUKS2: { char *dash; diff --git a/include/grub/emu/getroot.h b/include/grub/emu/getroot.h index 73fa2d34a..1a27faf28 100644 --- a/include/grub/emu/getroot.h +++ b/include/grub/emu/getroot.h @@ -29,7 +29,8 @@ enum grub_dev_abstraction_types { GRUB_DEV_ABSTRACTION_NONE, GRUB_DEV_ABSTRACTION_LVM, GRUB_DEV_ABSTRACTION_RAID, - GRUB_DEV_ABSTRACTION_LUKS, + GRUB_DEV_ABSTRACTION_LUKS1, + GRUB_DEV_ABSTRACTION_LUKS2, GRUB_DEV_ABSTRACTION_GELI, }; diff --git a/util/getroot.c b/util/getroot.c index a5eaa64fd..76d86c174 100644 --- a/util/getroot.c +++ b/util/getroot.c @@ -100,7 +100,8 @@ grub_util_pull_device (const char *os_dev) case GRUB_DEV_ABSTRACTION_LVM: grub_util_pull_lvm_by_command (os_dev); /* Fallthrough - in case that lvm-tools are unavailable. */ - case GRUB_DEV_ABSTRACTION_LUKS: + case GRUB_DEV_ABSTRACTION_LUKS1: + case GRUB_DEV_ABSTRACTION_LUKS2: grub_util_pull_devmapper (os_dev); return;