/*
 * zkey - Generate, re-encipher, and validate secure keys
 *
 * Copyright IBM Corp. 2017, 2024
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#include <ctype.h>
#include <dlfcn.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "lib/util_base.h"
#include "lib/util_libc.h"
#include "lib/util_opt.h"
#include "lib/util_panic.h"
#include "lib/util_prg.h"
#include "lib/zt_common.h"

#include "cca.h"
#include "ep11.h"
#include "keystore.h"
#include "misc.h"
#include "pkey.h"
#include "utils.h"
#include "kms.h"
#include "pvsecrets.h"

/*
 * Program configuration
 */
static const struct util_prg prg = {
	.desc = "Manage secure AES and HMAC keys",
	.command_args = "COMMAND [SECURE-KEY-FILE]",
	.args = "",
	.copyright_vec = {
		{
			.owner = "IBM Corp.",
			.pub_first = 2017,
			.pub_last = 2024,
		},
		UTIL_PRG_COPYRIGHT_END
	}
};

/*
 * Global variables for program options
 */
static struct zkey_globals {
	char *pos_arg;
	char *clearkeyfile;
	char *outputfile;
	bool xts;
	bool verbose;
	long int keybits;
	bool tonew;
	bool fromold;
	bool complete;
	bool inplace;
	bool staged;
	char *name;
	char *description;
	char *volumes;
	char *apqns;
	bool noapqncheck;
	bool novolcheck;
	long int sector_size;
	char *volume_type;
	char *newname;
	char *key_type;
	char *label;
	bool local;
	bool gen_passphrase;
	char *passphrase_file;
	bool remove_passphrase;
	bool exportable;
	bool wrap_with_trusted;
	bool kms_bound;
	bool run;
	bool batch_mode;
	char *keyfile;
	long long keyfile_offset;
	long long keyfile_size;
	long long tries;
	bool force;
	bool open;
	bool format;
	bool refresh_properties;
	bool all;
	bool hex;
	char *secret_type;
	char *secret_id;
	char *secret_name;
	struct ext_lib lib;
	struct cca_lib cca;
	struct ep11_lib ep11;
	int pkey_fd;
	int uv_fd;
	struct keystore *keystore;
	struct kms_info kms_info;
	int first_kms_option;
	struct kms_option *kms_options;
	size_t num_kms_options;
} g = {
	.pkey_fd = -1,
	.uv_fd = -1,
	.sector_size = -1,
	.lib.cca = &g.cca,
	.lib.ep11 = &g.ep11,
};

/*
 * Available commands
 */
#define COMMAND_GENERATE	"generate"
#define COMMAND_REENCIPHER	"reencipher"
#define COMMAND_VALIDATE	"validate"
#define COMMAND_IMPORT		"import"
#define COMMAND_EXPORT		"export"
#define COMMAND_LIST		"list  "
#define COMMAND_REMOVE		"remove"
#define COMMAND_CHANGE		"change"
#define COMMAND_RENAME		"rename"
#define COMMAND_COPY		"copy  "
#define COMMAND_CRYPTTAB	"crypttab"
#define COMMAND_CRYPTSETUP	"cryptsetup"
#define COMMAND_INTEGRITYTAB	"integritytab"
#define COMMAND_INTEGRITYSETUP	"integritysetup"
#define COMMAND_CONVERT		"convert"
#define COMMAND_KMS		"kms"
#define COMMAND_KMS_PLUGINS	"plugins"
#define COMMAND_KMS_BIND	"bind"
#define COMMAND_KMS_UNBIND	"unbind"
#define COMMAND_KMS_INFO	"info"
#define COMMAND_KMS_CONFIGURE	"configure"
#define COMMAND_KMS_REENCIPHER	"reencipher"
#define COMMAND_KMS_LIST	"list"
#define COMMAND_KMS_IMPORT	"import"
#define COMMAND_KMS_REFRESH	"refresh"
#define COMMAND_PVSECRETS	"pvsecrets"
#define COMMAND_PVSECRETS_LIST	"list"
#define COMMAND_PVSECRETS_IMPORT "import"

#define OPT_COMMAND_PLACEHOLDER	"PLACEHOLDER"

#define OPT_PLACEHOLDER					\
{							\
	.option = { "", 0, NULL, ' ' },			\
	.desc = OPT_COMMAND_PLACEHOLDER,		\
	.command = OPT_COMMAND_PLACEHOLDER,		\
}

#define ZKEY_COMMAND_MAX_LEN	15

#define ENVVAR_ZKEY_REPOSITORY	"ZKEY_REPOSITORY"
#define DEFAULT_KEYSTORE	"/etc/zkey/repository"

#define OPT_CRYPTSETUP_KEYFILE		256
#define OPT_CRYPTSETUP_KEYFILE_OFFSET	257
#define OPT_CRYPTSETUP_KEYFILE_SIZE	258
#define OPT_CRYPTSETUP_TRIES		259
#define OPT_CRYPTSETUP_OPEN		260
#define OPT_CRYPTSETUP_FORMAT		261
#define OPT_NO_APQN_CHECK		262
#define OPT_NO_VOLUME_CHECK		263
#define OPT_REFRESH_PROPERTIES		264
#define OPT_GEN_DUMMY_PASSPHRASE	265
#define OPT_SET_DUMMY_PASSPHRASE	266
#define OPT_REMOVE_DUMMY_PASSPHRASE	267
#define OPT_EXPORTABLE			268
#define OPT_WRAP_WITH_TRUSTED		269

/*
 * Configuration of command line options
 */
