From aee97817b0c8be0fc1a9943ce37570eb7775e1ee Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Wed, 26 Aug 2026 14:37:17 +0200 Subject: [PATCH 1/5] s390: Add support for pervasive encryption using zkey Add a new 'paes' technology and 'zkey' runtime dependency to the s390 plugin, along with new functions for managing secure keys: - bd_s390_zkey_generate() generates a new XTS/LUKS2 secure key in the secure key repository - bd_s390_zkey_list() lists the secure keys in the repository (all keys or a single one by name), returning the parsed information in the new BDS390ZkeyInfo boxed struct - bd_s390_zkey_remove() removes a secure key from the repository Co-Authored-By: Claude Opus 4.8 --- docs/libblockdev-sections.txt | 6 + src/lib/plugin_apis/s390.api | 149 ++++++++++++++++++ src/plugins/s390.c | 276 +++++++++++++++++++++++++++++++++- src/plugins/s390.h | 40 +++++ tests/s390_test.py | 11 ++ 5 files changed, 481 insertions(+), 1 deletion(-) diff --git a/docs/libblockdev-sections.txt b/docs/libblockdev-sections.txt index 2a62a1b7f..90787b966 100644 --- a/docs/libblockdev-sections.txt +++ b/docs/libblockdev-sections.txt @@ -743,6 +743,12 @@ bd_s390_zfcp_sanitize_lun_input bd_s390_zfcp_online bd_s390_zfcp_scsi_offline bd_s390_zfcp_offline +bd_s390_zkey_generate +BDS390ZkeyInfo +bd_s390_zkey_info_copy +bd_s390_zkey_info_free +bd_s390_zkey_list +bd_s390_zkey_remove BDS390Tech BDS390TechMode bd_s390_is_tech_avail diff --git a/src/lib/plugin_apis/s390.api b/src/lib/plugin_apis/s390.api index ec8242ae6..7ac955f86 100644 --- a/src/lib/plugin_apis/s390.api +++ b/src/lib/plugin_apis/s390.api @@ -15,16 +15,19 @@ typedef enum { BD_S390_ERROR_FORMAT_FAILED, BD_S390_ERROR_DASDFMT, BD_S390_ERROR_IO, + BD_S390_ERROR_ZKEY, } BDS390Error; typedef enum { BD_S390_TECH_DASD = 0, BD_S390_TECH_ZFCP, + BD_S390_TECH_PAES, } BDS390Tech; typedef enum { BD_S390_TECH_MODE_MODIFY = 1 << 0, BD_S390_TECH_MODE_QUERY = 1 << 1, + BD_S390_TECH_MODE_CREATE = 1 << 2, } BDS390TechMode; /** @@ -175,4 +178,150 @@ gboolean bd_s390_zfcp_scsi_offline(const gchar *devno, const gchar *wwpn, const */ gboolean bd_s390_zfcp_offline (const gchar *devno, const gchar *wwpn, const gchar *lun, GError **error); +/** + * bd_s390_zkey_generate: + * @name: name of the secure key to generate in the secure key repository + * @key_type: (nullable): type of the secure key to generate (e.g. "CCA-AESCIPHER") or %NULL for the zkey default + * @keybits: size of the key in bits or 0 for the zkey default + * @volumes: (nullable) (array zero-terminated=1): list of volumes to associate with the key, each given + * in the "volume:dmname" format, or %NULL + * @apqns: (nullable) (array zero-terminated=1): list of cryptographic adapters (APQNs) to associate with + * the key, each given in the "card.domain" format, or %NULL + * @sector_size: sector size in bytes to use with dm-crypt or 0 for the system default + * @dummy_passphrase: whether to generate and associate a dummy passphrase with the key + * @extra: (nullable) (array zero-terminated=1): extra options for the key generation (right now + * passed to the 'zkey' utility) + * @error: (out) (optional): place to store error (if any) + * + * Generates a new secure key for pervasive encryption using the 'zkey' utility and stores it + * in the secure key repository. The key is generated as an XTS key with the 'LUKS2' volume type. + * + * Returns: whether the secure key was successfully generated or not + * + * Tech category: %BD_S390_TECH_PAES-%BD_S390_TECH_MODE_CREATE + */ +gboolean bd_s390_zkey_generate (const gchar *name, const gchar *key_type, guint64 keybits, const gchar **volumes, const gchar **apqns, guint64 sector_size, gboolean dummy_passphrase, const BDExtraArg **extra, GError **error); + +#define BD_S390_TYPE_ZKEY_INFO (bd_s390_zkey_info_get_type ()) +GType bd_s390_zkey_info_get_type(); + +/** + * BDS390ZkeyInfo: + * @name: name of the secure key in the secure key repository; + * @description: user provided description of the key (empty if none was set); + * @secure_key_size: size of the secure key in bytes; + * @clear_key_size: size of the effective (clear) key in bits; + * @xts: whether the key is an XTS type key or not; + * @key_type: type of the secure key (e.g. "CCA-AESCIPHER"); + * @volumes: (array zero-terminated=1): volumes associated with the key, each in the "volume:dmname" format; + * @apqns: (array zero-terminated=1): cryptographic adapters (APQNs) associated with the key, each in the "card.domain" format; + * @key_file_name: full path to the file holding the secure key; + * @sector_size: sector size in bytes to use with dm-crypt or 0 for the system default; + * @volume_type: volume type the key is to be used with (e.g. "LUKS2"); + * @dummy_passphrase: (nullable): path to the dummy passphrase file associated with the key or %NULL if none is set; + */ +typedef struct BDS390ZkeyInfo { + gchar *name; + gchar *description; + guint64 secure_key_size; + guint64 clear_key_size; + gboolean xts; + gchar *key_type; + gchar **volumes; + gchar **apqns; + gchar *key_file_name; + guint64 sector_size; + gchar *volume_type; + gchar *dummy_passphrase; +} BDS390ZkeyInfo; + +/** + * bd_s390_zkey_info_free: (skip) + * @info: (nullable): %BDS390ZkeyInfo to free + * + * Frees @info. + */ +void bd_s390_zkey_info_free (BDS390ZkeyInfo *info) { + if (info == NULL) + return; + + g_free (info->name); + g_free (info->description); + g_free (info->key_type); + g_strfreev (info->volumes); + g_strfreev (info->apqns); + g_free (info->key_file_name); + g_free (info->volume_type); + g_free (info->dummy_passphrase); + g_free (info); +} + +/** + * bd_s390_zkey_info_copy: (skip) + * @info: (nullable): %BDS390ZkeyInfo to copy + * + * Creates a new copy of @info. + */ +BDS390ZkeyInfo* bd_s390_zkey_info_copy (BDS390ZkeyInfo *info) { + if (info == NULL) + return NULL; + + BDS390ZkeyInfo *new_info = g_new0 (BDS390ZkeyInfo, 1); + + new_info->name = g_strdup (info->name); + new_info->description = g_strdup (info->description); + new_info->secure_key_size = info->secure_key_size; + new_info->clear_key_size = info->clear_key_size; + new_info->xts = info->xts; + new_info->key_type = g_strdup (info->key_type); + new_info->volumes = g_strdupv (info->volumes); + new_info->apqns = g_strdupv (info->apqns); + new_info->key_file_name = g_strdup (info->key_file_name); + new_info->sector_size = info->sector_size; + new_info->volume_type = g_strdup (info->volume_type); + new_info->dummy_passphrase = g_strdup (info->dummy_passphrase); + + return new_info; +} + +GType bd_s390_zkey_info_get_type () { + static GType type = 0; + + if (G_UNLIKELY(type == 0)) { + type = g_boxed_type_register_static("BDS390ZkeyInfo", + (GBoxedCopyFunc) bd_s390_zkey_info_copy, + (GBoxedFreeFunc) bd_s390_zkey_info_free); + } + + return type; +} + +/** + * bd_s390_zkey_list: + * @name: (nullable): name of a single secure key to get information about or %NULL to list all keys + * @error: (out) (optional): place to store error (if any) + * + * Lists the secure keys stored in the secure key repository using the 'zkey' utility. If @name + * is given, only the information about the matching key is returned. + * + * Returns: (array zero-terminated=1) (transfer full): information about the secure keys in the + * repository (an empty list if there are none) or %NULL in case of error + * + * Tech category: %BD_S390_TECH_PAES-%BD_S390_TECH_MODE_QUERY + */ +BDS390ZkeyInfo** bd_s390_zkey_list (const gchar *name, GError **error); + +/** + * bd_s390_zkey_remove: + * @name: name of the secure key to remove from the secure key repository + * @error: (out) (optional): place to store error (if any) + * + * Removes the secure key @name from the secure key repository using the 'zkey' utility. + * + * Returns: whether the secure key was successfully removed or not + * + * Tech category: %BD_S390_TECH_PAES-%BD_S390_TECH_MODE_MODIFY + */ +gboolean bd_s390_zkey_remove (const gchar *name, GError **error); + #endif /* BD_S390_API */ diff --git a/src/plugins/s390.c b/src/plugins/s390.c index 97f831da8..5e8a5aa72 100644 --- a/src/plugins/s390.c +++ b/src/plugins/s390.c @@ -53,11 +53,14 @@ static GMutex deps_check_lock; #define DEPS_DASDFMT 0 #define DEPS_DASDFMT_MASK (1 << DEPS_DASDFMT) -#define DEPS_LAST 1 +#define DEPS_ZKEY 1 +#define DEPS_ZKEY_MASK (1 << DEPS_ZKEY) +#define DEPS_LAST 2 static const UtilDep deps[DEPS_LAST] = { /* dasdfmt doesn't return version info */ {"dasdfmt", NULL, NULL, NULL}, + {"zkey", NULL, NULL, NULL}, }; @@ -104,6 +107,9 @@ gboolean bd_s390_is_tech_avail (BDS390Tech tech, guint64 mode, GError **error) { return check_deps (&avail_deps, DEPS_DASDFMT_MASK, deps, DEPS_LAST, &deps_check_lock, error); else return TRUE; + case BD_S390_TECH_PAES: + /* pervasive encryption support requires the 'zkey' utility */ + return check_deps (&avail_deps, DEPS_ZKEY_MASK, deps, DEPS_LAST, &deps_check_lock, error); default: g_set_error_literal (error, BD_S390_ERROR, BD_S390_ERROR_TECH_UNAVAIL, "Unknown technology"); return FALSE; @@ -1055,3 +1061,271 @@ gboolean bd_s390_zfcp_offline (const gchar *devno, const gchar *wwpn, const gcha bd_utils_report_finished (progress_id, "Completed"); return TRUE; } + +/** + * bd_s390_zkey_generate: + * @name: name of the secure key to generate in the secure key repository + * @key_type: (nullable): type of the secure key to generate (e.g. "CCA-AESCIPHER") or %NULL for the zkey default + * @keybits: size of the key in bits or 0 for the zkey default + * @volumes: (nullable) (array zero-terminated=1): list of volumes to associate with the key, each given + * in the "volume:dmname" format, or %NULL + * @apqns: (nullable) (array zero-terminated=1): list of cryptographic adapters (APQNs) to associate with + * the key, each given in the "card.domain" format, or %NULL + * @sector_size: sector size in bytes to use with dm-crypt or 0 for the system default + * @dummy_passphrase: whether to generate and associate a dummy passphrase with the key + * @error: (out) (optional): place to store error (if any) + * + * Generates a new secure key for pervasive encryption using the 'zkey' utility and stores it + * in the secure key repository. The key is generated as an XTS key with the 'LUKS2' volume type. + * + * Returns: whether the secure key was successfully generated or not + * + * Tech category: %BD_S390_TECH_PAES-%BD_S390_TECH_MODE_CREATE + */ +gboolean bd_s390_zkey_generate (const gchar *name, const gchar *key_type, guint64 keybits, const gchar **volumes, const gchar **apqns, guint64 sector_size, gboolean dummy_passphrase, const BDExtraArg **extra, GError **error) { + gboolean success = FALSE; + GPtrArray *argv = NULL; + + if (!check_deps (&avail_deps, DEPS_ZKEY_MASK, deps, DEPS_LAST, &deps_check_lock, error)) + return FALSE; + + if (name == NULL || *name == '\0') { + g_set_error_literal (error, BD_S390_ERROR, BD_S390_ERROR_ZKEY, + "Key name must be specified"); + return FALSE; + } + + argv = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (argv, g_strdup ("zkey")); + g_ptr_array_add (argv, g_strdup ("generate")); + g_ptr_array_add (argv, g_strdup ("--name")); + g_ptr_array_add (argv, g_strdup (name)); + + if (key_type != NULL) { + g_ptr_array_add (argv, g_strdup ("--key-type")); + g_ptr_array_add (argv, g_strdup (key_type)); + } + if (keybits != 0) { + g_ptr_array_add (argv, g_strdup ("--keybits")); + g_ptr_array_add (argv, g_strdup_printf ("%"G_GUINT64_FORMAT, keybits)); + } + if (volumes != NULL && *volumes != NULL) { + /* zkey expects a single comma-separated list */ + g_ptr_array_add (argv, g_strdup ("--volumes")); + g_ptr_array_add (argv, g_strjoinv (",", (gchar **) volumes)); + } + if (apqns != NULL && *apqns != NULL) { + /* zkey expects a single comma-separated list */ + g_ptr_array_add (argv, g_strdup ("--apqns")); + g_ptr_array_add (argv, g_strjoinv (",", (gchar **) apqns)); + } + if (sector_size != 0) { + g_ptr_array_add (argv, g_strdup ("--sector-size")); + g_ptr_array_add (argv, g_strdup_printf ("%"G_GUINT64_FORMAT, sector_size)); + } + if (dummy_passphrase) + g_ptr_array_add (argv, g_strdup ("--gen-dummy-passphrase")); + + /* pervasive encryption of LUKS2 volumes always uses XTS secure keys */ + g_ptr_array_add (argv, g_strdup ("--xts")); + g_ptr_array_add (argv, g_strdup ("--volume-type")); + g_ptr_array_add (argv, g_strdup ("LUKS2")); + + g_ptr_array_add (argv, NULL); + + success = bd_utils_exec_and_report_error ((const gchar **) argv->pdata, extra, error); + g_ptr_array_free (argv, TRUE); + + return success; +} + +/** + * bd_s390_zkey_info_free: (skip) + * @info: (nullable): %BDS390ZkeyInfo to free + * + * Frees @info. + */ +void bd_s390_zkey_info_free (BDS390ZkeyInfo *info) { + if (info == NULL) + return; + + g_free (info->name); + g_free (info->description); + g_free (info->key_type); + g_strfreev (info->volumes); + g_strfreev (info->apqns); + g_free (info->key_file_name); + g_free (info->volume_type); + g_free (info->dummy_passphrase); + g_free (info); +} + +/** + * bd_s390_zkey_info_copy: (skip) + * @info: (nullable): %BDS390ZkeyInfo to copy + * + * Creates a new copy of @info. + */ +BDS390ZkeyInfo* bd_s390_zkey_info_copy (BDS390ZkeyInfo *info) { + if (info == NULL) + return NULL; + + BDS390ZkeyInfo *new_info = g_new0 (BDS390ZkeyInfo, 1); + + new_info->name = g_strdup (info->name); + new_info->description = g_strdup (info->description); + new_info->secure_key_size = info->secure_key_size; + new_info->clear_key_size = info->clear_key_size; + new_info->xts = info->xts; + new_info->key_type = g_strdup (info->key_type); + new_info->volumes = g_strdupv (info->volumes); + new_info->apqns = g_strdupv (info->apqns); + new_info->key_file_name = g_strdup (info->key_file_name); + new_info->sector_size = info->sector_size; + new_info->volume_type = g_strdup (info->volume_type); + new_info->dummy_passphrase = g_strdup (info->dummy_passphrase); + + return new_info; +} + +/** + * bd_s390_zkey_list: + * @name: (nullable): name of a single secure key to get information about or %NULL to list all keys + * @error: (out) (optional): place to store error (if any) + * + * Lists the secure keys stored in the secure key repository using the 'zkey' utility. If @name + * is given, only the information about the matching key is returned. + * + * Returns: (array zero-terminated=1) (transfer full): information about the secure keys in the + * repository (an empty list if there are none) or %NULL in case of error + * + * Tech category: %BD_S390_TECH_PAES-%BD_S390_TECH_MODE_QUERY + */ +BDS390ZkeyInfo** bd_s390_zkey_list (const gchar *name, GError **error) { + const gchar *argv[5] = {"zkey", "list", NULL, NULL, NULL}; + guint next = 2; + gchar *output = NULL; + gchar *stderr_data = NULL; + gint status = 0; + gboolean success = FALSE; + GPtrArray *keys = NULL; + gchar **lines = NULL; + BDS390ZkeyInfo *cur_info = NULL; + + if (!check_deps (&avail_deps, DEPS_ZKEY_MASK, deps, DEPS_LAST, &deps_check_lock, error)) + return NULL; + + if (name != NULL && *name != '\0') { + argv[next++] = "--name"; + argv[next++] = name; + } + + /* not using bd_utils_exec_and_capture_output because it treats an empty output + (i.e. no keys in the repository) as an error */ + success = bd_utils_exec_and_capture_output_no_progress (argv, NULL, &output, &stderr_data, &status, error); + if (!success) { + g_free (output); + g_free (stderr_data); + return NULL; + } + if (status != 0) { + g_set_error (error, BD_S390_ERROR, BD_S390_ERROR_ZKEY, + "Failed to list secure keys: %s", stderr_data ? stderr_data : ""); + g_free (output); + g_free (stderr_data); + return NULL; + } + g_free (stderr_data); + + keys = g_ptr_array_new (); + + lines = g_strsplit (output ? output : "", "\n", -1); + g_free (output); + + for (gchar **line_p = lines; *line_p != NULL; line_p++) { + gchar *colon = NULL; + gchar *label = NULL; + gchar *value = NULL; + + /* split the line on the first ':' -- the label never contains a colon while + some values (e.g. Volumes) do; lines without a colon are separators, blank + lines or continuation lines (e.g. the second line of Verification pattern) */ + colon = strchr (*line_p, ':'); + if (colon == NULL) + continue; + + label = g_strndup (*line_p, colon - *line_p); + label = g_strstrip (label); + value = g_strdup (colon + 1); + value = g_strstrip (value); + + if (g_strcmp0 (label, "Key") == 0) { + /* the "Key" line starts a new key record */ + cur_info = g_new0 (BDS390ZkeyInfo, 1); + cur_info->name = g_strdup (value); + g_ptr_array_add (keys, cur_info); + } else if (cur_info == NULL) { + /* a field line before any "Key" line -- ignore it */ + } else if (g_strcmp0 (label, "Description") == 0) { + cur_info->description = g_strdup (value); + } else if (g_strcmp0 (label, "Secure key size") == 0) { + cur_info->secure_key_size = g_ascii_strtoull (value, NULL, 0); + } else if (g_strcmp0 (label, "Clear key size") == 0) { + cur_info->clear_key_size = g_ascii_strtoull (value, NULL, 0); + } else if (g_strcmp0 (label, "XTS type key") == 0) { + cur_info->xts = (g_ascii_strcasecmp (value, "Yes") == 0); + } else if (g_strcmp0 (label, "Key type") == 0) { + cur_info->key_type = g_strdup (value); + } else if (g_strcmp0 (label, "Volumes") == 0) { + cur_info->volumes = g_strsplit (value, ",", -1); + } else if (g_strcmp0 (label, "APQNs") == 0) { + cur_info->apqns = g_strsplit (value, ",", -1); + } else if (g_strcmp0 (label, "Key file name") == 0) { + cur_info->key_file_name = g_strdup (value); + } else if (g_strcmp0 (label, "Sector size") == 0) { + /* "(system default)" is reported as 0 */ + cur_info->sector_size = g_ascii_strtoull (value, NULL, 0); + } else if (g_strcmp0 (label, "Volume type") == 0) { + cur_info->volume_type = g_strdup (value); + } else if (g_strcmp0 (label, "Dummy passphrase") == 0) { + /* "(none)" means no dummy passphrase is set, otherwise it is a path to the passphrase file */ + if (g_strcmp0 (value, "(none)") != 0) + cur_info->dummy_passphrase = g_strdup (value); + } + + g_free (label); + g_free (value); + } + + g_strfreev (lines); + + g_ptr_array_add (keys, NULL); + return (BDS390ZkeyInfo **) g_ptr_array_free (keys, FALSE); +} + +/** + * bd_s390_zkey_remove: + * @name: name of the secure key to remove from the secure key repository + * @error: (out) (optional): place to store error (if any) + * + * Removes the secure key @name from the secure key repository using the 'zkey' utility. + * + * Returns: whether the secure key was successfully removed or not + * + * Tech category: %BD_S390_TECH_PAES-%BD_S390_TECH_MODE_MODIFY + */ +gboolean bd_s390_zkey_remove (const gchar *name, GError **error) { + /* --force suppresses the interactive y/n confirmation */ + const gchar *argv[6] = {"zkey", "remove", "--name", name, "--force", NULL}; + + if (!check_deps (&avail_deps, DEPS_ZKEY_MASK, deps, DEPS_LAST, &deps_check_lock, error)) + return FALSE; + + if (name == NULL || *name == '\0') { + g_set_error_literal (error, BD_S390_ERROR, BD_S390_ERROR_ZKEY, + "Key name must be specified"); + return FALSE; + } + + return bd_utils_exec_and_report_error (argv, NULL, error); +} diff --git a/src/plugins/s390.h b/src/plugins/s390.h index 1053fe4d8..755fceea8 100644 --- a/src/plugins/s390.h +++ b/src/plugins/s390.h @@ -12,18 +12,54 @@ typedef enum { BD_S390_ERROR_FORMAT_FAILED, BD_S390_ERROR_DASDFMT, BD_S390_ERROR_IO, + BD_S390_ERROR_ZKEY, } BDS390Error; typedef enum { BD_S390_TECH_DASD = 0, BD_S390_TECH_ZFCP, + BD_S390_TECH_PAES, } BDS390Tech; typedef enum { BD_S390_TECH_MODE_MODIFY = 1 << 0, BD_S390_TECH_MODE_QUERY = 1 << 1, + BD_S390_TECH_MODE_CREATE = 1 << 2, } BDS390TechMode; +/** + * BDS390ZkeyInfo: + * @name: name of the secure key in the secure key repository; + * @description: user provided description of the key (empty if none was set); + * @secure_key_size: size of the secure key in bytes; + * @clear_key_size: size of the effective (clear) key in bits; + * @xts: whether the key is an XTS type key or not; + * @key_type: type of the secure key (e.g. "CCA-AESCIPHER"); + * @volumes: (array zero-terminated=1): volumes associated with the key, each in the "volume:dmname" format; + * @apqns: (array zero-terminated=1): cryptographic adapters (APQNs) associated with the key, each in the "card.domain" format; + * @key_file_name: full path to the file holding the secure key; + * @sector_size: sector size in bytes to use with dm-crypt or 0 for the system default; + * @volume_type: volume type the key is to be used with (e.g. "LUKS2"); + * @dummy_passphrase: (nullable): path to the dummy passphrase file associated with the key or %NULL if none is set; + */ +typedef struct BDS390ZkeyInfo { + gchar *name; + gchar *description; + guint64 secure_key_size; + guint64 clear_key_size; + gboolean xts; + gchar *key_type; + gchar **volumes; + gchar **apqns; + gchar *key_file_name; + guint64 sector_size; + gchar *volume_type; + gchar *dummy_passphrase; +} BDS390ZkeyInfo; + +void bd_s390_zkey_info_free (BDS390ZkeyInfo *info); +BDS390ZkeyInfo* bd_s390_zkey_info_copy (BDS390ZkeyInfo *info); + /* * If using the plugin as a standalone library, the following functions should * be called to: @@ -51,4 +87,8 @@ gboolean bd_s390_zfcp_online (const gchar *devno, const gchar *wwpn, const gchar gboolean bd_s390_zfcp_scsi_offline(const gchar *devno, const gchar *wwpn, const gchar *lun, GError **error); gboolean bd_s390_zfcp_offline(const gchar *devno, const gchar *wwpn, const gchar *lun, GError **error); +gboolean bd_s390_zkey_generate (const gchar *name, const gchar *key_type, guint64 keybits, const gchar **volumes, const gchar **apqns, guint64 sector_size, gboolean dummy_passphrase, const BDExtraArg **extra, GError **error); +BDS390ZkeyInfo** bd_s390_zkey_list (const gchar *name, GError **error); +gboolean bd_s390_zkey_remove (const gchar *name, GError **error); + #endif /* BD_S390 */ diff --git a/tests/s390_test.py b/tests/s390_test.py index da93e61bf..6469c83fa 100644 --- a/tests/s390_test.py +++ b/tests/s390_test.py @@ -111,3 +111,14 @@ def test_missing_dependencies(self): # dasdfmt is not available, so the s390 plugin should fail to load with self.assertRaisesRegex(GLib.GError, "The 'dasdfmt' utility is not available"): BlockDev.s390_is_tech_avail(BlockDev.S390Tech.DASD, BlockDev.S390TechMode.MODIFY) + + @tag_test(TestTags.EXTRADEPS, TestTags.NOSTORAGE) + def test_missing_zkey(self): + """Verify that checking for pervasive encryption support works as expected""" + + with fake_path(all_but="zkey"): + # zkey is not available, so the PAES technology should not be available + with self.assertRaisesRegex(GLib.GError, "The 'zkey' utility is not available"): + BlockDev.s390_is_tech_avail(BlockDev.S390Tech.PAES, BlockDev.S390TechMode.CREATE) + with self.assertRaisesRegex(GLib.GError, "The 'zkey' utility is not available"): + BlockDev.s390_is_tech_avail(BlockDev.S390Tech.PAES, BlockDev.S390TechMode.QUERY) From 3e0f4077fb53d8733b2af4d6e27ec4b4dd38d7fc Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Thu, 27 Aug 2026 13:45:29 +0200 Subject: [PATCH 2/5] s390: Add support for validating the PAES setup of a LUKS2 device Add two new functions wrapping the 'zkey-cryptsetup' utility for working with the pervasive encryption setup of an existing LUKS2 volume: - bd_s390_zkey_cryptsetup_setvp() sets the verification pattern of a secure key in the LUKS2 metadata of a device - bd_s390_zkey_cryptsetup_validate() validates that a LUKS2 device is correctly set up for pervasive encryption with a given secure key Both add 'zkey-cryptsetup' as a new runtime dependency of the s390 plugin. Co-Authored-By: Claude Opus 4.8 --- docs/libblockdev-sections.txt | 2 + src/lib/plugin_apis/s390.api | 39 +++++++++++++++ src/plugins/s390.c | 94 +++++++++++++++++++++++++++++++++-- src/plugins/s390.h | 2 + tests/s390_test.py | 17 +++++-- tests/utils.py | 3 +- 6 files changed, 147 insertions(+), 10 deletions(-) diff --git a/docs/libblockdev-sections.txt b/docs/libblockdev-sections.txt index 90787b966..c134c079b 100644 --- a/docs/libblockdev-sections.txt +++ b/docs/libblockdev-sections.txt @@ -749,6 +749,8 @@ bd_s390_zkey_info_copy bd_s390_zkey_info_free bd_s390_zkey_list bd_s390_zkey_remove +bd_s390_zkey_cryptsetup_setvp +bd_s390_zkey_cryptsetup_validate BDS390Tech BDS390TechMode bd_s390_is_tech_avail diff --git a/src/lib/plugin_apis/s390.api b/src/lib/plugin_apis/s390.api index 7ac955f86..7b48517ed 100644 --- a/src/lib/plugin_apis/s390.api +++ b/src/lib/plugin_apis/s390.api @@ -324,4 +324,43 @@ BDS390ZkeyInfo** bd_s390_zkey_list (const gchar *name, GError **error); */ gboolean bd_s390_zkey_remove (const gchar *name, GError **error); +/** + * bd_s390_zkey_cryptsetup_setvp: + * @device: LUKS2 device to set the verification pattern on + * @key_file: path to a file containing the LUKS passphrase used to unlock a keyslot of @device + * @extra: (nullable) (array zero-terminated=1): extra options for setting the verification pattern + * (right now passed to the 'zkey-cryptsetup' utility) + * @error: (out) (optional): place to store error (if any) + * + * Sets the verification pattern of the secure key in the metadata of the LUKS2 volume @device + * using the 'zkey-cryptsetup' utility. The verification pattern is used to identify the secure + * key associated with the volume. Setting the verification pattern requires unlocking a keyslot + * of @device, so the LUKS passphrase needs to be provided in @key_file (this is a passphrase + * file, not the secure key file). + * + * Returns: whether the verification pattern was successfully set or not + * + * Tech category: %BD_S390_TECH_PAES-%BD_S390_TECH_MODE_MODIFY + */ +gboolean bd_s390_zkey_cryptsetup_setvp (const gchar *device, const gchar *key_file, const BDExtraArg **extra, GError **error); + +/** + * bd_s390_zkey_cryptsetup_validate: + * @device: LUKS2 device to validate + * @key_file: path to a file containing the LUKS passphrase used to unlock a keyslot of @device + * @extra: (nullable) (array zero-terminated=1): extra options for the validation + * (right now passed to the 'zkey-cryptsetup' utility) + * @error: (out) (optional): place to store error (if any) + * + * Validates that the LUKS2 volume @device is correctly set up for pervasive encryption using the + * 'zkey-cryptsetup' utility. Validation requires unlocking a keyslot of @device, so the LUKS + * passphrase needs to be provided in @key_file (this is a passphrase file, not the secure key + * file). + * + * Returns: whether the LUKS2 volume @device is correctly set up for pervasive encryption or not + * + * Tech category: %BD_S390_TECH_PAES-%BD_S390_TECH_MODE_QUERY + */ +gboolean bd_s390_zkey_cryptsetup_validate (const gchar *device, const gchar *key_file, const BDExtraArg **extra, GError **error); + #endif /* BD_S390_API */ diff --git a/src/plugins/s390.c b/src/plugins/s390.c index 5e8a5aa72..45ec4dc18 100644 --- a/src/plugins/s390.c +++ b/src/plugins/s390.c @@ -55,12 +55,15 @@ static GMutex deps_check_lock; #define DEPS_DASDFMT_MASK (1 << DEPS_DASDFMT) #define DEPS_ZKEY 1 #define DEPS_ZKEY_MASK (1 << DEPS_ZKEY) -#define DEPS_LAST 2 +#define DEPS_ZKEY_CRYPTSETUP 2 +#define DEPS_ZKEY_CRYPTSETUP_MASK (1 << DEPS_ZKEY_CRYPTSETUP) +#define DEPS_LAST 3 static const UtilDep deps[DEPS_LAST] = { /* dasdfmt doesn't return version info */ {"dasdfmt", NULL, NULL, NULL}, {"zkey", NULL, NULL, NULL}, + {"zkey-cryptsetup", NULL, NULL, NULL}, }; @@ -107,9 +110,15 @@ gboolean bd_s390_is_tech_avail (BDS390Tech tech, guint64 mode, GError **error) { return check_deps (&avail_deps, DEPS_DASDFMT_MASK, deps, DEPS_LAST, &deps_check_lock, error); else return TRUE; - case BD_S390_TECH_PAES: - /* pervasive encryption support requires the 'zkey' utility */ - return check_deps (&avail_deps, DEPS_ZKEY_MASK, deps, DEPS_LAST, &deps_check_lock, error); + case BD_S390_TECH_PAES: { + /* pervasive encryption support always requires the 'zkey' utility; querying and + modifying the pervasive encryption setup of an existing LUKS2 volume additionally + requires the 'zkey-cryptsetup' utility */ + guint req_deps = DEPS_ZKEY_MASK; + if (mode & (BD_S390_TECH_MODE_QUERY | BD_S390_TECH_MODE_MODIFY)) + req_deps |= DEPS_ZKEY_CRYPTSETUP_MASK; + return check_deps (&avail_deps, req_deps, deps, DEPS_LAST, &deps_check_lock, error); + } default: g_set_error_literal (error, BD_S390_ERROR, BD_S390_ERROR_TECH_UNAVAIL, "Unknown technology"); return FALSE; @@ -1329,3 +1338,80 @@ gboolean bd_s390_zkey_remove (const gchar *name, GError **error) { return bd_utils_exec_and_report_error (argv, NULL, error); } + +/** + * bd_s390_zkey_cryptsetup_setvp: + * @device: LUKS2 device to set the verification pattern on + * @key_file: path to a file containing the LUKS passphrase used to unlock a keyslot of @device + * @extra: (nullable) (array zero-terminated=1): extra options for setting the verification pattern + * (right now passed to the 'zkey-cryptsetup' utility) + * @error: (out) (optional): place to store error (if any) + * + * Sets the verification pattern of the secure key in the metadata of the LUKS2 volume @device + * using the 'zkey-cryptsetup' utility. The verification pattern is used to identify the secure + * key associated with the volume. Setting the verification pattern requires unlocking a keyslot + * of @device, so the LUKS passphrase needs to be provided in @key_file (this is a passphrase + * file, not the secure key file). + * + * Returns: whether the verification pattern was successfully set or not + * + * Tech category: %BD_S390_TECH_PAES-%BD_S390_TECH_MODE_MODIFY + */ +gboolean bd_s390_zkey_cryptsetup_setvp (const gchar *device, const gchar *key_file, const BDExtraArg **extra, GError **error) { + const gchar *argv[6] = {"zkey-cryptsetup", "setvp", "--key-file", key_file, device, NULL}; + + if (!check_deps (&avail_deps, DEPS_ZKEY_CRYPTSETUP_MASK, deps, DEPS_LAST, &deps_check_lock, error)) + return FALSE; + + if (device == NULL || *device == '\0') { + g_set_error_literal (error, BD_S390_ERROR, BD_S390_ERROR_ZKEY, + "Device must be specified"); + return FALSE; + } + + if (key_file == NULL || *key_file == '\0') { + g_set_error_literal (error, BD_S390_ERROR, BD_S390_ERROR_ZKEY, + "Key file must be specified"); + return FALSE; + } + + return bd_utils_exec_and_report_error (argv, extra, error); +} + +/** + * bd_s390_zkey_cryptsetup_validate: + * @device: LUKS2 device to validate + * @key_file: path to a file containing the LUKS passphrase used to unlock a keyslot of @device + * @extra: (nullable) (array zero-terminated=1): extra options for the validation + * (right now passed to the 'zkey-cryptsetup' utility) + * @error: (out) (optional): place to store error (if any) + * + * Validates that the LUKS2 volume @device is correctly set up for pervasive encryption using the + * 'zkey-cryptsetup' utility. Validation requires unlocking a keyslot of @device, so the LUKS + * passphrase needs to be provided in @key_file (this is a passphrase file, not the secure key + * file). + * + * Returns: whether the LUKS2 volume @device is correctly set up for pervasive encryption or not + * + * Tech category: %BD_S390_TECH_PAES-%BD_S390_TECH_MODE_QUERY + */ +gboolean bd_s390_zkey_cryptsetup_validate (const gchar *device, const gchar *key_file, const BDExtraArg **extra, GError **error) { + const gchar *argv[6] = {"zkey-cryptsetup", "validate", "--key-file", key_file, device, NULL}; + + if (!check_deps (&avail_deps, DEPS_ZKEY_CRYPTSETUP_MASK, deps, DEPS_LAST, &deps_check_lock, error)) + return FALSE; + + if (device == NULL || *device == '\0') { + g_set_error_literal (error, BD_S390_ERROR, BD_S390_ERROR_ZKEY, + "Device must be specified"); + return FALSE; + } + + if (key_file == NULL || *key_file == '\0') { + g_set_error_literal (error, BD_S390_ERROR, BD_S390_ERROR_ZKEY, + "Key file must be specified"); + return FALSE; + } + + return bd_utils_exec_and_report_error (argv, extra, error); +} diff --git a/src/plugins/s390.h b/src/plugins/s390.h index 755fceea8..d95335095 100644 --- a/src/plugins/s390.h +++ b/src/plugins/s390.h @@ -90,5 +90,7 @@ gboolean bd_s390_zfcp_offline(const gchar *devno, const gchar *wwpn, const gchar gboolean bd_s390_zkey_generate (const gchar *name, const gchar *key_type, guint64 keybits, const gchar **volumes, const gchar **apqns, guint64 sector_size, gboolean dummy_passphrase, const BDExtraArg **extra, GError **error); BDS390ZkeyInfo** bd_s390_zkey_list (const gchar *name, GError **error); gboolean bd_s390_zkey_remove (const gchar *name, GError **error); +gboolean bd_s390_zkey_cryptsetup_setvp (const gchar *device, const gchar *key_file, const BDExtraArg **extra, GError **error); +gboolean bd_s390_zkey_cryptsetup_validate (const gchar *device, const gchar *key_file, const BDExtraArg **extra, GError **error); #endif /* BD_S390 */ diff --git a/tests/s390_test.py b/tests/s390_test.py index 6469c83fa..52b378a00 100644 --- a/tests/s390_test.py +++ b/tests/s390_test.py @@ -108,17 +108,24 @@ def test_missing_dependencies(self): """Verify that checking for technology support works as expected""" with fake_path(all_but="dasdfmt"): + BlockDev.reinit(self.requested_plugins, True, None) + # dasdfmt is not available, so the s390 plugin should fail to load with self.assertRaisesRegex(GLib.GError, "The 'dasdfmt' utility is not available"): BlockDev.s390_is_tech_avail(BlockDev.S390Tech.DASD, BlockDev.S390TechMode.MODIFY) - @tag_test(TestTags.EXTRADEPS, TestTags.NOSTORAGE) - def test_missing_zkey(self): - """Verify that checking for pervasive encryption support works as expected""" - with fake_path(all_but="zkey"): - # zkey is not available, so the PAES technology should not be available + BlockDev.reinit(self.requested_plugins, True, None) + with self.assertRaisesRegex(GLib.GError, "The 'zkey' utility is not available"): BlockDev.s390_is_tech_avail(BlockDev.S390Tech.PAES, BlockDev.S390TechMode.CREATE) with self.assertRaisesRegex(GLib.GError, "The 'zkey' utility is not available"): BlockDev.s390_is_tech_avail(BlockDev.S390Tech.PAES, BlockDev.S390TechMode.QUERY) + + with fake_path(all_but="zkey-cryptsetup"): + BlockDev.reinit(self.requested_plugins, True, None) + + with self.assertRaisesRegex(GLib.GError, "The 'zkey-cryptsetup' utility is not available"): + BlockDev.s390_is_tech_avail(BlockDev.S390Tech.PAES, BlockDev.S390TechMode.QUERY) + with self.assertRaisesRegex(GLib.GError, "The 'zkey-cryptsetup' utility is not available"): + BlockDev.s390_is_tech_avail(BlockDev.S390Tech.PAES, BlockDev.S390TechMode.MODIFY) diff --git a/tests/utils.py b/tests/utils.py index 847415fca..af7361571 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -85,7 +85,8 @@ def fake_utils(path="."): "mkfs.nilfs2", "nilfs-tune", "nilfs-resize", "mkntfs", "ntfsfix", "ntfsresize", "ntfslabel", "ntfsinfo", "mkfs.vfat", "fatlabel", "fsck.vfat", "vfat-resize", - "mkfs.xfs", "xfs_db", "xfs_repair", "xfs_admin", "xfs_growfs"} + "mkfs.xfs", "xfs_db", "xfs_repair", "xfs_admin", "xfs_growfs", + "zkey", "zkey-cryptsetup"} @contextmanager def fake_path(path=None, keep_utils=None, all_but=None): From d8f5e690bd4edf6db84fa1657c50d9aa412081c3 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Tue, 1 Sep 2026 12:57:35 +0200 Subject: [PATCH 3/5] s390: Fix parsing of multi-line values in bd_s390_zkey_list The 'zkey list' output prints multi-value fields (Volumes, APQNs) with the first item next to the label and any further items on continuation lines indented to the value column. The parser discarded lines without a colon and split values on commas, so only the first APQN/volume was ever captured (and comma splitting never matched the real output at all). Detect continuation lines by indentation instead of the absence of a colon -- a volume is formatted as "volume:dmname" and thus contains a colon on its continuation lines -- and accumulate the items into the resulting array. Add fake_utils-based tests covering multiple keys, multiple APQNs and multiple volumes. Co-Authored-By: Claude Opus 4.8 --- src/plugins/s390.c | 61 +++++++++++++++--- .../fake_utils/zkey_list_multiple_apqns/zkey | 29 +++++++++ tests/fake_utils/zkey_list_multiple_keys/zkey | 48 ++++++++++++++ .../zkey_list_multiple_volumes/zkey | 32 ++++++++++ tests/s390_test.py | 62 ++++++++++++++++++- 5 files changed, 224 insertions(+), 8 deletions(-) create mode 100755 tests/fake_utils/zkey_list_multiple_apqns/zkey create mode 100755 tests/fake_utils/zkey_list_multiple_keys/zkey create mode 100755 tests/fake_utils/zkey_list_multiple_volumes/zkey diff --git a/src/plugins/s390.c b/src/plugins/s390.c index 45ec4dc18..12ed06c2f 100644 --- a/src/plugins/s390.c +++ b/src/plugins/s390.c @@ -1220,6 +1220,9 @@ BDS390ZkeyInfo** bd_s390_zkey_list (const gchar *name, GError **error) { GPtrArray *keys = NULL; gchar **lines = NULL; BDS390ZkeyInfo *cur_info = NULL; + GPtrArray *cur_list = NULL; /* accumulates a multi-line list value (Volumes/APQNs) */ + gchar ***cur_list_dest = NULL; /* struct member to store the finalized NULL-terminated array in */ + gint value_col = -1; /* column (0-based) where field values start, learned from field lines */ if (!check_deps (&avail_deps, DEPS_ZKEY_MASK, deps, DEPS_LAST, &deps_check_lock, error)) return NULL; @@ -1252,18 +1255,46 @@ BDS390ZkeyInfo** bd_s390_zkey_list (const gchar *name, GError **error) { g_free (output); for (gchar **line_p = lines; *line_p != NULL; line_p++) { + gchar *line = *line_p; + gsize indent = strspn (line, " \t"); gchar *colon = NULL; gchar *label = NULL; gchar *value = NULL; - /* split the line on the first ':' -- the label never contains a colon while - some values (e.g. Volumes) do; lines without a colon are separators, blank - lines or continuation lines (e.g. the second line of Verification pattern) */ - colon = strchr (*line_p, ':'); + /* A multi-value field (Volumes/APQNs) can span several lines: the first item + follows the label, any further items appear on continuation lines that carry + no label and are indented to the value column. Such continuation lines cannot + be recognized by the absence of a colon -- a volume is formatted as + "volume:dmname" and thus contains one -- so they are detected by indentation + instead: everything left of the value column is whitespace. */ + if (cur_list != NULL && value_col >= 0 && (gint) indent >= value_col) { + gchar *item = g_strstrip (g_strdup (line + indent)); + if (*item != '\0') + g_ptr_array_add (cur_list, item); + else + g_free (item); + continue; + } + + /* any other line terminates the current multi-value field */ + if (cur_list != NULL) { + g_ptr_array_add (cur_list, NULL); + *cur_list_dest = (gchar **) g_ptr_array_free (cur_list, FALSE); + cur_list = NULL; + cur_list_dest = NULL; + } + + /* split the line on the first ':' -- the label never contains a colon; lines + without a colon are separators or blank lines */ + colon = strchr (line, ':'); if (colon == NULL) continue; - label = g_strndup (*line_p, colon - *line_p); + /* "label : value" -- remember where values start so the continuation lines of + the next multi-value field can be recognized */ + value_col = (gint) (colon - line) + 2; + + label = g_strndup (line, colon - line); label = g_strstrip (label); value = g_strdup (colon + 1); value = g_strstrip (value); @@ -1286,9 +1317,17 @@ BDS390ZkeyInfo** bd_s390_zkey_list (const gchar *name, GError **error) { } else if (g_strcmp0 (label, "Key type") == 0) { cur_info->key_type = g_strdup (value); } else if (g_strcmp0 (label, "Volumes") == 0) { - cur_info->volumes = g_strsplit (value, ",", -1); + /* the value continues on the following continuation lines (one volume each) */ + cur_list = g_ptr_array_new (); + cur_list_dest = &cur_info->volumes; + if (*value != '\0') + g_ptr_array_add (cur_list, g_strdup (value)); } else if (g_strcmp0 (label, "APQNs") == 0) { - cur_info->apqns = g_strsplit (value, ",", -1); + /* the value continues on the following continuation lines (one APQN each) */ + cur_list = g_ptr_array_new (); + cur_list_dest = &cur_info->apqns; + if (*value != '\0') + g_ptr_array_add (cur_list, g_strdup (value)); } else if (g_strcmp0 (label, "Key file name") == 0) { cur_info->key_file_name = g_strdup (value); } else if (g_strcmp0 (label, "Sector size") == 0) { @@ -1306,6 +1345,14 @@ BDS390ZkeyInfo** bd_s390_zkey_list (const gchar *name, GError **error) { g_free (value); } + /* finalize a multi-value field left open at the end of the output */ + if (cur_list != NULL) { + g_ptr_array_add (cur_list, NULL); + *cur_list_dest = (gchar **) g_ptr_array_free (cur_list, FALSE); + cur_list = NULL; + cur_list_dest = NULL; + } + g_strfreev (lines); g_ptr_array_add (keys, NULL); diff --git a/tests/fake_utils/zkey_list_multiple_apqns/zkey b/tests/fake_utils/zkey_list_multiple_apqns/zkey new file mode 100755 index 000000000..91f846338 --- /dev/null +++ b/tests/fake_utils/zkey_list_multiple_apqns/zkey @@ -0,0 +1,29 @@ +#!/bin/bash + +# fake 'zkey list' output with a single key associated with multiple APQNs (and a +# multi-line verification pattern); the extra APQNs are printed on continuation +# lines indented to the value column + +cat < Date: Tue, 1 Sep 2026 13:05:27 +0200 Subject: [PATCH 4/5] tests: Write s390 plugin to config_h when available --- tests/Makefile.am | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Makefile.am b/tests/Makefile.am index a3ad561be..15c7bec00 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -55,6 +55,10 @@ if WITH_NVME PLUGINS += nvme endif +if WITH_S390 +PLUGINS += s390 +endif + if WITH_SMART PLUGINS += smart endif From de8df28a9efde1d0968df1e0e665cb5042977bb8 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Wed, 2 Sep 2026 13:49:29 +0200 Subject: [PATCH 5/5] misc: Install s390utils on s390x --- misc/libblockdev-tasks.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/misc/libblockdev-tasks.yml b/misc/libblockdev-tasks.yml index aa077f85b..dc1b3a8e7 100644 --- a/misc/libblockdev-tasks.yml +++ b/misc/libblockdev-tasks.yml @@ -69,6 +69,14 @@ - xfsprogs when: ansible_facts['distribution'] == 'Fedora' and test_dependencies|bool +- name: Install s390utils (Fedora) + package: + state: present + name: + - s390utils + when: ansible_facts['distribution'] == 'Fedora' and ansible_facts['architecture'] == 's390x' and test_dependencies|bool + + ####### CentOS - name: Install basic build tools (CentOS) package: @@ -128,6 +136,13 @@ - xfsprogs when: ansible_facts['distribution'] == 'CentOS' and test_dependencies|bool +- name: Install s390utils (CentOS) + package: + state: present + name: + - s390utils + when: ansible_facts['distribution'] == 'CentOS' and ansible_facts['architecture'] == 's390x' and test_dependencies|bool + - name: Install pylint using pip (CentOS) pip: name: ['pylint']