diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index ac0c5fe5..bf878534 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -1666,6 +1666,41 @@ int crypt_volume_key_get(struct crypt_device *cd, const char *passphrase, size_t passphrase_size); +/** + * Get volume key from crypt device by keyslot context. + * + * @param cd crypt device handle + * @param keyslot use this keyslot or @e CRYPT_ANY_SLOT + * @param volume_key buffer for volume key + * @param volume_key_size on input, size of buffer @e volume_key, + * on output size of @e volume_key + * @param kc keyslot context used to unlock volume key + * + * @return unlocked key slot number or negative errno otherwise. + * + * @note See @link crypt-keyslot-context-types @endlink for info on keyslot + * context initialization. + * @note For TCRYPT cipher chain is the volume key concatenated + * for all ciphers in chain (kc may be NULL). + * @note For VERITY the volume key means root hash used for activation + * (kc may be NULL). + * @note For LUKS devices, if kc is @e NULL and volume key is cached in + * device context it returns the volume key generated in preceding + * @link crypt_format @endlink call. + * @note @link CRYPT_KC_TYPE_TOKEN @endlink keyslot context is usable only with LUKS2 devices. + * @note @link CRYPT_KC_TYPE_KEY @endlink keyslot context can not be used. + * @note To get LUKS2 unbound key, keyslot parameter must not be @e CRYPT_ANY_SLOT. + * @note EPERM errno means provided keyslot context could not unlock any (or selected) + * keyslot. + * @note ENOENT errno means no LUKS keyslot is available to retrieve volume key from + * and there's no cached volume key in device handle. + */ +int crypt_volume_key_get_by_keyslot_context(struct crypt_device *cd, + int keyslot, + char *volume_key, + size_t *volume_key_size, + struct crypt_keyslot_context *kc); + /** * Verify that provided volume key is valid for crypt device. * diff --git a/lib/libcryptsetup.sym b/lib/libcryptsetup.sym index df081b98..d0f0d985 100644 --- a/lib/libcryptsetup.sym +++ b/lib/libcryptsetup.sym @@ -163,4 +163,5 @@ CRYPTSETUP_2.6 { crypt_keyslot_context_set_pin; crypt_keyslot_context_get_type; crypt_keyslot_add_by_keyslot_context; + crypt_volume_key_get_by_keyslot_context; } CRYPTSETUP_2.5; diff --git a/lib/setup.c b/lib/setup.c index 02e7ff32..56ec13ba 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -4855,6 +4855,99 @@ int crypt_volume_key_get(struct crypt_device *cd, return r; } +int crypt_volume_key_get_by_keyslot_context(struct crypt_device *cd, + int keyslot, + char *volume_key, + size_t *volume_key_size, + struct crypt_keyslot_context *kc) +{ + size_t passphrase_size; + int key_len, r; + const char *passphrase = NULL; + struct volume_key *vk = NULL; + + if (!cd || !volume_key || !volume_key_size || + (!kc && !isLUKS(cd->type) && !isTCRYPT(cd->type) && !isVERITY(cd->type))) + return -EINVAL; + + if (isLUKS2(cd->type) && keyslot != CRYPT_ANY_SLOT) + key_len = LUKS2_get_keyslot_stored_key_size(&cd->u.luks2.hdr, keyslot); + else + key_len = crypt_get_volume_key_size(cd); + + if (key_len < 0) + return -EINVAL; + + if (key_len > (int)*volume_key_size) { + log_err(cd, _("Volume key buffer too small.")); + return -ENOMEM; + } + + if (kc && !kc->get_passphrase) + return -EINVAL; + + if (kc && kc->get_passphrase) { + r = kc->get_passphrase(cd, kc, &passphrase, &passphrase_size); + if (r < 0) + return r; + } + + r = -EINVAL; + + if (isLUKS2(cd->type)) { + if (kc && !kc->get_luks2_key) + log_err(cd, _("Cannot retrieve volume key for LUKS2 device.")); + else if (!kc) + r = -ENOENT; + else + r = kc->get_luks2_key(cd, kc, keyslot, + keyslot == CRYPT_ANY_SLOT ? CRYPT_DEFAULT_SEGMENT : CRYPT_ANY_SEGMENT, + &vk); + } else if (isLUKS1(cd->type)) { + if (kc && !kc->get_luks1_volume_key) + log_err(cd, _("Cannot retrieve volume key for LUKS1 device.")); + else if (!kc) + r = -ENOENT; + else + r = kc->get_luks1_volume_key(cd, kc, keyslot, &vk); + } else if (isPLAIN(cd->type)) { + if (passphrase && cd->u.plain.hdr.hash) + r = process_key(cd, cd->u.plain.hdr.hash, key_len, + passphrase, passphrase_size, &vk); + if (r < 0) + log_err(cd, _("Cannot retrieve volume key for plain device.")); + } else if (isVERITY(cd->type)) { + /* volume_key == root hash */ + if (cd->u.verity.root_hash) { + memcpy(volume_key, cd->u.verity.root_hash, cd->u.verity.root_hash_size); + *volume_key_size = cd->u.verity.root_hash_size; + r = 0; + } else + log_err(cd, _("Cannot retrieve root hash for verity device.")); + } else if (isTCRYPT(cd->type)) { + r = TCRYPT_get_volume_key(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params, &vk); + } else if (isBITLK(cd->type)) { + if (passphrase) + r = BITLK_get_volume_key(cd, passphrase, passphrase_size, &cd->u.bitlk.params, &vk); + if (r < 0) + log_err(cd, _("Cannot retrieve volume key for BITLK device.")); + } else + log_err(cd, _("This operation is not supported for %s crypt device."), cd->type ?: "(none)"); + + if (r == -ENOENT && isLUKS(cd->type) && cd->volume_key) { + vk = crypt_alloc_volume_key(cd->volume_key->keylength, cd->volume_key->key); + r = vk ? 0 : -ENOMEM; + } + + if (r >= 0 && vk) { + memcpy(volume_key, vk->key, vk->keylength); + *volume_key_size = vk->keylength; + } + + crypt_free_volume_key(vk); + return r; +} + int crypt_volume_key_verify(struct crypt_device *cd, const char *volume_key, size_t volume_key_size) diff --git a/tests/api-test-2.c b/tests/api-test-2.c index 34002d1a..b7c762d9 100644 --- a/tests/api-test-2.c +++ b/tests/api-test-2.c @@ -4879,6 +4879,125 @@ static void LuksKeyslotAdd(void) _cleanup_dmdevices(); } +static void VolumeKeyGet(void) +{ + struct crypt_params_luks2 params = { + .sector_size = 512 + }; + char key[256], key2[256]; +#ifdef KERNEL_KEYRING + key_serial_t kid; +#endif + const struct crypt_token_params_luks2_keyring tparams = { + .key_description = KEY_DESC_TEST0 + }; + + const char *vk_hex = "bb21158c733229347bd4e681891e213d94c685be6a5b84818afe7a78a6de7a1a" + "bb21158c733229347bd4e681891e213d94c685be6a5b84818afe7a78a6de7a1b"; + size_t key_size = strlen(vk_hex) / 2; + const char *cipher = "aes"; + const char *cipher_mode = "xts-plain64"; + uint64_t r_payload_offset; + struct crypt_keyslot_context *um1, *um2; + + crypt_decode_key(key, vk_hex, key_size); + + OK_(prepare_keyfile(KEYFILE1, PASSPHRASE1, strlen(PASSPHRASE1))); + +#ifdef KERNEL_KEYRING + kid = add_key("user", KEY_DESC_TEST0, PASSPHRASE1, strlen(PASSPHRASE1), KEY_SPEC_THREAD_KEYRING); + NOTFAIL_(kid, "Test or kernel keyring are broken."); +#endif + + // init test devices + OK_(get_luks2_offsets(0, 0, 0, NULL, &r_payload_offset)); + OK_(create_dmdevice_over_loop(H_DEVICE, r_payload_offset + 1)); + + // test support for embedded key (after crypt_format) + OK_(crypt_init(&cd, DMDIR H_DEVICE)); + OK_(set_fast_pbkdf(cd)); + OK_(crypt_format(cd, CRYPT_LUKS2, cipher, cipher_mode, NULL, NULL, key_size, ¶ms)); + key_size--; + FAIL_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, NULL), "buffer too small"); + + // check cached generated volume key can be retrieved + key_size++; + OK_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, NULL)); + OK_(crypt_volume_key_verify(cd, key2, key_size)); + CRYPT_FREE(cd); + + // check we can add keyslot via retrieved key + OK_(crypt_init(&cd, DMDIR H_DEVICE)); + OK_(crypt_load(cd, CRYPT_LUKS2, NULL)); + OK_(set_fast_pbkdf(cd)); + OK_(crypt_keyslot_context_init_by_volume_key(cd, key2, key_size, &um1)); + OK_(crypt_keyslot_context_init_by_passphrase(cd, PASSPHRASE, strlen(PASSPHRASE), &um2)); + EQ_(crypt_keyslot_add_by_keyslot_context(cd, CRYPT_ANY_SLOT, um1, 3, um2, 0), 3); + crypt_keyslot_context_free(um1); + crypt_keyslot_context_free(um2); + CRYPT_FREE(cd); + + // check selected volume key can be retrieved and added + OK_(crypt_init(&cd, DMDIR H_DEVICE)); + OK_(set_fast_pbkdf(cd)); + OK_(crypt_format(cd, CRYPT_LUKS2, cipher, cipher_mode, NULL, key, key_size, ¶ms)); + memset(key2, 0, key_size); + OK_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, NULL)); + OK_(memcmp(key, key2, key_size)); + OK_(crypt_keyslot_context_init_by_volume_key(cd, key2, key_size, &um1)); + OK_(crypt_keyslot_context_init_by_passphrase(cd, PASSPHRASE, strlen(PASSPHRASE), &um2)); + EQ_(crypt_keyslot_add_by_keyslot_context(cd, CRYPT_ANY_SLOT, um1, 0, um2, 0), 0); + crypt_keyslot_context_free(um2); + OK_(crypt_keyslot_context_init_by_keyfile(cd, KEYFILE1, 0, 0, &um2)); + EQ_(crypt_keyslot_add_by_keyslot_context(cd, CRYPT_ANY_SLOT, um1, 1, um2, 0), 1); + crypt_keyslot_context_free(um2); +#ifdef KERNEL_KEYRING + EQ_(crypt_token_luks2_keyring_set(cd, 0, &tparams), 0); + EQ_(crypt_token_assign_keyslot(cd, 0, 1), 0); +#endif + crypt_keyslot_context_free(um1); + CRYPT_FREE(cd); + + OK_(crypt_init(&cd, DMDIR H_DEVICE)); + OK_(crypt_load(cd, CRYPT_LUKS2, NULL)); + // check key context is not usable + OK_(crypt_keyslot_context_init_by_volume_key(cd, key, key_size, &um1)); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, um1), -EINVAL); + crypt_keyslot_context_free(um1); + + // by passphrase + memset(key2, 0, key_size); + OK_(crypt_keyslot_context_init_by_passphrase(cd, PASSPHRASE, strlen(PASSPHRASE), &um1)); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, um1), 0); + OK_(memcmp(key, key2, key_size)); + memset(key2, 0, key_size); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, 0, key2, &key_size, um1), 0); + OK_(memcmp(key, key2, key_size)); + crypt_keyslot_context_free(um1); + + // by keyfile + memset(key2, 0, key_size); + OK_(crypt_keyslot_context_init_by_keyfile(cd, KEYFILE1, 0, 0, &um1)); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, um1), 1); + OK_(memcmp(key, key2, key_size)); + memset(key2, 0, key_size); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, 1, key2, &key_size, um1), 1); + crypt_keyslot_context_free(um1); + +#ifdef KERNEL_KEYRING + // by token + OK_(crypt_keyslot_context_init_by_token(cd, CRYPT_ANY_TOKEN, NULL, NULL, 0, NULL, &um1)); + memset(key2, 0, key_size); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, um1), 1); + OK_(memcmp(key, key2, key_size)); + crypt_keyslot_context_free(um1); +#endif + CRYPT_FREE(cd); + + _remove_keyfiles(); + _cleanup_dmdevices(); +} + static int _crypt_load_check(struct crypt_device *cd) { #ifdef HAVE_BLKID @@ -5005,6 +5124,7 @@ int main(int argc, char *argv[]) RUN_(Luks2Reencryption, "LUKS2 reencryption"); #endif RUN_(LuksKeyslotAdd, "Adding keyslot via new API"); + RUN_(VolumeKeyGet, "Getting volume key via keyslot context API"); RUN_(Luks2Repair, "LUKS2 repair"); // test disables metadata locking. Run always last! _cleanup(); diff --git a/tests/api-test.c b/tests/api-test.c index 1840e3fb..2b2f0813 100644 --- a/tests/api-test.c +++ b/tests/api-test.c @@ -325,6 +325,7 @@ static void AddDevicePlain(void) }; int fd; char key[128], key2[128], path[128]; + struct crypt_keyslot_context *kc = NULL; const char *passphrase = PASSPHRASE; // hashed hex version of PASSPHRASE @@ -578,6 +579,11 @@ static void AddDevicePlain(void) key_size++; OK_(crypt_volume_key_get(cd, CRYPT_ANY_SLOT, key2, &key_size, passphrase, strlen(passphrase))); OK_(memcmp(key, key2, key_size)); + memset(key2, 0, key_size); + OK_(crypt_keyslot_context_init_by_passphrase(cd, passphrase, strlen(passphrase), &kc)); + OK_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, kc)); + OK_(memcmp(key, key2, key_size)); + crypt_keyslot_context_free(kc); OK_(strcmp(cipher, crypt_get_cipher(cd))); OK_(strcmp(cipher_mode, crypt_get_cipher_mode(cd))); @@ -1715,6 +1721,10 @@ static void VerityTest(void) OK_(crypt_volume_key_get(cd, CRYPT_ANY_SLOT, root_hash_out, &root_hash_out_size, NULL, 0)); EQ_(32, root_hash_out_size); OK_(memcmp(root_hash, root_hash_out, root_hash_out_size)); + memset(root_hash_out, 0, root_hash_out_size); + OK_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, root_hash_out, &root_hash_out_size, NULL)); + EQ_(32, root_hash_out_size); + OK_(memcmp(root_hash, root_hash_out, root_hash_out_size)); OK_(crypt_deactivate(cd, CDEVICE_1)); /* hash fail */ @@ -1791,6 +1801,9 @@ static void TcryptTest(void) key_size++; OK_(crypt_volume_key_get(cd, CRYPT_ANY_SLOT, key, &key_size, NULL, 0)); OK_(memcmp(key, key_def, key_size)); + memset(key, 0, key_size); + OK_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key, &key_size, NULL)); + OK_(memcmp(key, key_def, key_size)); reset_log(); OK_(crypt_dump(cd)); @@ -1805,6 +1818,7 @@ static void TcryptTest(void) GE_(crypt_status(cd, CDEVICE_1), CRYPT_ACTIVE); FAIL_(crypt_volume_key_get(cd, CRYPT_ANY_SLOT, key, &key_size, NULL, 0), "Need crypt_load"); + FAIL_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key, &key_size, NULL), "Need crypt_load"); // check params after init_by_name OK_(strcmp("xts-plain64", crypt_get_cipher_mode(cd))); @@ -2185,6 +2199,112 @@ static void LuksKeyslotAdd(void) _cleanup_dmdevices(); } +static void VolumeKeyGet(void) +{ + struct crypt_params_luks1 params = { + .hash = "sha512", + .data_alignment = 2048, // 2M, data offset will be 2048 + }; + struct crypt_pbkdf_type min_pbkdf2 = { + .type = "pbkdf2", + .hash = "sha256", + .iterations = 1000, + .flags = CRYPT_PBKDF_NO_BENCHMARK + }; + char key[128], key2[128]; + + const char *vk_hex = "bb21158c733229347bd4e681891e213d94c685be6a5b84818afe7a78a6de7a1a"; + size_t key_size = strlen(vk_hex) / 2; + const char *cipher = "aes"; + const char *cipher_mode = "cbc-essiv:sha256"; + uint64_t r_payload_offset; + struct crypt_keyslot_context *um1, *um2; + + crypt_decode_key(key, vk_hex, key_size); + + OK_(prepare_keyfile(KEYFILE1, PASSPHRASE1, strlen(PASSPHRASE1))); + + // init test devices + OK_(get_luks_offsets(0, key_size, params.data_alignment, 0, NULL, &r_payload_offset)); + OK_(create_dmdevice_over_loop(H_DEVICE, r_payload_offset + 1)); + + // test support for embedded key (after crypt_format) + OK_(crypt_init(&cd, DMDIR H_DEVICE)); + OK_(crypt_set_pbkdf_type(cd, &min_pbkdf2)); + OK_(crypt_format(cd, CRYPT_LUKS1, cipher, cipher_mode, NULL, key, key_size, ¶ms)); + key_size--; + FAIL_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, NULL), "buffer too small"); + + // check cached generated volume key can be retrieved + key_size++; + OK_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, NULL)); + OK_(crypt_volume_key_verify(cd, key2, key_size)); + CRYPT_FREE(cd); + + // check we can add keyslot via retrieved key + OK_(crypt_init(&cd, DMDIR H_DEVICE)); + OK_(crypt_load(cd, CRYPT_LUKS1, NULL)); + OK_(crypt_set_pbkdf_type(cd, &min_pbkdf2)); + OK_(crypt_keyslot_context_init_by_volume_key(cd, key2, key_size, &um1)); + OK_(crypt_keyslot_context_init_by_passphrase(cd, PASSPHRASE, strlen(PASSPHRASE), &um2)); + EQ_(crypt_keyslot_add_by_keyslot_context(cd, CRYPT_ANY_SLOT, um1, 3, um2, 0), 3); + crypt_keyslot_context_free(um1); + crypt_keyslot_context_free(um2); + CRYPT_FREE(cd); + + // check selected volume key can be retrieved and added + OK_(crypt_init(&cd, DMDIR H_DEVICE)); + OK_(crypt_set_pbkdf_type(cd, &min_pbkdf2)); + OK_(crypt_format(cd, CRYPT_LUKS1, cipher, cipher_mode, NULL, key, key_size, ¶ms)); + memset(key2, 0, key_size); + OK_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, NULL)); + OK_(memcmp(key, key2, key_size)); + OK_(crypt_keyslot_context_init_by_volume_key(cd, key2, key_size, &um1)); + OK_(crypt_keyslot_context_init_by_passphrase(cd, PASSPHRASE, strlen(PASSPHRASE), &um2)); + EQ_(crypt_keyslot_add_by_keyslot_context(cd, CRYPT_ANY_SLOT, um1, 0, um2, 0), 0); + crypt_keyslot_context_free(um2); + OK_(crypt_keyslot_context_init_by_keyfile(cd, KEYFILE1, 0, 0, &um2)); + EQ_(crypt_keyslot_add_by_keyslot_context(cd, CRYPT_ANY_SLOT, um1, 1, um2, 0), 1); + crypt_keyslot_context_free(um2); + crypt_keyslot_context_free(um1); + CRYPT_FREE(cd); + + OK_(crypt_init(&cd, DMDIR H_DEVICE)); + OK_(crypt_load(cd, CRYPT_LUKS1, NULL)); + // check key context is not usable + OK_(crypt_keyslot_context_init_by_volume_key(cd, key, key_size, &um1)); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, um1), -EINVAL); + crypt_keyslot_context_free(um1); + + // check token context is not usable + OK_(crypt_keyslot_context_init_by_token(cd, CRYPT_ANY_TOKEN, NULL, NULL, 0, NULL, &um1)); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, um1), -EINVAL); + crypt_keyslot_context_free(um1); + + // by passphrase + memset(key2, 0, key_size); + OK_(crypt_keyslot_context_init_by_passphrase(cd, PASSPHRASE, strlen(PASSPHRASE), &um1)); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, um1), 0); + OK_(memcmp(key, key2, key_size)); + memset(key2, 0, key_size); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, 0, key2, &key_size, um1), 0); + OK_(memcmp(key, key2, key_size)); + crypt_keyslot_context_free(um1); + + // by keyfile + memset(key2, 0, key_size); + OK_(crypt_keyslot_context_init_by_keyfile(cd, KEYFILE1, 0, 0, &um1)); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, CRYPT_ANY_SLOT, key2, &key_size, um1), 1); + OK_(memcmp(key, key2, key_size)); + memset(key2, 0, key_size); + EQ_(crypt_volume_key_get_by_keyslot_context(cd, 1, key2, &key_size, um1), 1); + crypt_keyslot_context_free(um1); + CRYPT_FREE(cd); + + _remove_keyfiles(); + _cleanup_dmdevices(); +} + // Check that gcrypt is properly initialised in format static void NonFIPSAlg(void) { @@ -2280,6 +2400,7 @@ int main(int argc, char *argv[]) RUN_(ResizeIntegrityWithKey, "Integrity raw resize with key"); RUN_(WipeTest, "Wipe device"); RUN_(LuksKeyslotAdd, "Adding keyslot via new API"); + RUN_(VolumeKeyGet, "Getting volume key via keyslot context API"); _cleanup(); return 0;