static struct util_opt opt_vec[] = {
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_GENERATE,
	},
	{
		.option = {"xts", 0, NULL, 'x'},
		.desc = "Generate a secure AES key for the XTS cipher mode",
		.command = COMMAND_GENERATE,
	},
	{
		.option = { "keybits", required_argument, NULL, 'k'},
		.argument = "SIZE",
		.desc = "Size of the AES or HMAC key to be generated in bits. "
			"Valid sizes for AES keys are 128, 192, and 256 bits. "
			"Secure keys for use with the XTS cipher mode can only "
			"use keys  of 128 or 256 bits. Valid sizes for HMAC "
			"keys are 256 and 512 bits. Default is 256 bits",
		.command = COMMAND_GENERATE,
	},
	{
		.option = { "clearkey", required_argument, NULL, 'c'},
		.argument = "CLEAR-KEY-FILE",
		.desc = "Name of a file containing the clear AES or HMAC key "
			"in binary. If option --keybits/-k is omitted, then "
			"the size of the CLEAR-KEY-FILE determines the size "
			"of the AES or HMAC key. If option --keybits/-k is "
			"specified, then the size of the CLEAR-KEY-FILE must "
			"match the specified key size. Valid file sizes for "
			"AES keys are 16, 24, or 32 bytes, and 32, or 64 bytes "
			"for keys to be used with XTS mode ciphers. For HMAC "
			"keys, valid file sizes are 32 or 64 bytes",
		.command = COMMAND_GENERATE,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure key in the repository. If "
			"option --name/-N is specified, then the generated "
			"secure key is stored in the repository. Parameter "
			"SECURE-KEY-FILE is not used when option --name/-N is "
			"specified",
		.command = COMMAND_GENERATE,
	},
	{
		.option = { "description", required_argument, NULL, 'd'},
		.argument = "DESCRIPTION",
		.desc = "Textual description of the secure key in the "
			"repository",
		.command = COMMAND_GENERATE,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME:DMNAME[,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the secure key in "
			"the repository",
		.command = COMMAND_GENERATE,
	},
	{
		.option = { "apqns", required_argument, NULL, 'a'},
		.argument = "CARD.DOMAIN[,...]",
		.desc = "Comma-separated pairs of crypto cards and domains "
			"that are associated with the secure key in the "
			"repository",
		.command = COMMAND_GENERATE,
	},
	{
		.option = {"no-apqn-check", 0, NULL, OPT_NO_APQN_CHECK},
		.desc = "Do not check if the specified APQN(s) are available. "
			"Use this option to associate APQN(s) with a secure "
			"key that are currently not available.",
		.command = COMMAND_GENERATE,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = { "sector-size", required_argument, NULL, 'S'},
		.argument = "bytes",
		.desc = "The sector size used with dm-crypt or dm-integrity. "
			"It must be a power of two and in range 512 - 4096 "
			"bytes. If this option is omitted, the system default "
			"sector size (512) is used",
		.command = COMMAND_GENERATE,
	},
	{
		.option = { "volume-type", required_argument, NULL, 't'},
		.argument = "type",
		.desc = "The type of the associated volume(s). Possible values "
			"for AES keys are 'plain' and 'luks2', and 'integrity' "
			"and 'luks2' for HMAC keys. When this option is omitted, "
			"the default is 'luks2' for AES keys and 'integrity' "
			"for HMAC keys.",
		.command = COMMAND_GENERATE,
	},
	{
		.option = { "key-type", required_argument, NULL, 'K'},
		.argument = "type",
		.desc = "The type of the key. Possible values are '"
			KEY_TYPE_CCA_AESDATA"', '"KEY_TYPE_CCA_AESCIPHER"' "
			"and '"KEY_TYPE_EP11_AES"'. When this option is "
			"omitted, the default is '"KEY_TYPE_CCA_AESDATA"'",
		.command = COMMAND_GENERATE,
	},
	{
		.option = { "local", 0, NULL, 'L'},
		.desc = "Generate the key locally. This is the default when no "
			"KMS plugin is bound to the repository. If the "
			"repository is bound to a KMS plugin, then keys are "
			"generated by the KMS per default.",
		.command = COMMAND_GENERATE,
	},
	{
		.option = { "gen-dummy-passphrase", 0, NULL,
						OPT_GEN_DUMMY_PASSPHRASE},
		.desc = "Generate a dummy passphrase and associate it with the "
			"secure AES key used to encrypt LUKS2 volume(s). The "
			"LUKS2 passphrase is of less or no relevance for the "
			"security of the volume(s), when an secure AES key is "
			"used to encrypt the volume(s), and can therefore be "
			"stored insecurely inside the secure key repository.",
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.command = COMMAND_GENERATE,
	},
	{
		.option = { "set-dummy-passphrase", required_argument, NULL,
						OPT_SET_DUMMY_PASSPHRASE},
		.argument = "passphrase-file",
		.desc = "Set a dummy passphrase to be associated with the "
			"secure AES key used to encrypt LUKS2 volume(s). The "
			"LUKS2 passphrase is of less or no relevance for the "
			"security of the volume(s), when an secure AES key is "
			"used to encrypt the volume(s), and can therefore be "
			"stored insecurely inside the secure key repository.",
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.command = COMMAND_GENERATE,
	},
	{
		.option = {"exportable", 0, NULL, OPT_EXPORTABLE},
		.desc = "Generate a secure AES key that is not export "
			"restricted. Exportable keys can be exported by means "
			"of wrapping them with a key encryption key (KEK). By "
			"default keys of type " KEY_TYPE_CCA_AESCIPHER " and "
			KEY_TYPE_EP11_AES " are export restricted, and thus can "
			"not be wrapped by a KEK. Keys of type "
			KEY_TYPE_CCA_AESDATA " are always exportable, and can "
			"not be export restricted.",
		.command = COMMAND_GENERATE,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = {"wrap-with-trusted", 0, NULL, OPT_WRAP_WITH_TRUSTED},
		.desc = "Generate a secure AES key that can only be wrapped "
			"with a trusted key encryption key (KEK). This option "
			"is only valid for keys of type " KEY_TYPE_EP11_AES
			" and only if option '--exportable' is also specified.",
		.command = COMMAND_GENERATE,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_REENCIPHER,
	},
	{
		.option = { "output", required_argument, NULL, 'f'},
		.argument = "OUTPUT-FILE",
		.desc = "Name of the output file to which the re-enciphered "
			"secure key is written. If this option is omitted, "
			"then the re-enciphered secure key will be replaced "
			"in the SECURE-KEY-FILE",
		.command = COMMAND_REENCIPHER,
	},
	{
		.option = {"to-new", 0, NULL, 'n'},
		.desc = "Re-enciphers a secure key that is currently "
			"enciphered with the master key in the CURRENT "
			"register with the master key in the NEW register",
		.command = COMMAND_REENCIPHER,
	},
	{
		.option = {"from-old", 0, NULL, 'o'},
		.desc = "Re-enciphers a secure key that is currently "
			"enciphered with the master key in the OLD register "
			"with the master key in the CURRENT register",
		.command = COMMAND_REENCIPHER,
	},
	{
		.option = {"complete", 0, NULL, 'p'},
		.desc = "Completes a staged re-enciphering. Use this option "
			"after the new master key has been set (made "
			"active)",
		.command = COMMAND_REENCIPHER,
	},
	{
		.option = {"in-place", 0, NULL, 'i'},
		.desc = "Forces an in-place re-enchipering of a secure "
			"key. Re-enciphering from OLD to CURRENT is performed "
			"in-place per default",
		.command = COMMAND_REENCIPHER,
	},
	{
		.option = {"staged", 0, NULL, 's'},
		.desc = "Forces that the re-enciphering of a secure key is "
			"performed in staged mode. Re-enciphering from CURRENT "
			"to NEW is performed in staged mode per default",
		.command = COMMAND_REENCIPHER,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure keys in the repository that "
			"are to be re-enciphered. You can use wild-cards to "
			"select the keys to re-encipher.",
		.command = COMMAND_REENCIPHER,
	},
	{
		.option = { "apqns", required_argument, NULL, 'a'},
		.argument = "CARD.DOMAIN[,...]",
		.desc = "Comma-separated pairs of crypto cards and domains "
			"that are associated with the secure key in the "
			"repository. Use this option to re-encipher all keys "
			"associated with specific crypto cards",
		.command = COMMAND_REENCIPHER,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_VALIDATE,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure keys in the repository that "
			"are to be validated. You can use wild-cards to select "
			"the keys to validate.",
		.command = COMMAND_VALIDATE,
	},
	{
		.option = { "apqns", required_argument, NULL, 'a'},
		.argument = "CARD.DOMAIN[,...]",
		.desc = "Comma-separated pairs of crypto cards and domains "
			"that are associated with the secure key in the "
			"repository. Use this option to validate all keys "
			"associated with specific crypto cards",
		.command = COMMAND_VALIDATE,
	},
	{
		.option = {"no-apqn-check", 0, NULL, OPT_NO_APQN_CHECK},
		.desc = "Do not check if the associated APQN(s) are available",
		.command = COMMAND_VALIDATE,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_IMPORT,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the imported secure key in the repository",
		.command = COMMAND_IMPORT,
	},
	{
		.option = { "description", required_argument, NULL, 'd'},
		.argument = "DESCRIPTION",
		.desc = "Textual description of the secure key in the "
			"repository",
		.command = COMMAND_IMPORT,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME:DMNAME[,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the secure key in "
			"the repository",
		.command = COMMAND_IMPORT,
	},
	{
		.option = { "apqns", required_argument, NULL, 'a'},
		.argument = "CARD.DOMAIN[,...]",
		.desc = "Comma-separated pairs of crypto cards and domains "
			"that are associated with the secure key in the "
			"repository",
		.command = COMMAND_IMPORT,
	},
	{
		.option = {"no-apqn-check", 0, NULL, OPT_NO_APQN_CHECK},
		.desc = "Do not check if the specified APQN(s) are available. "
			"Use this option to associate APQN(s) with a secure "
			"key that are currently not available.",
		.command = COMMAND_IMPORT,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = { "sector-size", required_argument, NULL, 'S'},
		.argument = "512|4096",
		.desc = "The sector size used with dm-crypt or dm-integrity. "
			"It must be a power of two and in range 512 - 4096 "
			"bytes. If this option is omitted, the system default "
			"sector size (512) is used",
		.command = COMMAND_IMPORT,
	},
	{
		.option = { "volume-type", required_argument, NULL, 't'},
		.argument = "type",
		.desc = "The type of the associated volume(s). Possible values "
			"for AES keys are 'plain' and 'luks2', and 'integrity' "
			"and 'luks2' for HMAC keys. When this option is omitted, "
			"the default is 'luks2' for AES keys and 'integrity' "
			"for HMAC keys.",
		.command = COMMAND_IMPORT,
	},
	{
		.option = { "gen-dummy-passphrase", 0, NULL,
						OPT_GEN_DUMMY_PASSPHRASE},
		.desc = "Generate a dummy passphrase and associate it with the "
			"secure AES key used to encrypt LUKS2 volume(s). The "
			"LUKS2 passphrase is of less or no relevance for the "
			"security of the volume(s), when an secure AES key is "
			"used to encrypt the volume(s), and can therefore be "
			"stored insecurely inside the secure key repository.",
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.command = COMMAND_IMPORT,
	},
	{
		.option = { "set-dummy-passphrase", required_argument, NULL,
						OPT_SET_DUMMY_PASSPHRASE},
		.argument = "passphrase-file",
		.desc = "Set a dummy passphrase to be associated with the "
			"secure AES key used to encrypt LUKS2 volume(s). The "
			"LUKS2 passphrase is of less or no relevance for the "
			"security of the volume(s), when an secure AES key is "
			"used to encrypt the volume(s), and can therefore be "
			"stored insecurely inside the secure key repository.",
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.command = COMMAND_IMPORT,
	},
	{
		.option = {"exportable", 0, NULL, OPT_EXPORTABLE},
		.desc = "Allow to import a secure AES key that is not export "
			"restricted. Exportable keys can be exported by means "
			"of wrapping them with a key encryption key (KEK). By "
			"default keys of type " KEY_TYPE_CCA_AESCIPHER " and "
			KEY_TYPE_EP11_AES " are export restricted, and thus can "
			"not be wrapped by a KEK. Keys of type "
			KEY_TYPE_CCA_AESDATA " are always exportable, and can "
			"not be export restricted.",
		.command = COMMAND_IMPORT,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_EXPORT,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure key in the repository that is "
			"to be exported",
		.command = COMMAND_EXPORT,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_LIST,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure keys in the repository that "
			"are to be listed. You can use wild-cards to select "
			"the keys to list.",
		.command = COMMAND_LIST,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME[:DMNAME][,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the secure key in "
			"the repository. Use this option to list all keys "
			"associated with specific volumes. The device-mapper "
			"name (DMNAME) is optional. If specified, only those "
			"keys are listed where both, the volume and the device-"
			"mapper name matches",
		.command = COMMAND_LIST,
	},
	{
		.option = { "apqns", required_argument, NULL, 'a'},
		.argument = "CARD.DOMAIN[,...]",
		.desc = "Comma-separated pairs of crypto cards and domains "
			"that are associated with the secure key in the "
			"repository. Use this option to list all keys "
			"associated with specific crypto cards",
		.command = COMMAND_LIST,
	},
	{
		.option = { "volume-type", required_argument, NULL, 't'},
		.argument = "type",
		.desc = "The type of the associated volume(s). Possible values "
			"are 'plain', 'luks2', and 'integrity'. Use this "
			"option to list all keys with the specified volumes "
			"type.",
		.command = COMMAND_LIST,
	},
	{
		.option = { "key-type", required_argument, NULL, 'K'},
		.argument = "type",
		.desc = "The type of the key. Possible values are '"
			KEY_TYPE_CCA_AESDATA "', '" KEY_TYPE_CCA_AESCIPHER
			"', '" KEY_TYPE_EP11_AES "', '" KEY_TYPE_PVSECRET_AES
			"', and '" KEY_TYPE_PVSECRET_HMAC " . Use this option "
			"to list all keys with the specified key type.",
		.command = COMMAND_LIST,
	},
	{
		.option = { "local", 0, NULL, 'L'},
		.desc = "List local keys only. Local keys are not bound to a "
			"KMS plugin.",
		.command = COMMAND_LIST,
	},
	{
		.option = { "kms-bound", 0, NULL, 'M'},
		.desc = "List KMS-bound keys only. KMS-bound keys are bound to "
			"a KMS plugin.",
		.command = COMMAND_LIST,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_REMOVE,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure key in the repository that is "
			"to be removed",
		.command = COMMAND_REMOVE,
	},
	{
		.option = {"force", 0, NULL, 'F'},
		.desc = "Do not prompt for a confirmation when removing a key",
		.command = COMMAND_REMOVE,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_CHANGE,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure key in the repository that is "
			"to be changed",
		.command = COMMAND_CHANGE,
	},
	{
		.option = { "description", required_argument, NULL, 'd'},
		.argument = "DESCRIPTION",
		.desc = "Textual description of the secure key in the "
			"repository",
		.command = COMMAND_CHANGE,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "[+|-]VOLUME:DMNAME[,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the secure key in "
			"the repository. To add pairs of volume and device-"
			"mapper names to the key specify '+VOLUME:DMNAME[,...]'. "
			"To remove pairs of volume and device-mapper names "
			"from the key specify '-VOLUME:DMNAME[,...]'",
		.command = COMMAND_CHANGE,
	},
	{
		.option = { "apqns", required_argument, NULL, 'a'},
		.argument = "[+|-]CARD.DOMAIN[,...]",
		.desc = "Comma-separated pairs of crypto cards and domains "
			"that are associated with the secure key in the "
			"repository. To add pairs of crypto cards and domains "
			"to the key specify '+CARD.DOMAIN[,...]'. To remove "
			"pairs of crypto cards and domains from the key "
			"specify '-CARD.DOMAIN[,...]'",
		.command = COMMAND_CHANGE,
	},
	{
		.option = {"no-apqn-check", 0, NULL, OPT_NO_APQN_CHECK},
		.desc = "Do not check if the specified APQN(s) are available. "
			"Use this option to associate APQN(s) with a secure "
			"key that are currently not available.",
		.command = COMMAND_CHANGE,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = { "sector-size", required_argument, NULL, 'S'},
		.argument = "0|512|4096",
		.desc = "The sector size used with dm-crypt or dm-integrity. "
			"It must be a power of two and in range 512 - 4096 "
			"bytes. If this option is omitted, the system default "
			"sector size (512) is used",
		.command = COMMAND_CHANGE,
	},
	{
		.option = { "volume-type", required_argument, NULL, 't'},
		.argument = "type",
		.desc = "The type of the associated volume(s). Possible values "
			"for AES keys are 'plain' and 'luks2', and 'integrity' "
			"and 'luks2' for HMAC keys.",
		.command = COMMAND_CHANGE,
	},
	{
		.option = { "gen-dummy-passphrase", 0, NULL,
						OPT_GEN_DUMMY_PASSPHRASE},
		.desc = "Generate a dummy passphrase and associate it with the "
			"secure AES key used to encrypt LUKS2 volume(s). The "
			"LUKS2 passphrase is of less or no relevance for the "
			"security of the volume(s), when an secure AES key is "
			"used to encrypt the volume(s), and can therefore be "
			"stored insecurely inside the secure key repository.",
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.command = COMMAND_CHANGE,
	},
	{
		.option = { "set-dummy-passphrase", required_argument, NULL,
						OPT_SET_DUMMY_PASSPHRASE},
		.argument = "passphrase-file",
		.desc = "Set a dummy passphrase to be associated with the "
			"secure AES key used to encrypt LUKS2 volume(s). The "
			"LUKS2 passphrase is of less or no relevance for the "
			"security of the volume(s), when an secure AES key is "
			"used to encrypt the volume(s), and can therefore be "
			"stored insecurely inside the secure key repository.",
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.command = COMMAND_CHANGE,
	},
	{
		.option = { "remove-dummy-passphrase", 0, NULL,
						OPT_REMOVE_DUMMY_PASSPHRASE},
		.desc = "Remove an associated dummy passphrase used with LUKS2 "
			"volume(s).",
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.command = COMMAND_CHANGE,
	},
	{
		.option = {"force", 0, NULL, 'F'},
		.desc = "Do not prompt for a confirmation when removing an "
			"associated dummy passphrase",
		.command = COMMAND_CHANGE,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_RENAME,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure key in the repository that is "
			"to be renamed",
		.command = COMMAND_RENAME,
	},
	{
		.option = { "new-name", required_argument, NULL, 'w'},
		.argument = "NEW-NAME",
		.desc = "New name of the secure key in the repository",
		.command = COMMAND_RENAME,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_COPY,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure key in the repository that is "
			"to be copied",
		.command = COMMAND_COPY,
	},
	{
		.option = { "new-name", required_argument, NULL, 'w'},
		.argument = "NEW-NAME",
		.desc = "New name of the secure key in the repository",
		.command = COMMAND_COPY,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME:DMNAME[,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the copied secure "
			"key in the repository. If option '--volumes/-l' is "
			"omitted, no volumes are associated with the copied "
			"key, because only one key can be associated to a "
			"specific volume.",
		.command = COMMAND_COPY,
	},
	{
		.option = { "local", 0, NULL, 'L'},
		.desc = "Copy the key to a local key. This is the default when "
			"no KMS plugin is bound to the repository. If the "
			"repository is bound to a KMS plugin, then keys are "
			"bound to the KMS per default, and KMS-bound key can "
			"only be copied to local keys.",
		.command = COMMAND_COPY,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_CRYPTTAB,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME[:DMNAME][,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the secure AES key in "
			"the repository. Use this option to select the volumes "
			"for which a crypttab entry is to be generated. The "
			"device-mapper name (DMNAME) is optional. If specified, "
			"only those volumes are selected where both, the "
			"volume and the device-mapper name matches",
		.command = COMMAND_CRYPTTAB,
	},
	{
		.option = { "volume-type", required_argument, NULL, 't'},
		.argument = "type",
		.desc = "The type of the associated volume(s). Possible values "
			"are 'plain' and 'luks2'. Use this option to select "
			"the keys by its volume type for which a crypttab "
			"entry is to be generated",
		.command = COMMAND_CRYPTTAB,
	},
	{
		.option = {"key-file", required_argument, NULL,
			   OPT_CRYPTSETUP_KEYFILE},
		.argument = "FILE-NAME",
		.desc = "Read the passphrase from the specified file. "
			"The specified file is passed to the generated "
			"crypttab entry for LUKS2 volumes",
		.command = COMMAND_CRYPTTAB,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = {"keyfile-offset", required_argument, NULL,
			   OPT_CRYPTSETUP_KEYFILE_OFFSET},
		.argument = "BYTES",
		.desc = "Specifies the number of bytes to skip in the file "
			"specified with option '--key-file'. "
			"The specified offset is passed to the generated "
			"crypttab entry for LUKS2 volumes. Not all "
			"distributions support the 'keyfile-offset' option in "
			"crypttab entries",
		.command = COMMAND_CRYPTTAB,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = {"keyfile-size", required_argument, NULL,
			   OPT_CRYPTSETUP_KEYFILE_SIZE},
		.argument = "BYTES",
		.desc = "Specifies the number of bytes to read from the file "
			"specified with option '--key-file'. "
			"The specified size is passed to the generated "
			"crypttab entry for LUKS2 volumes. Not all "
			"distributions support the 'keyfile-size' option in "
			"crypttab entries",
		.command = COMMAND_CRYPTTAB,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = {"tries", required_argument, NULL,
			   OPT_CRYPTSETUP_TRIES},
		.argument = "NUMBER",
		.desc = "Specifies how often the interactive input of the "
			"passphrase can be retried. "
			"The specified number is passed to the generated "
			"crypttab entry for LUKS2 volumes",
		.command = COMMAND_CRYPTTAB,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_CRYPTSETUP,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME[:DMNAME][,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the secure AES key in "
			"the repository. Use this option to select the volumes "
			"for which a cryptsetup command is to be generated or "
			"run. The device-mapper name (DMNAME) is optional."
			" If specified, only those volumes are selected where "
			"both, the volume and the device-mapper name matches",
		.command = COMMAND_CRYPTSETUP,
	},
	{
		.option = { "volume-type", required_argument, NULL, 't'},
		.argument = "type",
		.desc = "The type of the associated volume(s). Possible values "
			"are 'plain' and 'luks2'. Use this option to select "
			"the keys by its volume type for which a crypttab "
			"entry is to be generated",
		.command = COMMAND_CRYPTSETUP,
	},
	{
		.option = {"run", 0, NULL, 'r'},
		.desc = "Runs the generated cryptsetup command",
		.command = COMMAND_CRYPTSETUP,
	},
	{
		.option = {"key-file", required_argument, NULL,
			   OPT_CRYPTSETUP_KEYFILE},
		.argument = "FILE-NAME",
		.desc = "Read the passphrase from the specified file. "
			"This option is passed to the generated command(s) for "
			"LUKS2 volumes",
		.command = COMMAND_CRYPTSETUP,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = {"keyfile-offset", required_argument, NULL,
			   OPT_CRYPTSETUP_KEYFILE_OFFSET},
		.argument = "BYTES",
		.desc = "Specifies the number of bytes to skip in the file "
			"specified with option '--key-file'. "
			"This option is passed to the generated command(s) for "
			"LUKS2 volumes",
		.command = COMMAND_CRYPTSETUP,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = {"keyfile-size", required_argument, NULL,
			   OPT_CRYPTSETUP_KEYFILE_SIZE},
		.argument = "BYTES",
		.desc = "Specifies the number of bytes to read from the file "
			"specified with option '--key-file'. "
			"This option is passed to the generated command(s) for "
			"LUKS2 volumes",
		.command = COMMAND_CRYPTSETUP,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = {"tries", required_argument, NULL,
			   OPT_CRYPTSETUP_TRIES},
		.argument = "NUMBER",
		.desc = "Specifies how often the interactive input of the "
			"passphrase can be retried. "
			"This option is passed to the generated command(s) for "
			"LUKS2 volumes",
		.command = COMMAND_CRYPTSETUP,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = {"batch-mode", 0, NULL, 'q'},
		.desc = "Suppresses cryptsetup confirmation questions. "
			"This option is passed to the generated cryptsetup "
			"command(s)",
		.command = COMMAND_CRYPTSETUP,
	},
	{
		.option = {"open", 0, NULL, OPT_CRYPTSETUP_OPEN},
		.desc = "Generates luksOpen or plainOpen commands. For the "
			"plain volume type, this is the default",
		.command = COMMAND_CRYPTSETUP,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = {"format", 0, NULL, OPT_CRYPTSETUP_FORMAT},
		.desc = "Generates luksFormat commands. For the LUKS2 volume "
			"type, this is the default. If specified for the "
			"plain volume type, then no command is generated",
		.command = COMMAND_CRYPTSETUP,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_INTEGRITYTAB,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME[:DMNAME][,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the secure HMAC key in "
			"the repository. Use this option to select the volumes "
			"for which a integritytab entry is to be generated. "
			"The device-mapper name (DMNAME) is optional. If "
			"specified, only those volumes are selected where "
			"both, the volume and the device-mapper name matches",
		.command = COMMAND_INTEGRITYTAB,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_INTEGRITYSETUP,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME[:DMNAME][,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the secure HMAC key in "
			"the repository. Use this option to select the volumes "
			"for which a integritysetup command is to be generated "
			"or run. The device-mapper name (DMNAME) is optional. "
			"If specified, only those volumes are selected where "
			"both, the volume and the device-mapper name matches",
		.command = COMMAND_INTEGRITYSETUP,
	},
	{
		.option = {"run", 0, NULL, 'r'},
		.desc = "Runs the generated integritysetup command",
		.command = COMMAND_INTEGRITYSETUP,
	},
	{
		.option = {"batch-mode", 0, NULL, 'q'},
		.desc = "Suppresses integritysetup confirmation questions. "
			"This option is passed to the generated integritysetup "
			"command(s)",
		.command = COMMAND_INTEGRITYSETUP,
	},
	{
		.option = {"open", 0, NULL, OPT_CRYPTSETUP_OPEN},
		.desc = "Generates 'integritysetup open' commands. If this "
			"option is not specified, 'integritysetup format' "
			"commands are generated",
		.command = COMMAND_INTEGRITYSETUP,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_CONVERT,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure key in the repository that is "
			"to be converted",
		.command = COMMAND_CONVERT,
	},
	{
		.option = { "key-type", required_argument, NULL, 'K'},
		.argument = "type",
		.desc = "The type of the key to convert the secure key to. "
			"Possible values are '"KEY_TYPE_CCA_AESCIPHER"'. ",
		.command = COMMAND_CONVERT,
	},
	{
		.option = {"no-apqn-check", 0, NULL, OPT_NO_APQN_CHECK},
		.desc = "Do not check if the associated APQN(s) are available",
		.command = COMMAND_CONVERT,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	{
		.option = {"force", 0, NULL, 'F'},
		.desc = "Do not prompt for a confirmation when converting a "
			"key",
		.command = COMMAND_CONVERT,
	},
	{
		.option = {"exportable", 0, NULL, OPT_EXPORTABLE},
		.desc = "Convert to an secure AES key that is not export "
			"restricted. Exportable keys can be exported by means "
			"of wrapping them with a key encryption key (KEK).",
		.command = COMMAND_CONVERT,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_KMS " " COMMAND_KMS_CONFIGURE,
	},
	{
		.option = { "apqns", required_argument, NULL, 'a'},
		.argument = "[+|-]CARD.DOMAIN[,...]",
		.desc = "Comma-separated pairs of crypto cards and domains "
			"that are associated with the key management system "
			"(KMS) plugin that the repository is bound to, and all "
			"keys generated with the KMS. To add pairs of crypto "
			"cards and domains to the key, specify "
			"'+CARD.DOMAIN[,...]'. To remove pairs of crypto cards "
			"and domains from the key specify '-CARD.DOMAIN[,...]'",
		.command = COMMAND_KMS " " COMMAND_KMS_CONFIGURE,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_KMS " " COMMAND_KMS_REENCIPHER,
	},
	{
		.option = {"to-new", 0, NULL, 'n'},
		.desc = "Re-enciphers KMS plugin internal secure keys that are "
			"currently enciphered with the master key in the "
			"CURRENT register with the master key in the NEW "
			"register.",
		.command = COMMAND_KMS " " COMMAND_KMS_REENCIPHER,
	},
	{
		.option = {"from-old", 0, NULL, 'o'},
		.desc = "Re-enciphers KMS plugin internal secure keys that are "
			"currently enciphered with the master key in the OLD "
			"register with the master key in the CURRENT register.",
		.command = COMMAND_KMS " " COMMAND_KMS_REENCIPHER,
	},
	{
		.option = {"complete", 0, NULL, 'p'},
		.desc = "Completes a staged re-enciphering. Use this option "
			"after the new master key has been set (made "
			"active).",
		.command = COMMAND_KMS " " COMMAND_KMS_REENCIPHER,
	},
	{
		.option = {"in-place", 0, NULL, 'i'},
		.desc = "Forces an in-place re-enchipering of the KMS plugin "
			"internal secure keys. Re-enciphering from OLD to "
			"CURRENT is performed in-place per default.",
		.command = COMMAND_KMS " " COMMAND_KMS_REENCIPHER,
	},
	{
		.option = {"staged", 0, NULL, 's'},
		.desc = "Forces that the re-enciphering of the KMS plugin "
			"internal secure keys is performed in staged mode. "
			"Re-enciphering from CURRENT to NEW is performed in "
			"staged mode per default.",
		.command = COMMAND_KMS " " COMMAND_KMS_REENCIPHER,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_KMS " " COMMAND_KMS_LIST,
	},
	{
		.option = { "label", required_argument, NULL, 'B'},
		.argument = "LABEL",
		.desc = "Label of the secure keys as known by the KMS that "
			"are to be listed. You can use wildcards to select "
			"the keys to be listed.",
		.command = COMMAND_KMS " " COMMAND_KMS_LIST,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure keys as known by zkey that "
			"are to be listed. You can use wildcards to select "
			"the keys to be listed.",
		.command = COMMAND_KMS " " COMMAND_KMS_LIST,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME[:DMNAME][,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the secure key in "
			"the KMS. Use this option to list all keys "
			"associated with specific volumes. The device-mapper "
			"name (DMNAME) is optional. If specified, only those "
			"keys are listed where both, the volume and the device-"
			"mapper name matches.",
		.command = COMMAND_KMS " " COMMAND_KMS_LIST,
	},
	{
		.option = { "volume-type", required_argument, NULL, 't'},
		.argument = "type",
		.desc = "The type of the associated volume(s). Possible values "
			"are 'plain', 'luks2', and 'integrity'. Use this "
			"option to list all keys with the specified volumes "
			"type.",
			.command = COMMAND_KMS " " COMMAND_KMS_LIST,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_KMS " " COMMAND_KMS_IMPORT,
	},
	{
		.option = { "label", required_argument, NULL, 'B'},
		.argument = "LABEL",
		.desc = "Label of the secure keys as known by the KMS that "
			"are to be imported. You can use wildcards to select "
			"the keys to be imported.",
		.command = COMMAND_KMS " " COMMAND_KMS_IMPORT,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure keys as known by zkey that "
			"are to be imported. You can use wildcards to select "
			"the keys to be imported.",
		.command = COMMAND_KMS " " COMMAND_KMS_IMPORT,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME[:DMNAME][,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the secure key in "
			"the KMS. Use this option to import all keys "
			"associated with specific volumes. The device-mapper "
			"name (DMNAME) is optional. If specified, only those "
			"keys are listed where both, the volume and the device-"
			"mapper name matches.",
		.command = COMMAND_KMS " " COMMAND_KMS_IMPORT,
	},
	{
		.option = { "volume-type", required_argument, NULL, 't'},
		.argument = "type",
		.desc = "The type of the associated volume(s). Possible values "
			"for AES keys are 'plain' and 'luks2', and 'integrity' "
			"and 'luks2' for HMAC keys. When this option is omitted, "
			"the default is 'luks2' for AES keys and 'integrity' "
			"for HMAC keys.",
		.command = COMMAND_KMS " " COMMAND_KMS_IMPORT,
	},
	{
		.option = {"batch-mode", 0, NULL, 'q'},
		.desc = "Suppresses alternate name questions. When importing a "
			"key with a name that already exists in the "
			"repository, do not prompt for an alternate name, but "
			"skip the import of the duplicate key.",
		.command = COMMAND_KMS " " COMMAND_KMS_IMPORT,
	},
	{
		.option = {"no-volume-check", 0, NULL, OPT_NO_VOLUME_CHECK},
		.desc = "Do not check if the volume(s) associated with the "
			"secure key(s) to be imported are available, or are "
			"already associated with other secure keys.",
		.command = COMMAND_KMS " " COMMAND_KMS_IMPORT,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_KMS " " COMMAND_KMS_REFRESH,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the secure keys in the repository that "
			"are to be refreshed. You can use wildcards to select "
			"the keys to be refreshed.",
		.command = COMMAND_KMS " " COMMAND_KMS_REFRESH,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME[:DMNAME][,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the secure key in "
			"the repository. Use this option to refresh all keys "
			"associated with specific volumes. The device-mapper "
			"name (DMNAME) is optional. If specified, only those "
			"keys are refreshed where both, the volume and the "
			"device-mapper name matches",
		.command = COMMAND_KMS " " COMMAND_KMS_REFRESH,
	},
	{
		.option = { "volume-type", required_argument, NULL, 't'},
		.argument = "type",
		.desc = "The type of the associated volume(s). Possible values "
			"for AES keys are 'plain' and 'luks2', and 'integrity' "
			"and 'luks2' for HMAC keys. Use this option to refresh "
			"all keys with the specified volumes type.",
		.command = COMMAND_KMS " " COMMAND_KMS_REFRESH,
	},
	{
		.option = { "key-type", required_argument, NULL, 'K'},
		.argument = "type",
		.desc = "The type of the key. Possible values are '"
			KEY_TYPE_CCA_AESDATA"', '"KEY_TYPE_CCA_AESCIPHER"' "
			"and '"KEY_TYPE_EP11_AES"'. Use this option to refresh "
			"all keys with the specified key type.",
		.command = COMMAND_KMS " " COMMAND_KMS_REFRESH,
	},
	{
		.option = {"refresh-properties", 0, NULL, 'P'},
		.desc = "Also refresh the properties of the secure key "
			"and update them with the values from the KMS.",
		.command = COMMAND_KMS " " COMMAND_KMS_REFRESH,
	},
	{
		.option = {"no-volume-check", 0, NULL, OPT_NO_VOLUME_CHECK},
		.desc = "Do not check if the volume(s) associated with the "
			"secure key(s) to be refreshed are available, or are "
			"already associated with other secure keys.",
		.command = COMMAND_KMS " " COMMAND_KMS_REFRESH,
		.flags = UTIL_OPT_FLAG_NOSHORT,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST,
	},
	{
		.option = {"all", 0, NULL, 'A'},
		.desc = "List all protected virtualization (PV) secret types, "
			"not only those that can be used with zkey.",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST,
	},
	{
		.option = {"hex", 0, NULL, 'H'},
		.desc = "Show all protected virtualization (PV) secret IDs in "
			"hex, even if the ID contains only printable "
			"characters.",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST,
	},
	{
		.option = { "pvsecret-type", required_argument, NULL, 'T'},
		.argument = "PVSECRET-TYPE",
		.desc = "Type of the protected virtualization (PV) secret to "
			"list. If omitted, all pvsecret types are listed.",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST,
	},
	{
		.option = { "pvsecret-id", required_argument, NULL, 'I'},
		.argument = "PVSECRET-ID",
		.desc = "ID of the protected virtualization (PV) secret to "
			"list. Either '--pvsecret-id/-I' or "
			"'--pvsecret-name/-e' can be specified, but not both.",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST,
	},
	{
		.option = { "pvsecret-name", required_argument, NULL, 'e'},
		.argument = "PVSECRET-NAME",
		.desc = "Name of the protected virtualization (PV) secret to "
			"list. Either '--pvsecret-name/-e' or "
			"'--pvsecret-id/-I' can be specified, but not both.",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST,
	},
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "OPTIONS",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT,
	},
	{
		.option = { "pvsecret-id", required_argument, NULL, 'I'},
		.argument = "PVSECRET-ID",
		.desc = "ID of the protected virtualization (PV) secret to "
			"import. Either '--pvsecret-id/-I' or "
			"'--pvsecret-name/-e' can be specified, but not both.",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT,
	},
	{
		.option = { "pvsecret-name", required_argument, NULL, 'e'},
		.argument = "PVSECRET-NAME",
		.desc = "Name of the protected virtualization (PV) secret to "
			"import. Either '--pvsecret-name/-e' or "
			"'--pvsecret-id/-I' can be specified, but not both. "
			"If the '--pvsecret-name/-e' option is specified, but "
			"the '--name/-N' option is omitted, then the imported "
			"protected virtualisation secret will be named the "
			"same as the pvsecret name.",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT,
	},
	{
		.option = { "name", required_argument, NULL, 'N'},
		.argument = "NAME",
		.desc = "Name of the imported protected virtualisation secret "
			"in the repository. If the '--name/-N' option is "
			"omitted, but the '--pvsecret-name/-e' is specified, "
			"then the imported protected virtualisation secret "
			"will be named the same as the pvsecret name.",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT,
	},
	{
		.option = { "description", required_argument, NULL, 'd'},
		.argument = "DESCRIPTION",
		.desc = "Textual description of the protected virtualisation "
			"secret in the repository",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT,
	},
	{
		.option = { "volumes", required_argument, NULL, 'l'},
		.argument = "VOLUME:DMNAME[,...]",
		.desc = "Comma-separated pairs of volume and device-mapper "
			"names that are associated with the protected "
			"virtualisation secret in the repository",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT,
	},
	{
		.option = { "sector-size", required_argument, NULL, 'S'},
		.argument = "512|4096",
		.desc = "The sector size used with dm-crypt or dm-integrity. "
			"It must be a power of two and in range 512 - 4096 "
			"bytes. If this option is omitted, the system default "
			"sector size (512) is used",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT,
	},
	{
		.option = { "volume-type", required_argument, NULL, 't'},
		.argument = "type",
		.desc = "The type of the associated volume(s). Possible values "
			"for AES keys are 'plain' and 'luks2', and 'integrity' "
			"and 'luks2' for HMAC keys. When this option is omitted, "
			"the default is 'luks2' for AES keys and 'integrity' "
			"for HMAC keys.",
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT,
	},
	{
		.option = { "gen-dummy-passphrase", 0, NULL,
						OPT_GEN_DUMMY_PASSPHRASE},
		.desc = "Generate a dummy passphrase and associate it with the "
			"protected virtualisation secret used to encrypt LUKS2 "
			"volume(s). The LUKS2 passphrase is of less or no "
			"relevance for the security of the volume(s), when an "
			"protected virtualisation secret is used to encrypt "
			"the volume(s), and can therefore be stored insecurely "
			"inside the secure key repository.",
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT,
	},
	{
		.option = { "set-dummy-passphrase", required_argument, NULL,
						OPT_SET_DUMMY_PASSPHRASE},
		.argument = "passphrase-file",
		.desc = "Set a dummy passphrase to be associated with the "
			"protected virtualisation secret used to encrypt LUKS2 "
			"volume(s). The LUKS2 passphrase is of less or no "
			"relevance for the security of the volume(s), when a "
			"protected virtualisation secret is used to encrypt "
			"the volume(s), and can therefore be stored insecurely "
			"inside the secure key repository.",
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT,
	},
	/***********************************************************/
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	OPT_PLACEHOLDER,
	/***********************************************************/
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "COMMON OPTIONS"
	},
	{
		.option = {"verbose", 0, NULL, 'V'},
		.desc = "Print additional information messages during "
			"processing",
	},
	UTIL_OPT_HELP,
	UTIL_OPT_VERSION,
	UTIL_OPT_END
};

#define ZKEY_COMMAND_STR_LEN	80

/*
 * Table of supported commands
 */
struct zkey_command {
	char *command;
	unsigned int abbrev_len;
	int (*function)(void);
	int need_cca_library;
	int need_ep11_library;
	int need_pkey_device;
	int need_uv_device;
	char *short_desc;
	char *long_desc;
	int has_options;
	char *pos_arg;
	int pos_arg_optional;
	char *pos_arg_alternate;
	char **arg_alternate_value;
	int need_keystore;
	int use_kms_plugin;
	char *kms_plugin_opts_cmd;
	int need_kms_login;
	struct zkey_command *sub_commands;
};

static int command_generate(void);
static int command_reencipher(void);
static int command_validate(void);
static int command_import(void);
static int command_export(void);
static int command_list(void);
static int command_remove(void);
static int command_change(void);
static int command_rename(void);
static int command_copy(void);
static int command_crypttab(void);
static int command_cryptsetup(void);
static int command_integritytab(void);
static int command_integritysetup(void);
static int command_convert(void);
static int command_kms_plugins(void);
static int command_kms_bind(void);
static int command_kms_unbind(void);
static int command_kms_info(void);
static int command_kms_configure(void);
static int command_kms_reencipher(void);
static int command_kms_list(void);
static int command_kms_import(void);
static int command_kms_refresh(void);
static int command_pvsecrets_list(void);
static int command_pvsecrets_import(void);

static struct zkey_command zkey_kms_commands[] = {
	{
		.command = COMMAND_KMS_PLUGINS,
		.abbrev_len = 2,
		.function = command_kms_plugins,
		.short_desc = "Lists key management system plugins",
		.long_desc = "Lists available key management system (KMS) "
			     "plugins.",
		.has_options = 1,
	},
	{
		.command = COMMAND_KMS_BIND,
		.abbrev_len = 2,
		.function = command_kms_bind,
		.short_desc = "Binds the repository to a key management system "
			      "plugin",
		.long_desc = "Binds the repository to the specified key "
			     "management system (KMS) plugin.",
		.need_keystore = 1,
		.has_options = 1,
		.pos_arg = "[KMS-PLUGIN]",
	},
	{
		.command = COMMAND_KMS_UNBIND,
		.abbrev_len = 3,
		.function = command_kms_unbind,
		.short_desc = "Unbinds the repository from a key management "
			      "system plugin",
		.long_desc = "Unbinds the repository from the current key "
			     "management system (KMS) plugin, and turns all "
			     "KMS-bound keys into local keys",
		.need_keystore = 1,
		.has_options = 1,
		.use_kms_plugin = 1,
	},
	{
		.command = COMMAND_KMS_INFO,
		.abbrev_len = 2,
		.function = command_kms_info,
		.short_desc = "Displays information about a key management "
			      "system plugin",
		.long_desc = "Displays information about the current key "
			     "management system (KMS) plugin",
		.need_keystore = 1,
		.has_options = 1,
		.use_kms_plugin = 1,
	},
	{
		.command = COMMAND_KMS_CONFIGURE,
		.abbrev_len = 3,
		.function = command_kms_configure,
		.short_desc = "Configures a key management system plugin",
		.long_desc = "Configures or re-configures the current key "
			     "management system (KMS) plugin",
		.need_keystore = 1,
		.has_options = 1,
		.use_kms_plugin = 1,
		.kms_plugin_opts_cmd = KMS_COMMAND_CONFIGURE,
	},
	{
		.command = COMMAND_KMS_REENCIPHER,
		.abbrev_len = 2,
		.function = command_kms_reencipher,
		.short_desc = "Re-enciphers secure keys used by a key "
			      "management system plugin",
		.long_desc = "Re-enciphers secure keys internally used by a "
			     "key management system (KMS) plugin",
		.need_keystore = 1,
		.has_options = 1,
		.use_kms_plugin = 1,
		.kms_plugin_opts_cmd = KMS_COMMAND_REENCIPHER,
	},
	{
		.command = COMMAND_KMS_LIST,
		.abbrev_len = 2,
		.function = command_kms_list,
		.short_desc = "Lists secure keys managed by a key management "
			      "system",
		.long_desc = "Lists secure keys managed by a key management "
			     "system (KMS)",
		.need_keystore = 1,
		.has_options = 1,
		.use_kms_plugin = 1,
		.need_kms_login = 1,
		.kms_plugin_opts_cmd = KMS_COMMAND_LIST,
	},
	{
		.command = COMMAND_KMS_IMPORT,
		.abbrev_len = 2,
		.function = command_kms_import,
		.short_desc = "Imports secure keys managed by a key management "
			      "system",
		.long_desc = "Imports secure keys managed by a key management "
			     "system (KMS) into the repository",
		.need_keystore = 1,
		.has_options = 1,
		.use_kms_plugin = 1,
		.need_kms_login = 1,
		.need_pkey_device = 1,
		.kms_plugin_opts_cmd = KMS_COMMAND_LIST_IMPORT,
	},
	{
		.command = COMMAND_KMS_REFRESH,
		.abbrev_len = 3,
		.function = command_kms_refresh,
		.short_desc = "Refreshes secure keys that are bound to a key "
			      "management system",
		.long_desc = "Refreshes secure keys that are bound to a key "
			      "management system (KMS)",
		.need_keystore = 1,
		.has_options = 1,
		.use_kms_plugin = 1,
		.need_kms_login = 1,
		.need_pkey_device = 1,
	},
	{ .command = NULL }
};

static struct zkey_command zkey_pvsecrets_commands[] = {
	{
		.command = COMMAND_PVSECRETS_LIST,
		.abbrev_len = 2,
		.function = command_pvsecrets_list,
		.short_desc = "Lists protected virtualization (PV) secrets",
		.long_desc = "Lists available protected virtualization (PV) "
			     "secrets. This command is only available when "
			     "running in a secure execution guest. Only the "
			     "'root' user is allowed to perform this command",
		.has_options = 1,
		.need_uv_device = 1,
	},
	{
		.command = COMMAND_PVSECRETS_IMPORT,
		.abbrev_len = 2,
		.function = command_pvsecrets_import,
		.short_desc = "Imports a protected virtualization (PV) secret",
		.long_desc = "Imports a protected virtualization (PV) secret "
			      "into the repository. This command is only "
			      "available when running in a secure execution "
			      "guest.",
		.has_options = 1,
		.need_keystore = 1,
		.need_uv_device = 1,
		.need_pkey_device = 1,
	},
	{ .command = NULL }
};

static struct zkey_command zkey_commands[] = {
	{
		.command = COMMAND_GENERATE,
		.abbrev_len = 3,
		.function = command_generate,
		.need_pkey_device = 1,
		.short_desc = "Generate a secure AES or HMAC key",
		.long_desc = "Generate a secure AES or HMAC key either by "
			     "random or from a specified clear key and store "
			     "it either into SECURE-KEY-FILE or into the "
			     "repository",
		.has_options = 1,
		.pos_arg = "[SECURE-KEY-FILE]",
		.pos_arg_optional = 1,
		.pos_arg_alternate = "--name/-N",
		.arg_alternate_value = &g.name,
		.use_kms_plugin = 1,
		.kms_plugin_opts_cmd = KMS_COMMAND_GENERATE,
	},
	{
		.command = COMMAND_REENCIPHER,
		.abbrev_len = 2,
		.function = command_reencipher,
		/* Will load the CCA or EP11 library on demand */
		.need_pkey_device = 1,
		.short_desc = "Re-encipher an existing secure key",
		.long_desc = "Re-encipher an existing secure "
			     "key that is either contained in SECURE-KEY-FILE "
			     "or is stored in the repository with another "
			     "master key",
		.has_options = 1,
		.pos_arg = "[SECURE-KEY-FILE]",
		.pos_arg_optional = 1,
	},
	{
		.command = COMMAND_VALIDATE,
		.abbrev_len = 3,
		.function = command_validate,
		.need_pkey_device = 1,
		.short_desc = "Validate an existing secure key",
		.long_desc = "Validate an existing secure key that is "
			     "either contained in SECURE-KEY-FILE or is stored "
			     "in the repository and print information about "
			     "the key",
		.has_options = 1,
		.pos_arg = "[SECURE-KEY-FILE]",
		.pos_arg_optional = 1,
	},
	{
		.command = COMMAND_IMPORT,
		.abbrev_len = 2,
		.function = command_import,
		.need_pkey_device = 1,
		.short_desc = "Import a secure key",
		.long_desc = "Import a secure key from a file into the "
			     "repository",
		.has_options = 1,
		.pos_arg = "SECURE-KEY-FILE",
		.need_keystore = 1,
	},
	{
		.command = COMMAND_EXPORT,
		.abbrev_len = 2,
		.function = command_export,
		.short_desc = "Export a secure key",
		.long_desc = "Export a secure key from the repository to "
			     "a file",
		.has_options = 1,
		.pos_arg = "SECURE-KEY-FILE",
		.need_keystore = 1,
	},
	{
		.command = COMMAND_LIST,
		.abbrev_len = 2,
		.function = command_list,
		.short_desc = "List keys in the repository",
		.long_desc = "List secure keys in the repository",
		.has_options = 1,
		.need_keystore = 1,
	},
	{
		.command = COMMAND_REMOVE,
		.abbrev_len = 3,
		.function = command_remove,
		.short_desc = "Remove a secure key",
		.long_desc = "Remove a secure key from the repository",
		.has_options = 1,
		.need_keystore = 1,
		.use_kms_plugin = 1,
		.kms_plugin_opts_cmd = KMS_COMMAND_REMOVE,
	},
	{
		.command = COMMAND_CHANGE,
		.abbrev_len = 2,
		.function = command_change,
		.short_desc = "Change a secure key",
		.long_desc = "Change the properties of a secure key in "
			     "the repository",
		.has_options = 1,
		.need_keystore = 1,
		.use_kms_plugin = 1,
		.need_pkey_device = 1,
	},
	{
		.command = COMMAND_RENAME,
		.abbrev_len = 3,
		.function = command_rename,
		.short_desc = "Rename a secure key",
		.long_desc = "Rename a secure key in the repository",
		.has_options = 1,
		.need_keystore = 1,
		.use_kms_plugin = 1,
	},
	{
		.command = COMMAND_COPY,
		.abbrev_len = 2,
		.function = command_copy,
		.short_desc = "Copy a secure key",
		.long_desc = "Copy a secure key in the repository",
		.has_options = 1,
		.need_keystore = 1,
	},
	{
		.command = COMMAND_CRYPTTAB,
		.abbrev_len = 6,
		.function = command_crypttab,
		.short_desc = "Generate crypttab entries",
		.long_desc = "Generate crypttab entries for selected volumes",
		.has_options = 1,
		.need_keystore = 1,
	},
	{
		.command = COMMAND_CRYPTSETUP,
		.abbrev_len = 6,
		.function = command_cryptsetup,
		.short_desc = "Generate or run cryptsetup commands",
		.long_desc = "Generate or run cryptsetup commands for "
			     "selected volumes",
		.has_options = 1,
		.need_keystore = 1,
	},
	{
		.command = COMMAND_INTEGRITYTAB,
		.abbrev_len = 10,
		.function = command_integritytab,
		.short_desc = "Generate integritytab entries",
		.long_desc = "Generate integritytab entries for selected "
			     "volumes",
		.has_options = 1,
		.need_keystore = 1,
	},
	{
		.command = COMMAND_INTEGRITYSETUP,
		.abbrev_len = 10,
		.function = command_integritysetup,
		.short_desc = "Generate or run integritysetup commands",
		.long_desc = "Generate or run integritysetup commands for "
			     "selected volumes",
		.has_options = 1,
		.need_keystore = 1,
	},
	{
		.command = COMMAND_CONVERT,
		.abbrev_len = 3,
		.function = command_convert,
		.need_cca_library = 1,
		.need_pkey_device = 1,
		.short_desc = "Convert a secure key",
		.long_desc = "Convert an existing secure key that is "
			     "either contained in SECURE-KEY-FILE or is stored "
			     "in the repository from one key type to another "
			     "type.",
		.has_options = 1,
		.pos_arg = "[SECURE-KEY-FILE]",
		.pos_arg_optional = 1,
	},
	{
		.command = COMMAND_KMS,
		.abbrev_len = 2,
		.short_desc = "key management system (KMS) support",
		.long_desc = "Provides subcommands for key management system "
			     "(KMS) support.",
		.has_options = 1,
		.sub_commands = zkey_kms_commands,
	},
	{
		.command = COMMAND_PVSECRETS,
		.abbrev_len = 2,
		.short_desc = "Protected virtualization (PV) support",
		.long_desc = "Provides subcommands for working with protected "
			     "virtualization (PV) secrets. Protected "
			     "virtualization secrets can be made available to "
			     "a secure execution guest and can be used only "
			     "within that guest. Thus, these subcommands are "
			     "only available when running in a secure "
			     "execution guest. Only the 'root' user is allowed "
			     "to perform these subcommands.",
		.has_options = 1,
		.sub_commands = zkey_pvsecrets_commands,
	},
	{ .command = NULL }
};

#define pr_verbose(fmt...)	if (g.verbose) \
					warnx(fmt)

static void print_usage_sub_command_list(const struct zkey_command *sub_command)
{
	struct zkey_command *sub_cmd = (struct zkey_command *)sub_command;
	char command_str[ZKEY_COMMAND_STR_LEN];
	unsigned int i;

	printf("SUBCOMMANDS\n");
	while (sub_cmd->command) {
		strcpy(command_str, sub_cmd->command);
		for (i = 0; i < sub_cmd->abbrev_len; i++)
			command_str[i] =
				toupper(command_str[i]);
		printf("  %-*s    %s\n", ZKEY_COMMAND_MAX_LEN,
		       command_str, sub_cmd->short_desc);
		sub_cmd++;
	}
}

static void print_usage_command(const struct zkey_command *command,
				const struct zkey_command *sub_command)
{
	char sub_command_str[ZKEY_COMMAND_STR_LEN];
	char command_str[ZKEY_COMMAND_STR_LEN];
	const struct zkey_command *cmd;
	unsigned int i;

	strncpy(command_str, command->command, sizeof(command_str) - 1);
	for (i = 0; i < command->abbrev_len; i++)
		command_str[i] = toupper(command_str[i]);

	if (sub_command != NULL) {
		strncpy(sub_command_str, sub_command->command,
			sizeof(sub_command_str) - 1);
		for (i = 0; i < sub_command->abbrev_len; i++)
			sub_command_str[i] = toupper(sub_command_str[i]);

		printf("Usage: %s %s %s", program_invocation_short_name,
		       command_str, sub_command_str);
	} else {
		printf("Usage: %s %s", program_invocation_short_name,
		       command_str);
		if (command->sub_commands != NULL)
			printf(" SUBCOMMAND");
	}

	cmd = sub_command != NULL ? sub_command : command;

	if (cmd->pos_arg != NULL)
		printf(" %s", cmd->pos_arg);
	if (cmd->has_options)
		printf(" [OPTIONS]");

	if (prg.args)
		printf(" %s", prg.args);
	printf("\n\n");
	util_print_indented(cmd->long_desc, 0);

	if (cmd->has_options)
		printf("\n");

	if (sub_command == NULL && command->sub_commands != NULL)
		print_usage_sub_command_list(command->sub_commands);
}

static void print_usage_command_list(void)
{
	struct zkey_command *cmd = zkey_commands;
	char command_str[ZKEY_COMMAND_STR_LEN];
	unsigned int i;

	util_prg_print_help();

	printf("COMMANDS\n");
	while (cmd->command) {
		strcpy(command_str, cmd->command);
		for (i = 0; i < cmd->abbrev_len; i++)
			command_str[i] = toupper(command_str[i]);
		printf("  %-*s    %s\n", ZKEY_COMMAND_MAX_LEN, command_str,
		       cmd->short_desc);
		cmd++;
	}
	printf("\n");
}

/*
 * --help printout
 */
static void print_help(const struct zkey_command *command,
		       const struct zkey_command *sub_command)
{
	/* Print usage */
	if (command == NULL)
		print_usage_command_list();
	else
		print_usage_command(command, sub_command);

	/* Print parameter help */
	util_opt_print_help();

	if (command == NULL) {
		printf("\n");
		printf("For more information use '%s COMMAND --help'.\n",
			program_invocation_short_name);
	} else if (command->sub_commands != NULL && sub_command == NULL) {
		printf("\n");
		printf("For more information use '%s %s SUBCOMMAND --help'.\n",
			program_invocation_short_name, command->command);
	}
}

/*
 * Command handler for 'generate with clear key'
 *
 * Generate a secure key from the specified clear key.
 */
static int command_generate_clear(void)
{
	int rc;

	rc = generate_secure_key_clear(g.pkey_fd, g.pos_arg,
				       g.keybits, g.xts,
				       g.clearkeyfile, g.key_type,
				       NULL, g.exportable, g.wrap_with_trusted,
				       g.verbose);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'generate by random'.
 *
 * Generate a secure key by random using the pkey kernel module.
 */
static int command_generate_random(void)
{
	int rc;

	rc = generate_secure_key_random(g.pkey_fd, g.pos_arg,
					g.keybits, g.xts, g.key_type,
					NULL, g.exportable, g.wrap_with_trusted,
					g.verbose);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'generate in repository'.
 *
 * Generate a secure key and store it in the repository.
 */
static int command_generate_repository(void)
{
	int rc;

	if (g.sector_size < 0)
		g.sector_size = 0;

	if (g.gen_passphrase && g.passphrase_file != NULL) {
		warnx("Either '--gen-dummy-passphrase' or "
		      "'--set-dummy-passphrase' can be specified, but not "
		      "both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}

	if (g.kms_info.plugin_lib != NULL && !g.local) {
		if (g.apqns != NULL) {
			warnx("Option '--apqns|-a' is not valid for "
			      "generating a key in a KMS-bound repository");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}

		if (g.clearkeyfile != NULL) {
			warnx("Option '--clearkey|-c' is not valid for "
			      "generating a key in a KMS-bound repository, "
			      "unless option '--local|-L' is also specified");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}

		if (g.exportable) {
			warnx("Option '--exportable' is not supported for "
			      "generating a key in a KMS-bound repository");
			return EXIT_FAILURE;
		}
		if (g.wrap_with_trusted) {
			warnx("Option '--wrap-with-trusted' is not supported "
			      "for generating a key in a KMS-bound repository");
			return EXIT_FAILURE;
		}

		rc = perform_kms_login(&g.kms_info, g.verbose);
		if (rc != 0)
			rc = EXIT_FAILURE;

		rc = keystore_generate_key_kms(g.keystore, g.name,
					       g.description, g.volumes,
					       g.sector_size, g.keybits, g.xts,
					       g.volume_type, g.key_type,
					       g.gen_passphrase,
					       g.passphrase_file,
					       g.kms_options,
					       g.num_kms_options,
					       g.pkey_fd);
		goto out;
	}

	if (g.key_type == NULL)
		g.key_type = KEY_TYPE_CCA_AESDATA;

	if (g.wrap_with_trusted) {
		if (!g.exportable) {
			warnx("Option '--wrap-with-trusted' is only valid "
			      "together with the '--exportable' option");
			return EXIT_FAILURE;
		}
		if (strcasecmp(g.key_type, KEY_TYPE_EP11_AES) != 0) {
			warnx("Option '--wrap-with-trusted' is only valid for "
			      "keys of type '%s'", KEY_TYPE_EP11_AES);
			return EXIT_FAILURE;
		}
	}

	rc = keystore_generate_key(g.keystore, g.name, g.description, g.volumes,
				   g.apqns, g.noapqncheck, g.sector_size,
				   g.keybits, g.xts, g.clearkeyfile,
				   g.volume_type, g.key_type, g.gen_passphrase,
				   g.passphrase_file, g.exportable,
				   g.wrap_with_trusted, g.pkey_fd);

out:
	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}


/*
 * Command handler for 'generate'.
 *
 * Generate a new secure key either by random or from the specified clear key.
 */
static int command_generate(void)
{
	int rc;

	if (g.pos_arg != NULL && g.name != NULL) {
		warnx(" Option '--name|-N' is not valid for generating a key "
		      "outside of the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.apqns == NULL && g.noapqncheck) {
		warnx("Option '--no-apqn-check' is only valid together with "
		      "the '--apqns|-a' option");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.name != NULL)
		return command_generate_repository();
	if (g.key_type == NULL)
		g.key_type = KEY_TYPE_CCA_AESDATA;
	if (!is_secure_key_type(g.key_type)) {
		warnx("Keys of type '%s' can not be generated. Use 'zkey "
		      "pvsecret import' instead", g.key_type);
		return -EXIT_FAILURE;
	}
	if (g.wrap_with_trusted) {
		if (!g.exportable) {
			warnx("Option '--wrap-with-trusted' is only valid "
			      "together with the '--exportable' option");
			return EXIT_FAILURE;
		}
		if (strcasecmp(g.key_type, KEY_TYPE_EP11_AES) != 0) {
			warnx("Option '--wrap-with-trusted' is only valid for "
			      "keys of type '%s'", KEY_TYPE_EP11_AES);
			return EXIT_FAILURE;
		}
	}
	if (g.pos_arg != NULL) {
		if (g.volumes != NULL) {
			warnx("Option '--volumes|-l' is not valid for "
			      "generating a key outside of the repository");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}
		if (g.apqns != NULL) {
			warnx("Option '--apqns|-a' is not valid for "
			      "generating a key outside of the repository");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}
		if (g.noapqncheck) {
			warnx("Option '--no-apqn-check' is not valid for "
			      "generating a key outside of the repository");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}
		if (g.description != NULL) {
			warnx("Option '--description|-d' is not valid for "
			      "generating a key outside of the repository");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}
		if (g.local) {
			warnx("Option '--local|-L' is not valid for "
			      "generating a key outside of the repository");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}
		if (g.gen_passphrase) {
			warnx("Option '--gen-dummy-passphrase' is not valid "
			      "for generating a key outside of the repository");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}
		if (g.passphrase_file != NULL) {
			warnx("Option '--sen-dummy-passphrase' is not valid "
			      "for generating a key outside of the repository");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}

		rc = cross_check_apqns(NULL, NULL,
				get_min_card_level_for_keytype(g.key_type),
				get_min_fw_version_for_keytype(g.key_type),
				get_card_type_for_keytype(g.key_type),
				true, g.verbose);
		if (rc == -EINVAL)
			return EXIT_FAILURE;
		if (rc != 0 && rc != -ENOTSUP) {
			warnx("Your master key setup is improper");
			return EXIT_FAILURE;
		}

		return g.clearkeyfile ? command_generate_clear()
				      : command_generate_random();
	}

	return EXIT_FAILURE;
}


/*
 * Command handler for 'reencipher'.
 *
 * Re-encipher the specified secure key with the NEW or CURRENT CCA master key.
 */
static int command_reencipher_file(void)
{
	size_t secure_key_size;
	int rc, is_old_mk;
	u8 *secure_key;
	bool selected;

	if (g.name != NULL) {
		warnx("Option '--name|-N' is not valid for "
		      "re-enciphering a key outside of the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.apqns != NULL) {
		warnx("Option '--apqns|-a' is not valid for "
		      "re-enciphering a key outside of the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.inplace) {
		warnx("Option '--in-place|-i' is not valid for "
		      "re-enciphering a key outside of the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.staged) {
		warnx("Option '--staged|-s' is not valid for "
		      "re-enciphering a key outside of the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.complete) {
		warnx("Option '--complete|-p' is not valid for "
		      "re-enciphering a key outside of the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}

	/* Read the secure key to be re-enciphered */
	secure_key = read_secure_key(g.pos_arg, &secure_key_size, g.verbose);
	if (secure_key == NULL)
		return EXIT_FAILURE;

	if (!is_secure_key(secure_key, secure_key_size)) {
		warnx("A key of type %s can not be re-enciphered",
		      get_key_type(secure_key, secure_key_size));
		rc = EXIT_FAILURE;
		goto out;
	}

	rc = validate_secure_key(g.pkey_fd, secure_key, secure_key_size, NULL,
				 &is_old_mk, NULL, g.verbose);
	if (rc != 0) {
		warnx("The secure key in file '%s' is not valid", g.pos_arg);
		rc = EXIT_FAILURE;
		goto out;
	}

	if (!g.fromold && !g.tonew) {
		/* Autodetect reencipher option */
		if (is_old_mk) {
			g.fromold = 1;
			util_print_indented("The secure key is currently "
					    "enciphered with the OLD "
					    "master key and is being "
					    "re-enciphered with the CURRENT "
					    "master key\n", 0);
		} else {
			g.tonew = 1;
			util_print_indented("The secure key is currently "
					    "enciphered with the CURRENT "
					    "master key and is being "
					    "re-enciphered with the NEW "
					    "master key\n", 0);
		}
	}

	/* Re-encipher the secure key */
	if (g.fromold) {
		if (!is_old_mk) {
			warnx("The secure key is already enciphered "
			      "with the CURRENT master key");
			rc = EXIT_FAILURE;
			goto out;
		}

		pr_verbose("Secure key will be re-enciphered from OLD to the "
			   "CURRENT master key");

		rc = reencipher_secure_key(&g.lib, secure_key, secure_key_size,
					   NULL, REENCIPHER_OLD_TO_CURRENT,
					   &selected, g.verbose);
		if (rc != 0) {
			if (rc == -ENODEV) {
				warnx("No APQN found that is suitable for "
				      "re-enciphering the secure volume "
				      "key");
			} else {
				warnx("Re-encipher from OLD to CURRENT "
				      "master key has failed\n");
				if (!selected &&
				    !is_ep11_aes_key(secure_key,
						     secure_key_size) &&
				    !is_ep11_aes_key_with_header(secure_key,
							secure_key_size))
					print_msg_for_cca_envvars(
							"secure key");
			}
			rc = EXIT_FAILURE;
			goto out;
		}
	}
	if (g.tonew) {
		pr_verbose("Secure key will be re-enciphered from CURRENT "
			   "to the NEW master key");

		rc = reencipher_secure_key(&g.lib, secure_key, secure_key_size,
					   NULL, REENCIPHER_CURRENT_TO_NEW,
					   &selected, g.verbose);
		if (rc != 0) {
			if (rc == -ENODEV) {
				warnx("No APQN found that is suitable for "
				      "re-enciphering the secure volume "
				      "key and has the NEW master key loaded");
			} else {
				warnx("Re-encipher from CURRENT to NEW "
				      "master key has failed\n");
				if (!selected &&
				    !is_ep11_aes_key(secure_key,
						     secure_key_size) &&
				    !is_ep11_aes_key_with_header(secure_key,
							secure_key_size))
					print_msg_for_cca_envvars(
							"secure key");
			}
			rc = EXIT_FAILURE;
			goto out;
		}
	}

	pr_verbose("Secure key was re-enciphered successfully");

	/* Write the migrated secure key */
	rc = write_secure_key(g.outputfile ? g.outputfile : g.pos_arg,
			      secure_key, secure_key_size, g.verbose);
	if (rc != 0)
		rc = EXIT_FAILURE;
out:
	free(secure_key);
	return rc;
}

/*
 * Command handler for 'reencipher in repository'.
 *
 * Re-encipher the specified secure key with the NEW or CURRENT master key.
 */
static int command_reencipher_repository(void)
{
	int rc;

	if (g.outputfile) {
		warnx("Option '--output|-o' is not valid for "
		      "re-enciphering a key in the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.inplace && g.staged) {
		warnx("Either '--in-place|-i' or '--staged|-s' can be "
		      "specified, but not both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.complete) {
		if (g.inplace) {
			warnx("Option '--in-place|-i' is not valid together "
			      "with '--complete|-p'");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}
		if (g.staged) {
			warnx("Option '--staged|-s' is not valid together "
			      "with '--complete|-p'");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}
	}

	rc = keystore_reencipher_key(g.keystore, g.name, g.apqns, g.fromold,
				     g.tonew, g.inplace, g.staged, g.complete,
				     g.pkey_fd, &g.lib);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'reencipher'.
 *
 * Re-encipher the specified secure key with the NEW or CURRENT master key.
 */
static int command_reencipher(void)
{
	if (g.pos_arg != NULL)
		return command_reencipher_file();
	else
		return command_reencipher_repository();

	return EXIT_FAILURE;
}

/*
 * Command handler for 'validate'.
 *
 * Validates the specified secure key and prints out information about it.
 */
static int command_validate_file(void)
{
	char vp[VERIFICATION_PATTERN_LEN];
	size_t secure_key_size;
	size_t clear_key_size;
	const char *key_type;
	u8 mkvp[MKVP_LENGTH];
	u8 *secure_key;
	int is_old_mk;
	int rc;

	if (g.name != NULL) {
		warnx("Option '--name|-N' is not valid for "
		      "validating a key outside of the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.apqns != NULL) {
		warnx("Option '--apqns|-a' is not valid for "
		      "validating a key outside of the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.noapqncheck) {
		warnx("Option '--no-apqn-check' is not valid for "
		      "validating a key outside of the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}

	/* Read the secure key to be re-enciphered */
	secure_key = read_secure_key(g.pos_arg, &secure_key_size, g.verbose);
	if (secure_key == NULL)
		return EXIT_FAILURE;

	rc = validate_secure_key(g.pkey_fd, secure_key, secure_key_size,
				 &clear_key_size, &is_old_mk, NULL, g.verbose);
	if (rc != 0) {
		warnx("The secure key in file '%s' is not valid", g.pos_arg);
		rc = EXIT_FAILURE;
		goto out;
	}

	rc = generate_key_verification_pattern(g.pkey_fd, secure_key,
					       secure_key_size, vp, sizeof(vp),
					       g.verbose);
	if (rc != 0) {
		warnx("Failed to generate the verification pattern: %s",
		      strerror(-rc));
		rc = EXIT_FAILURE;
		goto out;
	}

	if (is_secure_key(secure_key, secure_key_size)) {
		rc = get_master_key_verification_pattern(secure_key,
							 secure_key_size,
							 mkvp, g.verbose);
		if (rc != 0) {
			warnx("Failed to get the master key verification "
			      "pattern: %s", strerror(-rc));
			rc = EXIT_FAILURE;
			goto out;
		}
	}

	key_type = get_key_type(secure_key, secure_key_size);

	printf("Validation of secure key in file '%s':\n", g.pos_arg);
	printf("  Status:                Valid\n");
	printf("  Secure key size:       %lu bytes\n", secure_key_size);
	printf("  Key type:              %s\n", key_type);
	printf("  Clear key size:        %lu bits\n", clear_key_size);
	printf("  XTS type key:          %s\n",
	       is_xts_key(secure_key, secure_key_size) ? "Yes" : "No");
	if (is_secure_key(secure_key, secure_key_size)) {
		printf("  Enciphered with:       %s master key (MKVP: %s)\n",
		       is_old_mk ? "OLD" : "CURRENT",
		       printable_mkvp(get_card_type_for_keytype(key_type),
				      mkvp));
	}
	printf("  Verification pattern:  %.*s\n", VERIFICATION_PATTERN_LEN / 2,
	       vp);
	printf("                         %.*s\n", VERIFICATION_PATTERN_LEN / 2,
	       &vp[VERIFICATION_PATTERN_LEN / 2]);

	if (is_secure_key(secure_key, secure_key_size)) {
		rc = cross_check_apqns(NULL, mkvp,
				       get_min_card_level_for_keytype(key_type),
				       get_min_fw_version_for_keytype(key_type),
				       get_card_type_for_keytype(key_type),
				       true, g.verbose);
		if (rc == -EINVAL)
			return EXIT_FAILURE;
		if (rc != 0 && rc != -ENOTSUP) {
			warnx("Your master key setup is improper");
			rc = EXIT_FAILURE;
			goto out;
		}
	}

out:
	free(secure_key);
	return rc;
}

/*
 * Command handler for 'validate in repository'.
 *
 * Validates the specified secure key and prints out information about it.
 */
static int command_validate_repository(void)
{
	int rc;

	rc = keystore_validate_key(g.keystore, g.name, g.apqns, g.noapqncheck,
				   g.pkey_fd);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'validate'.
 *
 * Validates the specified secure key and prints out information about it.
 */
static int command_validate(void)
{
	if (g.pos_arg != NULL)
		return command_validate_file();
	else
		return command_validate_repository();

	return EXIT_FAILURE;
}

/*
 * Command handler for 'import'.
 *
 * Imports a secure key from a file into the key repository.
 */
static int command_import(void)
{
	int rc;

	if (g.name == NULL) {
		misc_print_required_parm("--name/-N");
		return EXIT_FAILURE;
	}

	if (g.sector_size < 0)
		g.sector_size = 0;

	if (g.apqns == NULL && g.noapqncheck) {
		warnx("Option '--no-apqn-check' is only valid together with "
		      "the '--apqns|-a' option");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.gen_passphrase && g.passphrase_file != NULL) {
		warnx("Either '--gen-dummy-passphrase' or "
		      "'--set-dummy-passphrase' can be specified, but not "
		      "both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}


	rc = keystore_import_key(g.keystore, g.name, g.description, g.volumes,
				 g.apqns, g.noapqncheck, g.sector_size,
				 g.pos_arg, g.volume_type, g.gen_passphrase,
				 g.passphrase_file, g.exportable, &g.lib,
				 g.pkey_fd);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'export'.
 *
 * Exports a secure key from the repository to a file
 */
static int command_export(void)
{
	int rc;

	if (g.name == NULL) {
		misc_print_required_parm("--name/-N");
		return EXIT_FAILURE;
	}

	rc = keystore_export_key(g.keystore, g.name, g.pos_arg);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'list'.
 *
 * Lists keys stored in the repository
 */
static int command_list(void)
{
	int rc;

	if (g.local && g.kms_bound) {
		warnx("Either '--local|-L' or '--kms-bound|-M' can be "
		      "specified, but not both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}

	rc = keystore_list_keys(g.keystore, g.name, g.volumes, g.apqns,
				g.volume_type, g.key_type, g.local,
				g.kms_bound);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'remove'.
 *
 * Remove a key from the repository
 */
static int command_remove(void)
{
	int rc;

	if (g.name == NULL) {
		misc_print_required_parm("--name/-N");
		return EXIT_FAILURE;
	}

	rc = keystore_remove_key(g.keystore, g.name, g.force, g.kms_options,
				 g.num_kms_options);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'change'.
 *
 * Changes the properties of a key in the repository
 */
static int command_change(void)
{
	int rc;

	if (g.name == NULL) {
		misc_print_required_parm("--name/-N");
		return EXIT_FAILURE;
	}
	if (g.apqns == NULL && g.noapqncheck) {
		warnx("Option '--no-apqn-check' is only valid together with "
		      "the '--apqns|-a' option");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.gen_passphrase && g.passphrase_file != NULL) {
		warnx("Either '--gen-dummy-passphrase' or "
		      "'--set-dummy-passphrase' can be specified, but not "
		      "both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.gen_passphrase && g.remove_passphrase) {
		warnx("Either '--gen-dummy-passphrase' or "
		      "'--remove-dummy-passphrase' can be specified, but not "
		      "both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.passphrase_file != NULL && g.remove_passphrase) {
		warnx("Either '--set-dummy-passphrase' or "
		      "'--remove-dummy-passphrase' can be specified, but not "
		      "both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.force && !g.remove_passphrase) {
		warnx("Option '--force|-F' is only valid together with "
		      "the '--remove-dummy-passphrase' option");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}

	rc = keystore_change_key(g.keystore, g.name, g.description, g.volumes,
				 g.apqns, g.noapqncheck, g.sector_size,
				 g.volume_type, g.gen_passphrase,
				 g.passphrase_file, g.remove_passphrase,
				 g.force, g.pkey_fd);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'rname'.
 *
 * renames a key in the repository
 */
static int command_rename(void)
{
	int rc;

	if (g.name == NULL) {
		misc_print_required_parm("--name/-N");
		return EXIT_FAILURE;
	}
	if (g.newname == NULL) {
		misc_print_required_parm("--new-name/-w");
		return EXIT_FAILURE;
	}

	rc = keystore_rename_key(g.keystore, g.name, g.newname);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'copy'.
 *
 * Copies a key in the repository
 */
static int command_copy(void)
{
	int rc;

	if (g.name == NULL) {
		misc_print_required_parm("--name/-N");
		return EXIT_FAILURE;
	}
	if (g.newname == NULL) {
		misc_print_required_parm("--new-name/-w");
		return EXIT_FAILURE;
	}

	rc = keystore_copy_key(g.keystore, g.name, g.newname, g.volumes,
			       g.local);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'crypttab'.
 *
 * Generates crypttab entries for selected volumes
 */
static int command_crypttab(void)
{
	int rc;

	rc = keystore_crypttab(g.keystore, g.volumes, g.volume_type, g.keyfile,
			       g.keyfile_offset, g.keyfile_size, g.tries);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'cryptsetup'.
 *
 * Generates and runs cryptsetup commands for selected volumes
 */
static int command_cryptsetup(void)
{
	int rc;

	if (g.open && g.format) {
		warnx("Either '--open' or '--format' can be specified, but "
		      "not both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}

	rc = keystore_cryptsetup(g.keystore, g.volumes, g.run, g.volume_type,
				 g.keyfile, g.keyfile_offset, g.keyfile_size,
				 g.tries, g.batch_mode, g.open, g.format);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'integritytab'.
 *
 * Generates integritytab entries for selected volumes
 */
static int command_integritytab(void)
{
	int rc;

	rc = keystore_integritytab(g.keystore, g.volumes);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'integritysetup'.
 *
 * Generates and runs integritysetup commands for selected volumes
 */
static int command_integritysetup(void)
{
	int rc;

	rc = keystore_integritysetup(g.keystore, g.volumes, g.run,
				     g.batch_mode, g.open);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'convert'.
 *
 * Converts secure keys from one key type to another
 */
static int command_convert_file(void)
{
	u8 output_key[2 * MAX_SECURE_KEY_SIZE];
	unsigned int output_key_size;
	size_t secure_key_size;
	u8 mkvp[MKVP_LENGTH];
	int rc, is_old_mk;
	int selected = 1;
	u8 *secure_key;
	int min_level;

	if (g.name != NULL) {
		warnx("Option '--name|-N' is not valid for "
		      "converting a key outside of the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.noapqncheck) {
		warnx("Option '--no-apqn-check' is not valid for "
		      "converting a key outside of the repository");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}

	min_level = get_min_card_level_for_keytype(g.key_type);
	if (min_level < 0) {
		warnx("Invalid key-type specified: %s", g.key_type);
		return EXIT_FAILURE;
	}

	rc = cross_check_apqns(NULL, NULL, min_level,
			       get_min_fw_version_for_keytype(g.key_type),
			       get_card_type_for_keytype(g.key_type),
			       true, g.verbose);
	if (rc == -EINVAL)
		return EXIT_FAILURE;
	if (rc != 0 && rc != -ENOTSUP) {
		warnx("Your master key setup is improper");
		return EXIT_FAILURE;
	}

	/* Read the secure key to be re-enciphered */
	secure_key = read_secure_key(g.pos_arg, &secure_key_size, g.verbose);
	if (secure_key == NULL)
		return EXIT_FAILURE;

	if (!is_cca_aes_data_key(secure_key, secure_key_size)) {
		warnx("Only secure keys of type %s can "
		      "be converted. The secure key '%s' is of type %s",
		      KEY_TYPE_CCA_AESDATA, g.pos_arg,
		      get_key_type(secure_key, secure_key_size));
		rc = EXIT_FAILURE;
		goto out;
	}

	rc = validate_secure_key(g.pkey_fd, secure_key, secure_key_size, NULL,
				 &is_old_mk, NULL, g.verbose);
	if (rc != 0) {
		warnx("The secure key in file '%s' is not valid", g.pos_arg);
		rc = EXIT_FAILURE;
		goto out;
	}

	rc = get_master_key_verification_pattern(secure_key, secure_key_size,
						 mkvp, g.verbose);
	if (rc != 0) {
		warnx("Failed to get the master key verification pattern: %s",
		      strerror(-rc));
		rc = EXIT_FAILURE;
		goto out;
	}

	if (strcasecmp(get_key_type(secure_key, secure_key_size),
		       g.key_type) == 0) {
		warnx("The secure key in file '%s' is already of type %s",
		      g.pos_arg, get_key_type(secure_key, secure_key_size));
		rc = EXIT_FAILURE;
		goto out;
	}

	if (is_cca_aes_data_key(secure_key, secure_key_size)) {
		if (strcasecmp(g.key_type, KEY_TYPE_CCA_AESCIPHER) != 0) {
			warnx("The secure key in file '%s' can not be "
			      "converted into type %s", g.pos_arg, g.key_type);
			rc = EXIT_FAILURE;
			goto out;
		}
	} else if (is_cca_aes_cipher_key(secure_key, secure_key_size)) {
		warnx("The secure key in file '%s' is already of type %s",
		      g.pos_arg, KEY_TYPE_CCA_AESCIPHER);
		rc = EXIT_FAILURE;
		goto out;
	} else {
		warnx("The secure key in file '%s' has an unsupported key type",
		      g.pos_arg);
		rc = EXIT_FAILURE;
		goto out;
	}

	rc = select_cca_adapter_by_mkvp(&g.cca, mkvp, NULL,
					FLAG_SEL_CCA_MATCH_CUR_MKVP,
					g.verbose);
	if (rc == -ENOTSUP) {
		rc = 0;
		selected = 0;
	}
	if (rc != 0) {
		warnx("No APQN found that is suitable for "
		      "converting the secure key in file '%s'", g.pos_arg);
		rc = EXIT_FAILURE;
		goto out;
	}

	if (!g.force) {
		util_print_indented("ATTENTION: Converting a secure key is "
				    "irreversible, and might have an effect "
				    "on the volumes encrypted with it!", 0);
		printf("%s: Convert key in file '%s' [y/N]? ",
		       program_invocation_short_name, g.pos_arg);
		if (!prompt_for_yes(g.verbose)) {
			warnx("Operation aborted");
			rc = EXIT_FAILURE;
			goto out;
		}
	}

	memset(output_key, 0, sizeof(output_key));
	output_key_size = sizeof(output_key);
	rc = convert_aes_data_to_cipher_key(&g.cca, secure_key, secure_key_size,
					    output_key, &output_key_size,
					    g.verbose);
	if (rc != 0) {
		warnx("Converting the secure key from %s to %s has failed",
		      get_key_type(secure_key, secure_key_size), g.key_type);
		if (!selected)
			print_msg_for_cca_envvars("secure key");
		rc = EXIT_FAILURE;
		goto out;
	}

	if (!g.exportable) {
		rc = restrict_key_export(&g.cca, output_key, output_key_size,
					 g.verbose);
		if (rc != 0) {
			warnx("Export restricting the converted secure key "
			      "has failed");
			if (!selected)
				print_msg_for_cca_envvars("secure key");
			rc = EXIT_FAILURE;
			goto out;
		}
	}

	pr_verbose("Secure key was converted successfully");

	/* Write the converted secure key */
	rc = write_secure_key(g.outputfile ? g.outputfile : g.pos_arg,
			      output_key, output_key_size, g.verbose);
	if (rc != 0)
		rc = EXIT_FAILURE;
out:
	free(secure_key);
	return rc;
}

/*
 * Command handler for 'convert in repository'.
 *
 * Converts secure keys from one key type to another
 */
static int command_convert_repository(void)
{
	int rc;

	if (g.name == NULL) {
		misc_print_required_parm("--name/-N");
		return EXIT_FAILURE;
	}

	rc = keystore_convert_key(g.keystore, g.name, g.key_type, g.noapqncheck,
				  g.force, g.exportable, g.pkey_fd, &g.lib);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'convert'.
 *
 * Converts secure keys from one key type to another
 */
static int command_convert(void)
{
	if (g.key_type == NULL) {
		misc_print_required_parm("--key-type/-K");
		return EXIT_FAILURE;
	}
	if (strcasecmp(g.key_type, KEY_TYPE_CCA_AESCIPHER) != 0) {
		warnx("Secure keys can only be converted into key type %s",
		      KEY_TYPE_CCA_AESCIPHER);
		return EXIT_FAILURE;
	}

	if (g.pos_arg != NULL)
		return command_convert_file();
	else
		return command_convert_repository();

	return EXIT_SUCCESS;
}

/*
 * Command handler for 'kms plugins'.
 *
 * List KMS plugins
 */
static int command_kms_plugins(void)
{
	int rc;

	rc = list_kms_plugins(g.verbose);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'kms bind'.
 *
 * Bind repository to a KMS plugin
 */
static int command_kms_bind(void)
{
	int rc;

	rc = bind_kms_plugin(g.keystore, g.pos_arg, g.verbose);

	if (rc == 0)
		util_print_indented("The KMS plugin requires configuration, "
				    "run 'zkey kms configure [OPTIONS]' to "
				    "complete the KMS binding process", 0);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'kms unbind'.
 *
 * Unbind repository from a KMS plugin
 */
static int command_kms_unbind(void)
{
	char *msg;
	int rc;

	if (g.kms_info.plugin_lib == NULL) {
		rc = -ENOENT;
		warnx("The repository is not bound to a KMS plugin");
		return EXIT_FAILURE;
	}

	util_asprintf(&msg, "%s: Unbind this repository from KMS plugin '%s' "
		     "and turn all KMS-bound keys into local keys [y/N]? ",
		     program_invocation_short_name, g.kms_info.plugin_name);
	util_print_indented(msg, 0);
	free(msg);
	if (!prompt_for_yes(g.verbose)) {
		warnx("Operation aborted");
		return EXIT_FAILURE;
	}

	rc = keystore_kms_keys_unbind(g.keystore);
	if (rc != 0) {
		warnx("Failed to turn KMS-bound keys into local keys: %s",
		      strerror(-rc));
		return EXIT_FAILURE;
	}

	rc = unbind_kms_plugin(&g.kms_info, g.keystore, g.verbose);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'kms info'.
 *
 * Prints information about a KMS plugin
 */
static int command_kms_info(void)
{
	int rc;

	if (g.kms_info.plugin_lib == NULL) {
		rc = -ENOENT;
		warnx("The repository is not bound to a KMS plugin");
		return EXIT_FAILURE;
	}

	rc = print_kms_info(&g.kms_info);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'kms configure'.
 *
 * Configures or re-configures a KMS plugin
 */
static int command_kms_configure(void)
{
	int rc;

	if (g.kms_info.plugin_lib == NULL) {
		rc = -ENOENT;
		warnx("The repository is not bound to a KMS plugin");
		return EXIT_FAILURE;
	}

	rc = configure_kms_plugin(g.keystore, g.apqns, g.kms_options,
				  g.num_kms_options, g.first_kms_option >= 0,
				  g.verbose);
	if (rc == -EAGAIN) {
		util_print_indented("The KMS plugin has accepted the "
				    "configuration so far, but requires further"
				    " configuration. Run 'zkey kms info' to "
				    "find out which KMS plugin settings still "
				    "require configuration, and run 'zkey kms "
				    "configure' again with the appropriate "
				    "options to complete the KMS "
				    "configuration process", 0);
		rc = 0;
	}

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'kms reencipher'.
 *
 * Reenciphers secure keys internally used by a KMS plugin
 */
static int command_kms_reencipher(void)
{
	int rc;

	if (g.kms_info.plugin_lib == NULL) {
		rc = -ENOENT;
		warnx("The repository is not bound to a KMS plugin");
		return EXIT_FAILURE;
	}

	if (g.inplace && g.staged) {
		warnx("Either '--in-place|-i' or '--staged|-s' can be "
		      "specified, but not both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.complete) {
		if (g.inplace) {
			warnx("Option '--in-place|-i' is not valid together "
			      "with '--complete|-p'");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}
		if (g.staged) {
			warnx("Option '--staged|-s' is not valid together "
			      "with '--complete|-p'");
			util_prg_print_parse_error();
			return EXIT_FAILURE;
		}
	}

	rc = reencipher_kms(&g.kms_info, g.fromold, g.tonew, g.inplace,
			   g.staged, g.complete, g.kms_options,
			   g.num_kms_options, g.verbose);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'kms list'.
 *
 * List secure keys managed by a KMS
 */
static int command_kms_list(void)
{
	int rc;

	if (g.kms_info.plugin_lib == NULL) {
		rc = -ENOENT;
		warnx("The repository is not bound to a KMS plugin");
		return EXIT_FAILURE;
	}

	rc = list_kms_keys(&g.kms_info, g.label, g.name, g.volumes,
			   g.volume_type, g.kms_options, g.num_kms_options,
			   g.verbose);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'kms import'.
 *
 * Imports secure keys managed by a KMS
 */
static int command_kms_import(void)
{
	int rc;

	if (g.kms_info.plugin_lib == NULL) {
		rc = -ENOENT;
		warnx("The repository is not bound to a KMS plugin");
		return EXIT_FAILURE;
	}

	rc = keystore_import_kms_keys(g.keystore, g.label, g.name, g.volumes,
				      g.volume_type, g.kms_options,
				      g.num_kms_options, g.batch_mode,
				      g.novolcheck, g.pkey_fd);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'kms refresh'.
 *
 * Refreshes secure keys managed by a KMS
 */
static int command_kms_refresh(void)
{
	int rc;

	if (g.kms_info.plugin_lib == NULL) {
		rc = -ENOENT;
		warnx("The repository is not bound to a KMS plugin");
		return EXIT_FAILURE;
	}

	rc = keystore_refresh_kms_keys(g.keystore, g.name, g.volumes,
				       g.volume_type, g.key_type,
				       g.refresh_properties, g.novolcheck,
				       g.pkey_fd);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'pvsecrets list'.
 *
 * Lists available protected virtualization secrets
 */
static int command_pvsecrets_list(void)
{
	int rc;

	if (g.secret_id != NULL && g.secret_name != NULL) {
		warnx("Either '--pvsecret-id/-I' or '--pvsecret-name/-e' can "
		      "be specified, but not both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}

	rc = pvsecrets_list(g.uv_fd, g.all, g.hex, g.secret_type, g.secret_id,
			    g.secret_name, g.verbose);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/*
 * Command handler for 'pvsecrets import'.
 *
 * Import a protected virtualization secret into the repository
 */
static int command_pvsecrets_import(void)
{
	int rc;

	if (g.secret_id == NULL && g.secret_name == NULL) {
		misc_print_required_parm("--pvsecret-id/-I or "
					 "--pvsecret-name/-e");
		return EXIT_FAILURE;
	}
	if (g.secret_id != NULL && g.secret_name != NULL) {
		warnx("Either '--pvsecret-id/-I' or '--pvsecret-name/-e' can "
		      "be specified, but not both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}
	if (g.secret_name == NULL && g.name == NULL) {
		misc_print_required_parm("--name/-N");
		return EXIT_FAILURE;
	}

	if (g.sector_size < 0)
		g.sector_size = 0;

	if (g.gen_passphrase && g.passphrase_file != NULL) {
		warnx("Either '--gen-dummy-passphrase' or "
		      "'--set-dummy-passphrase' can be specified, but not "
		      "both");
		util_prg_print_parse_error();
		return EXIT_FAILURE;
	}

	rc = pvsecrets_import(g.keystore, g.uv_fd, g.secret_id, g.secret_name,
			      g.name != NULL ? g.name : g.secret_name,
			      g.description, g.volumes, g.volume_type,
			      g.sector_size, g.gen_passphrase,
			      g.passphrase_file, g.verbose, g.pkey_fd);

	return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

/**
 * Opens the keystore. The keystore directory is either the
 * default directory or as specified in an environment variable
 */
static int open_keystore(void)
{
	char *directory;

	directory = secure_getenv(ENVVAR_ZKEY_REPOSITORY);
	if (directory == NULL)
		directory = DEFAULT_KEYSTORE;

	g.keystore = keystore_new(directory, &g.kms_info, g.verbose);

	return g.keystore == NULL ? EXIT_FAILURE : EXIT_SUCCESS;
}


static bool is_command(struct zkey_command *command, const char *str)
{
	char command_str[ZKEY_COMMAND_STR_LEN];
	size_t str_len = strlen(str);

	util_assert(sizeof(command_str) > strlen(command->command),
		    "Buffer 'command_str' too small for %s", command->command);
	if (str_len < command->abbrev_len)
		return false;
	if (str_len > strlen(command->command))
		return false;
	strncpy(command_str, command->command, str_len);
	if (strncasecmp(str, command_str, str_len) != 0)
		return false;

	return true;
}

/*
 * Find the command in the command table
 */
static struct zkey_command *find_command(const struct zkey_command *commands,
				         const char *command)
{
	struct zkey_command *cmd = (struct zkey_command *)commands;

	while (cmd->command) {
		if (is_command(cmd, command))
			return cmd;
		cmd++;
	}
	return NULL;
}

/*
 * Check if positional arguments are specified as needed by the command
 */
static int check_positional_arg(struct zkey_command *command)
{
	if (command->pos_arg_optional) {
		if (g.pos_arg == NULL &&
		    command->arg_alternate_value != NULL &&
		    *command->arg_alternate_value == NULL) {
			misc_print_required_parms(command->pos_arg,
						  command->pos_arg_alternate);
			return EXIT_FAILURE;
		}
	} else {
		if (g.pos_arg == NULL) {
			misc_print_required_parm(command->pos_arg);
			return EXIT_FAILURE;
		}
	}

	return EXIT_SUCCESS;
}

/*
 * Entry point
 */
int main(int argc, char *argv[])
{
	char command_str[2 * ZKEY_COMMAND_STR_LEN + 2];
	struct zkey_command *sub_command = NULL;
	struct zkey_command *command = NULL;
	struct zkey_command *cmd = NULL;
	int arg_count = argc;
	char **args = argv;
	char *endp;
	int rc, c;

	util_prg_init(&prg);

	/* Get command and subcommand if one is specified */
	if (arg_count >= 2 && strncmp(args[1], "-", 1) != 0) {
		command = find_command(zkey_commands, args[1]);
		if (command == NULL) {
			misc_print_invalid_command(args[1]);
			return EXIT_FAILURE;
		}

		arg_count--;
		args = &args[1];

		if (arg_count >= 2 && strncmp(args[1], "-", 1) != 0 &&
		    command->sub_commands != NULL) {
			sub_command = find_command(command->sub_commands,
						   args[1]);
			if (sub_command == NULL) {
				misc_print_invalid_command(args[1]);
				return EXIT_FAILURE;
			}

			arg_count--;
			args = &args[1];
		}

		if (arg_count >= 2 && strncmp(args[1], "-", 1) != 0) {
			g.pos_arg = args[1];
			arg_count--;
			args = &args[1];
		}
	}

	if (sub_command != NULL) {
		snprintf(command_str, sizeof(command_str), "%s %s",
			 command->command, sub_command->command);
		util_prg_set_command(command_str);
		util_opt_set_command(command_str);
	} else {
		util_prg_set_command(command ? command->command : NULL);
		util_opt_set_command(command ? command->command : NULL);
	}

	if (sub_command != NULL && command->sub_commands != NULL)
		cmd = sub_command;
	else
		cmd = command;

	if (cmd != NULL && cmd->use_kms_plugin) {
		rc = check_for_kms_plugin(&g.kms_info, g.verbose);
		if (rc != 0)
			return EXIT_FAILURE;

		if (cmd->kms_plugin_opts_cmd) {
			rc = get_kms_options(&g.kms_info, opt_vec,
					     OPT_COMMAND_PLACEHOLDER,
					     cmd->kms_plugin_opts_cmd,
					     sub_command != NULL ?
						command_str : cmd->command,
					     &g.first_kms_option,
					     g.verbose);
			if (rc != 0)
				return EXIT_FAILURE;
		}
	}

	util_opt_init(opt_vec, NULL);

	while (1) {
		c = util_opt_getopt_long(arg_count, args);
		if (c == -1)
			break;

		if (cmd != NULL && cmd->kms_plugin_opts_cmd) {
			rc = handle_kms_option(&g.kms_info, opt_vec,
					       g.first_kms_option,
					       sub_command != NULL ?
						    command_str : cmd->command,
					       c, optarg, &g.kms_options,
					       &g.num_kms_options, g.verbose);
			if (rc != 0 && rc != -ENOENT) {
				warnx("Failed to process KMS plugin option: "
				      "'%c': %s", c, strerror(-rc));
				return EXIT_FAILURE;
			}

			if (rc == 0)
				continue;
		}

		switch (c) {
		case 'x':
			g.xts = 1;
			break;
		case 'k':
			g.keybits = strtol(optarg, &endp, 0);
			if (*optarg == '\0' || *endp != '\0' ||
			    g.keybits <= 0 ||
			    (g.keybits == LONG_MAX && errno == ERANGE)) {
				warnx("Invalid value for '--keybits'|'-c': "
				      "'%s'", optarg);
				util_prg_print_parse_error();
				return EXIT_FAILURE;
			}
			break;
		case 'c':
			g.clearkeyfile = optarg;
			break;
		case 'f':
			g.outputfile = optarg;
			break;
		case 'n':
			g.tonew = 1;
			break;
		case 'o':
			g.fromold = 1;
			break;
		case 'p':
			g.complete = 1;
			break;
		case 'i':
			g.inplace = 1;
			break;
		case 's':
			g.staged = 1;
			break;
		case 'N':
			g.name = optarg;
			break;
		case 'd':
			g.description = optarg;
			break;
		case 'l':
			g.volumes = optarg;
			break;
		case 'a':
			g.apqns = optarg;
			break;
		case OPT_NO_APQN_CHECK:
			g.noapqncheck = 1;
			break;
		case OPT_NO_VOLUME_CHECK:
			g.novolcheck = 1;
			break;
		case 'S':
			g.sector_size = strtol(optarg, &endp, 0);
			if (*optarg == '\0' || *endp != '\0' ||
			    g.sector_size < 0 ||
			    (g.sector_size == LONG_MAX && errno == ERANGE)) {
				warnx("Invalid value for '--sector-size'|'-S': "
				      "'%s'", optarg);
				util_prg_print_parse_error();
				return EXIT_FAILURE;
			}
			break;
		case 't':
			g.volume_type = optarg;
			break;
		case 'w':
			g.newname = optarg;
			break;
		case 'r':
			g.run = 1;
			break;
		case 'K':
			g.key_type = optarg;
			break;
		case 'F':
			g.force = 1;
			break;
		case 'V':
			g.verbose = 1;
			break;
		case OPT_CRYPTSETUP_KEYFILE:
			g.keyfile = optarg;
			break;
		case OPT_CRYPTSETUP_KEYFILE_OFFSET:
			g.keyfile_offset = strtoll(optarg, &endp, 0);
			if (*optarg == '\0' || *endp != '\0' ||
			    g.keyfile_offset < 0 ||
			    (g.keyfile_offset == LLONG_MAX &&
			     errno == ERANGE)) {
				warnx("Invalid value for '--keyfile-offset': "
				      "'%s'", optarg);
				util_prg_print_parse_error();
				return EXIT_FAILURE;
			}
			break;
		case OPT_CRYPTSETUP_KEYFILE_SIZE:
			g.keyfile_size = strtoll(optarg, &endp, 0);
			if (*optarg == '\0' || *endp != '\0' ||
			    g.keyfile_size <= 0 ||
			    (g.keyfile_size == LLONG_MAX && errno == ERANGE)) {
				warnx("Invalid value for '--keyfile-size': "
				      "'%s'", optarg);
				util_prg_print_parse_error();
				return EXIT_FAILURE;
			}
			break;
		case OPT_CRYPTSETUP_TRIES:
			g.tries = strtoll(optarg, &endp, 0);
			if (*optarg == '\0' || *endp != '\0' ||
			    g.tries <= 0 ||
			    (g.tries == LLONG_MAX && errno == ERANGE)) {
				warnx("Invalid value for '--tries': '%s'",
				      optarg);
				util_prg_print_parse_error();
				return EXIT_FAILURE;
			}
			break;
		case 'q':
			g.batch_mode = 1;
			break;
		case OPT_CRYPTSETUP_OPEN:
			g.open = 1;
			break;
		case OPT_CRYPTSETUP_FORMAT:
			g.format = 1;
			break;
		case 'L':
			g.local = 1;
			break;
		case 'M':
			g.kms_bound = 1;
			break;
		case 'B':
			g.label = optarg;
			break;
		case 'P':
			g.refresh_properties = 1;
			break;
		case OPT_GEN_DUMMY_PASSPHRASE:
			g.gen_passphrase = 1;
			break;
		case OPT_SET_DUMMY_PASSPHRASE:
			g.passphrase_file = optarg;
			break;
		case OPT_REMOVE_DUMMY_PASSPHRASE:
			g.remove_passphrase = 1;
			break;
		case OPT_EXPORTABLE:
			g.exportable = 1;
			break;
		case OPT_WRAP_WITH_TRUSTED:
			g.wrap_with_trusted = 1;
			break;
		case 'A':
			g.all = 1;
			break;
		case 'H':
			g.hex = 1;
			break;
		case 'T':
			g.secret_type = optarg;
			break;
		case 'I':
			g.secret_id = optarg;
			break;
		case 'e':
			g.secret_name = optarg;
			break;
		case 'h':
			print_help(command, sub_command);
			return EXIT_SUCCESS;
		case 'v':
			util_prg_print_version();
			return EXIT_SUCCESS;
		default:
			util_opt_print_parse_error(c, args);
			return EXIT_FAILURE;
		}
	}

	if (optind < arg_count) {
		util_prg_print_arg_error(args[optind]);
		return EXIT_FAILURE;
	}

	if (cmd == NULL) {
		misc_print_missing_command();
		return EXIT_FAILURE;
	}

	if (cmd->sub_commands != NULL) {
		misc_print_missing_sub_command();
		return EXIT_FAILURE;
	}

	if (cmd->pos_arg != NULL) {
		if (check_positional_arg(cmd) != EXIT_SUCCESS)
			return EXIT_FAILURE;
	}

	if (cmd->need_keystore ||
	    (cmd->pos_arg_optional && g.pos_arg == NULL)) {
		rc = open_keystore();
		if (rc != EXIT_SUCCESS)
			goto out;
	}

	if (cmd->need_cca_library) {
		rc = load_cca_library(&g.cca, g.verbose);
		if (rc != 0) {
			rc = EXIT_FAILURE;
			goto out;
		}
	}
	if (cmd->need_ep11_library) {
		rc = load_ep11_library(&g.ep11, g.verbose);
		if (rc != 0) {
			rc = EXIT_FAILURE;
			goto out;
		}
	}
	if (cmd->need_pkey_device) {
		g.pkey_fd = open_pkey_device(g.verbose);
		if (g.pkey_fd == -1) {
			rc = EXIT_FAILURE;
			goto out;
		}
	}
	if (cmd->need_uv_device) {
		g.uv_fd = uv_open_device(g.verbose);
		if (g.uv_fd == -1) {
			rc = EXIT_FAILURE;
			goto out;
		}
	}

	if (g.kms_info.plugin_lib != NULL) {
		rc = init_kms_plugin(&g.kms_info, g.verbose);
		if (rc != 0) {
			rc = EXIT_FAILURE;
			goto out;
		}

		if (cmd->need_kms_login) {
			rc = perform_kms_login(&g.kms_info, g.verbose);
			if (rc != 0) {
				rc = EXIT_FAILURE;
				goto out;
			}
		}
	}

	umask(0077);

	rc = cmd->function();

out:
	free_kms_plugin(&g.kms_info);
	if (g.cca.lib_csulcca)
		dlclose(g.cca.lib_csulcca);
	if (g.ep11.lib_ep11)
		dlclose(g.ep11.lib_ep11);
	if (g.pkey_fd >= 0)
		close(g.pkey_fd);
	if (g.uv_fd >= 0)
		close(g.uv_fd);
	if (g.keystore)
		keystore_free(g.keystore);
	if (g.kms_options != NULL)
		free(g.kms_options);
	return rc;
}
