From ba03af0ddc0048dc1bd3d806409f01a623feb7fa Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Tue, 28 Apr 2026 16:25:06 -0500 Subject: [PATCH 1/8] evmctl: Notify user if first --v3 is passed and then --v2 Notify the user which signature version will be created when first --v3 (--v2) is passed and then switched to --v2 (--v3). Signed-off-by: Stefan Berger --- README | 2 +- src/evmctl.c | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README b/README index 34dfddfa..aaf0037c 100644 --- a/README +++ b/README @@ -85,7 +85,7 @@ OPTIONS --ignore-violations ignore ToMToU measurement violations --hmackey path to symmetric key (default: /etc/keys/evm-key-plain) --v2 create V2 signatures; this is the default - --v3 create V3 signatures; this requires Linux 7.1 or later + --v3 create V3 signatures; this requires Linux 7.2 or later -v increase verbosity level -h, --help display this help and exit diff --git a/src/evmctl.c b/src/evmctl.c index de671788..64115c27 100644 --- a/src/evmctl.c +++ b/src/evmctl.c @@ -3012,7 +3012,7 @@ static void usage(void) " --hmackey path to symmetric key (default: /etc/keys/evm-key-plain)\n" #endif " --v2 create V2 signatures; this is the default\n" - " --v3 create V3 signatures; this requires Linux 7.1 or later\n" + " --v3 create V3 signatures; this requires Linux 7.2 or later\n" " -v increase verbosity level\n" " -h, --help display this help and exit\n" "\n" @@ -3176,6 +3176,7 @@ static ENGINE *setup_engine(const char *engine_id) int main(int argc, char *argv[]) { + bool version_chosen = false; int err = 0, c, lind; unsigned long keyid; char *eptr; @@ -3369,9 +3370,19 @@ int main(int argc, char *argv[]) break; #endif case 150: /* --v2 */ + if (version_chosen && + g_signature_version != SIGNATURE_V2) { + log_info("Switching to use v2 signatures.\n"); + } + version_chosen = true; g_signature_version = SIGNATURE_V2; break; case 151: /* --v3 */ + if (version_chosen && + g_signature_version != SIGNATURE_V3) { + log_info("Switching to use v3 signatures.\n"); + } + version_chosen = true; g_signature_version = SIGNATURE_V3; break; case '?': From 25ce73c3ee6008745549b44acc11c9175dc292e1 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Tue, 28 Apr 2026 16:26:48 -0500 Subject: [PATCH 2/8] tests: Rename suffix a temporary file and fix an error message Rename the suffix of temporary file from $FILE.tmp to $FILE.ima_file_id to encode in its name that it holds and ima_file_id structure. Fix an error message. Signed-off-by: Stefan Berger --- tests/sign_verify.test | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/sign_verify.test b/tests/sign_verify.test index 93191230..a1ab6293 100755 --- a/tests/sign_verify.test +++ b/tests/sign_verify.test @@ -216,11 +216,12 @@ check_sign() { if [[ "$OPTS" =~ "--v3" ]]; then # In case of v3 signatures we need to create ima_file_id now. # All data for it can be found in PREFIX and by hashing $FILE. - echo -en "\x${PREFIX:2:2}\x${PREFIX:6:2}" > "$FILE.tmp" + echo -en "\x${PREFIX:2:2}\x${PREFIX:6:2}" > "$FILE.ima_file_id" # shellcheck disable=SC2086 - openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -"$ALG" -binary "$FILE" >> "$FILE.tmp" + openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -"$ALG" -binary "$FILE" >> \ + "$FILE.ima_file_id" cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -verify ${verifykey} \ - -signature $FILE.sig2 $FILE.tmp" + -signature $FILE.sig2 $FILE.ima_file_id" sigver=3 else cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -verify ${verifykey} \ @@ -230,13 +231,13 @@ check_sign() { echo - "$cmd" if ! $cmd; then color_red_on_failure - echo "Signature v${sigver} verification with openssl is failed." + echo "Signature v${sigver} verification with openssl failed." color_restore - rm "$FILE.sig2" "$FILE.tmp" + rm -f "$FILE.sig2" "$FILE.ima_file_id" return "$FAIL" fi - rm "$FILE.sig2" "$FILE.tmp" + rm -f "$FILE.sig2" "$FILE.ima_file_id" return "$OK" } From c2069c7ff1ccf135088b6adfd542b64065009af4 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Sun, 19 Apr 2026 10:35:22 -0500 Subject: [PATCH 3/8] ci: Ignore embedded function names when checking patches Add IGNORE_EMBEDDED_FUNCTION name to checkpatch ignore list to suppress the following type of warning: WARNING: Prefer using '"%s...", __func__' to using 'create_sigv3_mldsa', \ this function's name, in a string Signed-off-by: Stefan Berger --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ce9f3dc..5487d111 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: if [ "$count" -eq 0 ]; then exit 0 fi - scripts/checkpatch.pl patches/* --strict --terse --no-tree --ignore CONST_STRUCT,VOLATILE,SPLIT_STRING,FILE_PATH_CHANGES,EXECUTE_PERMISSIONS,UNKNOWN_COMMIT_ID,BAD_SIGN_OFF,PREFER_DEFINED_ATTRIBUTE_MACRO,PREFER_KERNEL_TYPES + scripts/checkpatch.pl patches/* --strict --terse --no-tree --ignore CONST_STRUCT,VOLATILE,SPLIT_STRING,FILE_PATH_CHANGES,EXECUTE_PERMISSIONS,UNKNOWN_COMMIT_ID,BAD_SIGN_OFF,PREFER_DEFINED_ATTRIBUTE_MACRO,PREFER_KERNEL_TYPES,EMBEDDED_FUNCTION_NAME build: needs: review From 3f1f38814629ef41b4481b38f4757918b1a496a4 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Tue, 24 Mar 2026 10:25:59 -0500 Subject: [PATCH 4/8] evmctl: Use MAX_SIGNATURE_SIZE for xattr_value buffer to support ML-DSA Set the size of an xattr_value that can be read to MAX_SIGNATURE_SIZE so that ML-DSA keys can also be read once enabled (and MAX_SIGNATURE_SIZE gets a larger value). Signed-off-by: Stefan Berger --- src/evmctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evmctl.c b/src/evmctl.c index 64115c27..d30aade3 100644 --- a/src/evmctl.c +++ b/src/evmctl.c @@ -347,7 +347,7 @@ static int calc_evm_hash(const char *file, const char *hash_algo, EVP_MD_CTX *pctx; unsigned int mdlen; char **xattrname; - char xattr_value[1024]; + char xattr_value[MAX_SIGNATURE_SIZE]; char list[1024]; ssize_t list_size; char uuid[16]; From 7d512c1e0445b12dc5923b49ddff5c1bd3fd0cad Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Fri, 17 Apr 2026 13:06:19 -0500 Subject: [PATCH 5/8] Fix a memory leak by freeing pkey when hash_algo is unknown Fix a memory leak by freeing pkey. Have it not report an error message again since an error was already reported. Signed-off-by: Stefan Berger --- src/libimaevm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libimaevm.c b/src/libimaevm.c index 49bfb629..777f675a 100644 --- a/src/libimaevm.c +++ b/src/libimaevm.c @@ -1343,7 +1343,8 @@ static int sign_hash_v2(const char *algo, const unsigned char *hash, hdr->hash_algo = imaevm_get_hash_algo(algo); if (hdr->hash_algo == (uint8_t)-1) { log_err("sign_hash_v2: hash algo is unknown: %s\n", algo); - return -1; + len = -1; + goto err_nomsg; } #if defined(EVP_PKEY_SM2) && OPENSSL_VERSION_NUMBER < 0x30000000 @@ -1395,6 +1396,7 @@ static int sign_hash_v2(const char *algo, const unsigned char *hash, ERR_reason_error_string(ERR_peek_error()), st); output_openssl_errors(); } +err_nomsg: EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(pkey); return len; From 2bdbb0f61f1c4e79dd48c732a3d36ab4884f712d Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Thu, 26 Jun 2025 14:30:45 -0500 Subject: [PATCH 6/8] Support signing with ML-DSA keys when OpenSSL >=3.5 is available OpenSSL >= v3.5.0 supports signing with ML-DSA-44/65/87. Add support for it to the imaevm_create_sigv3 library function. Since the ML-DSA signatures require a lot more space for the signature now, increase the size of the array where the signatures are stored. The following are the sizes of ML-DSA signatures by key type: - ML-DSA-44: 2420 - ML-DSA-65: 3309 - ML-DSA-87: 4627 Prevent signature V2 from being created with any other key types than 'RSA', 'EC', 'GOST' (ECRDSA), or 'SM2'. In the functions that created a v2 signature, only RSA, ECDSA, and ECRDSA signatures are created and they can easily work with the old buffer size of less than 1024 bytes. The size available for extended attributes may be smaller than what is required by the ML-DSA signature size, and therefore may not be possible to store for example ML-DSA-87 signatures (depends on type of filesystem). Nevertheless, extend the MAX_SIGNATURE_SIZE to the required size of ML-DSA-87 and display an error if writing the signature of a size larger than 4k did not work. Signed-off-by: Stefan Berger --- README | 3 +- src/evmctl.c | 4 + src/imaevm.h | 5 +- src/libimaevm.c | 270 ++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 270 insertions(+), 12 deletions(-) diff --git a/README b/README index aaf0037c..a1dd08b7 100644 --- a/README +++ b/README @@ -153,7 +153,8 @@ in the kernel (since kernel 3.9). CONFIG_INTEGRITY_ASYMMETRIC_KEYS must be enabl For v2 and v3 signatures x509 certificate (containing the public key) could be appended to the private key (they both are in PEM format) to automatically extract keyid from its Subject -Key Identifier (SKID). +Key Identifier (SKID). v3 signatures can be created with the --v3 option. This signature format +is required for signing with ML-DSA keys. Integrity keyrings ---------------- diff --git a/src/evmctl.c b/src/evmctl.c index d30aade3..95a0f406 100644 --- a/src/evmctl.c +++ b/src/evmctl.c @@ -617,6 +617,10 @@ static int sign_evm(const char *file, char *hash_algo, const char *key) if (err < 0) { log_errno_reset(LOG_ERR, "Setting EVM xattr failed: %s", file); + if (len >= 4096) + log_err("The signature with %zu bytes is likely too large for the file " + "extended attribute. Consider using a different key type.\n", + len); return err; } } diff --git a/src/imaevm.h b/src/imaevm.h index 5a8441b8..a098ee36 100644 --- a/src/imaevm.h +++ b/src/imaevm.h @@ -74,8 +74,11 @@ typedef struct ossl_provider_st OSSL_PROVIDER; #define DATA_SIZE 4096 #define SHA1_HASH_LEN 20 +#define ML_DSA_87_SIGNATURE_SIZE 4627 + #define MAX_DIGEST_SIZE 64 -#define MAX_SIGNATURE_SIZE 1024 +#define MAX_SIGNATURE_SIZE (1 + sizeof(struct signature_v2_hdr) + \ + ML_DSA_87_SIGNATURE_SIZE) /* * The maximum template data size is dependent on the template format. For diff --git a/src/libimaevm.c b/src/libimaevm.c index 777f675a..adb32506 100644 --- a/src/libimaevm.c +++ b/src/libimaevm.c @@ -37,6 +37,9 @@ #include #include #include +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#include +#endif #if CONFIG_IMA_EVM_ENGINE #include @@ -81,8 +84,25 @@ struct libimaevm_params imaevm_params = { .hash_algo = DEFAULT_HASH_ALGO, }; +#define HASH_MAX_DIGESTSIZE 64 /* kernel HASH_MAX_DIGESTSIZE is 64 bytes */ + +struct ima_file_id { + __u8 hash_type; /* xattr type [enum evm_ima_xattr_type] */ + __u8 hash_algorithm; /* Digest algorithm [enum hash_algo] */ + __u8 hash[HASH_MAX_DIGESTSIZE]; +} __packed; + static void __attribute__ ((constructor)) libinit(void); +/* RSA, ECDSA, ECRDSA, and SM2 all use hashes for signing */ +static inline bool keytype_uses_hash_for_signing(const char *keytype) +{ + return strcmp("RSA", keytype) == 0 || + strcmp("EC", keytype) == 0 || + strncmp("GOST2012_", keytype, 9) == 0 || + strcmp("SM2", keytype) == 0; +} + void imaevm_do_hexdump(FILE *fp, const void *ptr, int len, bool newline) { int i; @@ -460,6 +480,81 @@ void init_public_keys(const char *keyfiles) imaevm_init_public_keys(keyfiles, &g_public_keys); } +#if OPENSSL_VERSION_NUMBER >= 0x30500000 +static int verify_mldsa(EVP_PKEY *pkey, + const char *algo, + const unsigned char *hash, int hash_size, + unsigned char *sig, int siglen, + enum evm_ima_xattr_type type, + const char *file) +{ + struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig; + struct ima_file_id file_id = { .hash_type = type, }; + const char *keytype = EVP_PKEY_get0_type_name(pkey); + uint8_t *data = (uint8_t *)&file_id; + EVP_SIGNATURE *sig_alg = NULL; + unsigned int unused; + EVP_PKEY_CTX *ctx; + int hash_algo; + const char *st; + int ret = -1; + + if (!algo) { + log_err("Hash algorithm unspecified\n"); + return -EINVAL; + } + if (hash_size > HASH_MAX_DIGESTSIZE) { + log_err("hash_size is too large\n"); + return -EINVAL; + } + + hash_algo = imaevm_get_hash_algo(algo); + if (hash_algo < 0) { + log_err("Hash algorithm %s not supported\n", algo); + return -EINVAL; + } + file_id.hash_algorithm = hash_algo; + + memcpy(file_id.hash, hash, hash_size); + unused = HASH_MAX_DIGESTSIZE - hash_size; + + st = "EVP_PKEY_CTX_new"; + ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) + goto err; + st = "EVP_SIGNATURE_fetch"; + sig_alg = EVP_SIGNATURE_fetch(NULL, keytype, NULL); + if (!sig_alg) + goto err; + st = "EVP_PKEY_verify_message_init"; + if (!EVP_PKEY_verify_message_init(ctx, sig_alg, NULL)) + goto err; + st = "EVP_PKEY_verify"; + ret = EVP_PKEY_verify(ctx, sig + sizeof(*hdr), + siglen - sizeof(*hdr), + data, sizeof(file_id) - unused); + if (ret == 1) { + ret = 0; + } else if (ret == 0) { + log_err("%s: verification failed: %d (%s)\n", + file, ret, ERR_reason_error_string(ERR_get_error())); + output_openssl_errors(); + ret = 1; + } +err: + if (ret < 0 || ret > 1) { + log_err("%s: verification failed: %d (%s) in %s\n", + file, ret, ERR_reason_error_string(ERR_peek_error()), + st); + output_openssl_errors(); + ret = -1; + } + EVP_SIGNATURE_free(sig_alg); + EVP_PKEY_CTX_free(ctx); + return ret; +} +#endif + /* * Verify a signature, prefixed with the signature_v2_hdr, either based * directly or indirectly on the file data hash. @@ -577,6 +672,28 @@ static int verify_hash_v3(struct public_key_entry *public_keys, { unsigned char sigv3_hash[MAX_DIGEST_SIZE]; int ret; +#if OPENSSL_VERSION_NUMBER >= 0x30500000 + struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)(sig + 1); + const char *keytype; + EVP_PKEY *pkey; // do not free here + + pkey = find_keyid(public_keys, hdr->keyid); + if (!pkey) { + uint32_t keyid = hdr->keyid; + + if (imaevm_params.verbose > LOG_INFO) + log_info("%s: verification failed: unknown keyid %x\n", + file, __be32_to_cpup(&keyid)); + return -1; + } + + keytype = EVP_PKEY_get0_type_name(pkey); + + if (keytype && !keytype_uses_hash_for_signing(keytype)) + return verify_mldsa(pkey, hash_algo, + hash, size, sig + 1, siglen - 1, + sig[0], file); +#endif ret = calc_hash_sigv3(sig[0], hash_algo, hash, sigv3_hash); if (ret < 0) @@ -587,14 +704,6 @@ static int verify_hash_v3(struct public_key_entry *public_keys, size, sig + 1, siglen - 1); } -#define HASH_MAX_DIGESTSIZE 64 /* kernel HASH_MAX_DIGESTSIZE is 64 bytes */ - -struct ima_file_id { - __u8 hash_type; /* xattr type [enum evm_ima_xattr_type] */ - __u8 hash_algorithm; /* Digest algorithm [enum hash_algo] */ - __u8 hash[HASH_MAX_DIGESTSIZE]; -} __packed; - /* * Calculate the signature format version 3 hash based on the portion * of the ima_file_id structure used, not the entire structure. @@ -1292,7 +1401,7 @@ static int sign_hash_v1(const char *hashalgo, const unsigned char *hash, #endif /* CONFIG_SIGV1 */ /* - * @sig is assumed to be of (MAX_SIGNATURE_SIZE - 1) size + * @sig is assumed to be of at least (1024 - 1) size * Return: -1 signing error, >0 length of signature */ static int sign_hash_v2(const char *algo, const unsigned char *hash, @@ -1306,6 +1415,7 @@ static int sign_hash_v2(const char *algo, const unsigned char *hash, EVP_PKEY *pkey; char name[20]; EVP_PKEY_CTX *ctx = NULL; + const char *keytype; const EVP_MD *md; size_t sigsize; const char *st; @@ -1337,6 +1447,16 @@ static int sign_hash_v2(const char *algo, const unsigned char *hash, if (!pkey) return -1; +#if OPENSSL_VERSION_NUMBER >= 0x30000000 + keytype = EVP_PKEY_get0_type_name(pkey); + /* if it's neither an RSA, EC(R)DSA, nor SM2 key then it cannot be used here */ + if (keytype && !keytype_uses_hash_for_signing(keytype)) { + log_err("sign_hash_v2: Cannot use '%s' type of key\n", keytype); + len = -1; + goto err_nomsg; + } +#endif + hdr = (struct signature_v2_hdr *)sig; hdr->version = (uint8_t) DIGSIG_VERSION_2; @@ -1380,7 +1500,7 @@ static int sign_hash_v2(const char *algo, const unsigned char *hash, if (!EVP_PKEY_CTX_set_signature_md(ctx, md)) goto err; st = "EVP_PKEY_sign"; - sigsize = MAX_SIGNATURE_SIZE - sizeof(struct signature_v2_hdr) - 1; + sigsize = 1024 - sizeof(struct signature_v2_hdr) - 1; if (!EVP_PKEY_sign(ctx, hdr->sig, &sigsize, hash, size)) goto err; len = (int)sigsize; @@ -1453,6 +1573,113 @@ int imaevm_signhash(const char *hashalgo, const unsigned char *hash, int size, access_info, keyid); } +#if OPENSSL_VERSION_NUMBER >= 0x30500000 +static int create_sigv3_mldsa(EVP_PKEY *pkey, const char *algo, + const unsigned char *hash, int hash_size, + unsigned char **sig, size_t siglen, + enum evm_ima_xattr_type type, + const struct imaevm_ossl_access *access_info, + uint32_t keyid) +{ + const char *keytype = EVP_PKEY_get0_type_name(pkey); + struct ima_file_id file_id = { .hash_type = type, }; + uint8_t *data = (uint8_t *)&file_id; + EVP_SIGNATURE *sig_alg = NULL; + struct signature_v2_hdr *hdr; + EVP_PKEY_CTX *ctx = NULL; + bool allocated = false; + unsigned int unused; + ssize_t slen = -1; + size_t needlen; + const char *st; + int hash_algo; + char name[20]; + int ret = -1; + + hash_algo = imaevm_get_hash_algo(algo); + if (hash_algo < 0) { + log_err("Hash algorithm %s not supported\n", algo); + return -EINVAL; + } + if (hash_size > HASH_MAX_DIGESTSIZE) { + log_err("hash_size is too large\n"); + return -EINVAL; + } + file_id.hash_algorithm = hash_algo; + memcpy(file_id.hash, hash, hash_size); + unused = HASH_MAX_DIGESTSIZE - hash_size; + + st = "EVP_PKEY_CTX_new"; + ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) + goto err; + st = "EVP_SIGNATURE_fetch"; + sig_alg = EVP_SIGNATURE_fetch(NULL, keytype, NULL); + if (!sig_alg) + goto err; + st = "EVP_PKEY_sign_init"; + if (!EVP_PKEY_sign_message_init(ctx, sig_alg, NULL)) + goto err; + st = "EVP_PKEY_sign"; + /* query for size of signature */ + if (!EVP_PKEY_sign(ctx, NULL, (size_t *)&slen, + data, sizeof(file_id) - unused)) { + slen = -1; + goto err; + } + + needlen = 1 + sizeof(*hdr) + slen; + if (*sig) { + if (siglen < needlen) { + log_err("Buffer of size %zu is too small for signature " + "of size %zu\n", siglen, needlen); + goto err; + } + } else { + *sig = malloc(needlen); + if (!*sig) { + perror("malloc"); + goto err; + } + allocated = true; + } + hdr = (struct signature_v2_hdr *)(*sig + 1); + + if (!EVP_PKEY_sign(ctx, hdr->sig, (size_t *)&slen, + data, sizeof(file_id) - unused)) { + slen = -1; + goto err; + } + + (*sig)[0] = type; + hdr->version = DIGSIG_VERSION_3; + hdr->hash_algo = hash_algo; + if (keyid) + keyid = htonl(keyid); + else + calc_keyid_v2(&keyid, name, pkey); + hdr->keyid = keyid; + hdr->sig_size = __cpu_to_be16(slen); + + ret = needlen; + log_info("evm/ima signature: %d bytes\n", ret); + +err: + EVP_SIGNATURE_free(sig_alg); + EVP_PKEY_CTX_free(ctx); + if (slen == -1) { + log_err("create_sigv3_mldsa signing failed: (%s) in %s\n", + ERR_reason_error_string(ERR_peek_error()), st); + output_openssl_errors(); + } + if (ret < 0 && allocated) { + free(*sig); + *sig = NULL; + } + return ret; +} +#endif + /* * Create a v3 signature given a file hash * @@ -1484,6 +1711,29 @@ int imaevm_create_sigv3(const char *hash_algo, const unsigned char *hash, int si /* buffer capable of holding (more than) RSA-4096 signature; */ unsigned char sigbuf[1024]; int len, slen, err; +#if OPENSSL_VERSION_NUMBER >= 0x30500000 + const char *keytype; + EVP_PKEY *pkey; + + if (access_info) { + err = check_ossl_access(access_info); + if (err) + return err; + } + pkey = read_priv_pkey(keyfile, keypass, access_info, keyid); + if (!pkey) + return -1; + + keytype = EVP_PKEY_get0_type_name(pkey); + if (keytype && strncmp("ML-DSA-", keytype, 7) == 0) { + slen = create_sigv3_mldsa(pkey, hash_algo, hash, size, + sig, siglen, + xattr_type, access_info, keyid); + EVP_PKEY_free(pkey); + return slen; + } + EVP_PKEY_free(pkey); +#endif len = calc_hash_sigv3(xattr_type, hash_algo, hash, sigv3_hash); if (len < 0 || len == 1) { From ee2668ed9dba4fa4e894f23dc318a234f395a005 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Wed, 25 Feb 2026 15:25:24 -0600 Subject: [PATCH 7/8] examples: Implement script to create ML-DSA-65 CA and signing keys ima-gen-local-ca-mldsa65.sh creates a CA with an ML-DSA-65 key and ima-genkey-mldsa65.sh creates an ML-DSA-65 IMA file signing key along with its certificate. Also add a scripts for creating a ML-DSA-44 and ML-DSA-87 IMA file signing keys. The latter key is good for local testing with the largest possible signature. Signed-off-by: Stefan Berger --- examples/functions | 47 ++++++++++++++++++++++++++++ examples/ima-gen-local-ca-mldsa65.sh | 29 +++++++++++++++++ examples/ima-genkey-mldsa44.sh | 11 +++++++ examples/ima-genkey-mldsa65.sh | 11 +++++++ examples/ima-genkey-mldsa87.sh | 11 +++++++ 5 files changed, 109 insertions(+) create mode 100755 examples/functions create mode 100755 examples/ima-gen-local-ca-mldsa65.sh create mode 100755 examples/ima-genkey-mldsa44.sh create mode 100755 examples/ima-genkey-mldsa65.sh create mode 100755 examples/ima-genkey-mldsa87.sh diff --git a/examples/functions b/examples/functions new file mode 100755 index 00000000..d7b1ea19 --- /dev/null +++ b/examples/functions @@ -0,0 +1,47 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +ima_genkey_mldsa() +{ + keyalgo="$1" + + GENKEY=ima.genkey + + maj=$(openssl version | awk '{print $2}' | cut -d. -f1) + min=$(openssl version | awk '{print $2}' | cut -d. -f2) + + if [ "${maj}" -lt 3 ] || { [ "${maj}" -eq 3 ] && [ "${min}" -lt 5 ]; }; then + echo "The openssl tool is too old (v${maj}.${min}) to support ML-DSA. Need at least v3.5.0." + exit 1 + fi + cat << __EOF__ >$GENKEY +[ req ] +distinguished_name = req_distinguished_name +prompt = no +string_mask = utf8only +x509_extensions = v3_usr + +[ req_distinguished_name ] +O = $(hostname) +CN = $(whoami) signing key +emailAddress = $(whoami)@$(hostname) + +[ v3_usr ] +basicConstraints=critical,CA:FALSE +#basicConstraints=CA:FALSE +keyUsage=digitalSignature +#keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage=critical,codeSigning +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid +#authorityKeyIdentifier=keyid,issuer +__EOF__ + + openssl req -new -nodes -batch -config $GENKEY \ + -out csr_ima.pem -keyout privkey_ima.pem \ + -newkey "${keyalgo}" || return $? + openssl x509 -req -in csr_ima.pem -days 365 -extfile $GENKEY -extensions v3_usr \ + -CA ima-local-ca.pem -CAkey ima-local-ca.priv -CAcreateserial \ + -outform DER -out x509_ima.der + return $? +} diff --git a/examples/ima-gen-local-ca-mldsa65.sh b/examples/ima-gen-local-ca-mldsa65.sh new file mode 100755 index 00000000..ac27f216 --- /dev/null +++ b/examples/ima-gen-local-ca-mldsa65.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +GENKEY=ima-local-ca.genkey + +cat << __EOF__ >$GENKEY +[ req ] +distinguished_name = req_distinguished_name +prompt = no +string_mask = utf8only +x509_extensions = v3_ca + +[ req_distinguished_name ] +O = IMA-CA +CN = IMA/EVM certificate signing key +emailAddress = ca@ima-ca + +[ v3_ca ] +basicConstraints=CA:TRUE +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer +keyUsage = cRLSign, keyCertSign +__EOF__ + +openssl req -new -x509 -utf8 -days 3650 -batch -config $GENKEY \ + -outform DER -out ima-local-ca.x509 -keyout ima-local-ca.priv \ + -newkey mldsa65 + +openssl x509 -inform DER -in ima-local-ca.x509 -out ima-local-ca.pem diff --git a/examples/ima-genkey-mldsa44.sh b/examples/ima-genkey-mldsa44.sh new file mode 100755 index 00000000..55ebe0b4 --- /dev/null +++ b/examples/ima-genkey-mldsa44.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +DIR=$(dirname "$0") + +cd "${DIR}" 1>/dev/null || exit 1 + +. ./functions +ima_genkey_mldsa mldsa44 +exit "$?" + diff --git a/examples/ima-genkey-mldsa65.sh b/examples/ima-genkey-mldsa65.sh new file mode 100755 index 00000000..6406daf6 --- /dev/null +++ b/examples/ima-genkey-mldsa65.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +DIR=$(dirname "$0") + +cd "${DIR}" 1>/dev/null || exit 1 + +. ./functions +ima_genkey_mldsa mldsa65 +exit "$?" + diff --git a/examples/ima-genkey-mldsa87.sh b/examples/ima-genkey-mldsa87.sh new file mode 100755 index 00000000..c7d7908a --- /dev/null +++ b/examples/ima-genkey-mldsa87.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +DIR=$(dirname "$0") + +cd "${DIR}" 1>/dev/null || exit 1 + +. ./functions +ima_genkey_mldsa mldsa87 +exit "$?" + From 7d0f11f7c85b35f96292598be117f053adc7679c Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Thu, 26 Jun 2025 15:55:21 -0500 Subject: [PATCH 8/8] test: Add tests for signing and verifying with ML-DSA keys Create ML-DSA-44 & ML-DSA-65 keys if ML-DSA-44 can be created with the installed version of OpenSSL. Add test cases for signing and verifying with these types of keys. Do not test with ML-DSA-87 keys since the signatures they create may be too large for some filesystems' xattrs. On Btrfs for example it would be possible to store the large signatures. Signed-off-by: Stefan Berger --- tests/gen-keys.sh | 22 ++++++++++++++++++++++ tests/sign_verify.test | 31 +++++++++++++++++++++++++++---- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/tests/gen-keys.sh b/tests/gen-keys.sh index db0189af..e568ef8f 100755 --- a/tests/gen-keys.sh +++ b/tests/gen-keys.sh @@ -148,6 +148,28 @@ if [ -x /opt/openssl3/bin/openssl ]; then done) fi +# If creating mldsa44 key works, create all ML-DSA sizes +if openssl genpkey -algorithm mldsa44 &>/dev/null; then + for mldsa in mldsa44 mldsa65; do + if [ "$1" = clean ] || [ "$1" = force ]; then + rm -f test-$mldsa.cer test-$mldsa.key test-$mldsa.pub + fi + if [ "$1" = clean ]; then + continue + fi + if [ ! -e test-$mldsa.key ]; then + log openssl req -verbose -new -nodes -utf8 -days 10000 -batch -x509 \ + -config test-ca.conf \ + -newkey "$mldsa" \ + -out test-$mldsa.cer -outform DER \ + -keyout test-$mldsa.key + if [ -s test-$mldsa.key ]; then + log openssl pkey -in test-$mldsa.key -out test-$mldsa.pub -pubout + fi + fi + done +fi + # This script leaves test-ca.conf, *.cer, *.pub, *.key files for sing/verify tests. # They are never deleted except by `make distclean'. diff --git a/tests/sign_verify.test b/tests/sign_verify.test index a1ab6293..5cb37865 100755 --- a/tests/sign_verify.test +++ b/tests/sign_verify.test @@ -166,8 +166,15 @@ check_sign() { fi # Can openssl sign with this digest and key? - cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -sign $key -hex $FILE" - echo - "$cmd" + case "${KEY:0:10}" in + "test-mldsa") + cmd="openssl pkeyutl -sign -inkey $key -in $FILE" + echo >> "$FILE" # need at least 1 byte in the file for signing to work + ;; + *) + cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -sign $key -hex $FILE" + ;; + esac if ! $cmd >/dev/null; then echo "${CYAN}$ALG ($key) test is skipped (openssl is unable to sign)$NORM" return "$SKIP" @@ -220,8 +227,17 @@ check_sign() { # shellcheck disable=SC2086 openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -"$ALG" -binary "$FILE" >> \ "$FILE.ima_file_id" - cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -verify ${verifykey} \ - -signature $FILE.sig2 $FILE.ima_file_id" + + case "${KEY:0:10}" in + "test-mldsa") + # ML-DSA does not accept a hash algorithm on command line + cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -verify ${verifykey} \ + -signature $FILE.sig2 $FILE.ima_file_id" + ;; + *) + cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -verify ${verifykey} \ + -signature $FILE.sig2 $FILE.ima_file_id" + esac sigver=3 else cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -verify ${verifykey} \ @@ -425,6 +441,13 @@ sign_verify prime256v1 sha256 0x030304:K:004[345678] --v3 sign_verify prime256v1 sha384 0x030305:K:004[345678] --v3 sign_verify prime256v1 sha512 0x030306:K:004[345678] --v3 +sign_verify mldsa44 sha256 0x030304:K:0974 --v3 +sign_verify mldsa44 sha384 0x030305:K:0974 --v3 +sign_verify mldsa44 sha512 0x030306:K:0974 --v3 +sign_verify mldsa65 sha256 0x030304:K:0ced --v3 +sign_verify mldsa65 sha384 0x030305:K:0ced --v3 +sign_verify mldsa65 sha512 0x030306:K:0ced --v3 + # If openssl 3.0 is installed, test the SM2/3 algorithm combination ssl_major_version=$(openssl version | sed -n 's/^OpenSSL \([^\.]\).*/\1/p') if [ "${ssl_major_version}" = 3 ]; then