From be0f5d7752a7798a9790caa6c43606e68e18608f Mon Sep 17 00:00:00 2001 From: "Stepan \"Sciapan\" Yakimovich" Date: Sat, 28 Sep 2024 18:37:15 +0000 Subject: [PATCH 1/3] Core: Add support for LUKS re-encryption. --- data/org.freedesktop.UDisks2.xml | 13 ++ src/tests/dbus-tests/test_70_encrypted.py | 29 ++++ src/udiskslinuxencrypted.c | 166 ++++++++++++++++++++++ udisks/udisksclient.c | 1 + 4 files changed, 209 insertions(+) diff --git a/data/org.freedesktop.UDisks2.xml b/data/org.freedesktop.UDisks2.xml index be13a0c6c..a46f96a9b 100644 --- a/data/org.freedesktop.UDisks2.xml +++ b/data/org.freedesktop.UDisks2.xml @@ -3039,6 +3039,19 @@ + + + + + + diff --git a/src/tests/dbus-tests/test_70_encrypted.py b/src/tests/dbus-tests/test_70_encrypted.py index fb77b615c..4147005b4 100644 --- a/src/tests/dbus-tests/test_70_encrypted.py +++ b/src/tests/dbus-tests/test_70_encrypted.py @@ -657,6 +657,35 @@ def test_header_backup_restore(self): device = self.get_device(disk) self.assertHasIface(device, "org.freedesktop.UDisks2.Encrypted") + def test_reencrypt(self): + disk = self.vdevs[0] + device = self.get_device(disk) + self._create_luks(device, self.PASSPHRASE) + + self.addCleanup(self._remove_luks, device) + self.udev_settle() + + d = dbus.Dictionary(signature='sv') + d['key-size'] = dbus.UInt32(256) + d['cipher'] = "aes" + d['cipher-mode'] = "cbc-essiv:sha256" + d['resilience'] = "checksum" + d['hash'] = "sha256" + d['max-hotzone-size'] = dbus.UInt64(0) + d['sector-size'] = dbus.UInt32(512) + d['new-volume_key'] = True + d['pbkdf-type'] = "" + + # Online + device.Reencrypt(self.PASSPHRASE, d, + dbus_interface=self.iface_prefix + '.Encrypted') + + device.Lock(self.no_options, dbus_interface=self.iface_prefix + '.Encrypted') + # Offline + device.Reencrypt(self.PASSPHRASE, d, + dbus_interface=self.iface_prefix + '.Encrypted') + + def _get_default_luks_version(self): manager = self.get_object('/Manager') default_encryption_type = self.get_property(manager, '.Manager', 'DefaultEncryptionType') diff --git a/src/udiskslinuxencrypted.c b/src/udiskslinuxencrypted.c index f6ae85c7b..15178fa0a 100644 --- a/src/udiskslinuxencrypted.c +++ b/src/udiskslinuxencrypted.c @@ -1429,6 +1429,171 @@ handle_header_backup (UDisksEncrypted *encrypted, /* ---------------------------------------------------------------------------------------------------- */ +/* runs in thread dedicated to handling @invocation */ +static gboolean +handle_reencrypt (UDisksEncrypted *encrypted, + GDBusMethodInvocation *invocation, + const gchar *passphrase, + GVariant *options) +{ + UDisksObject *object = NULL; + UDisksBlock *block; + UDisksDaemon *daemon; + UDisksState *state = NULL; + UDisksObject *cleartext_object = NULL; + UDisksBlock *unlocked_block = NULL; + uid_t caller_uid; + GError *error = NULL; + UDisksBaseJob *job = NULL; + + const gchar *device; + BDCryptoKeyslotContext *context = NULL; + guint32 key_size; + gchar *cipher; + gchar *cipher_mode; + gchar *resilience; + gchar *hash; + guint64 max_hotzone_size; + guint32 sector_size; + gboolean new_volume_key; + gboolean offline; + gchar *pbkdf_type; + BDCryptoLUKSPBKDF *pbkdf = NULL; + BDCryptoLUKSReencryptParams *params = NULL; + + + object = udisks_daemon_util_dup_object (encrypted, &error); + if (object == NULL) + { + g_dbus_method_invocation_return_gerror (invocation, error); + goto out; + } + + block = udisks_object_peek_block (object); + daemon = udisks_linux_block_object_get_daemon (UDISKS_LINUX_BLOCK_OBJECT (object)); + state = udisks_daemon_get_state (daemon); + + udisks_linux_block_object_lock_for_cleanup (UDISKS_LINUX_BLOCK_OBJECT (object)); + udisks_state_check_block (state, udisks_linux_block_object_get_device_number (UDISKS_LINUX_BLOCK_OBJECT (object))); + + /* Fail if the device is not a LUKS2 device */ + if (!(g_strcmp0 (udisks_block_get_id_usage (block), "crypto") == 0 && + g_strcmp0 (udisks_block_get_id_type (block), "crypto_LUKS") == 0 && + g_strcmp0 (udisks_block_get_id_version (block), "2") == 0)) // not that future-proof I guess, haha + { + g_dbus_method_invocation_return_error (invocation, + UDISKS_ERROR, + UDISKS_ERROR_FAILED, + "Device %s does not appear to be a LUKS2 device", + udisks_block_get_device (block)); + goto out; + } + + if (!udisks_daemon_util_get_caller_uid_sync (daemon, invocation, NULL /* GCancellable */, &caller_uid, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + goto out; + } + + job = udisks_daemon_launch_simple_job (daemon, + UDISKS_OBJECT (object), + "encrypted-reencrypt", + caller_uid, + NULL); + if (job == NULL) + { + g_dbus_method_invocation_return_error (invocation, UDISKS_ERROR, UDISKS_ERROR_FAILED, + "Failed to create a job object"); + goto out; + } + + udisks_linux_block_encrypted_lock (block); + + // do stuff here + cleartext_object = udisks_daemon_wait_for_object_sync (daemon, + wait_for_cleartext_object, + g_strdup (g_dbus_object_get_object_path (G_DBUS_OBJECT (object))), + g_free, + 0, /* timeout_seconds */ + NULL); /* error */ + + if (cleartext_object == NULL) + { + offline = TRUE; + device = udisks_block_get_device (block); + } + else + { + offline = FALSE; + unlocked_block = udisks_object_peek_block (cleartext_object); + device = udisks_block_get_device (unlocked_block); + } + + context = bd_crypto_keyslot_context_new_passphrase ((const guint8 *) passphrase, + strlen(passphrase), + &error); + if (!context) + { + g_dbus_method_invocation_return_error (invocation, + UDISKS_ERROR, + UDISKS_ERROR_FAILED, + "Error reencrypting encrypted device %s: %s", + device, + error->message); + udisks_simple_job_complete (UDISKS_SIMPLE_JOB (job), FALSE, error->message); + udisks_linux_block_encrypted_unlock (block); + goto out; + } + + g_variant_lookup (options, "key-size", "u", &key_size); + g_variant_lookup (options, "cipher", "&s", &cipher); + g_variant_lookup (options, "cipher-mode", "&s", &cipher_mode); + g_variant_lookup (options, "resilience", "&s", &resilience); + g_variant_lookup (options, "hash", "&s", &hash); + g_variant_lookup (options, "max-hotzone-size", "u", &max_hotzone_size); + g_variant_lookup (options, "sector-size", "u", §or_size); + g_variant_lookup (options, "new-volume_key", "b", &new_volume_key); + // `offline` is already determined + g_variant_lookup (options, "pbkdf-type", "&s", &pbkdf_type); + + pbkdf = bd_crypto_luks_pbkdf_new (pbkdf_type, NULL, 0, 0, 0, 0); + params = bd_crypto_luks_reencrypt_params_new (key_size, cipher, cipher_mode, resilience, hash, max_hotzone_size, sector_size, new_volume_key, offline, pbkdf); + + if (! bd_crypto_luks_reencrypt (device, params, context, NULL /* prog_func -- TODO */, &error)) + { + g_dbus_method_invocation_return_error (invocation, + UDISKS_ERROR, + UDISKS_ERROR_FAILED, + "Error reencrypting encrypted device %s: %s", + device, + error->message); + udisks_simple_job_complete (UDISKS_SIMPLE_JOB (job), FALSE, error->message); + udisks_linux_block_encrypted_unlock (block); + goto out; + } + + // + udisks_linux_block_encrypted_unlock (block); + + udisks_encrypted_complete_reencrypt (encrypted, invocation); + udisks_simple_job_complete (UDISKS_SIMPLE_JOB (job), TRUE, NULL); + + out: + bd_crypto_luks_pbkdf_free (pbkdf); + bd_crypto_luks_reencrypt_params_free (params); + bd_crypto_keyslot_context_free (context); + if (object != NULL) + udisks_linux_block_object_release_cleanup_lock (UDISKS_LINUX_BLOCK_OBJECT (object)); + if (state != NULL) + udisks_state_check (state); + g_clear_object (&object); + g_clear_error (&error); + return TRUE; /* returning TRUE means that we handled the method invocation */ + +} + +/* ---------------------------------------------------------------------------------------------------- */ + static void encrypted_iface_init (UDisksEncryptedIface *iface) { @@ -1438,4 +1603,5 @@ encrypted_iface_init (UDisksEncryptedIface *iface) iface->handle_resize = handle_resize; iface->handle_convert = handle_convert; iface->handle_header_backup = handle_header_backup; + iface->handle_reencrypt = handle_reencrypt; } diff --git a/udisks/udisksclient.c b/udisks/udisksclient.c index 90ecac655..56ca6ca57 100644 --- a/udisks/udisksclient.c +++ b/udisks/udisksclient.c @@ -2736,6 +2736,7 @@ udisks_client_get_job_description_from_operation (const gchar *operation) g_hash_table_insert (hash, (gpointer) "encrypted-resize", (gpointer) C_("job", "Resizing Encrypted Device")); g_hash_table_insert (hash, (gpointer) "encrypted-convert", (gpointer) C_("job", "Converting Encrypted Device")); g_hash_table_insert (hash, (gpointer) "encrypted-header-backup", (gpointer) C_("job", "Backing Up Header of an Encrypted Device")); + g_hash_table_insert (hash, (gpointer) "encrypted-reencrypt", (gpointer) C_("job", "Reencrypting an Encrypted Device")); g_hash_table_insert (hash, (gpointer) "block-restore-encrypted-header", (gpointer) C_("job", "Restoring Header of an Encrypted Device")); g_hash_table_insert (hash, (gpointer) "swapspace-start", (gpointer) C_("job", "Starting Swap Device")); g_hash_table_insert (hash, (gpointer) "swapspace-stop", (gpointer) C_("job", "Stopping Swap Device")); From ced0a050b41025758ad3eedab9310a5729105df6 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Mon, 16 Dec 2024 10:22:13 +0100 Subject: [PATCH 2/3] Remove before merge Temporarily switch to copr repo containing libblockdev with reencryption support. --- .packit.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.packit.yaml b/.packit.yaml index 95e135c12..40a8eb745 100644 --- a/.packit.yaml +++ b/.packit.yaml @@ -24,7 +24,7 @@ jobs: - fedora-latest-stable-ppc64le - fedora-latest-stable-x86_64 additional_repos: - - "copr://@storage/udisks-daily" + - "copr://@storage/temp-reencryption" - job: tests trigger: pull_request @@ -34,7 +34,7 @@ jobs: environments: - artifacts: - type: repository-file - id: https://copr.fedorainfracloud.org/coprs/g/storage/udisks-daily/repo/fedora-$releasever/group_storage-udisks-daily-fedora-$releasever.repo + id: https://copr.fedorainfracloud.org/coprs/g/storage/reencryption/repo/fedora-$releasever/group_storage-reencryption-fedora-$releasever.repo # run tests for udisks consumers, see plans/ with `revdeps == yes` - job: tests @@ -53,7 +53,7 @@ jobs: - type: repository-file id: https://copr.fedorainfracloud.org/coprs/g/cockpit/main-builds/repo/fedora-$releasever/group_cockpit-main-builds-fedora-$releasever.repo - type: repository-file - id: https://copr.fedorainfracloud.org/coprs/g/storage/udisks-daily/repo/fedora-$releasever/group_storage-udisks-daily-fedora-$releasever.repo + id: https://copr.fedorainfracloud.org/coprs/g/storage/reencryption/repo/fedora-$releasever/group_storage-reencryption-fedora-$releasever.repo tmt: context: revdeps: "yes" From a91e0494985b0a10cf0f70d0d3409f749a73cf09 Mon Sep 17 00:00:00 2001 From: "Stepan \"Sciapan\" Yakimovich" Date: Mon, 14 Oct 2024 17:56:52 +0000 Subject: [PATCH 3/3] Core: Add support for LUKS encryption. --- data/org.freedesktop.UDisks2.xml | 13 +++ src/tests/dbus-tests/test_50_block.py | 29 ++++++ src/udiskslinuxblock.c | 124 ++++++++++++++++++++++++++ udisks/udisksclient.c | 1 + 4 files changed, 167 insertions(+) diff --git a/data/org.freedesktop.UDisks2.xml b/data/org.freedesktop.UDisks2.xml index a46f96a9b..700488c51 100644 --- a/data/org.freedesktop.UDisks2.xml +++ b/data/org.freedesktop.UDisks2.xml @@ -2360,6 +2360,19 @@ + + + + + + diff --git a/src/tests/dbus-tests/test_50_block.py b/src/tests/dbus-tests/test_50_block.py index 82d8a84c8..f210142e5 100644 --- a/src/tests/dbus-tests/test_50_block.py +++ b/src/tests/dbus-tests/test_50_block.py @@ -357,6 +357,35 @@ def test_rescan(self): disk.Rescan(self.no_options, dbus_interface=self.iface_prefix + '.Block') + def test_encrypt(self): + disk = self.vdevs[0] + device = self.get_device(disk) + self.assertIsNotNone(device) + + d = dbus.Dictionary(signature='sv') + d['passphrase'] = "shouldnotseeme" + d['key-size'] = dbus.UInt32(256) + d['cipher'] = "aes" + d['cipher-mode'] = "cbc-essiv:sha256" + d['resilience'] = "datashift" # required, otherwise won't work + d['hash'] = "sha256" + d['max-hotzone-size'] = dbus.UInt64(0) + d['sector-size'] = dbus.UInt32(512) + d['new-volume_key'] = True + + device.Encrypt(self.LUKS_PASSPHRASE, d, dbus_interface=self.iface_prefix + '.Block') + + # verify that device now has the .Encrypted interface + device = self.get_device(disk) + self.assertHasIface(device, "org.freedesktop.UDisks2.Encrypted") + + # verify that the newly encrypted device can be unlocked + luks_obj = device.Unlock("shouldnotseeme", self.no_options, + dbus_interface=self.iface_prefix + '.Encrypted') + self.assertIsNotNone(luks_obj) + ret, _ = self.run_command("ls /dev/mapper/luks*") + self.assertEqual(ret, 0) + class UdisksBlockRemovableTest(udiskstestcase.UdisksTestCase): '''Extra block device tests over a scsi_debug removable device''' diff --git a/src/udiskslinuxblock.c b/src/udiskslinuxblock.c index 5b5588b2c..708e855a1 100644 --- a/src/udiskslinuxblock.c +++ b/src/udiskslinuxblock.c @@ -4308,6 +4308,129 @@ handle_restore_encrypted_header (UDisksBlock *encrypted, /* ---------------------------------------------------------------------------------------------------- */ +/* runs in thread dedicated to handling method call */ +static gboolean +handle_encrypt (UDisksBlock *block, + GDBusMethodInvocation *invocation, + const gchar *passphrase, + GVariant *options) +{ + UDisksObject *object = NULL; + UDisksDaemon *daemon; + UDisksState *state = NULL; + uid_t caller_uid; + GError *error = NULL; + UDisksBaseJob *job = NULL; + + const gchar *device; + BDCryptoKeyslotContext *context = NULL; + guint32 key_size; + gchar *cipher; + gchar *cipher_mode; + gchar *resilience; + gchar *hash; + guint64 max_hotzone_size; + guint32 sector_size; + gboolean new_volume_key; + gboolean offline = TRUE; +// gchar *pbkdf_type; +// BDCryptoLUKSPBKDF *pbkdf = NULL; + BDCryptoLUKSReencryptParams *params = NULL; + + object = udisks_daemon_util_dup_object (block, &error); + if (object == NULL) + { + g_dbus_method_invocation_return_gerror (invocation, error); + goto out; + } + + daemon = udisks_linux_block_object_get_daemon (UDISKS_LINUX_BLOCK_OBJECT (object)); + state = udisks_daemon_get_state (daemon); + + udisks_linux_block_object_lock_for_cleanup (UDISKS_LINUX_BLOCK_OBJECT (object)); + udisks_state_check_block (state, udisks_linux_block_object_get_device_number (UDISKS_LINUX_BLOCK_OBJECT (object))); + + if (!udisks_daemon_util_get_caller_uid_sync (daemon, invocation, NULL /* GCancellable */, &caller_uid, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + goto out; + } + + job = udisks_daemon_launch_simple_job (daemon, + UDISKS_OBJECT (object), + "block-encrypt", + caller_uid, + NULL); + if (job == NULL) + { + g_dbus_method_invocation_return_error (invocation, UDISKS_ERROR, UDISKS_ERROR_FAILED, + "Failed to create a job object"); + goto out; + } + + udisks_linux_block_encrypted_lock (block); + // do stuff here + device = udisks_block_get_device (block); + + context = bd_crypto_keyslot_context_new_passphrase ((const guint8 *) passphrase, + strlen(passphrase), + &error); + if (!context) + { + g_dbus_method_invocation_return_error (invocation, + UDISKS_ERROR, + UDISKS_ERROR_FAILED, + "Error encrypting block device %s (ctx): %s", + device, + error->message); + udisks_simple_job_complete (UDISKS_SIMPLE_JOB (job), FALSE, error->message); + udisks_linux_block_encrypted_unlock (block); + goto out; + } + + g_variant_lookup (options, "key-size", "u", &key_size); + g_variant_lookup (options, "cipher", "&s", &cipher); + g_variant_lookup (options, "cipher-mode", "&s", &cipher_mode); + g_variant_lookup (options, "resilience", "&s", &resilience); + g_variant_lookup (options, "hash", "&s", &hash); + g_variant_lookup (options, "max-hotzone-size", "u", &max_hotzone_size); + g_variant_lookup (options, "sector-size", "u", §or_size); + g_variant_lookup (options, "new-volume_key", "b", &new_volume_key); + // `offline` is already determined + + params = bd_crypto_luks_reencrypt_params_new (key_size, cipher, cipher_mode, resilience, hash, max_hotzone_size, sector_size, new_volume_key, offline, NULL); + + if (! bd_crypto_luks_encrypt (device, params, context, NULL /* prog_func -- TODO */, &error)) + { + g_dbus_method_invocation_return_error (invocation, + UDISKS_ERROR, + UDISKS_ERROR_FAILED, + "Error encrypting block device %s (enc): %s", + device, + error->message); + udisks_simple_job_complete (UDISKS_SIMPLE_JOB (job), FALSE, error->message); + udisks_linux_block_encrypted_unlock (block); + goto out; + } + + // + udisks_linux_block_encrypted_unlock (block); + + udisks_block_complete_encrypt (block, invocation); + udisks_simple_job_complete (UDISKS_SIMPLE_JOB (job), TRUE, NULL); + + out: + if (object != NULL) + udisks_linux_block_object_release_cleanup_lock (UDISKS_LINUX_BLOCK_OBJECT (object)); + if (state != NULL) + udisks_state_check (state); + g_clear_object (&object); + g_clear_error (&error); + return TRUE; /* returning TRUE means that we handled the method invocation */ +} + +/* ---------------------------------------------------------------------------------------------------- */ + static void block_iface_init (UDisksBlockIface *iface) { @@ -4322,4 +4445,5 @@ block_iface_init (UDisksBlockIface *iface) iface->handle_open_device = handle_open_device; iface->handle_rescan = handle_rescan; iface->handle_restore_encrypted_header = handle_restore_encrypted_header; + iface->handle_encrypt = handle_encrypt; } diff --git a/udisks/udisksclient.c b/udisks/udisksclient.c index 56ca6ca57..38c1ccaf8 100644 --- a/udisks/udisksclient.c +++ b/udisks/udisksclient.c @@ -2738,6 +2738,7 @@ udisks_client_get_job_description_from_operation (const gchar *operation) g_hash_table_insert (hash, (gpointer) "encrypted-header-backup", (gpointer) C_("job", "Backing Up Header of an Encrypted Device")); g_hash_table_insert (hash, (gpointer) "encrypted-reencrypt", (gpointer) C_("job", "Reencrypting an Encrypted Device")); g_hash_table_insert (hash, (gpointer) "block-restore-encrypted-header", (gpointer) C_("job", "Restoring Header of an Encrypted Device")); + g_hash_table_insert (hash, (gpointer) "block-encrypt", (gpointer) C_("job", "Encrypting a Block Device")); g_hash_table_insert (hash, (gpointer) "swapspace-start", (gpointer) C_("job", "Starting Swap Device")); g_hash_table_insert (hash, (gpointer) "swapspace-stop", (gpointer) C_("job", "Stopping Swap Device")); g_hash_table_insert (hash, (gpointer) "swapspace-modify", (gpointer) C_("job", "Modifying Swap Device"));