From 0e526d0d41b572f3f7857278526f3668be4b68a4 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 14 2026 18:32:07 +0000
Subject: [PATCH 1/6] Replace deprecated OpenSSL 3.0.0 function calls


Use EVP_PKEY instead of algorithm-specific calls to generate
required parameters and keys.

This is a prerequisite for supporting PQ algorithms in a clean
way and will future-proof certmonger.

While not strictly related to OSSL 3.0.0 deprecation, add a
global error logging function so we can reduce code duplication
and make it easier to log what happened when a call fails.
This was helpful during development of this change.

Fixes: https://pagure.io/certmonger/issue/292

Signed-off-by: Rob Crittenden <rcritten@redhat.com>

---

diff --git a/src/Makefile.am b/src/Makefile.am
index b20cb43..ef3b743 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -196,7 +196,8 @@ if WITH_XMLRPC
 noinst_PROGRAMS += submit-x
 endif
 tdbusm_check_SOURCES = tdbusm-check.c tm.c tm.h
-tdbusm_check_LDADD = libcm.a $(CERTMONGER_LIBS) $(POPT_LIBS) $(LDAP_LIBS)
+tdbusm_check_LDADD = libcm.a $(CERTMONGER_LIBS) $(POPT_LIBS) $(LDAP_LIBS) \
+			$(OPENSSL_LIBS) $(NSS_LIBS)
 serial_check_LDADD = libcm.a $(CERTMONGER_LIBS) $(LTLIBICONV) $(LDAP_LIBS)
 nl_check_LDADD = libcm.a $(CERTMONGER_LIBS) $(LDAP_LIBS)
 if WITH_XMLRPC
@@ -230,7 +231,7 @@ ipa_submit_SOURCES = ipa.c srvloc.c srvloc.h store.h store-gen.c \
 ipa_submit_LDADD = $(XMLRPC_LIBS) $(LDAP_LIBS) $(KRB5_LIBS) $(TALLOC_LIBS) \
 		   $(GMP_LIBS) $(IDN_LIBS) $(OPENSSL_LIBS) $(UUID_LIBS) \
 		   $(RESOLV_LIBS) $(LTLIBICONV) $(POPT_LIBS) $(CURL_LIBS) \
-		   $(JANSSON_LIBS)
+		   $(JANSSON_LIBS) $(OPENSSL_LIBS) $(NSS_LIBS)
 if WITH_XMLRPC
 certmaster_submit_CFLAGS = $(AM_CFLAGS) $(NSS_CFLAGS)
 certmaster_submit_SOURCES = certmaster.c submit-x.c submit-x.h \
@@ -285,17 +286,18 @@ local_submit_SOURCES = local.c env.h store.h store-gen.c submit-o.c submit-o.h \
 		       util.c util.h util-o.c util-o.h pin.c pin.h
 local_submit_LDADD = $(NSS_LIBS) $(OPENSSL_LIBS) $(TALLOC_LIBS) $(UUID_LIBS) \
 		     $(POPT_LIBS)
-submit_d_CFLAGS = $(AM_CFLAGS) $(CURL_CFLAGS) $(XML_CFLAGS) -DCM_SUBMIT_D_MAIN
+submit_d_CFLAGS = $(AM_CFLAGS) $(CURL_CFLAGS) $(XML_CFLAGS) $(OPENSSL_CFLAGS) \
+		 $(NSS_CFLAGS) -DCM_SUBMIT_D_MAIN
 submit_d_SOURCES = submit-d.c submit-d.h submit-h.c submit-h.h \
 		   submit-u.c submit-u.h log.c log.h tm.c tm.h util-m.c util-m.h
 submit_d_LDADD = libcm-o.a $(CURL_LIBS) $(OPENSSL_LIBS) $(XML_LIBS) \
 		 $(TALLOC_LIBS) $(GMP_LIBS) $(UUID_LIBS) $(LTLIBICONV) \
-		 $(POPT_LIBS) $(JANSSON_LIBS)
-submit_h_CFLAGS = $(AM_CFLAGS) $(CURL_CFLAGS) $(XML_CFLAGS) \
-				  $(JANSSON_CFLAGS) -DCM_SUBMIT_H_MAIN
+		 $(POPT_LIBS) $(JANSSON_LIBS) $(OPENSSL_LIBS) $(NSS_LIBS)
+submit_h_CFLAGS = $(AM_CFLAGS) $(NSS_CFLAGS) $(CURL_CFLAGS) $(XML_CFLAGS) \
+		 $(JANSSON_CFLAGS) -DCM_SUBMIT_H_MAIN
 submit_h_SOURCES = submit-h.c submit-h.h log.c log.h tm.c tm.h
-submit_h_LDADD = $(CURL_LIBS) $(XML_LIBS) $(TALLOC_LIBS) $(LTLIBICONV) \
-		 $(POPT_LIBS)
+submit_h_LDADD = $(NSS_LIBS) $(OPENSSL_LIBS) $(CURL_LIBS) $(XML_LIBS) $(TALLOC_LIBS) \
+		 $(LTLIBICONV) $(POPT_LIBS)
 
 .PHONY: manlint
 manlint: $(man_MANS)
diff --git a/src/certsave-o.c b/src/certsave-o.c
index b145a21..caab151 100644
--- a/src/certsave-o.c
+++ b/src/certsave-o.c
@@ -285,7 +285,7 @@ cm_certsave_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 			/* Double-check that we're not trying to rotate in a
 			 * key that we won't actually be using. */
 			if ((old_pkey != NULL) &&
-			    (EVP_PKEY_cmp(old_pkey, X509_get_pubkey(cert)) == 1)) {
+			    (EVP_PKEY_eq(old_pkey, X509_get_pubkey(cert)) == 1)) {
 				entry->cm_key_next_marker = NULL;
 				if (next_key != NULL) {
 					cm_log(1, "Public key was not changed.\n");
diff --git a/src/keygen-o.c b/src/keygen-o.c
index 676db8b..ff929fd 100644
--- a/src/keygen-o.c
+++ b/src/keygen-o.c
@@ -99,6 +99,8 @@ cm_keygen_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 	struct cm_pin_cb_data cb_data;
 	FILE *fp, *status;
 	EVP_PKEY *pkey;
+	EVP_PKEY_CTX *pctx = NULL;
+	EVP_PKEY_CTX *check_ctx = NULL;
 	char buf[LINE_MAX], *pin, *pubhex, *pubihex, *oldfile;
 	unsigned char *p, *q;
 	long error, errno_save;
@@ -106,13 +108,9 @@ cm_keygen_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 	int cm_key_size;
 	int len;
 	int keyfd;
+	int ret;
 	char *filename;
 	char *marker;
-	BIGNUM *exponent;
-	RSA *rsa;
-#ifdef CM_ENABLE_DSA
-	DSA *dsa;
-#endif
 #ifdef CM_ENABLE_EC
 	EC_KEY *ec;
 	int ecurve;
@@ -146,95 +144,155 @@ retry_gen:
 	}
 	switch (cm_key_algorithm) {
 	case cm_key_rsa:
-		exponent = BN_new();
-		if (exponent == NULL) {
-			cm_log(1, "Error setting up exponent.\n");
+		unsigned int exponent = CM_DEFAULT_RSA_EXPONENT;
+		unsigned int bits = cm_key_size;
+		OSSL_PARAM params[3];
+
+		pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+		if (pctx == NULL) {
+			cm_log(1, "Error allocating new RSA context.\n");
 			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 		}
-		BN_set_word(exponent, CM_DEFAULT_RSA_EXPONENT);
-		rsa = RSA_new();
-		if (rsa == NULL) {
-			cm_log(1, "Error allocating new RSA key.\n");
+
+		if (EVP_PKEY_keygen_init(pctx) == 0) {
+			cm_log(1, "Error initializing RSA key generation.\n");
+			EVP_PKEY_CTX_free(pctx);
 			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 		}
-		if (RSA_generate_key_ex(rsa, cm_key_size, exponent, NULL) != 1) {
+
+		params[0] = OSSL_PARAM_construct_uint("bits", &bits);
+		params[1] = OSSL_PARAM_construct_uint("e", &exponent);
+		params[2] = OSSL_PARAM_construct_end();
+		if (EVP_PKEY_CTX_set_params(pctx, params) == 0) {
+			cm_log(1, "Error setting RSA key parameters.\n");
+			EVP_PKEY_CTX_free(pctx);
+			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+		}
+
+		if (EVP_PKEY_generate(pctx, &pkey) != 1) {
 			if (cm_key_size != CM_DEFAULT_PUBKEY_SIZE) {
-				cm_log(1, "Error generating %d-bit key, "
+				cm_log(1, "Error generating RSA %d-bit key, "
 				       "attempting %d bits.\n",
 				       cm_key_size, CM_DEFAULT_PUBKEY_SIZE);
 				cm_key_size = CM_DEFAULT_PUBKEY_SIZE;
+				EVP_PKEY_CTX_free(pctx);
 				goto retry_gen;
 			}
-			cm_log(1, "Error generating key.\n");
+			cm_log(1, "Error generating RSA key.\n");
+			EVP_PKEY_CTX_free(pctx);
 			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 		}
-		if (RSA_check_key(rsa) != 1) { /* should be unnecessary */
-			cm_log(1, "Key fails checks.  Retrying.\n");
-			goto retry_gen;
+		EVP_PKEY_CTX_free(pctx);
+
+		check_ctx = EVP_PKEY_CTX_new(pkey, NULL);
+
+		ret = EVP_PKEY_check(check_ctx);
+		if (ret == 0) {
+			cm_log(1, "keygen-o: EVP_PKEY_check): Key pair is invalid.\n");
+			EVP_PKEY_CTX_free(check_ctx);
+			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+		} else if (ret == -2) {
+			cm_log(1, "keygen-o: EVP_PKEY_check: RSA operation not supported for this algorithm.\n");
+			EVP_PKEY_CTX_free(check_ctx);
+			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 		}
-		EVP_PKEY_set1_RSA(pkey, rsa);
+		EVP_PKEY_CTX_free(check_ctx);
 		break;
 #ifdef CM_ENABLE_DSA
 	case cm_key_dsa:
-		dsa = DSA_new();
-		if (dsa == NULL) {
-			cm_log(1, "Error allocating new DSA key.\n");
+		EVP_PKEY_CTX *pctx_params = NULL;
+		EVP_PKEY* pkey_params = NULL;
+
+		bits = cm_key_size;
+
+		pctx_params = EVP_PKEY_CTX_new_from_name(NULL, "DSA", NULL);
+		if (pctx_params == NULL) {
+			cm_log(1, "keygen-o: Error allocating new DSA params context.\n");
 			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 		}
-		if (DSA_generate_parameters_ex(dsa, cm_key_size,
-					       NULL, 0,
-					       NULL, NULL, NULL) != 1) {
+
+		if (EVP_PKEY_paramgen_init(pctx_params) == 0) {
+			cm_log(1, "keygen-o: Error initializing DSA param generation.\n");
+			EVP_PKEY_CTX_free(pctx_params);
+			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+		}
+		if (EVP_PKEY_CTX_set_dsa_paramgen_bits(pctx_params, bits) == 0) {
+			cm_log(1, "keygen-o: Error setting DSA bits to %d.\n", bits);
+			EVP_PKEY_CTX_free(pctx_params);
+			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+		}
+
+		if (EVP_PKEY_paramgen(pctx_params, &pkey_params) != 1) {
+			cm_log(1, "keygen-o: Error in DSA paramgen.\n");
+			EVP_PKEY_CTX_free(pctx_params);
+			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+		}
+
+		pctx = EVP_PKEY_CTX_new(pkey_params, NULL);
+		if (EVP_PKEY_keygen_init(pctx) == 0) {
+			cm_log(1, "keygen-o: Error initializing parameter DSA key generation.\n");
+			EVP_PKEY_CTX_free(pctx);
+			EVP_PKEY_CTX_free(pctx_params);
+			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+		}
+
+		if (EVP_PKEY_generate(pctx, &pkey) != 1) {
 			if (cm_key_size != CM_DEFAULT_PUBKEY_SIZE) {
-				cm_log(1, "Error generating %d-bit key, "
+				cm_log(1, "keygen-o: Error generating DSA %d-bit key, "
 				       "attempting %d bits.\n",
 				       cm_key_size, CM_DEFAULT_PUBKEY_SIZE);
 				cm_key_size = CM_DEFAULT_PUBKEY_SIZE;
+				EVP_PKEY_CTX_free(pctx);
+				EVP_PKEY_CTX_free(pctx_params);
 				goto retry_gen;
 			}
-			cm_log(1, "Error generating parameters.\n");
+			cm_log(1, "Error generating DSA key.\n");
 			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 		}
-		if (DSA_generate_key(dsa) != 1) {
-			cm_log(1, "Error generating key.\n");
+		EVP_PKEY_free(pkey_params);
+		EVP_PKEY_CTX_free(pctx);
+		EVP_PKEY_CTX_free(pctx_params);
+
+		check_ctx = EVP_PKEY_CTX_new(pkey, NULL);
+		ret = EVP_PKEY_param_check(check_ctx);
+		if (ret == 0) {
+			cm_log(1, "keygen-o: DSA key pair is invalid\n");
+			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+		} else if (ret == -2) {
+			cm_log(1, "keygen-o: EVP_PKEY_check: DSA operation not supported for this algorithm.\n");
 			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 		}
-		EVP_PKEY_set1_DSA(pkey, dsa);
+		EVP_PKEY_CTX_free(check_ctx);
 		break;
 #endif
 #ifdef CM_ENABLE_EC
 	case cm_key_ecdsa:
+		char *curve = NULL;
 		if (cm_key_size <= 256)
-			ecurve = NID_X9_62_prime256v1;
+			// ecurve = NID_X9_62_prime256v1;
+			curve = "prime256v1";
 		else if (cm_key_size <= 384)
-			ecurve = NID_secp384r1;
+			// ecurve = NID_secp384r1;
+			curve = "secp384r1";
 		else
-			ecurve = NID_secp521r1;
-		ec = EC_KEY_new_by_curve_name(ecurve);
-		while ((ec == NULL) && (ecurve != NID_X9_62_prime256v1)) {
-			cm_log(1, "Error allocating new EC key.\n");
-			switch (ecurve) {
-			case NID_secp521r1:
-				cm_log(1, "Trying with a smaller key.\n");
-				ecurve = NID_secp384r1;
-				ec = EC_KEY_new_by_curve_name(ecurve);
-				break;
-			case NID_secp384r1:
-				cm_log(1, "Trying with a smaller key.\n");
-				ecurve = NID_X9_62_prime256v1;
-				ec = EC_KEY_new_by_curve_name(ecurve);
-				break;
+			// ecurve = NID_secp521r1;
+			curve = "secp521r1";
+
+		pkey = EVP_EC_gen(curve);
+		if (pkey == NULL) {
+			if (strcmp(curve, "prime256v1") == 0) {
+				cm_log(1, "Error generating EC %s key, "
+				       "attempting prime256v1.\n",
+				       curve);
+				curve = "prime256v1";
+				EVP_PKEY_CTX_free(pctx);
+				goto retry_gen;
 			}
-		}
-		if (ec == NULL) {
-			cm_log(1, "Error allocating new EC key.\n");
-			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
-		}
-		if (EC_KEY_generate_key(ec) != 1) {
-			cm_log(1, "Error generating key.\n");
+			cm_log(1, "Error generating RSA key.\n");
+			EVP_PKEY_CTX_free(pctx);
 			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 		}
-		EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE);
-		EVP_PKEY_set1_EC_KEY(pkey, ec);
+		EVP_PKEY_CTX_free(pctx);
 		break;
 #endif
 	default:
@@ -372,6 +430,7 @@ retry_gen:
 	fprintf(status, "%s\n%s\n%s\n", pubihex, pubhex, marker);
 	fclose(fp);
 	fclose(status);
+	EVP_PKEY_free(pkey);
 
 	/* Try to remove any keys with old candidate names. */
 	if ((entry->cm_key_next_marker != NULL) &&
diff --git a/src/local.c b/src/local.c
index a62ebda..7d2e942 100644
--- a/src/local.c
+++ b/src/local.c
@@ -199,11 +199,14 @@ get_signer_info(void *parent, char *localdir, X509 ***roots,
 	char *creds, *hexserial = NULL, *serial, buf[LINE_MAX], *csr;
 	STACK_OF(X509) *cas = NULL;
 	PKCS12 *p12 = NULL;
-	BIGNUM *exponent = NULL;
-	RSA *rsa;
+	unsigned int bits = CM_DEFAULT_PUBKEY_SIZE;
+	unsigned int exponent = CM_DEFAULT_RSA_EXPONENT;
+	OSSL_PARAM rsa_params[3];
+	EVP_PKEY_CTX *pctx = NULL;
+	EVP_PKEY_CTX *check_ctx = NULL;
 	dbus_bool_t save = FALSE;
 	time_t now, then, life, lifedelta;
-	int i;
+	int i, ret;
 
 	*roots = NULL;
 	*signer_cert = NULL;
@@ -301,29 +304,39 @@ get_signer_info(void *parent, char *localdir, X509 ***roots,
 		}
 		/* Generate a new key.  For now at least, generate RSA of the
 		 * default size with the default exponent. */
-		exponent = BN_new();
-		if (exponent == NULL) {
-			cm_log(1, "Error setting up exponent.\n");
+		pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+		if (pctx == NULL) {
+			cm_log(1, "Error allocating new RSA context.\n");
 			return CM_SUBMIT_STATUS_UNREACHABLE;
 		}
-		BN_set_word(exponent, CM_DEFAULT_RSA_EXPONENT);
-		rsa = RSA_new();
-		if (rsa == NULL) {
-			cm_log(1, "Error allocating new RSA key.\n");
+
+		if (EVP_PKEY_keygen_init(pctx) == 0) {
+			cm_log(1, "Error initializing RSA key generation.\n");
+			return CM_SUBMIT_STATUS_UNREACHABLE;
+		}
+
+		rsa_params[0] = OSSL_PARAM_construct_uint("bits", &bits);
+		rsa_params[1] = OSSL_PARAM_construct_uint("e", &exponent);
+		rsa_params[2] = OSSL_PARAM_construct_end();
+		if (EVP_PKEY_CTX_set_params(pctx, rsa_params) == 0) {
+			cm_log(1, "Error setting RSA key parameters.\n");
 			return CM_SUBMIT_STATUS_UNREACHABLE;
 		}
+
+
 	retry_gen:
-		if (RSA_generate_key_ex(rsa, CM_DEFAULT_PUBKEY_SIZE, exponent,
-					NULL) != 1) {
-			cm_log(1, "Error generating key.\n");
+		if (EVP_PKEY_generate(pctx, signer_key) != 1) {
+			cm_log(1, "Error generating RSA %d-bit key.\n", bits);
+			EVP_PKEY_CTX_free(pctx);
 			return CM_SUBMIT_STATUS_UNREACHABLE;
 		}
-		if (RSA_check_key(rsa) != 1) { /* should be unnecessary */
+		check_ctx = EVP_PKEY_CTX_new(*signer_key, NULL);
+
+		ret = EVP_PKEY_check(check_ctx);
+		if (ret == 0) {
 			cm_log(1, "Key fails checks.  Retrying.\n");
 			goto retry_gen;
 		}
-		*signer_key = EVP_PKEY_new();
-		EVP_PKEY_set1_RSA(*signer_key, rsa);
 		/* Build a suitable CA signing request. */
 		csr = make_ca_csr(parent, *signer_key, *signer_cert);
 		if (csr == NULL) {
diff --git a/src/log.c b/src/log.c
index 4cc8368..b0a1a31 100644
--- a/src/log.c
+++ b/src/log.c
@@ -28,6 +28,9 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <openssl/err.h>
+#include <secport.h>
+#include <prerror.h>
 #include <talloc.h>
 
 #include "log.h"
@@ -112,3 +115,28 @@ cm_log(int level, const char *fmt, ...)
 		}
 	}
 }
+
+/* Log the passed message at level and then display all errors reported
+ * by OpenSSL and NSS. Then clear all errors in both.
+ */
+void
+cm_log_errors(int level, char *msg)
+{
+	char buf[LINE_MAX] = "";
+	long error;
+	int nss_err;
+
+	cm_log(level, "%s\n", msg);
+	while ((error = ERR_get_error()) != 0) {
+		memset(buf, '\0', sizeof(buf));
+		ERR_error_string_n(error, buf, sizeof(buf));
+		cm_log(level, "%s\n", buf);
+	}
+	ERR_clear_error();
+
+	nss_err = PORT_GetError();
+	if (nss_err < 0) {
+		cm_log(level, "%d: %s\n", nss_err, PR_ErrorToString(nss_err, 0));
+	}
+	PORT_SetError(0);
+}
diff --git a/src/log.h b/src/log.h
index e4645e1..5ad2fd5 100644
--- a/src/log.h
+++ b/src/log.h
@@ -43,5 +43,6 @@ void cm_log(int level, const char *fmt, ...)
 __attribute__((format(printf,2,3)))
 #endif
 ;
+void cm_log_errors(int level, char *msg);
 
 #endif
diff --git a/src/pkcs7.c b/src/pkcs7.c
index b2cccc0..3b94043 100644
--- a/src/pkcs7.c
+++ b/src/pkcs7.c
@@ -275,25 +275,6 @@ cm_pkcs7_parse_buffer(const unsigned char *buffer, size_t length,
 	}
 }
 
-void
-log_pkcs7_errors(int level, char *msg)
-{
-    char buf[LINE_MAX] = "";
-    long error;
-	int nss_err;   
-
-    cm_log(level, "%s\n", msg);
-    while ((error = ERR_get_error()) != 0) {
-            memset(buf, '\0', sizeof(buf));
-            ERR_error_string_n(error, buf, sizeof(buf));
-            cm_log(level, "%s\n", buf);
-    }
-	nss_err = PORT_GetError();
-    if (nss_err < 0) {
-		cm_log(level, "%d: %s\n", nss_err, PR_ErrorToString(nss_err, 0));
-	}
-}
-
 int
 cm_pkcs7_parsev(unsigned int flags, void *parent,
 		char **certleaf, char **certtop, char ***certothers,
@@ -545,7 +526,7 @@ cm_pkcs7_envelope_data(char *encryption_cert, enum cm_prefs_cipher cipher,
 	}
 	recipient = PEM_read_bio_X509(in, NULL, NULL, NULL);
 	if (recipient == NULL) {
-		log_pkcs7_errors(0, "Error parsing recipient certificate.\n");
+		cm_log_errors(0, "Error parsing recipient certificate.\n");
 		goto done;
 	}
 	BIO_free(in);
@@ -567,12 +548,12 @@ cm_pkcs7_envelope_data(char *encryption_cert, enum cm_prefs_cipher cipher,
 	BIO_free(in);
 
 	if (p7 == NULL) {
-		log_pkcs7_errors(0, "Error encrypting signing request.\n");
+		cm_log_errors(0, "Error encrypting signing request.\n");
 		goto done;
 	}
 	len = i2d_PKCS7(p7, NULL);
 	if (len < 0) {
-		log_pkcs7_errors(0, "Error encoding encrypted signing request.\n");
+		cm_log_errors(0, "Error encoding encrypted signing request.\n");
 		goto done;
 	}
 	dp7 = malloc(len);
@@ -582,7 +563,7 @@ cm_pkcs7_envelope_data(char *encryption_cert, enum cm_prefs_cipher cipher,
 	}
 	u = dp7;
 	if (i2d_PKCS7(p7, &u) != len) {
-		log_pkcs7_errors(0, "Error encoding encrypted signing request.\n");
+		cm_log_errors(0, "Error encoding encrypted signing request.\n");
 		goto done;
 	}
 	*enveloped = dp7;
@@ -619,13 +600,13 @@ cm_pkcs7_envelope_csr(char *encryption_cert, enum cm_prefs_cipher cipher,
 	req = PEM_read_bio_X509_REQ(in, NULL, NULL, NULL);
 	BIO_free(in);
 	if (req == NULL) {
-		log_pkcs7_errors(0, "Error parsing certificate signing request.\n");
+		cm_log_errors(0, "Error parsing certificate signing request.\n");
 		goto done;
 	}
 
 	dlen = i2d_X509_REQ(req, NULL);
 	if (dlen < 0) {
-		log_pkcs7_errors(0, "Error encoding certificate signing request.\n");
+		cm_log_errors(0, "Error encoding certificate signing request.\n");
 		goto done;
 	}
 	dreq = malloc(dlen);
@@ -635,7 +616,7 @@ cm_pkcs7_envelope_csr(char *encryption_cert, enum cm_prefs_cipher cipher,
 	}
 	u = dreq;
 	if (i2d_X509_REQ(req, &u) != dlen) {
-		log_pkcs7_errors(0, "Error encoding certificate signing request.\n");
+		cm_log_errors(0, "Error encoding certificate signing request.\n");
 		goto done;
 	}
 	ret = cm_pkcs7_envelope_data(encryption_cert, cipher, dreq, dlen,
@@ -697,7 +678,7 @@ cm_pkcs7_generate_ias(char *cacert, char *minicert,
 	ca = PEM_read_bio_X509(in, NULL, NULL, NULL);
 	BIO_free(in);
 	if (ca == NULL) {
-		log_pkcs7_errors(0, "Error parsing CA certificate.\n");
+		cm_log_errors(0, "Error parsing CA certificate.\n");
 		goto done;
 	}
 
@@ -709,7 +690,7 @@ cm_pkcs7_generate_ias(char *cacert, char *minicert,
 	mini = PEM_read_bio_X509(in, NULL, NULL, NULL);
 	BIO_free(in);
 	if (mini == NULL) {
-		log_pkcs7_errors(0, "Error parsing client certificate.\n");
+		cm_log_errors(0, "Error parsing client certificate.\n");
 		goto done;
 	}
 
@@ -725,7 +706,7 @@ cm_pkcs7_generate_ias(char *cacert, char *minicert,
 	}
 	u = issuer;
 	if (i2d_X509_NAME(X509_get_issuer_name(ca), &u) != issuerlen) {
-		log_pkcs7_errors(0, "Error encoding CA certificate issuer name.\n");
+		cm_log_errors(0, "Error encoding CA certificate issuer name.\n");
 		goto done;
 	}
 
@@ -741,7 +722,7 @@ cm_pkcs7_generate_ias(char *cacert, char *minicert,
 	}
 	u = subject;
 	if (i2d_X509_NAME(X509_get_subject_name(mini), &u) != subjectlen) {
-		log_pkcs7_errors(0, "Error encoding client certificate subject name.\n");
+		cm_log_errors(0, "Error encoding client certificate subject name.\n");
 		goto done;
 	}
 	PORT_SetError(0);
@@ -753,7 +734,7 @@ cm_pkcs7_generate_ias(char *cacert, char *minicert,
 	issuerandsubject.subject.len = subjectlen;
 	if (SEC_ASN1EncodeItem(NULL, &encoded, &issuerandsubject,
 			       cm_pkcs7_ias_template) != &encoded) {
-		log_pkcs7_errors(0, "Error encoding issuer and subject names.\n");
+		cm_log_errors(0, "Error encoding issuer and subject names.\n");
 		goto done;
 	}
 	*ias = malloc(encoded.len);
diff --git a/src/scep.c b/src/scep.c
index 120bd3a..c9dd10e 100644
--- a/src/scep.c
+++ b/src/scep.c
@@ -441,7 +441,7 @@ main(int argc, const char **argv)
 				       NULL, NULL,
 				       NULL, NULL, NULL, NULL);
 		if (i != 0) {
-			log_pkcs7_errors(0, "Error: failed to verify signature on "
+			cm_log_errors(0, "Error: failed to verify signature on "
 					"rekey PKCSReq.\n");
 		}
 		if ((msgtype == NULL) ||
@@ -471,7 +471,7 @@ main(int argc, const char **argv)
 				       &sent_nonce, &sent_nonce_length,
 				       NULL, NULL, NULL, NULL);
 		if (i != 0) {
-			log_pkcs7_errors(0, "Error: failed to verify signature on "
+			cm_log_errors(0, "Error: failed to verify signature on "
 					"message.\n");
 		}
 		if ((msgtype == NULL) ||
@@ -1005,7 +1005,7 @@ main(int argc, const char **argv)
 					printf("%s", buf);
 				}
 				printf("\n");
-				log_pkcs7_errors(0, "Error: failed to verify signature on "
+				cm_log_errors(0, "Error: failed to verify signature on "
 						  "server response.\n");
 				s = cm_store_base64_from_bin(ctx, (unsigned char *) results2,
 							     results_length2);
@@ -1126,7 +1126,7 @@ main(int argc, const char **argv)
 				p7 = d2i_PKCS7(NULL, &u, payload_length);
 				if (p7 == NULL) {
 					printf(_("Error: couldn't parse signed-data.\n"));
-					log_pkcs7_errors(0, "Error: couldn't parse signed-data.\n");
+					cm_log_errors(0, "Error: couldn't parse signed-data.\n");
 					s = cm_store_base64_from_bin(ctx,
 								     (unsigned char *) results2,
 								     results_length2);
@@ -1138,7 +1138,7 @@ main(int argc, const char **argv)
 				}
 				if (!PKCS7_type_is_enveloped(p7)) {
 					printf(_("Error: signed-data payload is not enveloped-data.\n"));
-					log_pkcs7_errors(0, "Error: signed-data payload is not "
+					cm_log_errors(0, "Error: signed-data payload is not "
 								"enveloped-data.\n");
 					s = cm_store_base64_from_bin(ctx,
 								     (unsigned char *) results2,
@@ -1154,7 +1154,7 @@ main(int argc, const char **argv)
 				    (p7->d.enveloped->enc_data->content_type == NULL) ||
 				    (OBJ_obj2nid(p7->d.enveloped->enc_data->content_type) != NID_pkcs7_data)) {
 					printf(_("Error: enveloped-data payload is not data.\n"));
-					log_pkcs7_errors(0, "Error: enveloped-data payload is "
+					cm_log_errors(0, "Error: enveloped-data payload is "
 								"not data.\n");
 					s = cm_store_base64_from_bin(ctx,
 								     (unsigned char *) results2,
diff --git a/src/scepgen-n.c b/src/scepgen-n.c
index e5a0a81..46244de 100644
--- a/src/scepgen-n.c
+++ b/src/scepgen-n.c
@@ -134,10 +134,14 @@ cm_scepgen_n_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 	struct cm_keyiread_n_ctx_and_keys *keys;
 	const char *p, *es, *reason;
 	int ec;
+	int ret;
 	PKCS7 *csr_new, *csr_old, *ias_new, *ias_old;
+	unsigned int bits = CM_DEFAULT_PUBKEY_SIZE;
+	unsigned int exponent = CM_DEFAULT_RSA_EXPONENT;
 	EVP_PKEY *key;
-	RSA *rsa;
-	BIGNUM *exponent;
+	OSSL_PARAM params[3];
+	EVP_PKEY_CTX *pctx = NULL;
+	EVP_PKEY_CTX *check_ctx = NULL;
 
 	status = fdopen(fd, "w");
 	if (status == NULL) {
@@ -205,32 +209,48 @@ cm_scepgen_n_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 
 	/* Use a dummy key to sign using OpenSSL. */
 	cm_log(1, "Generating dummy key.\n");
+	pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+	if (pctx == NULL) {
+		cm_log(1, "Error allocating new RSA context.\n");
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+	}
+
+	if (EVP_PKEY_keygen_init(pctx) == 0) {
+		cm_log(1, "Error initializing RSA key generation.\n");
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+	}
+
 	key = EVP_PKEY_new();
 	if (key == NULL) {
 		cm_log(0, "Error allocating new key.\n");
 		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 	}
-	exponent = BN_new();
-	if (exponent == NULL) {
-		cm_log(0, "Error setting up exponent.\n");
-		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
-	}
-	BN_set_word(exponent, CM_DEFAULT_RSA_EXPONENT);
-	rsa = RSA_new();
-	if (rsa == NULL) {
-		cm_log(0, "Error allocating new RSA key.\n");
+	params[0] = OSSL_PARAM_construct_uint("bits", &bits);
+	params[1] = OSSL_PARAM_construct_uint("e", &exponent);
+	params[2] = OSSL_PARAM_construct_end();
+	if (EVP_PKEY_CTX_set_params(pctx, params) == 0) {
+		cm_log(1, "Error setting RSA key parameters.\n");
 		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 	}
+
 retry_gen:
-	if (RSA_generate_key_ex(rsa, CM_DEFAULT_PUBKEY_SIZE, exponent, NULL) != 1) {
-		cm_log(0, "Error generating key.\n");
+	if (EVP_PKEY_generate(pctx, &key) != 1) {
+		cm_log(1, "Error generating RSA %d-bit key.\n", bits);
+		EVP_PKEY_CTX_free(pctx);
 		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 	}
-	if (RSA_check_key(rsa) != 1) { /* should be unnecessary */
-		cm_log(1, "Key fails checks.  Retrying.\n");
+
+	check_ctx = EVP_PKEY_CTX_new(key, NULL);
+
+	ret = EVP_PKEY_check(check_ctx);
+	if (ret == 0) {
+		cm_log(1, "EVP_PKEY_check (pairwise check): Key pair is invalid.\n");
 		goto retry_gen;
+	} else if (ret == -2) {
+		cm_log(1, "EVP_PKEY_check: Operation not supported for this algorithm.\n");
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 	}
-	BN_free(exponent);
+	EVP_PKEY_CTX_free(check_ctx);
 
 	/* Read the proper keys. */
 	keys = cm_keyiread_n_get_keys(entry, 0);
@@ -242,7 +262,6 @@ retry_gen:
 	}
 
 	/* Sign using a dummy key. */
-	EVP_PKEY_set1_RSA(key, rsa);
 	csr_new = NULL;
 	csr_old = NULL;
 	ias_new = NULL;
diff --git a/src/submit-n.c b/src/submit-n.c
index 2b64902..dcb88b8 100644
--- a/src/submit-n.c
+++ b/src/submit-n.c
@@ -93,10 +93,14 @@ try_to_decode(void *parent, PLArenaPool *arena, SECItem *item,
 	ASN1_STRING *params = NULL;
 	PKCS7 *p7 = NULL;
 	PKCS7_RECIP_INFO *p7i = NULL;
-	BIGNUM *exponent = NULL;
 	EVP_PKEY *pkey = NULL;
+	unsigned int bits = CM_DEFAULT_PUBKEY_SIZE;
+	unsigned int exponent = CM_DEFAULT_RSA_EXPONENT;
+	OSSL_PARAM rsa_params[3];
+	EVP_PKEY_CTX *pctx = NULL;
+	EVP_PKEY_CTX *check_ctx = NULL;
+	int rc = 0;
 	BIO *out;
-	RSA *rsa = NULL;
 	char buf[BUFSIZ];
 	const unsigned char *u;
 	unsigned char *enc_key, *dec, *reenc, *param_data;
@@ -191,36 +195,69 @@ try_to_decode(void *parent, PLArenaPool *arena, SECItem *item,
 	pkey = EVP_PKEY_new();
 	if (pkey == NULL) {
 		cm_log(1, "Error allocating new key.\n");
-		goto done;
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 	}
-	exponent = BN_new();
-	if (exponent == NULL) {
-		cm_log(1, "Error setting up exponent.\n");
-		goto done;
+retry_gen:
+	pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
+	if (pctx == NULL) {
+		cm_log(1, "Error allocating new RSA context.\n");
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 	}
-	BN_set_word(exponent, CM_DEFAULT_RSA_EXPONENT);
-	rsa = RSA_new();
-	if (rsa == NULL) {
-		cm_log(1, "Error allocating new RSA key.\n");
-		goto done;
+
+	if (EVP_PKEY_keygen_init(pctx) == 0) {
+		cm_log(1, "Error initializing RSA key generation.\n");
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 	}
-retry_gen:
-	if (RSA_generate_key_ex(rsa, CM_DEFAULT_PUBKEY_SIZE, exponent, NULL) != 1) {
-		cm_log(1, "Error generating key.\n");
-		goto done;
+
+
+	rsa_params[0] = OSSL_PARAM_construct_uint("bits", &bits);
+	rsa_params[1] = OSSL_PARAM_construct_uint("e", &exponent);
+	rsa_params[2] = OSSL_PARAM_construct_end();
+	if (EVP_PKEY_CTX_set_params(pctx, rsa_params) == 0) {
+		cm_log(1, "Error setting RSA key parameters.\n");
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+	}
+
+	if (EVP_PKEY_generate(pctx, &pkey) != 1) {
+		cm_log(1, "Error generating RSA %d-bit key.\n", bits);
+		EVP_PKEY_CTX_free(pctx);
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 	}
-	if (RSA_check_key(rsa) != 1) { /* should be unnecessary */
-		cm_log(1, "Key fails checks.  Retrying.\n");
+
+	check_ctx = EVP_PKEY_CTX_new(pkey, NULL);
+
+	rc = EVP_PKEY_check(check_ctx);
+	if (rc == 0) {
+		cm_log(1, "submit-n: EVP_PKEY_check (pairwise check): Key pair %d bits is invalid.\n", bits);
+
+		EVP_PKEY_CTX_free(pctx);
 		goto retry_gen;
+	} else if (rc == -2) {
+		cm_log(1, "EVP_PKEY_check: Operation not supported for this algorithm.\n");
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 	}
-	EVP_PKEY_set1_RSA(pkey, rsa);
+	EVP_PKEY_CTX_free(pctx);
+	EVP_PKEY_CTX_free(check_ctx);
 
 	/* Encrypt the bulk key.  We're about to decrypt it again, so do it the
 	 * simplest way that we can. */
-	reenc_len = dec_len + RSA_size(rsa);
+	pctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
+	if (pctx == NULL) {
+		cm_log(1, "submit-n: Error allocating new RSA context.\n");
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+	}
+	if (EVP_PKEY_encrypt_init(pctx) <= 0) {
+		cm_log(1, "submit-n: Error initializing RSA encrypt context.\n");
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+	}
+	if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) <= 0) {
+		cm_log(1, "submit-n: Error setting RSA encrypt padding to RSA_PKCS1_PADDING\n");
+		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+	}
+
+	reenc_len = dec_len + EVP_PKEY_size(pkey);
 	reenc = talloc_size(parent, reenc_len);
-	padding = RSA_PKCS1_PADDING;
-	reenc_len = RSA_public_encrypt(dec_len, dec, reenc, rsa, padding);
+	EVP_PKEY_encrypt(pctx, reenc, &reenc_len, dec, dec_len);
 	if (reenc_len < 0) {
 		cm_log(1, "Error reencrypting.\n");
 		goto retry_gen;
@@ -253,17 +290,18 @@ retry_gen:
 
 done:
 	if (ret == NULL) {
+		cm_log(1, "something failed, ret is NULL\n");
 		while ((error = ERR_get_error()) != 0) {
 			ERR_error_string_n(error, buf, sizeof(buf));
 			cm_log(1, "%s\n", buf);
 		}
 	}
+	if (pctx) {
+		EVP_PKEY_CTX_free(pctx);
+	}
 	if (pkey != NULL) {
 		EVP_PKEY_free(pkey);
 	}
-	if (exponent != NULL) {
-		BN_free(exponent);
-	}
 	if (p7 != NULL) {
 		PKCS7_free(p7);
 	}

From c18445f900ffc5600c45c7c7274aacee64320b21 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 14 2026 18:32:07 +0000
Subject: [PATCH 2/6] Add initial ML-DSA support with OpenSSL 3.5.0


OpenSSL 3.5.0 has added support for Post-Quantum ML-DSA keys.
Allow them to be generated by certmonger.

The minimum key size is not fully implemented because key size
has a different meaning in this context. It is plumbed in, such that
one can set a numeric size, but it isn't used and is effectively
meaningless.

---

diff --git a/configure.ac b/configure.ac
index f621a07..78e7d54 100644
--- a/configure.ac
+++ b/configure.ac
@@ -712,6 +712,54 @@ if ! ${configure_dist_target_only:-false} ; then
 	AC_SUBST(MAN_EC)
 	AC_SUBST(NO_MAN_EC)
 
+	AC_ARG_ENABLE(ml_dsa,
+	AS_HELP_STRING(--disable-ml-dsa,[Disable ML-DSA key support, even if available.]),
+	ml_dsa=$enableval,
+	ml_dsa=maybe)
+	if test x$ml_dsa != xno ; then
+		CFLAGSsave="$CFLAGS"
+		LIBSsave="$LIBS"
+		CFLAGS="$OPENSSL_CFLAGS $CFLAGS"
+		LIBS="$OPENSSL_LIBS $LIBS"
+		AC_CHECK_LIB(crypto,EVP_PKEY_Q_keygen)
+		# CFLAGS="$NSS_CFLAGS $CFLAGS"
+		# LIBS="$NSS_LIBS $LIBS"
+		# AC_CHECK_FUNCS(SECKEY_CreateECPrivateKey)
+		# AC_CHECK_FUNCS(PK11_PrivDecrypt)
+		# AC_CHECK_FUNCS(NSS_OptionGet NSS_OptionSet)
+		CFLAGS="$CFLAGSsave"
+		LIBS="$LIBSsave"
+		can_ml_dsa=true
+		if test x$ac_cv_lib_crypto_EVP_PKEY_Q_keygen = xno ; then
+			can_ml_dsa=false
+		fi
+		AC_MSG_CHECKING([for ML-DSA support])
+		if $can_ml_dsa ; then
+			AC_MSG_RESULT(enabling ML-DSA support)
+			AC_DEFINE_UNQUOTED(CM_ENABLE_ML_DSA,1,[Define to enable ML-DSA support.])
+			MAN_ML_DSA=""
+			NO_MAN_ML_DSA=".\\\" "
+			ml_dsa=yes
+		else
+			if test x$ml_dsa != xyes ; then
+				AC_MSG_WARN([unavailable, disabling])
+				ml_dsa=no
+			else
+				AC_MSG_ERROR([unavailable])
+			fi
+			MAN_ML_DSA=".\\\" "
+			NO_MAN_EC=""
+		fi
+	else
+		AC_MSG_RESULT(NOT enabling ML-DSA support)
+		MAN_ML_DSA=".\\\" "
+		NO_MAN_ML_DSA=""
+		ml_dsa=no
+	fi
+	AM_CONDITIONAL(HAVE_ML_DSA,[test x$ml_dsa = xyes])
+	AC_SUBST(MAN_ML_DSA)
+	AC_SUBST(NO_MAN_ML_DSA)
+
 	AC_DEFINE_UNQUOTED(CM_DEFAULT_KEY_STORAGE_TYPE,cm_key_storage_nssdb,[Define to the default type of storage used for keys.])
 	AC_DEFINE_UNQUOTED(CM_DEFAULT_KEY_STORAGE_LOCATION,"/etc/pki/nssdb",[Define to the default location of storage used for keys.])
 	AC_DEFINE_UNQUOTED(CM_DEFAULT_KEY_TOKEN,NULL,[Define to the default token used for holding keys.])
@@ -730,9 +778,11 @@ if ! ${configure_dist_target_only:-false} ; then
 	AC_SUBST(CM_MINIMUM_RSA_KEY_SIZE)
 	CM_MINIMUM_DSA_KEY_SIZE=512
 	CM_MINIMUM_EC_KEY_SIZE=256
+	CM_MINIMUM_ML_DSA_KEY_SIZE=44
 	AC_DEFINE_UNQUOTED(CM_MINIMUM_RSA_KEY_SIZE,$CM_MINIMUM_RSA_KEY_SIZE,[Define to the minimum key size when generating RSA keys.  Requests to generate smaller keys will be forced to this key size.])
 	AC_DEFINE_UNQUOTED(CM_MINIMUM_DSA_KEY_SIZE,$CM_MINIMUM_DSA_KEY_SIZE,[Define to the minimum key size when generating DSA parameters and keys.  Requests to generate smaller keys will be forced to this key size.])
 	AC_DEFINE_UNQUOTED(CM_MINIMUM_EC_KEY_SIZE,$CM_MINIMUM_EC_KEY_SIZE,[Define to the minimum key size when selecting elliptic curve parameters.  Requests to generate smaller keys will be forced to this key size.])
+	AC_DEFINE_UNQUOTED(CM_MINIMUM_ML_DSA_KEY_SIZE,$CM_MINIMUM_ML_DSA_KEY_SIZE,[Define to the minimum key size when selecting ML-DSA parameters.  Requests to generate smaller keys will be forced to this key size.])
 	AC_SUBST(CM_MINIMUM_RSA_KEY_SIZE)
 	AC_SUBST(CM_MINIMUM_DSA_KEY_SIZE)
 	AC_SUBST(CM_MINIMUM_EC_KEY_SIZE)
diff --git a/src/certmonger.conf.5.in b/src/certmonger.conf.5.in
index 49b6e09..de41105 100644
--- a/src/certmonger.conf.5.in
+++ b/src/certmonger.conf.5.in
@@ -49,6 +49,7 @@ signing requests, and used when self\-signing certificates.
 @NO_MAN_DSA@\fIRSA\fR is supported.
 @MAN_DSA@\fIRSA\fR and \fIDSA\fR are supported.
 @MAN_EC@\fIEC\fR (also known as \fIECDSA\fR) is also supported.
+@MAN_ML_DSA@\fIML-DSA-44, ML-DSA-65, ML-DSA-87\fR are also supported.
 The default is \fIRSA\fP.
 
 .IP rsa_key_size
diff --git a/src/getcert.c b/src/getcert.c
index b4a02d4..a835328 100644
--- a/src/getcert.c
+++ b/src/getcert.c
@@ -895,6 +895,11 @@ request(const char *argv0, int argc, const char **argv)
 			    && (strcasecmp(poptarg, "ECDSA") != 0)
 			    && (strcasecmp(poptarg, "EC") != 0)
 #endif
+#ifdef CM_ENABLE_ML_DSA
+			    && (strcasecmp(poptarg, "ML-DSA-44") != 0)
+			    && (strcasecmp(poptarg, "ML-DSA-65") != 0)
+			    && (strcasecmp(poptarg, "ML-DSA-87") != 0)
+#endif
 			    ) {
 				printf(_("No support for generating \"%s\" keys.\n"),
 				       poptarg);
@@ -906,6 +911,9 @@ request(const char *argv0, int argc, const char **argv)
 #ifdef CM_ENABLE_EC
 				printf(" EC");
 #endif
+#ifdef CM_ENABLE_ML_DSA
+				printf(" ML-DSA-44 ML-DA-65 ML-DSA-87");
+#endif
 				printf("\n");
 				return 1;
 			}
@@ -2702,6 +2710,9 @@ rekey_or_resubmit(const char *argv0, const char *category, int argc,
 			    && (strcasecmp(poptarg, "ECDSA") != 0)
 			    && (strcasecmp(poptarg, "EC") != 0)
 #endif
+#ifdef CM_ENABLE_ML_DSA
+			    && (strcasecmp(poptarg, "ML-DSA-65") != 0)
+#endif
 			    ) {
 				printf(_("No support for generating \"%s\" keys.\n"),
 				       poptarg);
diff --git a/src/keygen-n.c b/src/keygen-n.c
index 27c1efc..3c3b6cd 100644
--- a/src/keygen-n.c
+++ b/src/keygen-n.c
@@ -271,6 +271,14 @@ cm_keygen_n_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 		pmech = CKM_EC_KEY_PAIR_GEN;
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA:
+	case cm_key_ml_dsa_44:
+	case cm_key_ml_dsa_65:
+	case cm_key_ml_dsa_87:
+		mech = CKM_ML_DSA_KEY_PAIR_GEN;
+		pmech = CKM_ML_DSA_KEY_PAIR_GEN;
+		break;
+#endif
 	default:
 		fprintf(status, "Unknown or unsupported key type.\n");
 		cm_log(1, "Unknown or unsupported key type.\n");
@@ -530,6 +538,13 @@ retry_gen:
 		/* no parameters to generate */
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA:
+	case cm_key_ml_dsa_44:
+	case cm_key_ml_dsa_65:
+	case cm_key_ml_dsa_87:
+		/* no parameters to generate */
+		break;
+#endif
 	default:
 		params = NULL;
 		break;
@@ -565,6 +580,13 @@ retry_gen:
 		params = &ec_params;
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA:
+	case cm_key_ml_dsa_44:
+	case cm_key_ml_dsa_65:
+	case cm_key_ml_dsa_87:
+		/* FIXME */
+		break;
+#endif
 	default:
 		params = NULL;
 		break;
diff --git a/src/keygen-o.c b/src/keygen-o.c
index ff929fd..2f5a212 100644
--- a/src/keygen-o.c
+++ b/src/keygen-o.c
@@ -111,10 +111,6 @@ cm_keygen_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 	int ret;
 	char *filename;
 	char *marker;
-#ifdef CM_ENABLE_EC
-	EC_KEY *ec;
-	int ecurve;
-#endif
 
 	status = fdopen(fd, "w");
 	if (status == NULL) {
@@ -295,12 +291,38 @@ retry_gen:
 		EVP_PKEY_CTX_free(pctx);
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+	case cm_key_ml_dsa_44:
+	case cm_key_ml_dsa_65:
+	case cm_key_ml_dsa_87:
+		const char *alg = cm_store_algorithm_to_name(cm_key_algorithm);
+		EVP_PKEY_CTX *ctx = NULL;
+		if ((ctx = EVP_PKEY_CTX_new_from_name(NULL, alg, NULL)) == NULL) {
+			ERR_print_errors_fp(stderr);
+			cm_log(1, "Initializing key context failed\n");
+				_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+		}
+		if (EVP_PKEY_keygen_init(ctx) <= 0) {
+			ERR_print_errors_fp(stderr);
+			cm_log(1, "keygen_init failed\n");
+			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+		}
+		if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
+			cm_log(1, "Error generating key.\n");
+			_exit(CM_SUB_STATUS_INTERNAL_ERROR);
+		}
+		EVP_PKEY_CTX_free(ctx);
+	break;
+#endif
 	default:
 		cm_log(1, "Unknown or unsupported key type.\n");
 		_exit(CM_SUB_STATUS_INTERNAL_ERROR);
 		break;
 	}
 
+	char *n = EVP_PKEY_get0_type_name(pkey);
+	cm_log(1, "debug: generated type %s for %d\n", n, cm_key_algorithm);
+
 	filename = strdup(entry->cm_key_storage_location);
 	marker = "";
 	keyfd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
diff --git a/src/keyiread-n.c b/src/keyiread-n.c
index c2f3928..ed87b09 100644
--- a/src/keyiread-n.c
+++ b/src/keyiread-n.c
@@ -522,6 +522,7 @@ cm_keyiread_n_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 			cm_log(3, "Key is an EC key.\n");
 			alg = "EC";
 			break;
+		/* FIXME: case mlKey? */
 		case nullKey:
 		default:
 			cm_log(3, "Key is of an unknown type.\n");
@@ -581,6 +582,7 @@ cm_keyiread_n_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 					cm_log(3, "Next key is an EC key.\n");
 					alg = "EC";
 					break;
+				/* FIXME: case mlKey? */
 				case nullKey:
 				default:
 					cm_log(3, "Next key is of an unknown type.\n");
diff --git a/src/keyiread-o.c b/src/keyiread-o.c
index 51f7f82..383b2e7 100644
--- a/src/keyiread-o.c
+++ b/src/keyiread-o.c
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2009,2010,2011,2012,2014,2015,2017 Red Hat, Inc.
- * 
+ *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
@@ -34,6 +34,17 @@
 #include <openssl/err.h>
 #include <openssl/pem.h>
 
+/************************
+ * FIXME: these definitions aren't public in openssl? wrong algos?
+************************/
+#define NID_ML_DSA_44          1457
+#define NID_ML_DSA_65          1458
+#define NID_ML_DSA_87          1459
+
+# define EVP_PKEY_ML_DSA_44           NID_ML_DSA_44
+# define EVP_PKEY_ML_DSA_65           NID_ML_DSA_65
+# define EVP_PKEY_ML_DSA_87           NID_ML_DSA_87
+
 #include <talloc.h>
 
 #include "keyiread.h"
@@ -152,6 +163,12 @@ cm_keyiread_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 		pubkey = "";
 		pubikey = "";
 		if (pkey != NULL) {
+			cm_log(1, "debug: pkey base is %d\n", util_EVP_PKEY_base_id(pkey));
+			cm_log(1, "debug: pkey ID is %d\n", EVP_PKEY_get_id(pkey));
+
+			char *n = EVP_PKEY_get0_type_name(pkey);
+			cm_log(1, "debug: type %s\n", n);
+
 			switch (util_EVP_PKEY_base_id(pkey)) {
 			case EVP_PKEY_RSA:
 				cm_log(3, "Key is an RSA key.\n");
@@ -169,6 +186,24 @@ cm_keyiread_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 				alg = "EC";
 				break;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+			case 0:  /* no key type base */
+				char *name = EVP_PKEY_get0_type_name(pkey);
+				cm_log(3, "Key is a %s key.\n", name);
+
+				if (strcasecmp(name, "ML-DSA-44") == 0) {
+					alg = "ML-DSA-44";
+					break;
+				} else
+				if (strcasecmp(name, "ML-DSA-65") == 0) {
+					alg = "ML-DSA-65";
+					break;
+				} else
+				if (strcasecmp(name, "ML-DSA-87") == 0) {
+					alg = "ML-DSA-87";
+					break;
+				}
+#endif
 			default:
 				cm_log(3, "Key is for an unknown algorithm.\n");
 				alg = "";
@@ -190,6 +225,8 @@ cm_keyiread_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 					pubkey = cm_store_hex_from_bin(NULL, tmp, length);
 				}
 			}
+		} else {
+			cm_log(1, "pkey IS NULL\n");
 		}
 		fprintf(fp, "%s/%d/%s/%s\n", alg, bits, pubikey, pubkey);
 		if (nextpkey != NULL) {
@@ -210,6 +247,23 @@ cm_keyiread_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 				alg = "EC";
 				break;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+			case 0:  /* no key type base */
+				char *name = EVP_PKEY_get0_type_name(pkey);
+
+				if (strcasecmp(name, "ML-DSA-44") == 0) {
+					alg = "ML-DSA-44";
+					break;
+				} else
+				if (strcasecmp(name, "ML-DSA-65") == 0) {
+					alg = "ML-DSA-65";
+					break;
+				} else
+				if (strcasecmp(name, "ML-DSA-87") == 0) {
+					alg = "ML-DSA-87";
+					break;
+				}
+#endif
 			default:
 				cm_log(3, "Next key is for an unknown algorithm.\n");
 				alg = "";
diff --git a/src/keyiread.c b/src/keyiread.c
index db34c4b..668b6d3 100644
--- a/src/keyiread.c
+++ b/src/keyiread.c
@@ -141,8 +141,23 @@ cm_keyiread_read_data_from_buffer(struct cm_store_entry *entry, const char *p)
 	    (strncasecmp(p, "EC", 2) == 0)) {
 		alg = cm_key_ecdsa;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+	} else
+	if (((q - p) == strlen("ML-DSA-44")) &&
+	    (strncasecmp(p, "ML-DSA-44", 9) == 0)) {
+		alg = cm_key_ml_dsa_44;
+	} else
+	if (((q - p) == strlen("ML-DSA-65")) &&
+	    (strncasecmp(p, "ML-DSA-65", 9) == 0)) {
+		alg = cm_key_ml_dsa_65;
+	} else
+	if (((q - p) == strlen("ML-DSA-87")) &&
+	    (strncasecmp(p, "ML-DSA-87", 9) == 0)) {
+		alg = cm_key_ml_dsa_87;
+#endif
 	} else {
 		alg = cm_key_unspecified;
+		cm_log(1, "debug: determined unspcified from %d\n", alg);
 	}
 	if (alg != cm_key_unspecified) {
 		p = q + strspn(q, "/\r\n");
@@ -150,6 +165,7 @@ cm_keyiread_read_data_from_buffer(struct cm_store_entry *entry, const char *p)
 		if (p != q) {
 			size = atoi(p);
 			if (size > 0) {
+				cm_log(1, "debug: setting alg %d size %d\n", alg, size);
 				entry->cm_key_type.cm_key_algorithm = alg;
 				entry->cm_key_type.cm_key_size = size;
 				if (entry->cm_key_type.cm_key_gen_algorithm == 0) {
@@ -202,7 +218,22 @@ cm_keyiread_read_data_from_buffer(struct cm_store_entry *entry, const char *p)
 	    (strncasecmp(p, "EC", 2) == 0)) {
 		alg = cm_key_ecdsa;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+	} else
+	if (((q - p) == strlen("ML-DSA-44")) &&
+	    (strncasecmp(p, "ML-DSA-44", 9) == 0)) {
+		alg = cm_key_ml_dsa_44;
+	} else
+	if (((q - p) == strlen("ML-DSA-65")) &&
+	    (strncasecmp(p, "ML-DSA-65", 9) == 0)) {
+		alg = cm_key_ml_dsa_65;
+	} else
+	if (((q - p) == strlen("ML-DSA-87")) &&
+	    (strncasecmp(p, "ML-DSA-87", 9) == 0)) {
+		alg = cm_key_ml_dsa_87;
+#endif
 	} else {
+		cm_log(1, "debug: setting next unspcified from %s\n", p);
 		alg = cm_key_unspecified;
 	}
 	if (alg != cm_key_unspecified) {
diff --git a/src/prefs.c b/src/prefs.c
index 52ffc90..917a9f4 100644
--- a/src/prefs.c
+++ b/src/prefs.c
@@ -516,6 +516,20 @@ cm_prefs_preferred_key_algorithm(void)
 			return cm_key_rsa;
 		}
 #endif
+#ifdef CM_ENABLE_ML_DSA
+		else if (strcasecmp(keytype, "ML-DSA-44") == 0) {
+			free(keytype);
+			return cm_key_ml_dsa_44;
+		}
+		else if (strcasecmp(keytype, "ML-DSA-65") == 0) {
+			free(keytype);
+			return cm_key_ml_dsa_65;
+		}
+		else if (strcasecmp(keytype, "ML-DSA-87") == 0) {
+			free(keytype);
+			return cm_key_ml_dsa_87;
+		}
+#endif
 	}
 	return CM_DEFAULT_PUBKEY_TYPE;
 }
diff --git a/src/store-files.c b/src/store-files.c
index 848de41..dee2fb0 100644
--- a/src/store-files.c
+++ b/src/store-files.c
@@ -57,6 +57,7 @@
 
 static unsigned long long cm_entry_name_last, cm_ca_name_last;
 
+
 enum cm_store_file_field {
 	cm_store_file_field_invalid = 0,
 	cm_store_file_field_id,
@@ -841,6 +842,20 @@ cm_store_entry_read(void *parent, const char *filename, FILE *fp)
 					ret->cm_key_type.cm_key_algorithm =
 						cm_key_ecdsa;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+				} else
+				if (strcasecmp(s[i], "ML-DSA-44") == 0) {
+					ret->cm_key_type.cm_key_algorithm =
+						cm_key_ml_dsa_44;
+				} else
+				if (strcasecmp(s[i], "ML-DSA-65") == 0) {
+					ret->cm_key_type.cm_key_algorithm =
+						cm_key_ml_dsa_65;
+				} else
+				if (strcasecmp(s[i], "ML-DSA-87") == 0) {
+					ret->cm_key_type.cm_key_algorithm =
+						cm_key_ml_dsa_87;
+#endif
 				} else {
 					ret->cm_key_type.cm_key_algorithm =
 						cm_key_unspecified;
@@ -864,6 +879,20 @@ cm_store_entry_read(void *parent, const char *filename, FILE *fp)
 					ret->cm_key_type.cm_key_gen_algorithm =
 						cm_key_ecdsa;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+				} else
+				if (strcasecmp(s[i], "ML-DSA-44") == 0) {
+					ret->cm_key_type.cm_key_gen_algorithm =
+						cm_key_ml_dsa_44;
+				} else
+				if (strcasecmp(s[i], "ML-DSA-65") == 0) {
+					ret->cm_key_type.cm_key_gen_algorithm =
+						cm_key_ml_dsa_65;
+				} else
+				if (strcasecmp(s[i], "ML-DSA-87") == 0) {
+					ret->cm_key_type.cm_key_gen_algorithm =
+						cm_key_ml_dsa_87;
+#endif
 				} else {
 					ret->cm_key_type.cm_key_gen_algorithm =
 						cm_key_unspecified;
@@ -895,6 +924,20 @@ cm_store_entry_read(void *parent, const char *filename, FILE *fp)
 					ret->cm_key_next_type.cm_key_algorithm =
 						cm_key_ecdsa;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+				} else
+				if (strcasecmp(s[i], "ML-DSA-44") == 0) {
+					ret->cm_key_type.cm_key_algorithm =
+						cm_key_ml_dsa_44;
+				} else
+				if (strcasecmp(s[i], "ML-DSA-65") == 0) {
+					ret->cm_key_type.cm_key_algorithm =
+						cm_key_ml_dsa_65;
+				} else
+				if (strcasecmp(s[i], "ML-DSA-87") == 0) {
+					ret->cm_key_type.cm_key_algorithm =
+						cm_key_ml_dsa_87;
+#endif
 				} else {
 					ret->cm_key_next_type.cm_key_algorithm =
 						cm_key_unspecified;
@@ -918,6 +961,20 @@ cm_store_entry_read(void *parent, const char *filename, FILE *fp)
 					ret->cm_key_next_type.cm_key_gen_algorithm =
 						cm_key_ecdsa;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+				} else
+				if (strcasecmp(s[i], "ML-DSA-44") == 0) {
+					ret->cm_key_type.cm_key_gen_algorithm =
+						cm_key_ml_dsa_44;
+				} else
+				if (strcasecmp(s[i], "ML-DSA-65") == 0) {
+					ret->cm_key_type.cm_key_gen_algorithm =
+						cm_key_ml_dsa_65;
+				} else
+				if (strcasecmp(s[i], "ML-DSA-87") == 0) {
+					ret->cm_key_type.cm_key_gen_algorithm =
+						cm_key_ml_dsa_87;
+#endif
 				} else {
 					ret->cm_key_next_type.cm_key_gen_algorithm =
 						cm_key_unspecified;
@@ -1830,6 +1887,20 @@ cm_store_entry_write(FILE *fp, struct cm_store_entry *entry)
 					"EC");
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+	case cm_key_ml_dsa_44:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_type,
+					"ML-DSA-44");
+		break;
+	case cm_key_ml_dsa_65:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_type,
+					"ML-DSA-65");
+		break;
+	case cm_key_ml_dsa_87:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_type,
+					"ML-DSA-87");
+		break;
+#endif
 	}
 	switch (entry->cm_key_type.cm_key_gen_algorithm) {
 	case cm_key_unspecified:
@@ -1852,6 +1923,20 @@ cm_store_entry_write(FILE *fp, struct cm_store_entry *entry)
 					"EC");
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+	case cm_key_ml_dsa_44:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_gen_type,
+					"ML-DSA-44");
+		break;
+	case cm_key_ml_dsa_65:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_gen_type,
+					"ML-DSA-65");
+		break;
+	case cm_key_ml_dsa_87:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_gen_type,
+					"ML-DSA-87");
+		break;
+#endif
 	}
 	cm_store_file_write_int(fp, cm_store_entry_field_key_size,
 				entry->cm_key_type.cm_key_size);
@@ -1878,6 +1963,20 @@ cm_store_entry_write(FILE *fp, struct cm_store_entry *entry)
 					"EC");
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+	case cm_key_ml_dsa_44:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_next_type,
+					"ML-DSA-44");
+		break;
+	case cm_key_ml_dsa_65:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_next_type,
+					"ML-DSA-65");
+		break;
+	case cm_key_ml_dsa_87:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_next_type,
+					"ML-DSA-87");
+		break;
+#endif
 	}
 	switch (entry->cm_key_next_type.cm_key_gen_algorithm) {
 	case cm_key_unspecified:
@@ -1900,6 +1999,20 @@ cm_store_entry_write(FILE *fp, struct cm_store_entry *entry)
 					"EC");
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+	case cm_key_ml_dsa_44:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_next_gen_type,
+					"ML-DSA-44");
+		break;
+	case cm_key_ml_dsa_65:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_next_gen_type,
+					"ML-DSA-65");
+		break;
+	case cm_key_ml_dsa_87:
+		cm_store_file_write_str(fp, cm_store_entry_field_key_next_gen_type,
+					"ML-DSA-87");
+		break;
+#endif
 	}
 	cm_store_file_write_int(fp, cm_store_entry_field_key_next_size,
 				entry->cm_key_next_type.cm_key_size);
@@ -2989,3 +3102,37 @@ cm_store_ca_dup(void *parent, struct cm_store_ca *ca)
 
 	return ret;
 }
+
+char *cm_store_algorithm_to_name(int algorithm)
+{
+	/* See cm_key_algorithm in store-int.h */
+	switch(algorithm) {
+	case cm_key_rsa:
+		return "RSA";
+		break;
+#ifdef CM_ENABLE_DSA
+	case cm_key_dsa:
+		return "DSA";
+		break;
+#endif
+#ifdef CM_ENABLE_EC
+	case cm_key_ecdsa:
+		return "EC";
+		break;
+#endif
+#ifdef CM_ENABLE_ML_DSA
+	case cm_key_ml_dsa_44:
+		return "ML-DSA-44";
+		break;
+	case cm_key_ml_dsa_65:
+		return "ML-DSA-65";
+		break;
+	case cm_key_ml_dsa_87:
+		return "ML-DSA-87";
+		break;
+#endif
+	default:
+		return "UNKNOWN";
+		break;
+	}
+};
diff --git a/src/store-int.h b/src/store-int.h
index 7557919..fca9877 100644
--- a/src/store-int.h
+++ b/src/store-int.h
@@ -40,6 +40,11 @@ struct cm_store_entry {
 #ifdef CM_ENABLE_EC
 			cm_key_ecdsa,
 #endif
+#ifdef CM_ENABLE_ML_DSA
+			cm_key_ml_dsa_44,
+			cm_key_ml_dsa_65,
+			cm_key_ml_dsa_87
+#endif
 		} cm_key_algorithm, cm_key_gen_algorithm;
 		int cm_key_size, cm_key_gen_size;
 	} cm_key_type, cm_key_next_type;
diff --git a/src/store.h b/src/store.h
index 2c2b326..6781411 100644
--- a/src/store.h
+++ b/src/store.h
@@ -70,5 +70,6 @@ void cm_store_set_if_not_set_as(void *parent, char ***dest, char **src);
 int cm_store_utf8_to_bmp_string(char *s,
 				unsigned char **bmp, unsigned int *len);
 char *cm_store_utf8_from_bmp_string(unsigned char *bmp, unsigned int len);
+char *cm_store_algorithm_to_name(int algorithm);
 
 #endif
diff --git a/src/submit-e.c b/src/submit-e.c
index 69b4f8e..8f3c087 100644
--- a/src/submit-e.c
+++ b/src/submit-e.c
@@ -884,6 +884,17 @@ cm_submit_e_helper_main(int fd, struct cm_store_ca *ca,
 		key_type = "EC";
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA:
+	case cm_key_ml_dsa_44:
+		key_type = "ML-DSA-44";
+		break;
+	case cm_key_ml_dsa_65:
+		key_type = "ML-DSA-65";
+		break;
+	case cm_key_ml_dsa_87:
+		key_type = "ML-DSA-87";
+		break;
+#endif
 	case cm_key_unspecified:
 		key_type = NULL;
 		break;
diff --git a/src/tdbush.c b/src/tdbush.c
index d440863..bb83a38 100644
--- a/src/tdbush.c
+++ b/src/tdbush.c
@@ -1317,6 +1317,20 @@ base_add_request(DBusConnection *conn, DBusMessage *msg,
 			new_entry->cm_key_type.cm_key_gen_algorithm =
 				cm_key_ecdsa;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+		} else
+		if (strcasecmp(param->value.s, "ML-DSA-44") == 0) {
+			new_entry->cm_key_type.cm_key_gen_algorithm =
+				cm_key_ml_dsa_44;
+		} else
+		if (strcasecmp(param->value.s, "ML-DSA-65") == 0) {
+			new_entry->cm_key_type.cm_key_gen_algorithm =
+				cm_key_ml_dsa_65;
+		} else
+		if (strcasecmp(param->value.s, "ML-DSA-87") == 0) {
+			new_entry->cm_key_type.cm_key_gen_algorithm =
+				cm_key_ml_dsa_87;
+#endif
 		} else {
 			cm_log(1, "No support for generating \"%s\" keys.\n",
 			       param->value.s);
@@ -1362,6 +1376,16 @@ base_add_request(DBusConnection *conn, DBusMessage *msg,
 		}
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA:
+	case cm_key_ml_dsa_44:
+	case cm_key_ml_dsa_65:
+	case cm_key_ml_dsa_87:
+		if (new_entry->cm_key_type.cm_key_gen_size < CM_MINIMUM_ML_DSA_KEY_SIZE)
+		{
+			new_entry->cm_key_type.cm_key_gen_size = CM_MINIMUM_ML_DSA_KEY_SIZE;
+		}
+		break;
+#endif
 	case cm_key_unspecified:
 	default:
 		break;
@@ -1838,6 +1862,11 @@ base_get_supported_key_types(DBusConnection *conn, DBusMessage *msg,
 #ifdef CM_ENABLE_EC
 		"EC",
 #endif
+#ifdef CM_ENABLE_ML_DSA
+		"ML-DSA-44",
+		"ML-DSA-65",
+		"ML-DSA-87",
+#endif
 		NULL
 	};
 	DBusMessage *rep;
@@ -2924,27 +2953,7 @@ request_get_key_type_and_size(DBusConnection *conn, DBusMessage *msg,
 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 	}
 	rep = dbus_message_new_method_return(msg);
-	switch (entry->cm_key_type.cm_key_algorithm) {
-	case cm_key_unspecified:
-		type = "UNKNOWN";
-		break;
-	case cm_key_rsa:
-		type = "RSA";
-		break;
-#ifdef CM_ENABLE_DSA
-	case cm_key_dsa:
-		type = "DSA";
-		break;
-#endif
-#ifdef CM_ENABLE_EC
-	case cm_key_ecdsa:
-		type = "EC";
-		break;
-#endif
-	default:
-		type = "UNKNOWN";
-		break;
-	}
+	type = cm_store_algorithm_to_name(entry->cm_key_type.cm_key_algorithm);
 	if (rep != NULL) {
 		size = entry->cm_key_type.cm_key_size;
 		cm_tdbusm_set_sn(rep, type, size);
@@ -3251,6 +3260,17 @@ request_modify(DBusConnection *conn, DBusMessage *msg,
 				    (strcasecmp(param->value.s, "EC") == 0)) {
 					entry->cm_key_type.cm_key_gen_algorithm = cm_key_ecdsa;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+				} else
+				if (strcasecmp(param->value.s, "ML-DSA-44") == 0) {
+					entry->cm_key_type.cm_key_gen_algorithm = cm_key_ml_dsa_44;
+				} else
+				if (strcasecmp(param->value.s, "ML-DSA-65") == 0) {
+					entry->cm_key_type.cm_key_gen_algorithm = cm_key_ml_dsa_65;
+				} else
+				if (strcasecmp(param->value.s, "ML-DSA-87") == 0) {
+					entry->cm_key_type.cm_key_gen_algorithm = cm_key_ml_dsa_87;
+#endif
 				} else {
 					cm_log(1, "No support for generating \"%s\" keys.\n",
 					       param->value.s);
@@ -4049,6 +4069,17 @@ request_prop_get_key_type(struct cm_context *ctx, void *parent,
 		return "EC";
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA:
+       case cm_key_ml_dsa_44:
+		return "ML-DSA-44";
+		break;
+       case cm_key_ml_dsa_65:
+		return "ML-DSA-65";
+		break;
+       case cm_key_ml_dsa_87:
+		return "ML-DSA-87";
+		break;
+#endif
 	}
 	return "";
 }
@@ -4071,6 +4102,12 @@ request_prop_get_key_size(struct cm_context *ctx, void *parent,
 #ifdef CM_ENABLE_EC
 	case cm_key_ecdsa:
 #endif
+#ifdef CM_ENABLE_ML_DSA:
+       case cm_key_ml_dsa_44:
+       case cm_key_ml_dsa_65:
+       case cm_key_ml_dsa_87:
+		/* fall through */
+#endif
 		return entry->cm_key_type.cm_key_size;
 		break;
 	}
diff --git a/tests/002-keygen-ml-dsa/expected.out b/tests/002-keygen-ml-dsa/expected.out
new file mode 100644
index 0000000..b303959
--- /dev/null
+++ b/tests/002-keygen-ml-dsa/expected.out
@@ -0,0 +1,14 @@
+[openssl:44]
+OK.
+OK (ML-DSA-44:10496).
+[openssl:65]
+OK.
+OK (ML-DSA-65:15616).
+[openssl:87]
+OK.
+OK (ML-DSA-87:20736).
+[openssl:rosubdir]
+Failed to save FILE:${tmpdir}/rosubdir/sample.87: need fs permissions.
+[openssl:rwsubdir]
+Failed to save FILE:${tmpdir}/rwsubdir/sample.87: need fs permissions.
+Test complete.
diff --git a/tests/002-keygen-ml-dsa/run.sh b/tests/002-keygen-ml-dsa/run.sh
new file mode 100755
index 0000000..55360f7
--- /dev/null
+++ b/tests/002-keygen-ml-dsa/run.sh
@@ -0,0 +1,43 @@
+#!/bin/bash -e
+
+cd "$tmpdir"
+
+source "$srcdir"/functions
+initnssdb "$tmpdir"
+
+for size in 44 65 87 ; do
+	echo "[openssl:$size]"
+	# Generate a key.
+	cat > entry.$size <<- EOF
+	key_storage_type=FILE
+	key_storage_location=$tmpdir/sample.$size
+	key_gen_size=$size
+	key_gen_type=ML-DSA-$size
+	EOF
+	$toolsdir/keygen entry.$size
+	# Read the size.
+	sed -i 's,^key_gen_size.*,,g' entry.$size
+	$toolsdir/keyiread entry.$size
+done
+
+echo "[openssl:rosubdir]"
+cat > entry.$size <<- EOF
+key_storage_type=FILE
+key_storage_location=$tmpdir/rosubdir/sample.$size
+key_gen_size=$size
+key_gen_type=ML-DSA-$size
+EOF
+$toolsdir/keygen entry.$size || true
+
+echo "[openssl:rwsubdir]"
+cat > entry.$size <<- EOF
+key_storage_type=FILE
+key_storage_location=$tmpdir/rwsubdir/sample.$size
+key_gen_size=$size
+key_gen_type=ML-DSA-$size
+EOF
+touch $tmpdir/rwsubdir/sample.$size
+chmod u-w $tmpdir/rwsubdir/sample.$size
+$toolsdir/keygen entry.$size || true
+
+echo Test complete.
diff --git a/tests/028-dbus/expected.out b/tests/028-dbus/expected.out
index 86cba02..28bed7c 100644
--- a/tests/028-dbus/expected.out
+++ b/tests/028-dbus/expected.out
@@ -185,7 +185,7 @@ dbus.Array([dbus.ObjectPath('/org/fedorahosted/certmonger/cas/CA1'), dbus.Object
 dbus.Array([dbus.ObjectPath('/org/fedorahosted/certmonger/requests/Request2')], signature=dbus.Signature('o'))
 
 [ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_supported_key_types ]
-dbus.Array([dbus.String('RSA'), dbus.String('DSA'), dbus.String('EC')], signature=dbus.Signature('s'))
+dbus.Array([dbus.String('RSA'), dbus.String('DSA'), dbus.String('EC'), dbus.String('ML-DSA-44'), dbus.String('ML-DSA-65'), dbus.String('ML-DSA-87')], signature=dbus.Signature('s'))
 
 [ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_supported_key_storage ]
 dbus.Array([dbus.String('NSSDB'), dbus.String('FILE')], signature=dbus.Signature('s'))
@@ -712,7 +712,7 @@ dbus.Array([], signature=dbus.Signature('s'))
 </node>
 
 [ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_config_file_path ]
-$tmpdir/cas/20180327134236
+$tmpdir/cas/20250807195047
 
 [ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_nickname ]
 SelfSign
@@ -827,7 +827,7 @@ dbus.Array([], signature=dbus.Signature('s'))
 </node>
 
 [ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_config_file_path ]
-$tmpdir/cas/20180327134236-1
+$tmpdir/cas/20250807195047-1
 
 [ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_nickname ]
 IPA
@@ -940,7 +940,7 @@ dbus.Array([], signature=dbus.Signature('s'))
 </node>
 
 [ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_config_file_path ]
-$tmpdir/cas/20180327134236-3
+$tmpdir/cas/20250807195047-2
 
 [ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_nickname ]
 dogtag-ipa-renew-agent
diff --git a/tests/tools/keyiread.c b/tests/tools/keyiread.c
index 5934124..f6140ca 100644
--- a/tests/tools/keyiread.c
+++ b/tests/tools/keyiread.c
@@ -64,6 +64,17 @@ type_name(enum cm_key_algorithm alg)
 		return "EC";
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA:
+	case cm_key_ml_dsa_44:
+		return "ML-DSA-44";
+		break;
+	case cm_key_ml_dsa_65:
+		return "ML-DSA-65";
+		break;
+	case cm_key_ml_dsa_87:
+		return "ML-DSA-87";
+		break;
+#endif
 	default:
 		assert(0);
 		break;
@@ -86,6 +97,12 @@ munge_key_size(struct cm_key_type *key)
 	case cm_key_ecdsa:
 		break;
 #endif
+#ifdef CM_ENABLE_ML_DSA:
+	case cm_key_ml_dsa_44:
+	case cm_key_ml_dsa_65:
+	case cm_key_ml_dsa_87:
+		break;
+#endif
 	default:
 		assert(0);
 		break;

From 0fc2d593b7c68f996989bd16daef159882a7041e Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 14 2026 18:32:07 +0000
Subject: [PATCH 3/6] Add initial ML-DSA support with NSS 3.112.0-4


NSS 3.112 has added support for Post-Quantum ML-DSA keys.
Allow them to be generated by certmonger.

The minimum key size is not fully implemented because key size
has a different meaning in this context. It is plumbed in, such that
one can set a numeric size, but it isn't used and is effectively
meaningless.

This includes what may be a temporary change to identify the key
type when reading a private key. NSS has an internal function
for this that may be exposed in the future. This works for now.

---

diff --git a/src/keygen-n.c b/src/keygen-n.c
index 3c3b6cd..45b1426 100644
--- a/src/keygen-n.c
+++ b/src/keygen-n.c
@@ -140,6 +140,9 @@ cm_keygen_n_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 	SECOidData *ecurve;
 	SECItem ec_params;
 #endif
+#ifdef CM_ENABLE_ML_DSA
+	CK_ULONG ml_params;
+#endif
 	SECKEYPrivateKey *privkey, *delkey, *ckey;
 	SECKEYPrivateKeyList *privkeys;
 	SECKEYPrivateKeyListNode *node;
@@ -273,8 +276,15 @@ cm_keygen_n_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 #endif
 #ifdef CM_ENABLE_ML_DSA:
 	case cm_key_ml_dsa_44:
+		cm_requested_key_size = ML_DSA_44_PRIVATEKEY_LEN;
+		mech = CKM_ML_DSA_KEY_PAIR_GEN;
+		pmech = CKM_ML_DSA_KEY_PAIR_GEN;
 	case cm_key_ml_dsa_65:
+		cm_requested_key_size = ML_DSA_65_PRIVATEKEY_LEN;
+		mech = CKM_ML_DSA_KEY_PAIR_GEN;
+		pmech = CKM_ML_DSA_KEY_PAIR_GEN;
 	case cm_key_ml_dsa_87:
+		cm_requested_key_size = ML_DSA_87_PRIVATEKEY_LEN;
 		mech = CKM_ML_DSA_KEY_PAIR_GEN;
 		pmech = CKM_ML_DSA_KEY_PAIR_GEN;
 		break;
@@ -330,6 +340,8 @@ next_slot:
 		cm_log(1, "Error locating token for key generation.\n");
 		_exit(CM_SUB_STATUS_ERROR_NO_TOKEN);
 	}
+	/* FIXME */
+	cm_log(1, "Selected token %s\n", PK11_GetTokenName(slot));
 	/* Be ready to count our uses of a PIN. */
 	memset(&cb_data, 0, sizeof(cb_data));
 	cb_data.entry = entry;
@@ -582,9 +594,19 @@ retry_gen:
 #endif
 #ifdef CM_ENABLE_ML_DSA:
 	case cm_key_ml_dsa_44:
+		memset(&ml_params, 0, sizeof(ml_params));
+		ml_params = CKP_ML_DSA_44;
+		params = &ml_params;
+		break;
 	case cm_key_ml_dsa_65:
+		memset(&ml_params, 0, sizeof(ml_params));
+		ml_params = CKP_ML_DSA_65;
+		params = &ml_params;
+		break;
 	case cm_key_ml_dsa_87:
-		/* FIXME */
+		memset(&ml_params, 0, sizeof(ml_params));
+		ml_params = CKP_ML_DSA_87;
+		params = &ml_params;
 		break;
 #endif
 	default:
@@ -592,7 +614,7 @@ retry_gen:
 		break;
 	}
 	/* Generate the key pair. */
-	cm_log(1, "Generating key pair.\n");
+	cm_log(1, "Generating key pair of size %d.\n", cm_key_size);
 	pubkey = NULL;
 	privkey = PK11_GenerateKeyPair(slot, mech, params, &pubkey,
 				       PR_TRUE, PR_TRUE, NULL);
diff --git a/src/keyiread-n.c b/src/keyiread-n.c
index ed87b09..7f9cdf0 100644
--- a/src/keyiread-n.c
+++ b/src/keyiread-n.c
@@ -52,6 +52,60 @@
 #define PRIVKEY_LIST_EMPTY(l) PRIVKEY_LIST_END(PRIVKEY_LIST_HEAD(l), l)
 #endif
 
+
+/* FIXME: replace when NSS exposes the capability to get the alg */
+/* look up the an oid by string.
+ *
+ * The following code was ripped from NSS.
+ * */
+#include <cryptohi.h>
+
+/* If SECKEY_GetParameterSet() is exported then this can be replaced */
+const char *
+cm_GetSignatureAlgorithmFromPrivateKey(SECKEYPrivateKey *privKey)
+{
+    SECItem item;
+    CK_ULONG paramSet;
+    SECStatus rv;
+
+    /* don't modify hashAlg if we aren't a DSA key */
+    if (privKey->keyType != mldsaKey) {
+	/* should not get here */
+        return "Invalid ML-DSA key";
+    }
+
+    rv = PK11_ReadRawAttribute(PK11_TypePrivKey, privKey,
+                                CKA_PARAMETER_SET, &item);
+
+    if (rv != SECSuccess) {
+        return "UNKNOWN";
+    }
+    if (item.len != sizeof (paramSet)) {
+        PORT_Free(item.data);
+        PORT_SetError(SEC_ERROR_INVALID_KEY);
+        return "UNKNOWN";
+    }
+    paramSet = *(CK_ULONG *)item.data;
+    PORT_Free(item.data);
+    switch  (paramSet) {
+        case CKP_ML_DSA_44:
+            return "ML-DSA-44";
+            break;
+        case CKP_ML_DSA_65:
+            return "ML-DSA-65";
+            break;
+        case CKP_ML_DSA_87:
+            return "ML-DSA-87";
+            break;
+        default:
+            return "UNKNOWN";
+            break;
+    }
+    return "UNKNOWN";
+}
+
+/* end FIXME */
+
 struct cm_keyiread_state {
 	struct cm_keyiread_state_pvt pvt;
 	struct cm_store_entry *entry;
@@ -522,7 +576,10 @@ cm_keyiread_n_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 			cm_log(3, "Key is an EC key.\n");
 			alg = "EC";
 			break;
-		/* FIXME: case mlKey? */
+		case mldsaKey:
+			cm_log(3, "Key is an ML-DSA key.\n");
+			alg = cm_GetSignatureAlgorithmFromPrivateKey(keys->privkey);
+			break;
 		case nullKey:
 		default:
 			cm_log(3, "Key is of an unknown type.\n");
diff --git a/tests/002-keygen-ml-dsa/expected.out b/tests/002-keygen-ml-dsa/expected.out
index b303959..88da9f1 100644
--- a/tests/002-keygen-ml-dsa/expected.out
+++ b/tests/002-keygen-ml-dsa/expected.out
@@ -1,3 +1,16 @@
+[nss:44]
+OK.
+OK (ML-DSA-44:10496).
+[nss:65]
+OK.
+OK (ML-DSA-65:15616).
+[nss:87]
+OK.
+OK (ML-DSA-87:20736).
+[nss:rosubdir]
+Failed to save NSS:${tmpdir}/rosubdir: need fs permissions.
+[nss:rwsubdir]
+Failed to save NSS:${tmpdir}/rwsubdir: need fs permissions.
 [openssl:44]
 OK.
 OK (ML-DSA-44:10496).
diff --git a/tests/002-keygen-ml-dsa/prequal.sh b/tests/002-keygen-ml-dsa/prequal.sh
new file mode 100644
index 0000000..d146a65
--- /dev/null
+++ b/tests/002-keygen-ml-dsa/prequal.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+if test `id -u` -eq 0 ; then
+	echo "This test won't work right if run as root."
+	exit 1
+fi
diff --git a/tests/002-keygen-ml-dsa/run.sh b/tests/002-keygen-ml-dsa/run.sh
index 55360f7..6adc541 100755
--- a/tests/002-keygen-ml-dsa/run.sh
+++ b/tests/002-keygen-ml-dsa/run.sh
@@ -6,6 +6,42 @@ source "$srcdir"/functions
 initnssdb "$tmpdir"
 
 for size in 44 65 87 ; do
+	echo "[nss:$size]"
+	# Generate a key.
+	cat > entry.$size <<- EOF
+	key_storage_type=NSSDB
+	key_storage_location=$tmpdir
+	key_nickname=keyi$size
+	key_gen_size=$size
+	key_gen_type=ML-DSA-$size
+	EOF
+	$toolsdir/keygen entry.$size
+	# Read the type and size.
+	sed -i 's,^key_gen_size.*,,g' entry.$size
+	$toolsdir/keyiread entry.$size
+done
+
+echo "[nss:rosubdir]"
+cat > entry.$size <<- EOF
+key_storage_type=NSSDB
+key_storage_location=$tmpdir/rosubdir
+key_nickname=keyi$size
+key_gen_size=$size
+key_gen_type=ML-DSA-$size
+EOF
+$toolsdir/keygen entry.$size || true
+
+echo "[nss:rwsubdir]"
+cat > entry.$size <<- EOF
+key_storage_type=NSSDB
+key_storage_location=$tmpdir/rwsubdir
+key_nickname=keyi$size
+key_gen_size=$size
+key_gen_type=ML-DSA-$size
+EOF
+$toolsdir/keygen entry.$size || true
+
+for size in 44 65 87 ; do
 	echo "[openssl:$size]"
 	# Generate a key.
 	cat > entry.$size <<- EOF

From c06ebb0f791f9acacb2ebeb2ee478ab4f57fb243 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 14 2026 18:32:07 +0000
Subject: [PATCH 4/6] Test for PQ support in NSS, print summary at end of configure


Add test to see if PQ is supported by NSS. If both it and
OpenSSL have support then enable it in certmonger.

Print a summary of directories and enabled ciphers at the end
of configure.

---

diff --git a/configure.ac b/configure.ac
index 78e7d54..47e31c0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -722,14 +722,17 @@ if ! ${configure_dist_target_only:-false} ; then
 		CFLAGS="$OPENSSL_CFLAGS $CFLAGS"
 		LIBS="$OPENSSL_LIBS $LIBS"
 		AC_CHECK_LIB(crypto,EVP_PKEY_Q_keygen)
-		# CFLAGS="$NSS_CFLAGS $CFLAGS"
-		# LIBS="$NSS_LIBS $LIBS"
-		# AC_CHECK_FUNCS(SECKEY_CreateECPrivateKey)
-		# AC_CHECK_FUNCS(PK11_PrivDecrypt)
-		# AC_CHECK_FUNCS(NSS_OptionGet NSS_OptionSet)
+		CFLAGS="$NSS_CFLAGS $CFLAGS"
+		LIBS="$NSS_LIBS $LIBS"
+		AC_CHECK_TYPES([CKM_ML_DSA_KEY_PAIR_GEN],,,[[#include <pkcs11t.h>]])
+		AC_CHECK_FUNCS(PK11_GenerateKeyPair)
+		AC_CHECK_FUNCS(NSS_OptionGet NSS_OptionSet)
 		CFLAGS="$CFLAGSsave"
 		LIBS="$LIBSsave"
 		can_ml_dsa=true
+		if test x$HAVE_CKM_ML_DSA_KEY_PAIR_GEN = xno ; then
+			can_ml_dsa=false
+		fi
 		if test x$ac_cv_lib_crypto_EVP_PKEY_Q_keygen = xno ; then
 			can_ml_dsa=false
 		fi
@@ -942,4 +945,47 @@ else
 fi
 
 AC_CONFIG_COMMANDS(src_introspect_sh,[chmod +x src/introspect.sh])
-AC_OUTPUT(Makefile src/Makefile dbus/Makefile systemd/Makefile sysvinit/Makefile sysvinit/certmonger tests/Makefile tests/tools/Makefile dbus/org.fedorahosted.certmonger.conf dbus/org.fedorahosted.certmonger.service src/introspect.sh src/certmonger.8 src/getcert.1 src/getcert-request.1 src/getcert-list.1 src/getcert-list-cas.1 src/getcert-start-tracking.1 src/getcert-stop-tracking.1 src/selfsign-getcert.1 src/ipa-getcert.1 src/getcert-resubmit.1 src/certmonger-certmaster-submit.8 src/certmonger-ipa-submit.8 src/certmonger-dogtag-ipa-renew-agent-submit.8 src/certmaster-getcert.1 src/certmonger.conf.5 po/Makefile.in src/certmonger.conf systemd/certmonger.service systemd/certmonger.conf systemd/org.fedorahosted.certmonger.service src/getcert-refresh.1 src/getcert-refresh-ca.1 src/local-getcert.1 src/certmonger-local-submit.8 src/getcert-status.1 src/certmonger-dogtag-submit.8 src/certmonger-scep-submit.8 src/getcert-add-ca.1 src/getcert-add-scep-ca.1 src/getcert-modify-ca.1 src/getcert-remove-ca.1 src/getcert-rekey.1)
+AC_CONFIG_FILES([Makefile src/Makefile dbus/Makefile systemd/Makefile sysvinit/Makefile sysvinit/certmonger tests/Makefile tests/tools/Makefile dbus/org.fedorahosted.certmonger.conf dbus/org.fedorahosted.certmonger.service src/introspect.sh src/certmonger.8 src/getcert.1 src/getcert-request.1 src/getcert-list.1 src/getcert-list-cas.1 src/getcert-start-tracking.1 src/getcert-stop-tracking.1 src/selfsign-getcert.1 src/ipa-getcert.1 src/getcert-resubmit.1 src/certmonger-certmaster-submit.8 src/certmonger-ipa-submit.8 src/certmonger-dogtag-ipa-renew-agent-submit.8 src/certmaster-getcert.1 src/certmonger.conf.5 po/Makefile.in src/certmonger.conf systemd/certmonger.service systemd/certmonger.conf systemd/org.fedorahosted.certmonger.service src/getcert-refresh.1 src/getcert-refresh-ca.1 src/local-getcert.1 src/certmonger-local-submit.8 src/getcert-status.1 src/certmonger-dogtag-submit.8 src/certmonger-scep-submit.8 src/getcert-add-ca.1 src/getcert-add-scep-ca.1 src/getcert-modify-ca.1 src/getcert-remove-ca.1 src/getcert-rekey.1])
+
+AC_OUTPUT
+
+echo "
+        prefix:                   ${prefix}
+        exec_prefix:              ${exec_prefix}
+        libdir:                   ${libdir}
+        bindir:                   ${bindir}
+        sbindir:                  ${sbindir}
+        sysconfdir:               ${sysconfdir}
+        localstatedir:            ${localstatedir}
+        datadir:                  ${datadir}
+        source code location:     ${srcdir}
+        compiler:                 ${CC}
+        cflags:                   ${CFLAGS}
+        LDAP libs:                ${LDAP_LIBS}
+        OpenSSL crypto libs:      ${OPENSSL_LIBS}
+        NSS libs:                 ${NSS_LIBS}
+        JANSSON libs:             ${JANSSON_LIBS}"
+
+AM_COND_IF([HAVE_DSA], [
+    echo "\
+        DSA support enabled:      yes"
+], [
+    echo "\
+        DSA support enabled:      no"
+])
+
+AM_COND_IF([HAVE_EC], [
+    echo "\
+        EC support enabled:       yes"
+], [
+    echo "\
+        EC support enabled:       no"
+])
+
+AM_COND_IF([HAVE_ML_DSA], [
+    echo "\
+        ML-DSA support enabled:   yes"
+], [
+    echo "\
+        ML-DSA support enabled:   no"
+])

From 0d988f887da7c152ea9e418ce96896e548276093 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 14 2026 18:32:07 +0000
Subject: [PATCH 5/6] Implement more PQ testing for both NSS and OpenSSL


This adds keyiread and csrgen tests for both NSS and
OpenSSL. keyiread, keygen and csrgen will now run by
default when PQ is enabled.

It also fixes the 028-dbus test if PQ is not enabled either
by configuration (--disable-ml-dsa) or because it isn't
available.

---

diff --git a/src/csrgen-o.c b/src/csrgen-o.c
index f1091c5..e1f8c4a 100644
--- a/src/csrgen-o.c
+++ b/src/csrgen-o.c
@@ -191,6 +191,7 @@ cm_csrgen_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 	unsigned int bmpcount, mdlen;
 	long error;
 	int i;
+	const EVP_MD *sign_md = cm_prefs_ossl_sig_alg(entry->cm_key_type.cm_key_algorithm);
 
 	status = fdopen(fd, "w");
 	if (status == NULL) {
@@ -344,8 +345,15 @@ cm_csrgen_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 							  upassword,
 							  strlen(password));
 			}
-			X509_REQ_sign(req, pkey, cm_prefs_ossl_hash());
-			PEM_write_X509_REQ(status, req);
+
+			if (X509_REQ_sign(req, pkey, sign_md) == 0) {
+				cm_log_errors(1, "Error signing the CSR.\n");
+				_exit(CM_SUB_STATUS_ERROR_AUTH); /* XXX */
+			}
+			if (PEM_write_X509_REQ(status, req) != 1) {
+				cm_log_errors(1, "Error while writing the CSR PEM.\n");
+				_exit(CM_SUB_STATUS_ERROR_AUTH); /* XXX */
+			}
 			/* Generate the SPKAC. */
 			memset(&spkac, 0, sizeof(spkac));
 			spkac.challenge = util_ASN1_IA5STRING_new();
@@ -362,7 +370,9 @@ cm_csrgen_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 			util_NETSCAPE_SPKI_set_sig_alg(&spki, sig_alg);
 			spki.signature = util_ASN1_BIT_STRING_new();
 			NETSCAPE_SPKI_set_pubkey(&spki, pkey);
-			NETSCAPE_SPKI_sign(&spki, pkey, cm_prefs_ossl_hash());
+			if (NETSCAPE_SPKI_sign(&spki, pkey, sign_md) == 0) {
+				cm_log_errors(1, "NETSCAPE_SPKI_sign failed.\n");
+			}
 			s = NETSCAPE_SPKI_b64_encode(&spki);
 			if (s != NULL) {
 				fprintf(status, "%s", s);
@@ -429,7 +439,9 @@ cm_csrgen_o_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 			}
 			X509_set_serialNumber(minicert, serial);
 			X509_set_pubkey(minicert, pkey);
-			X509_sign(minicert, pkey, cm_prefs_ossl_hash());
+			if (X509_sign(minicert, pkey, sign_md) == 0) {
+				cm_log_errors(1, "Signing minicert failed.\n");
+			}
 			len = i2d_X509(minicert, NULL);
 			mcb64 = NULL;
 			if (len > 0) {
diff --git a/src/prefs-n.c b/src/prefs-n.c
index 584bdd7..d080f0a 100644
--- a/src/prefs-n.c
+++ b/src/prefs-n.c
@@ -19,7 +19,9 @@
 
 #include <nss.h>
 #include <nss.h>
+#include <keyhi.h>
 #include <keythi.h>
+#include <pk11pub.h>
 #include <secoidt.h>
 
 #include "prefs.h"
@@ -114,6 +116,45 @@ cm_prefs_nss_sig_alg(SECKEYPrivateKey *pkey)
 			return SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE;
 			break;
 		}
+#ifdef CM_ENABLE_ML_DSA
+	case mldsaKey:
+		/* This is a simplified version of
+		 * cm_GetSignatureAlgorithmFromPrivateKey from keyiread-n.c.
+		 * Once the NSS function is exported that can be moved to
+		 * something like util-n.c and be generalized. For now I
+		 * duplicate.
+		 */
+		SECItem item;
+		CK_ULONG paramSet;
+		SECStatus rv;
+
+		rv = PK11_ReadRawAttribute(PK11_TypePrivKey, pkey, CKA_PARAMETER_SET, &item);
+		if (rv != SECSuccess) {
+			return SEC_OID_UNKNOWN;
+		}
+
+		if (item.len != sizeof(paramSet)) {
+			PORT_Free(item.data);	
+			PORT_SetError(SEC_ERROR_INVALID_KEY);
+			return SEC_OID_UNKNOWN;
+		}
+		paramSet = *(CK_ULONG *)item.data;
+		PORT_Free(item.data);
+		switch (paramSet) {
+		case CKP_ML_DSA_44:
+			return SEC_OID_ML_DSA_44_SIGNATURE;
+			break;
+		case CKP_ML_DSA_65:
+			return SEC_OID_ML_DSA_65_SIGNATURE;
+			break;
+		case CKP_ML_DSA_87:
+			return SEC_OID_ML_DSA_87_SIGNATURE;
+			break;
+		default:
+			return SEC_OID_UNKNOWN;
+			break;
+		}
+#endif
 	default:
 		return SEC_OID_UNKNOWN;
 		break;
diff --git a/src/prefs-o.c b/src/prefs-o.c
index 4b6ef6f..39cabb7 100644
--- a/src/prefs-o.c
+++ b/src/prefs-o.c
@@ -32,6 +32,39 @@
 #include "prefs.h"
 #include "prefs-o.h"
 #include "util-o.h"
+#include "log.h"
+#include "store-int.h"
+
+const EVP_MD *
+cm_prefs_ossl_sig_alg(enum cm_key_algorithm cm_key_algorithm)
+{
+	switch(cm_key_algorithm) {
+	case cm_key_rsa:
+#ifdef CM_ENABLE_DSA
+	case cm_key_dsa:
+#endif
+#ifdef CM_ENABLE_EC
+	case cm_key_ecdsa:
+#endif
+		return cm_prefs_ossl_hash();
+		break;
+#ifdef CM_ENABLE_ML_DSA
+	case cm_key_ml_dsa_44:
+		return EVP_get_digestbyname("ML-DSA-44");
+		break;
+	case cm_key_ml_dsa_65:
+		return EVP_get_digestbyname("ML-DSA-65");
+		break;
+	case cm_key_ml_dsa_87:
+		return EVP_get_digestbyname("ML-DSA-87");
+		break;
+#endif
+	default:
+		cm_log(1, "Unknown or unsupported key type.\n");
+		return NULL;
+		break;
+	}
+}
 
 const EVP_MD *
 cm_prefs_ossl_hash_by_pref(enum cm_prefs_digest digest)
diff --git a/src/prefs-o.h b/src/prefs-o.h
index 1dcd4e8..9c233ab 100644
--- a/src/prefs-o.h
+++ b/src/prefs-o.h
@@ -18,6 +18,7 @@
 #ifndef cmprefso_h
 #define cmprefso_h
 
+const EVP_MD * cm_prefs_ossl_sig_alg(enum cm_key_algorithm cm_key_algorithm);
 const EVP_CIPHER *cm_prefs_ossl_cipher(void);
 const EVP_CIPHER *cm_prefs_ossl_cipher_by_pref(enum cm_prefs_cipher cipher);
 const EVP_MD *cm_prefs_ossl_hash(void);
diff --git a/tests/001-keyiread-mldsa/expected.out b/tests/001-keyiread-mldsa/expected.out
new file mode 100644
index 0000000..82ab39a
--- /dev/null
+++ b/tests/001-keyiread-mldsa/expected.out
@@ -0,0 +1,7 @@
+OK (ML-DSA-44:10496).
+OK (ML-DSA-44:10496).
+OK (ML-DSA-65:15616).
+OK (ML-DSA-65:15616).
+OK (ML-DSA-87:20736).
+OK (ML-DSA-87:20736).
+Test complete.
diff --git a/tests/001-keyiread-mldsa/run.sh b/tests/001-keyiread-mldsa/run.sh
new file mode 100755
index 0000000..9c01c2f
--- /dev/null
+++ b/tests/001-keyiread-mldsa/run.sh
@@ -0,0 +1,36 @@
+#!/bin/bash -e
+
+cd "$tmpdir"
+
+source "$srcdir"/functions
+initnssdb "$tmpdir"
+
+for size in ML-DSA-44 ML-DSA-65 ML-DSA-87 ; do
+	# Generate a self-signed cert.
+	run_certutil -d "$tmpdir" -S -n keyi$size \
+		-s "cn=T$size" -c "cn=T$size" \
+		-x -t u -k mldsa -q $size
+	# Check the size of the key.
+	cat > entry.nss.$size <<- EOF
+	key_storage_type=NSSDB
+	key_storage_location=$tmpdir
+	key_nickname=keyi$size
+	EOF
+	$toolsdir/keyiread entry.nss.$size
+	# Export the key.
+	if ! pk12util -C AES-128-CBC -c AES-128-CBC -d "$tmpdir" -o $size.p12 -W "" -n "keyi$size" > /dev/null 2>&1 ; then
+		echo Error exporting key for $size, continuing.
+		continue
+	fi
+	if ! openssl pkcs12 -in $size.p12 -out key.$size -passin pass: -nodes -nocerts > /dev/null 2>&1 ; then
+		echo Error parsing exported key for $size, continuing.
+		continue
+	fi
+	cat > entry.openssl.$size <<- EOF
+	key_storage_type=FILE
+	key_storage_location=$tmpdir/key.$size
+	key_nickname=keyi$size
+	EOF
+	$toolsdir/keyiread entry.openssl.$size
+done
+echo Test complete.
diff --git a/tests/002-keygen-ml-dsa/expected.out b/tests/002-keygen-ml-dsa/expected.out
deleted file mode 100644
index 88da9f1..0000000
--- a/tests/002-keygen-ml-dsa/expected.out
+++ /dev/null
@@ -1,27 +0,0 @@
-[nss:44]
-OK.
-OK (ML-DSA-44:10496).
-[nss:65]
-OK.
-OK (ML-DSA-65:15616).
-[nss:87]
-OK.
-OK (ML-DSA-87:20736).
-[nss:rosubdir]
-Failed to save NSS:${tmpdir}/rosubdir: need fs permissions.
-[nss:rwsubdir]
-Failed to save NSS:${tmpdir}/rwsubdir: need fs permissions.
-[openssl:44]
-OK.
-OK (ML-DSA-44:10496).
-[openssl:65]
-OK.
-OK (ML-DSA-65:15616).
-[openssl:87]
-OK.
-OK (ML-DSA-87:20736).
-[openssl:rosubdir]
-Failed to save FILE:${tmpdir}/rosubdir/sample.87: need fs permissions.
-[openssl:rwsubdir]
-Failed to save FILE:${tmpdir}/rwsubdir/sample.87: need fs permissions.
-Test complete.
diff --git a/tests/002-keygen-ml-dsa/prequal.sh b/tests/002-keygen-ml-dsa/prequal.sh
deleted file mode 100644
index d146a65..0000000
--- a/tests/002-keygen-ml-dsa/prequal.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/sh
-if test `id -u` -eq 0 ; then
-	echo "This test won't work right if run as root."
-	exit 1
-fi
diff --git a/tests/002-keygen-ml-dsa/run.sh b/tests/002-keygen-ml-dsa/run.sh
deleted file mode 100755
index 6adc541..0000000
--- a/tests/002-keygen-ml-dsa/run.sh
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/bin/bash -e
-
-cd "$tmpdir"
-
-source "$srcdir"/functions
-initnssdb "$tmpdir"
-
-for size in 44 65 87 ; do
-	echo "[nss:$size]"
-	# Generate a key.
-	cat > entry.$size <<- EOF
-	key_storage_type=NSSDB
-	key_storage_location=$tmpdir
-	key_nickname=keyi$size
-	key_gen_size=$size
-	key_gen_type=ML-DSA-$size
-	EOF
-	$toolsdir/keygen entry.$size
-	# Read the type and size.
-	sed -i 's,^key_gen_size.*,,g' entry.$size
-	$toolsdir/keyiread entry.$size
-done
-
-echo "[nss:rosubdir]"
-cat > entry.$size <<- EOF
-key_storage_type=NSSDB
-key_storage_location=$tmpdir/rosubdir
-key_nickname=keyi$size
-key_gen_size=$size
-key_gen_type=ML-DSA-$size
-EOF
-$toolsdir/keygen entry.$size || true
-
-echo "[nss:rwsubdir]"
-cat > entry.$size <<- EOF
-key_storage_type=NSSDB
-key_storage_location=$tmpdir/rwsubdir
-key_nickname=keyi$size
-key_gen_size=$size
-key_gen_type=ML-DSA-$size
-EOF
-$toolsdir/keygen entry.$size || true
-
-for size in 44 65 87 ; do
-	echo "[openssl:$size]"
-	# Generate a key.
-	cat > entry.$size <<- EOF
-	key_storage_type=FILE
-	key_storage_location=$tmpdir/sample.$size
-	key_gen_size=$size
-	key_gen_type=ML-DSA-$size
-	EOF
-	$toolsdir/keygen entry.$size
-	# Read the size.
-	sed -i 's,^key_gen_size.*,,g' entry.$size
-	$toolsdir/keyiread entry.$size
-done
-
-echo "[openssl:rosubdir]"
-cat > entry.$size <<- EOF
-key_storage_type=FILE
-key_storage_location=$tmpdir/rosubdir/sample.$size
-key_gen_size=$size
-key_gen_type=ML-DSA-$size
-EOF
-$toolsdir/keygen entry.$size || true
-
-echo "[openssl:rwsubdir]"
-cat > entry.$size <<- EOF
-key_storage_type=FILE
-key_storage_location=$tmpdir/rwsubdir/sample.$size
-key_gen_size=$size
-key_gen_type=ML-DSA-$size
-EOF
-touch $tmpdir/rwsubdir/sample.$size
-chmod u-w $tmpdir/rwsubdir/sample.$size
-$toolsdir/keygen entry.$size || true
-
-echo Test complete.
diff --git a/tests/002-keygen-mldsa/expected.out b/tests/002-keygen-mldsa/expected.out
new file mode 100644
index 0000000..88da9f1
--- /dev/null
+++ b/tests/002-keygen-mldsa/expected.out
@@ -0,0 +1,27 @@
+[nss:44]
+OK.
+OK (ML-DSA-44:10496).
+[nss:65]
+OK.
+OK (ML-DSA-65:15616).
+[nss:87]
+OK.
+OK (ML-DSA-87:20736).
+[nss:rosubdir]
+Failed to save NSS:${tmpdir}/rosubdir: need fs permissions.
+[nss:rwsubdir]
+Failed to save NSS:${tmpdir}/rwsubdir: need fs permissions.
+[openssl:44]
+OK.
+OK (ML-DSA-44:10496).
+[openssl:65]
+OK.
+OK (ML-DSA-65:15616).
+[openssl:87]
+OK.
+OK (ML-DSA-87:20736).
+[openssl:rosubdir]
+Failed to save FILE:${tmpdir}/rosubdir/sample.87: need fs permissions.
+[openssl:rwsubdir]
+Failed to save FILE:${tmpdir}/rwsubdir/sample.87: need fs permissions.
+Test complete.
diff --git a/tests/002-keygen-mldsa/prequal.sh b/tests/002-keygen-mldsa/prequal.sh
new file mode 100644
index 0000000..d146a65
--- /dev/null
+++ b/tests/002-keygen-mldsa/prequal.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+if test `id -u` -eq 0 ; then
+	echo "This test won't work right if run as root."
+	exit 1
+fi
diff --git a/tests/002-keygen-mldsa/run.sh b/tests/002-keygen-mldsa/run.sh
new file mode 100755
index 0000000..6631717
--- /dev/null
+++ b/tests/002-keygen-mldsa/run.sh
@@ -0,0 +1,84 @@
+#!/bin/bash -e
+
+cd "$tmpdir"
+
+source "$srcdir"/functions
+initnssdb "$tmpdir"
+
+cd "$tmpdir"
+
+source "$srcdir"/functions
+initnssdb "$tmpdir"
+
+for size in 44 65 87 ; do
+	echo "[nss:$size]"
+	# Generate a key.
+	cat > entry.$size <<- EOF
+	key_storage_type=NSSDB
+	key_storage_location=$tmpdir
+	key_nickname=keyi$size
+	key_gen_size=$size
+	key_gen_type=ML-DSA-$size
+	EOF
+	$toolsdir/keygen entry.$size
+	# Read the type and size.
+	sed -i 's,^key_gen_size.*,,g' entry.$size
+	$toolsdir/keyiread entry.$size
+done
+
+echo "[nss:rosubdir]"
+cat > entry.$size <<- EOF
+key_storage_type=NSSDB
+key_storage_location=$tmpdir/rosubdir
+key_nickname=keyi$size
+key_gen_size=$size
+key_gen_type=ML-DSA-$size
+EOF
+$toolsdir/keygen entry.$size || true
+
+echo "[nss:rwsubdir]"
+cat > entry.$size <<- EOF
+key_storage_type=NSSDB
+key_storage_location=$tmpdir/rwsubdir
+key_nickname=keyi$size
+key_gen_size=$size
+key_gen_type=ML-DSA-$size
+EOF
+$toolsdir/keygen entry.$size || true
+
+for size in 44 65 87 ; do
+	echo "[openssl:$size]"
+	# Generate a key.
+	cat > entry.$size <<- EOF
+	key_storage_type=FILE
+	key_storage_location=$tmpdir/sample.$size
+	key_gen_size=$size
+	key_gen_type=ML-DSA-$size
+	EOF
+	$toolsdir/keygen entry.$size
+	# Read the size.
+	sed -i 's,^key_gen_size.*,,g' entry.$size
+	$toolsdir/keyiread entry.$size
+done
+
+echo "[openssl:rosubdir]"
+cat > entry.$size <<- EOF
+key_storage_type=FILE
+key_storage_location=$tmpdir/rosubdir/sample.$size
+key_gen_size=$size
+key_gen_type=ML-DSA-$size
+EOF
+$toolsdir/keygen entry.$size || true
+
+echo "[openssl:rwsubdir]"
+cat > entry.$size <<- EOF
+key_storage_type=FILE
+key_storage_location=$tmpdir/rwsubdir/sample.$size
+key_gen_size=$size
+key_gen_type=ML-DSA-$size
+EOF
+touch $tmpdir/rwsubdir/sample.$size
+chmod u-w $tmpdir/rwsubdir/sample.$size
+$toolsdir/keygen entry.$size || true
+
+echo Test complete.
diff --git a/tests/003-csrgen-mldsa/expected.out b/tests/003-csrgen-mldsa/expected.out
new file mode 100644
index 0000000..ed22729
--- /dev/null
+++ b/tests/003-csrgen-mldsa/expected.out
@@ -0,0 +1,7 @@
+verify OK
+verify OK
+Signature OK
+Signature OK
+minicert.openssl.ML-DSA-65.pem: OK
+minicert.nss.ML-DSA-65.pem: OK
+Test complete.
diff --git a/tests/003-csrgen-mldsa/run.sh b/tests/003-csrgen-mldsa/run.sh
new file mode 100755
index 0000000..0dc0720
--- /dev/null
+++ b/tests/003-csrgen-mldsa/run.sh
@@ -0,0 +1,52 @@
+#!/bin/bash -e
+
+cd "$tmpdir"
+
+source "$srcdir"/functions
+initnssdb "$tmpdir"
+
+size=ML-DSA-65
+
+# Build a self-signed certificate.
+run_certutil -d "$tmpdir" -S -n keyi$size \
+	-s "cn=T$size" -c "cn=T$size" \
+	-x -t u -k mldsa -q $size
+# Export the key.
+pk12util -C AES-128-CBC -c AES-128-CBC -d "$tmpdir" -o $size.p12 -W "" -n "keyi$size" > /dev/null 2>&1
+openssl pkcs12 -in $size.p12 -out key.$size -passin pass: -nodes -nocerts > /dev/null 2>&1 | ( grep -v '^MAC verified OK$' || : )
+# Read the public key and cache it.
+cat > entry.openssl.$size <<- EOF
+key_storage_type=FILE
+key_storage_location=$tmpdir/key.$size
+key_nickname=keyi$size
+id=keyi$size
+EOF
+$toolsdir/keyiread entry.openssl.$size > /dev/null 2>&1
+# Add the cached value to the prepping for the NSS copy.
+cat > entry.nss.$size <<- EOF
+key_storage_type=NSSDB
+key_storage_location=$tmpdir
+key_nickname=keyi$size
+id=keyi$size
+EOF
+# Generate a new CSR for that certificate's key.
+$toolsdir/csrgen entry.nss.$size > csr.nss.$size
+grep ^spkac= entry.nss.$size | sed s,spkac,SPKAC, > spkac.nss.$size
+# Generate a new CSR using the extracted key.
+$toolsdir/csrgen entry.openssl.$size > csr.openssl.$size
+grep ^spkac= entry.openssl.$size | sed s,spkac,SPKAC, > spkac.openssl.$size
+# Pull out the mini-cert.
+grep ^minicert= entry.openssl.$size | sed s,^minicert=,, | base64 -d > minicert.openssl.$size
+openssl x509 -out minicert.openssl.$size.pem -in minicert.openssl.$size -inform der
+grep ^minicert= entry.nss.$size | sed s,^minicert=,, | base64 -d > minicert.nss.$size
+openssl x509 -out minicert.nss.$size.pem -in minicert.nss.$size -inform der
+# The RSA tests already verify the contents of the requests, so we really only
+# need to care about the signatures passing verification.
+openssl req   -verify -noout -in csr.nss.$size 2>&1 | sed 's/Certificate request self-signature //'
+openssl req   -verify -noout -in csr.openssl.$size 2>&1 | sed 's/Certificate request self-signature //'
+openssl spkac -verify -noout < spkac.nss.$size 2>&1
+openssl spkac -verify -noout < spkac.openssl.$size 2>&1
+openssl verify -CAfile minicert.openssl.$size.pem minicert.openssl.$size.pem 2>&1
+openssl verify -CAfile minicert.nss.$size.pem minicert.nss.$size.pem 2>&1
+
+echo Test complete.
diff --git a/tests/028-dbus/expected.out.nodsa.withmldsa b/tests/028-dbus/expected.out.nodsa.withmldsa
new file mode 100644
index 0000000..99d9356
--- /dev/null
+++ b/tests/028-dbus/expected.out.nodsa.withmldsa
@@ -0,0 +1,965 @@
+Certificate in file "${tmpdir}/test.crt" issued by CA and saved.
+Certificate in file "${tmpdir}/test.crt" issued by CA and saved.
+[[ getcert ]]
+State MONITORING, stuck: no.
+Number of certificates and requests being tracked: 1.
+Request ID 'Buddy':
+	status: MONITORING
+	stuck: no
+	key pair storage: type=FILE,location='$tmpdir/test.key'
+	certificate: type=FILE,location='$tmpdir/test.crt'
+	CA: local
+	issuer: CN=$UUID,CN=Local Signing Authority
+	subject: CN=localhost
+	issued: sometime
+	expires: sometime
+	dns: localhost
+	principal name: host/localhost@LOCALHOST
+	key usage: digitalSignature,dataEncipherment
+	eku: id-kp-serverAuth
+	certificate template/profile: SomeProfileName
+	profile: SomeProfileName
+	pre-save command: echo Pre
+	post-save command: echo Post
+	track: yes
+	auto-renew: yes
+CA 'local':
+	is-default: no
+	ca-type: EXTERNAL
+	helper-location: $tmpdir/local-submit
+CA 'SelfSign':
+	is-default: no
+	ca-type: INTERNAL:SELF
+	next-serial-number: 01
+CA 'IPA':
+	is-default: no
+	ca-type: EXTERNAL
+	helper-location: $libexecdir/ipa-submit
+CA 'dogtag-ipa-renew-agent':
+	is-default: no
+	ca-type: EXTERNAL
+	helper-location: $libexecdir/dogtag-ipa-renew-agent-submit
+
+[[ API ]]
+[ simpleprop.py ]
+/org/fedorahosted/certmonger/cas/CA5
+/org/fedorahosted/certmonger/cas/CA5
+: -> : -k admin@localhost -> :
+0 -> 1 -> 0
+[ walk.py ]
+[ /: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <node name="org"/>
+</node>
+
+[ /org: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <node name="fedorahosted"/>
+</node>
+
+[ /org/fedorahosted: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <node name="certmonger"/>
+</node>
+
+[ /org/fedorahosted/certmonger: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger">
+  <method name="add_known_ca">
+   <arg name="nickname" type="s" direction="in"/>
+   <arg name="command" type="s" direction="in"/>
+   <arg name="known_names" type="as" direction="in"/>
+   <arg name="status" type="b" direction="out"/>
+   <arg name="name" type="o" direction="out"/>
+  </method>
+  <method name="add_request">
+   <arg name="template" type="a{sv}" direction="in"/>
+   <arg name="status" type="b" direction="out"/>
+   <arg name="name" type="o" direction="out"/>
+  </method>
+  <method name="find_ca_by_nickname">
+   <arg name="nickname" type="s" direction="in"/>
+   <arg name="ca" type="o" direction="out"/>
+  </method>
+  <method name="find_request_by_nickname">
+   <arg name="nickname" type="s" direction="in"/>
+   <arg name="request" type="o" direction="out"/>
+  </method>
+  <method name="get_known_cas">
+   <arg name="ca_list" type="ao" direction="out"/>
+  </method>
+  <method name="get_requests">
+   <arg name="requests" type="ao" direction="out"/>
+  </method>
+  <method name="get_supported_key_types">
+   <arg name="key_type_list" type="as" direction="out"/>
+  </method>
+  <method name="get_supported_key_storage">
+   <arg name="key_storage_type_list" type="as" direction="out"/>
+  </method>
+  <method name="get_supported_cert_storage">
+   <arg name="cert_storage_type_list" type="as" direction="out"/>
+  </method>
+  <method name="remove_known_ca">
+   <arg name="ca" type="o" direction="in"/>
+   <arg name="status" type="b" direction="out"/>
+  </method>
+  <method name="remove_request">
+   <arg name="request" type="o" direction="in"/>
+   <arg name="status" type="b" direction="out"/>
+  </method>
+ </interface>
+ <node name="requests"/>
+ <node name="cas"/>
+</node>
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.add_known_ca ]
+OK
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.add_request ]
+OK
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.find_ca_by_nickname ]
+OK
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.find_request_by_nickname ]
+OK
+
+[ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_known_cas ]
+dbus.Array([dbus.ObjectPath('/org/fedorahosted/certmonger/cas/CA1'), dbus.ObjectPath('/org/fedorahosted/certmonger/cas/CA2'), dbus.ObjectPath('/org/fedorahosted/certmonger/cas/CA3'), dbus.ObjectPath('/org/fedorahosted/certmonger/cas/CA4')], signature=dbus.Signature('o'))
+
+[ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_requests ]
+dbus.Array([dbus.ObjectPath('/org/fedorahosted/certmonger/requests/Request2')], signature=dbus.Signature('o'))
+
+[ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_supported_key_types ]
+dbus.Array([dbus.String('RSA'), dbus.String('EC'), dbus.String('ML-DSA-44'), dbus.String('ML-DSA-65'), dbus.String('ML-DSA-87')], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_supported_key_storage ]
+dbus.Array([dbus.String('NSSDB'), dbus.String('FILE')], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_supported_cert_storage ]
+dbus.Array([dbus.String('NSSDB'), dbus.String('FILE')], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.remove_known_ca ]
+OK
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.remove_request ]
+OK
+
+[ /org/fedorahosted/certmonger/requests: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/requests">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <node name="Request2"/>
+</node>
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/requests/Request2">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger.request">
+  <method name="get_nickname">
+   <arg name="nickname" type="s" direction="out"/>
+  </method>
+  <property name="nickname" type="s" access="read"/>
+  <method name="get_autorenew">
+   <arg name="enabled" type="b" direction="out"/>
+  </method>
+  <property name="autorenew" type="b" access="read"/>
+  <method name="get_cert_data">
+   <arg name="pem" type="s" direction="out"/>
+  </method>
+  <property name="cert" type="s" access="read">
+   <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+  </property>
+  <property name="cert-chain" type="a(ss)" access="read"/>
+  <property name="cert-roots" type="a(ss)" access="read"/>
+  <method name="get_cert_info">
+   <arg name="issuer" type="s" direction="out"/>
+   <arg name="serial_hex" type="s" direction="out"/>
+   <arg name="subject" type="s" direction="out"/>
+   <arg name="not_after" type="x" direction="out"/>
+   <arg name="email" type="as" direction="out"/>
+   <arg name="dns" type="as" direction="out"/>
+   <arg name="principal_names" type="as" direction="out"/>
+   <arg name="key_usage" type="x" direction="out"/>
+   <arg name="extended_key_usage" type="as" direction="out"/>
+   <arg name="not_before" type="x" direction="out"/>
+  </method>
+  <property name="issuer" type="s" access="read"/>
+  <property name="serial" type="s" access="read"/>
+  <property name="subject" type="s" access="read"/>
+  <property name="not-valid-before" type="x" access="read"/>
+  <property name="not-valid-after" type="x" access="read"/>
+  <property name="email" type="as" access="read"/>
+  <property name="ku" type="s" access="read"/>
+  <property name="eku" type="as" access="read"/>
+  <property name="hostname" type="as" access="read"/>
+  <property name="principal" type="as" access="read"/>
+  <method name="get_cert_last_checked">
+   <arg name="date" type="x" direction="out"/>
+  </method>
+  <property name="last-checked" type="x" access="read"/>
+  <method name="get_cert_storage_info">
+   <arg name="type" type="s" direction="out"/>
+   <arg name="location_or_nickname" type="s" direction="out"/>
+   <arg name="nss_token" type="s" direction="out"/>
+  </method>
+  <property name="cert-storage" type="s" access="read"/>
+  <property name="cert-file" type="s" access="read"/>
+  <property name="cert-database" type="s" access="read"/>
+  <property name="cert-nickname" type="s" access="read"/>
+  <property name="cert-token" type="s" access="read"/>
+  <property name="cert-owner" type="s" access="readwrite"/>
+  <property name="cert-perms" type="x" access="readwrite"/>
+  <method name="get_csr_data">
+   <arg name="pem" type="s" direction="out"/>
+  </method>
+  <property name="csr" type="s" access="read"/>
+  <method name="get_csr_info">
+   <arg name="subject" type="s" direction="out"/>
+   <arg name="email" type="as" direction="out"/>
+   <arg name="dns" type="as" direction="out"/>
+   <arg name="principal_names" type="as" direction="out"/>
+   <arg name="key_usage" type="x" direction="out"/>
+   <arg name="extended_key_usage" type="as" direction="out"/>
+  </method>
+  <property name="key-pin" type="s" access="readwrite"/>
+  <property name="key-pin-file" type="s" access="readwrite"/>
+  <property name="key-generated-date" type="x" access="read"/>
+  <property name="key-issued-count" type="x" access="read"/>
+  <property name="template-subject" type="s" access="readwrite"/>
+  <property name="template-email" type="as" access="readwrite"/>
+  <property name="template-ku" type="s" access="readwrite"/>
+  <property name="template-eku" type="as" access="readwrite"/>
+  <property name="template-hostname" type="as" access="readwrite"/>
+  <property name="template-principal" type="as" access="readwrite"/>
+  <property name="template-ip-address" type="as" access="readwrite"/>
+  <property name="template-is-ca" type="b" access="read"/>
+  <property name="template-ca-path-length" type="x" access="read"/>
+  <property name="template-ocsp" type="as" access="readwrite"/>
+  <property name="template-crldp" type="as" access="readwrite"/>
+  <property name="template-freshest-crl" type="as" access="readwrite"/>
+  <property name="template-ns-comment" type="s" access="readwrite"/>
+  <property name="template-profile" type="s" access="readwrite"/>
+  <property name="template-issuer" type="s" access="readwrite"/>
+  <property name="template-ms-certificate-template" type="s" access="readwrite"/>
+  <property name="template-ns-certtype" type="s" access="readwrite"/>
+  <property name="template-challenge-password" type="s" access="readwrite"/>
+  <property name="template-challenge-password-file" type="s" access="readwrite"/>
+  <method name="get_key_pin">
+   <arg name="pin" type="s" direction="out"/>
+  </method>
+  <method name="get_key_pin_file">
+   <arg name="pin_file" type="s" direction="out"/>
+  </method>
+  <method name="get_key_storage_info">
+   <arg name="type" type="s" direction="out"/>
+   <arg name="location_or_nickname" type="s" direction="out"/>
+   <arg name="nss_token" type="s" direction="out"/>
+  </method>
+  <property name="key-storage" type="s" access="read"/>
+  <property name="key-file" type="s" access="read"/>
+  <property name="key-database" type="s" access="read"/>
+  <property name="key-nickname" type="s" access="read"/>
+  <property name="key-token" type="s" access="read"/>
+  <property name="key-owner" type="s" access="readwrite"/>
+  <property name="key-perms" type="x" access="readwrite"/>
+  <method name="get_key_type_and_size">
+   <arg name="type" type="s" direction="out"/>
+   <arg name="size" type="x" direction="out"/>
+  </method>
+  <property name="key-type" type="s" access="read"/>
+  <property name="key-size" type="x" access="read"/>
+  <method name="get_monitoring">
+   <arg name="enabled" type="b" direction="out"/>
+  </method>
+  <property name="monitoring" type="b" access="read"/>
+  <method name="get_notification_info">
+   <arg name="method" type="s" direction="out"/>
+   <arg name="destination" type="s" direction="out"/>
+  </method>
+  <property name="notification-type" type="s" access="read"/>
+  <property name="notification-syslog-priority" type="s" access="read"/>
+  <property name="notification-email" type="s" access="read"/>
+  <property name="notification-command" type="s" access="read"/>
+  <method name="get_status">
+   <arg name="state" type="s" direction="out"/>
+   <arg name="blocked" type="b" direction="out"/>
+  </method>
+  <property name="status" type="s" access="read"/>
+  <property name="stuck" type="b" access="read"/>
+  <method name="get_ca">
+   <arg name="name" type="o" direction="out"/>
+  </method>
+  <property name="ca" type="o" access="read"/>
+  <property name="ca-profile" type="s" access="read"/>
+  <property name="nss-user" type="s" access="readwrite"/>
+  <property name="root-cert-files" type="as" access="read"/>
+  <property name="root-other-cert-files" type="as" access="read"/>
+  <property name="other-cert-files" type="as" access="read"/>
+  <property name="root-cert-nssdbs" type="as" access="read"/>
+  <property name="root-other-cert-nssdbs" type="as" access="read"/>
+  <property name="other-cert-nssdbs" type="as" access="read"/>
+  <method name="get_submitted_cookie">
+   <arg name="cookie" type="s" direction="out"/>
+  </method>
+  <property name="ca-cookie" type="s" access="read"/>
+  <method name="get_ca_error">
+   <arg name="text" type="s" direction="out"/>
+  </method>
+  <property name="ca-error" type="s" access="read"/>
+  <method name="get_submitted_date">
+   <arg name="date" type="x" direction="out"/>
+  </method>
+  <property name="submitted-date" type="x" access="read"/>
+  <method name="modify">
+   <arg name="updates" type="a{sv}" direction="in"/>
+   <arg name="status" type="b" direction="out"/>
+   <arg name="path" type="o" direction="out"/>
+  </method>
+  <method name="rekey">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <method name="resubmit">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <method name="refresh">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <property name="cert-presave-command" type="s" access="read"/>
+  <property name="cert-presave-uid" type="s" access="read"/>
+  <property name="cert-postsave-command" type="s" access="read"/>
+  <property name="cert-postsave-uid" type="s" access="read"/>
+  <signal name="SavedCertificate">
+  </signal>
+ </interface>
+</node>
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_nickname ]
+Buddy
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_autorenew ]
+1
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_cert_data ]
+
+
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_cert_info ]
+(dbus.String('CN=$UUID,CN=Local Signing Authority'), dbus.String('$UUID'), dbus.String('CN=localhost'), dbus.Int64(tomorrow), dbus.Array([], signature=dbus.Signature('s')), dbus.Array([dbus.String('localhost')], signature=dbus.Signature('s')), dbus.Array([dbus.String('host/localhost@LOCALHOST')], signature=dbus.Signature('s')), dbus.Int64(9), dbus.Array([dbus.String('1.3.6.1.5.5.7.3.1')], signature=dbus.Signature('s')), dbus.Int64(recently))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_cert_last_checked ]
+recently
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_cert_storage_info ]
+(dbus.String('FILE'), dbus.String('$tmpdir/test.crt'))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_csr_data ]
+
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_csr_info ]
+(dbus.String('CN=localhost'), dbus.Array([], signature=dbus.Signature('s')), dbus.Array([dbus.String('localhost')], signature=dbus.Signature('s')), dbus.Array([dbus.String('host/localhost@LOCALHOST')], signature=dbus.Signature('s')), dbus.Int64(9), dbus.Array([dbus.String('id-kp-serverAuth')], signature=dbus.Signature('s')))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_key_pin ]
+
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_key_pin_file ]
+
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_key_storage_info ]
+(dbus.String('FILE'), dbus.String('$tmpdir/test.key'))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_key_type_and_size ]
+(dbus.String('RSA'), dbus.Int64(512))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_monitoring ]
+1
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_notification_info ]
+(dbus.String('stdout'), dbus.String('daemon.notice'))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_status ]
+(dbus.String('MONITORING'), dbus.Boolean(False))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_ca ]
+/org/fedorahosted/certmonger/cas/CA1
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_submitted_cookie ]
+None
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_ca_error ]
+None
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_submitted_date ]
+recently
+
+[ /org/fedorahosted/certmonger/requests/Request2 : org.fedorahosted.certmonger.request.modify ]
+1 on /org/fedorahosted/certmonger/requests/Request2
+After setting template-eku to 1.2.3.4.5.6.7.8.9.10, we got dbus.Array([dbus.String('1.2.3.4.5.6.7.8.9.10')], signature=dbus.Signature('s'), variant_level=1)
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.rekey ]
+1
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.resubmit ]
+1
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.refresh ]
+0
+
+[ /org/fedorahosted/certmonger/cas: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/cas">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <node name="CA1"/>
+ <node name="CA2"/>
+ <node name="CA3"/>
+ <node name="CA4"/>
+</node>
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/cas/CA1">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger.ca">
+  <method name="get_config_file_path">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <method name="get_nickname">
+   <arg name="nickname" type="s" direction="out"/>
+  </method>
+  <property name="nickname" type="s" access="read"/>
+  <property name="aka" type="s" access="read"/>
+  <method name="get_is_default">
+   <arg name="default" type="b" direction="out"/>
+  </method>
+  <property name="is-default" type="b" access="readwrite"/>
+  <method name="get_type">
+   <arg name="type" type="s" direction="out"/>
+  </method>
+  <method name="get_serial">
+   <arg name="serial_hex" type="s" direction="out"/>
+  </method>
+  <method name="get_location">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <property name="external-helper" type="s" access="readwrite"/>
+  <method name="get_issuer_names">
+   <arg name="names" type="as" direction="out"/>
+  </method>
+  <method name="refresh">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <property name="ca-error" type="s" access="read"/>
+  <property name="issuer-names" type="as" access="read"/>
+  <property name="root-certs" type="a(ss)" access="read"/>
+  <property name="root-other-certs" type="a(ss)" access="read"/>
+  <property name="other-certs" type="a(ss)" access="read"/>
+  <property name="required-enroll-attributes" type="as" access="read"/>
+  <property name="required-renew-attributes" type="as" access="read"/>
+  <property name="supported-profiles" type="as" access="read"/>
+  <property name="default-profile" type="s" access="read"/>
+  <property name="root-cert-files" type="as" access="readwrite"/>
+  <property name="root-other-cert-files" type="as" access="readwrite"/>
+  <property name="other-cert-files" type="as" access="readwrite"/>
+  <property name="root-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="root-other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="ca-presave-command" type="s" access="read"/>
+  <property name="ca-presave-uid" type="s" access="read"/>
+  <property name="ca-postsave-command" type="s" access="read"/>
+  <property name="ca-postsave-uid" type="s" access="read"/>
+  <property name="scep-cipher" type="s" access="readwrite"/>
+  <property name="scep-digest" type="s" access="readwrite"/>
+  <property name="scep-ca-identifier" type="s" access="readwrite"/>
+  <property name="scep-ca-capabilities" type="as" access="read"/>
+  <property name="scep-ra-cert" type="s" access="read"/>
+  <property name="scep-ca-cert" type="s" access="read"/>
+  <property name="scep-other-certs" type="s" access="read"/>
+ </interface>
+</node>
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_config_file_path ]
+$tmpdir/cas/local
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_nickname ]
+local
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_is_default ]
+0
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_type ]
+EXTERNAL
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_serial ]
+None
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_location ]
+$tmpdir/local-submit
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_issuer_names ]
+dbus.Array([], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.refresh ]
+1
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/cas/CA2">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger.ca">
+  <method name="get_config_file_path">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <method name="get_nickname">
+   <arg name="nickname" type="s" direction="out"/>
+  </method>
+  <property name="nickname" type="s" access="read"/>
+  <property name="aka" type="s" access="read"/>
+  <method name="get_is_default">
+   <arg name="default" type="b" direction="out"/>
+  </method>
+  <property name="is-default" type="b" access="readwrite"/>
+  <method name="get_type">
+   <arg name="type" type="s" direction="out"/>
+  </method>
+  <method name="get_serial">
+   <arg name="serial_hex" type="s" direction="out"/>
+  </method>
+  <method name="get_location">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <property name="external-helper" type="s" access="readwrite"/>
+  <method name="get_issuer_names">
+   <arg name="names" type="as" direction="out"/>
+  </method>
+  <method name="refresh">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <property name="ca-error" type="s" access="read"/>
+  <property name="issuer-names" type="as" access="read"/>
+  <property name="root-certs" type="a(ss)" access="read"/>
+  <property name="root-other-certs" type="a(ss)" access="read"/>
+  <property name="other-certs" type="a(ss)" access="read"/>
+  <property name="required-enroll-attributes" type="as" access="read"/>
+  <property name="required-renew-attributes" type="as" access="read"/>
+  <property name="supported-profiles" type="as" access="read"/>
+  <property name="default-profile" type="s" access="read"/>
+  <property name="root-cert-files" type="as" access="readwrite"/>
+  <property name="root-other-cert-files" type="as" access="readwrite"/>
+  <property name="other-cert-files" type="as" access="readwrite"/>
+  <property name="root-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="root-other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="ca-presave-command" type="s" access="read"/>
+  <property name="ca-presave-uid" type="s" access="read"/>
+  <property name="ca-postsave-command" type="s" access="read"/>
+  <property name="ca-postsave-uid" type="s" access="read"/>
+  <property name="scep-cipher" type="s" access="readwrite"/>
+  <property name="scep-digest" type="s" access="readwrite"/>
+  <property name="scep-ca-identifier" type="s" access="readwrite"/>
+  <property name="scep-ca-capabilities" type="as" access="read"/>
+  <property name="scep-ra-cert" type="s" access="read"/>
+  <property name="scep-ca-cert" type="s" access="read"/>
+  <property name="scep-other-certs" type="s" access="read"/>
+ </interface>
+</node>
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_config_file_path ]
+$tmpdir/cas/20260112190743
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_nickname ]
+SelfSign
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_is_default ]
+0
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_type ]
+INTERNAL:SELF
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_serial ]
+01
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_location ]
+
+
+/org/fedorahosted/certmonger/cas/CA2: warning: property org.fedorahosted.certmonger.ca.external-helper not settable on this object
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_issuer_names ]
+dbus.Array([], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.refresh ]
+1
+
+/org/fedorahosted/certmonger/cas/CA2: property org.fedorahosted.certmonger.ca.scep-cipher not set: (, x)
+[ /org/fedorahosted/certmonger/cas/CA3: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/cas/CA3">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger.ca">
+  <method name="get_config_file_path">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <method name="get_nickname">
+   <arg name="nickname" type="s" direction="out"/>
+  </method>
+  <property name="nickname" type="s" access="read"/>
+  <property name="aka" type="s" access="read"/>
+  <method name="get_is_default">
+   <arg name="default" type="b" direction="out"/>
+  </method>
+  <property name="is-default" type="b" access="readwrite"/>
+  <method name="get_type">
+   <arg name="type" type="s" direction="out"/>
+  </method>
+  <method name="get_serial">
+   <arg name="serial_hex" type="s" direction="out"/>
+  </method>
+  <method name="get_location">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <property name="external-helper" type="s" access="readwrite"/>
+  <method name="get_issuer_names">
+   <arg name="names" type="as" direction="out"/>
+  </method>
+  <method name="refresh">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <property name="ca-error" type="s" access="read"/>
+  <property name="issuer-names" type="as" access="read"/>
+  <property name="root-certs" type="a(ss)" access="read"/>
+  <property name="root-other-certs" type="a(ss)" access="read"/>
+  <property name="other-certs" type="a(ss)" access="read"/>
+  <property name="required-enroll-attributes" type="as" access="read"/>
+  <property name="required-renew-attributes" type="as" access="read"/>
+  <property name="supported-profiles" type="as" access="read"/>
+  <property name="default-profile" type="s" access="read"/>
+  <property name="root-cert-files" type="as" access="readwrite"/>
+  <property name="root-other-cert-files" type="as" access="readwrite"/>
+  <property name="other-cert-files" type="as" access="readwrite"/>
+  <property name="root-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="root-other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="ca-presave-command" type="s" access="read"/>
+  <property name="ca-presave-uid" type="s" access="read"/>
+  <property name="ca-postsave-command" type="s" access="read"/>
+  <property name="ca-postsave-uid" type="s" access="read"/>
+  <property name="scep-cipher" type="s" access="readwrite"/>
+  <property name="scep-digest" type="s" access="readwrite"/>
+  <property name="scep-ca-identifier" type="s" access="readwrite"/>
+  <property name="scep-ca-capabilities" type="as" access="read"/>
+  <property name="scep-ra-cert" type="s" access="read"/>
+  <property name="scep-ca-cert" type="s" access="read"/>
+  <property name="scep-other-certs" type="s" access="read"/>
+ </interface>
+</node>
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_config_file_path ]
+$tmpdir/cas/20260112190743-1
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_nickname ]
+IPA
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_is_default ]
+0
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_type ]
+EXTERNAL
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_serial ]
+None
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_location ]
+$libexecdir/ipa-submit
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_issuer_names ]
+dbus.Array([], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.refresh ]
+1
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/cas/CA4">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger.ca">
+  <method name="get_config_file_path">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <method name="get_nickname">
+   <arg name="nickname" type="s" direction="out"/>
+  </method>
+  <property name="nickname" type="s" access="read"/>
+  <property name="aka" type="s" access="read"/>
+  <method name="get_is_default">
+   <arg name="default" type="b" direction="out"/>
+  </method>
+  <property name="is-default" type="b" access="readwrite"/>
+  <method name="get_type">
+   <arg name="type" type="s" direction="out"/>
+  </method>
+  <method name="get_serial">
+   <arg name="serial_hex" type="s" direction="out"/>
+  </method>
+  <method name="get_location">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <property name="external-helper" type="s" access="readwrite"/>
+  <method name="get_issuer_names">
+   <arg name="names" type="as" direction="out"/>
+  </method>
+  <method name="refresh">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <property name="ca-error" type="s" access="read"/>
+  <property name="issuer-names" type="as" access="read"/>
+  <property name="root-certs" type="a(ss)" access="read"/>
+  <property name="root-other-certs" type="a(ss)" access="read"/>
+  <property name="other-certs" type="a(ss)" access="read"/>
+  <property name="required-enroll-attributes" type="as" access="read"/>
+  <property name="required-renew-attributes" type="as" access="read"/>
+  <property name="supported-profiles" type="as" access="read"/>
+  <property name="default-profile" type="s" access="read"/>
+  <property name="root-cert-files" type="as" access="readwrite"/>
+  <property name="root-other-cert-files" type="as" access="readwrite"/>
+  <property name="other-cert-files" type="as" access="readwrite"/>
+  <property name="root-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="root-other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="ca-presave-command" type="s" access="read"/>
+  <property name="ca-presave-uid" type="s" access="read"/>
+  <property name="ca-postsave-command" type="s" access="read"/>
+  <property name="ca-postsave-uid" type="s" access="read"/>
+  <property name="scep-cipher" type="s" access="readwrite"/>
+  <property name="scep-digest" type="s" access="readwrite"/>
+  <property name="scep-ca-identifier" type="s" access="readwrite"/>
+  <property name="scep-ca-capabilities" type="as" access="read"/>
+  <property name="scep-ra-cert" type="s" access="read"/>
+  <property name="scep-ca-cert" type="s" access="read"/>
+  <property name="scep-other-certs" type="s" access="read"/>
+ </interface>
+</node>
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_config_file_path ]
+$tmpdir/cas/20260112190743-2
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_nickname ]
+dogtag-ipa-renew-agent
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_is_default ]
+0
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_type ]
+EXTERNAL
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_serial ]
+None
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_location ]
+$libexecdir/dogtag-ipa-renew-agent-submit
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_issuer_names ]
+dbus.Array([], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.refresh ]
+1
+
diff --git a/tests/028-dbus/expected.out.nomldsa b/tests/028-dbus/expected.out.nomldsa
new file mode 100644
index 0000000..86cba02
--- /dev/null
+++ b/tests/028-dbus/expected.out.nomldsa
@@ -0,0 +1,965 @@
+Certificate in file "${tmpdir}/test.crt" issued by CA and saved.
+Certificate in file "${tmpdir}/test.crt" issued by CA and saved.
+[[ getcert ]]
+State MONITORING, stuck: no.
+Number of certificates and requests being tracked: 1.
+Request ID 'Buddy':
+	status: MONITORING
+	stuck: no
+	key pair storage: type=FILE,location='$tmpdir/test.key'
+	certificate: type=FILE,location='$tmpdir/test.crt'
+	CA: local
+	issuer: CN=$UUID,CN=Local Signing Authority
+	subject: CN=localhost
+	issued: sometime
+	expires: sometime
+	dns: localhost
+	principal name: host/localhost@LOCALHOST
+	key usage: digitalSignature,dataEncipherment
+	eku: id-kp-serverAuth
+	certificate template/profile: SomeProfileName
+	profile: SomeProfileName
+	pre-save command: echo Pre
+	post-save command: echo Post
+	track: yes
+	auto-renew: yes
+CA 'local':
+	is-default: no
+	ca-type: EXTERNAL
+	helper-location: $tmpdir/local-submit
+CA 'SelfSign':
+	is-default: no
+	ca-type: INTERNAL:SELF
+	next-serial-number: 01
+CA 'IPA':
+	is-default: no
+	ca-type: EXTERNAL
+	helper-location: $libexecdir/ipa-submit
+CA 'dogtag-ipa-renew-agent':
+	is-default: no
+	ca-type: EXTERNAL
+	helper-location: $libexecdir/dogtag-ipa-renew-agent-submit
+
+[[ API ]]
+[ simpleprop.py ]
+/org/fedorahosted/certmonger/cas/CA5
+/org/fedorahosted/certmonger/cas/CA5
+: -> : -k admin@localhost -> :
+0 -> 1 -> 0
+[ walk.py ]
+[ /: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <node name="org"/>
+</node>
+
+[ /org: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <node name="fedorahosted"/>
+</node>
+
+[ /org/fedorahosted: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <node name="certmonger"/>
+</node>
+
+[ /org/fedorahosted/certmonger: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger">
+  <method name="add_known_ca">
+   <arg name="nickname" type="s" direction="in"/>
+   <arg name="command" type="s" direction="in"/>
+   <arg name="known_names" type="as" direction="in"/>
+   <arg name="status" type="b" direction="out"/>
+   <arg name="name" type="o" direction="out"/>
+  </method>
+  <method name="add_request">
+   <arg name="template" type="a{sv}" direction="in"/>
+   <arg name="status" type="b" direction="out"/>
+   <arg name="name" type="o" direction="out"/>
+  </method>
+  <method name="find_ca_by_nickname">
+   <arg name="nickname" type="s" direction="in"/>
+   <arg name="ca" type="o" direction="out"/>
+  </method>
+  <method name="find_request_by_nickname">
+   <arg name="nickname" type="s" direction="in"/>
+   <arg name="request" type="o" direction="out"/>
+  </method>
+  <method name="get_known_cas">
+   <arg name="ca_list" type="ao" direction="out"/>
+  </method>
+  <method name="get_requests">
+   <arg name="requests" type="ao" direction="out"/>
+  </method>
+  <method name="get_supported_key_types">
+   <arg name="key_type_list" type="as" direction="out"/>
+  </method>
+  <method name="get_supported_key_storage">
+   <arg name="key_storage_type_list" type="as" direction="out"/>
+  </method>
+  <method name="get_supported_cert_storage">
+   <arg name="cert_storage_type_list" type="as" direction="out"/>
+  </method>
+  <method name="remove_known_ca">
+   <arg name="ca" type="o" direction="in"/>
+   <arg name="status" type="b" direction="out"/>
+  </method>
+  <method name="remove_request">
+   <arg name="request" type="o" direction="in"/>
+   <arg name="status" type="b" direction="out"/>
+  </method>
+ </interface>
+ <node name="requests"/>
+ <node name="cas"/>
+</node>
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.add_known_ca ]
+OK
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.add_request ]
+OK
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.find_ca_by_nickname ]
+OK
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.find_request_by_nickname ]
+OK
+
+[ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_known_cas ]
+dbus.Array([dbus.ObjectPath('/org/fedorahosted/certmonger/cas/CA1'), dbus.ObjectPath('/org/fedorahosted/certmonger/cas/CA2'), dbus.ObjectPath('/org/fedorahosted/certmonger/cas/CA3'), dbus.ObjectPath('/org/fedorahosted/certmonger/cas/CA4')], signature=dbus.Signature('o'))
+
+[ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_requests ]
+dbus.Array([dbus.ObjectPath('/org/fedorahosted/certmonger/requests/Request2')], signature=dbus.Signature('o'))
+
+[ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_supported_key_types ]
+dbus.Array([dbus.String('RSA'), dbus.String('DSA'), dbus.String('EC')], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_supported_key_storage ]
+dbus.Array([dbus.String('NSSDB'), dbus.String('FILE')], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger: org.fedorahosted.certmonger.get_supported_cert_storage ]
+dbus.Array([dbus.String('NSSDB'), dbus.String('FILE')], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.remove_known_ca ]
+OK
+
+[ /org/fedorahosted/certmonger : org.fedorahosted.certmonger.remove_request ]
+OK
+
+[ /org/fedorahosted/certmonger/requests: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/requests">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <node name="Request2"/>
+</node>
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/requests/Request2">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger.request">
+  <method name="get_nickname">
+   <arg name="nickname" type="s" direction="out"/>
+  </method>
+  <property name="nickname" type="s" access="read"/>
+  <method name="get_autorenew">
+   <arg name="enabled" type="b" direction="out"/>
+  </method>
+  <property name="autorenew" type="b" access="read"/>
+  <method name="get_cert_data">
+   <arg name="pem" type="s" direction="out"/>
+  </method>
+  <property name="cert" type="s" access="read">
+   <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+  </property>
+  <property name="cert-chain" type="a(ss)" access="read"/>
+  <property name="cert-roots" type="a(ss)" access="read"/>
+  <method name="get_cert_info">
+   <arg name="issuer" type="s" direction="out"/>
+   <arg name="serial_hex" type="s" direction="out"/>
+   <arg name="subject" type="s" direction="out"/>
+   <arg name="not_after" type="x" direction="out"/>
+   <arg name="email" type="as" direction="out"/>
+   <arg name="dns" type="as" direction="out"/>
+   <arg name="principal_names" type="as" direction="out"/>
+   <arg name="key_usage" type="x" direction="out"/>
+   <arg name="extended_key_usage" type="as" direction="out"/>
+   <arg name="not_before" type="x" direction="out"/>
+  </method>
+  <property name="issuer" type="s" access="read"/>
+  <property name="serial" type="s" access="read"/>
+  <property name="subject" type="s" access="read"/>
+  <property name="not-valid-before" type="x" access="read"/>
+  <property name="not-valid-after" type="x" access="read"/>
+  <property name="email" type="as" access="read"/>
+  <property name="ku" type="s" access="read"/>
+  <property name="eku" type="as" access="read"/>
+  <property name="hostname" type="as" access="read"/>
+  <property name="principal" type="as" access="read"/>
+  <method name="get_cert_last_checked">
+   <arg name="date" type="x" direction="out"/>
+  </method>
+  <property name="last-checked" type="x" access="read"/>
+  <method name="get_cert_storage_info">
+   <arg name="type" type="s" direction="out"/>
+   <arg name="location_or_nickname" type="s" direction="out"/>
+   <arg name="nss_token" type="s" direction="out"/>
+  </method>
+  <property name="cert-storage" type="s" access="read"/>
+  <property name="cert-file" type="s" access="read"/>
+  <property name="cert-database" type="s" access="read"/>
+  <property name="cert-nickname" type="s" access="read"/>
+  <property name="cert-token" type="s" access="read"/>
+  <property name="cert-owner" type="s" access="readwrite"/>
+  <property name="cert-perms" type="x" access="readwrite"/>
+  <method name="get_csr_data">
+   <arg name="pem" type="s" direction="out"/>
+  </method>
+  <property name="csr" type="s" access="read"/>
+  <method name="get_csr_info">
+   <arg name="subject" type="s" direction="out"/>
+   <arg name="email" type="as" direction="out"/>
+   <arg name="dns" type="as" direction="out"/>
+   <arg name="principal_names" type="as" direction="out"/>
+   <arg name="key_usage" type="x" direction="out"/>
+   <arg name="extended_key_usage" type="as" direction="out"/>
+  </method>
+  <property name="key-pin" type="s" access="readwrite"/>
+  <property name="key-pin-file" type="s" access="readwrite"/>
+  <property name="key-generated-date" type="x" access="read"/>
+  <property name="key-issued-count" type="x" access="read"/>
+  <property name="template-subject" type="s" access="readwrite"/>
+  <property name="template-email" type="as" access="readwrite"/>
+  <property name="template-ku" type="s" access="readwrite"/>
+  <property name="template-eku" type="as" access="readwrite"/>
+  <property name="template-hostname" type="as" access="readwrite"/>
+  <property name="template-principal" type="as" access="readwrite"/>
+  <property name="template-ip-address" type="as" access="readwrite"/>
+  <property name="template-is-ca" type="b" access="read"/>
+  <property name="template-ca-path-length" type="x" access="read"/>
+  <property name="template-ocsp" type="as" access="readwrite"/>
+  <property name="template-crldp" type="as" access="readwrite"/>
+  <property name="template-freshest-crl" type="as" access="readwrite"/>
+  <property name="template-ns-comment" type="s" access="readwrite"/>
+  <property name="template-profile" type="s" access="readwrite"/>
+  <property name="template-issuer" type="s" access="readwrite"/>
+  <property name="template-ms-certificate-template" type="s" access="readwrite"/>
+  <property name="template-ns-certtype" type="s" access="readwrite"/>
+  <property name="template-challenge-password" type="s" access="readwrite"/>
+  <property name="template-challenge-password-file" type="s" access="readwrite"/>
+  <method name="get_key_pin">
+   <arg name="pin" type="s" direction="out"/>
+  </method>
+  <method name="get_key_pin_file">
+   <arg name="pin_file" type="s" direction="out"/>
+  </method>
+  <method name="get_key_storage_info">
+   <arg name="type" type="s" direction="out"/>
+   <arg name="location_or_nickname" type="s" direction="out"/>
+   <arg name="nss_token" type="s" direction="out"/>
+  </method>
+  <property name="key-storage" type="s" access="read"/>
+  <property name="key-file" type="s" access="read"/>
+  <property name="key-database" type="s" access="read"/>
+  <property name="key-nickname" type="s" access="read"/>
+  <property name="key-token" type="s" access="read"/>
+  <property name="key-owner" type="s" access="readwrite"/>
+  <property name="key-perms" type="x" access="readwrite"/>
+  <method name="get_key_type_and_size">
+   <arg name="type" type="s" direction="out"/>
+   <arg name="size" type="x" direction="out"/>
+  </method>
+  <property name="key-type" type="s" access="read"/>
+  <property name="key-size" type="x" access="read"/>
+  <method name="get_monitoring">
+   <arg name="enabled" type="b" direction="out"/>
+  </method>
+  <property name="monitoring" type="b" access="read"/>
+  <method name="get_notification_info">
+   <arg name="method" type="s" direction="out"/>
+   <arg name="destination" type="s" direction="out"/>
+  </method>
+  <property name="notification-type" type="s" access="read"/>
+  <property name="notification-syslog-priority" type="s" access="read"/>
+  <property name="notification-email" type="s" access="read"/>
+  <property name="notification-command" type="s" access="read"/>
+  <method name="get_status">
+   <arg name="state" type="s" direction="out"/>
+   <arg name="blocked" type="b" direction="out"/>
+  </method>
+  <property name="status" type="s" access="read"/>
+  <property name="stuck" type="b" access="read"/>
+  <method name="get_ca">
+   <arg name="name" type="o" direction="out"/>
+  </method>
+  <property name="ca" type="o" access="read"/>
+  <property name="ca-profile" type="s" access="read"/>
+  <property name="nss-user" type="s" access="readwrite"/>
+  <property name="root-cert-files" type="as" access="read"/>
+  <property name="root-other-cert-files" type="as" access="read"/>
+  <property name="other-cert-files" type="as" access="read"/>
+  <property name="root-cert-nssdbs" type="as" access="read"/>
+  <property name="root-other-cert-nssdbs" type="as" access="read"/>
+  <property name="other-cert-nssdbs" type="as" access="read"/>
+  <method name="get_submitted_cookie">
+   <arg name="cookie" type="s" direction="out"/>
+  </method>
+  <property name="ca-cookie" type="s" access="read"/>
+  <method name="get_ca_error">
+   <arg name="text" type="s" direction="out"/>
+  </method>
+  <property name="ca-error" type="s" access="read"/>
+  <method name="get_submitted_date">
+   <arg name="date" type="x" direction="out"/>
+  </method>
+  <property name="submitted-date" type="x" access="read"/>
+  <method name="modify">
+   <arg name="updates" type="a{sv}" direction="in"/>
+   <arg name="status" type="b" direction="out"/>
+   <arg name="path" type="o" direction="out"/>
+  </method>
+  <method name="rekey">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <method name="resubmit">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <method name="refresh">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <property name="cert-presave-command" type="s" access="read"/>
+  <property name="cert-presave-uid" type="s" access="read"/>
+  <property name="cert-postsave-command" type="s" access="read"/>
+  <property name="cert-postsave-uid" type="s" access="read"/>
+  <signal name="SavedCertificate">
+  </signal>
+ </interface>
+</node>
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_nickname ]
+Buddy
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_autorenew ]
+1
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_cert_data ]
+
+
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_cert_info ]
+(dbus.String('CN=$UUID,CN=Local Signing Authority'), dbus.String('$UUID'), dbus.String('CN=localhost'), dbus.Int64(tomorrow), dbus.Array([], signature=dbus.Signature('s')), dbus.Array([dbus.String('localhost')], signature=dbus.Signature('s')), dbus.Array([dbus.String('host/localhost@LOCALHOST')], signature=dbus.Signature('s')), dbus.Int64(9), dbus.Array([dbus.String('1.3.6.1.5.5.7.3.1')], signature=dbus.Signature('s')), dbus.Int64(recently))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_cert_last_checked ]
+recently
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_cert_storage_info ]
+(dbus.String('FILE'), dbus.String('$tmpdir/test.crt'))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_csr_data ]
+
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_csr_info ]
+(dbus.String('CN=localhost'), dbus.Array([], signature=dbus.Signature('s')), dbus.Array([dbus.String('localhost')], signature=dbus.Signature('s')), dbus.Array([dbus.String('host/localhost@LOCALHOST')], signature=dbus.Signature('s')), dbus.Int64(9), dbus.Array([dbus.String('id-kp-serverAuth')], signature=dbus.Signature('s')))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_key_pin ]
+
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_key_pin_file ]
+
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_key_storage_info ]
+(dbus.String('FILE'), dbus.String('$tmpdir/test.key'))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_key_type_and_size ]
+(dbus.String('RSA'), dbus.Int64(512))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_monitoring ]
+1
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_notification_info ]
+(dbus.String('stdout'), dbus.String('daemon.notice'))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_status ]
+(dbus.String('MONITORING'), dbus.Boolean(False))
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_ca ]
+/org/fedorahosted/certmonger/cas/CA1
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_submitted_cookie ]
+None
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_ca_error ]
+None
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.get_submitted_date ]
+recently
+
+[ /org/fedorahosted/certmonger/requests/Request2 : org.fedorahosted.certmonger.request.modify ]
+1 on /org/fedorahosted/certmonger/requests/Request2
+After setting template-eku to 1.2.3.4.5.6.7.8.9.10, we got dbus.Array([dbus.String('1.2.3.4.5.6.7.8.9.10')], signature=dbus.Signature('s'), variant_level=1)
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.rekey ]
+1
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.resubmit ]
+1
+
+[ /org/fedorahosted/certmonger/requests/Request2: org.fedorahosted.certmonger.request.refresh ]
+0
+
+[ /org/fedorahosted/certmonger/cas: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/cas">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <node name="CA1"/>
+ <node name="CA2"/>
+ <node name="CA3"/>
+ <node name="CA4"/>
+</node>
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/cas/CA1">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger.ca">
+  <method name="get_config_file_path">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <method name="get_nickname">
+   <arg name="nickname" type="s" direction="out"/>
+  </method>
+  <property name="nickname" type="s" access="read"/>
+  <property name="aka" type="s" access="read"/>
+  <method name="get_is_default">
+   <arg name="default" type="b" direction="out"/>
+  </method>
+  <property name="is-default" type="b" access="readwrite"/>
+  <method name="get_type">
+   <arg name="type" type="s" direction="out"/>
+  </method>
+  <method name="get_serial">
+   <arg name="serial_hex" type="s" direction="out"/>
+  </method>
+  <method name="get_location">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <property name="external-helper" type="s" access="readwrite"/>
+  <method name="get_issuer_names">
+   <arg name="names" type="as" direction="out"/>
+  </method>
+  <method name="refresh">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <property name="ca-error" type="s" access="read"/>
+  <property name="issuer-names" type="as" access="read"/>
+  <property name="root-certs" type="a(ss)" access="read"/>
+  <property name="root-other-certs" type="a(ss)" access="read"/>
+  <property name="other-certs" type="a(ss)" access="read"/>
+  <property name="required-enroll-attributes" type="as" access="read"/>
+  <property name="required-renew-attributes" type="as" access="read"/>
+  <property name="supported-profiles" type="as" access="read"/>
+  <property name="default-profile" type="s" access="read"/>
+  <property name="root-cert-files" type="as" access="readwrite"/>
+  <property name="root-other-cert-files" type="as" access="readwrite"/>
+  <property name="other-cert-files" type="as" access="readwrite"/>
+  <property name="root-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="root-other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="ca-presave-command" type="s" access="read"/>
+  <property name="ca-presave-uid" type="s" access="read"/>
+  <property name="ca-postsave-command" type="s" access="read"/>
+  <property name="ca-postsave-uid" type="s" access="read"/>
+  <property name="scep-cipher" type="s" access="readwrite"/>
+  <property name="scep-digest" type="s" access="readwrite"/>
+  <property name="scep-ca-identifier" type="s" access="readwrite"/>
+  <property name="scep-ca-capabilities" type="as" access="read"/>
+  <property name="scep-ra-cert" type="s" access="read"/>
+  <property name="scep-ca-cert" type="s" access="read"/>
+  <property name="scep-other-certs" type="s" access="read"/>
+ </interface>
+</node>
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_config_file_path ]
+$tmpdir/cas/local
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_nickname ]
+local
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_is_default ]
+0
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_type ]
+EXTERNAL
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_serial ]
+None
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_location ]
+$tmpdir/local-submit
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.get_issuer_names ]
+dbus.Array([], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger/cas/CA1: org.fedorahosted.certmonger.ca.refresh ]
+1
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/cas/CA2">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger.ca">
+  <method name="get_config_file_path">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <method name="get_nickname">
+   <arg name="nickname" type="s" direction="out"/>
+  </method>
+  <property name="nickname" type="s" access="read"/>
+  <property name="aka" type="s" access="read"/>
+  <method name="get_is_default">
+   <arg name="default" type="b" direction="out"/>
+  </method>
+  <property name="is-default" type="b" access="readwrite"/>
+  <method name="get_type">
+   <arg name="type" type="s" direction="out"/>
+  </method>
+  <method name="get_serial">
+   <arg name="serial_hex" type="s" direction="out"/>
+  </method>
+  <method name="get_location">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <property name="external-helper" type="s" access="readwrite"/>
+  <method name="get_issuer_names">
+   <arg name="names" type="as" direction="out"/>
+  </method>
+  <method name="refresh">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <property name="ca-error" type="s" access="read"/>
+  <property name="issuer-names" type="as" access="read"/>
+  <property name="root-certs" type="a(ss)" access="read"/>
+  <property name="root-other-certs" type="a(ss)" access="read"/>
+  <property name="other-certs" type="a(ss)" access="read"/>
+  <property name="required-enroll-attributes" type="as" access="read"/>
+  <property name="required-renew-attributes" type="as" access="read"/>
+  <property name="supported-profiles" type="as" access="read"/>
+  <property name="default-profile" type="s" access="read"/>
+  <property name="root-cert-files" type="as" access="readwrite"/>
+  <property name="root-other-cert-files" type="as" access="readwrite"/>
+  <property name="other-cert-files" type="as" access="readwrite"/>
+  <property name="root-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="root-other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="ca-presave-command" type="s" access="read"/>
+  <property name="ca-presave-uid" type="s" access="read"/>
+  <property name="ca-postsave-command" type="s" access="read"/>
+  <property name="ca-postsave-uid" type="s" access="read"/>
+  <property name="scep-cipher" type="s" access="readwrite"/>
+  <property name="scep-digest" type="s" access="readwrite"/>
+  <property name="scep-ca-identifier" type="s" access="readwrite"/>
+  <property name="scep-ca-capabilities" type="as" access="read"/>
+  <property name="scep-ra-cert" type="s" access="read"/>
+  <property name="scep-ca-cert" type="s" access="read"/>
+  <property name="scep-other-certs" type="s" access="read"/>
+ </interface>
+</node>
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_config_file_path ]
+$tmpdir/cas/20180327134236
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_nickname ]
+SelfSign
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_is_default ]
+0
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_type ]
+INTERNAL:SELF
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_serial ]
+01
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_location ]
+
+
+/org/fedorahosted/certmonger/cas/CA2: warning: property org.fedorahosted.certmonger.ca.external-helper not settable on this object
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.get_issuer_names ]
+dbus.Array([], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger/cas/CA2: org.fedorahosted.certmonger.ca.refresh ]
+1
+
+/org/fedorahosted/certmonger/cas/CA2: property org.fedorahosted.certmonger.ca.scep-cipher not set: (, x)
+[ /org/fedorahosted/certmonger/cas/CA3: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/cas/CA3">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger.ca">
+  <method name="get_config_file_path">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <method name="get_nickname">
+   <arg name="nickname" type="s" direction="out"/>
+  </method>
+  <property name="nickname" type="s" access="read"/>
+  <property name="aka" type="s" access="read"/>
+  <method name="get_is_default">
+   <arg name="default" type="b" direction="out"/>
+  </method>
+  <property name="is-default" type="b" access="readwrite"/>
+  <method name="get_type">
+   <arg name="type" type="s" direction="out"/>
+  </method>
+  <method name="get_serial">
+   <arg name="serial_hex" type="s" direction="out"/>
+  </method>
+  <method name="get_location">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <property name="external-helper" type="s" access="readwrite"/>
+  <method name="get_issuer_names">
+   <arg name="names" type="as" direction="out"/>
+  </method>
+  <method name="refresh">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <property name="ca-error" type="s" access="read"/>
+  <property name="issuer-names" type="as" access="read"/>
+  <property name="root-certs" type="a(ss)" access="read"/>
+  <property name="root-other-certs" type="a(ss)" access="read"/>
+  <property name="other-certs" type="a(ss)" access="read"/>
+  <property name="required-enroll-attributes" type="as" access="read"/>
+  <property name="required-renew-attributes" type="as" access="read"/>
+  <property name="supported-profiles" type="as" access="read"/>
+  <property name="default-profile" type="s" access="read"/>
+  <property name="root-cert-files" type="as" access="readwrite"/>
+  <property name="root-other-cert-files" type="as" access="readwrite"/>
+  <property name="other-cert-files" type="as" access="readwrite"/>
+  <property name="root-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="root-other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="ca-presave-command" type="s" access="read"/>
+  <property name="ca-presave-uid" type="s" access="read"/>
+  <property name="ca-postsave-command" type="s" access="read"/>
+  <property name="ca-postsave-uid" type="s" access="read"/>
+  <property name="scep-cipher" type="s" access="readwrite"/>
+  <property name="scep-digest" type="s" access="readwrite"/>
+  <property name="scep-ca-identifier" type="s" access="readwrite"/>
+  <property name="scep-ca-capabilities" type="as" access="read"/>
+  <property name="scep-ra-cert" type="s" access="read"/>
+  <property name="scep-ca-cert" type="s" access="read"/>
+  <property name="scep-other-certs" type="s" access="read"/>
+ </interface>
+</node>
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_config_file_path ]
+$tmpdir/cas/20180327134236-1
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_nickname ]
+IPA
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_is_default ]
+0
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_type ]
+EXTERNAL
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_serial ]
+None
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_location ]
+$libexecdir/ipa-submit
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.get_issuer_names ]
+dbus.Array([], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger/cas/CA3: org.fedorahosted.certmonger.ca.refresh ]
+1
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.freedesktop.DBus.Introspectable.Introspect ]
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
+<node name="/org/fedorahosted/certmonger/cas/CA4">
+ <interface name="org.freedesktop.DBus.Introspectable">
+  <method name="Introspect">
+   <arg name="xml_data" type="s" direction="out"/>
+  </method>
+ </interface>
+ <interface name="org.freedesktop.DBus.Properties">
+  <method name="Get">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="out"/>
+  </method>
+  <method name="Set">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="property_name" type="s" direction="in"/>
+   <arg name="value" type="v" direction="in"/>
+  </method>
+  <method name="GetAll">
+   <arg name="interface_name" type="s" direction="in"/>
+   <arg name="props" type="a{sv}" direction="out"/>
+  </method>
+  <signal name="PropertiesChanged">
+   <arg name="interface_name" type="s"/>
+   <arg name="changed_properties" type="a{sv}"/>
+   <arg name="invalidated_properties" type="as"/>
+  </signal>
+ </interface>
+ <interface name="org.fedorahosted.certmonger.ca">
+  <method name="get_config_file_path">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <method name="get_nickname">
+   <arg name="nickname" type="s" direction="out"/>
+  </method>
+  <property name="nickname" type="s" access="read"/>
+  <property name="aka" type="s" access="read"/>
+  <method name="get_is_default">
+   <arg name="default" type="b" direction="out"/>
+  </method>
+  <property name="is-default" type="b" access="readwrite"/>
+  <method name="get_type">
+   <arg name="type" type="s" direction="out"/>
+  </method>
+  <method name="get_serial">
+   <arg name="serial_hex" type="s" direction="out"/>
+  </method>
+  <method name="get_location">
+   <arg name="path" type="s" direction="out"/>
+  </method>
+  <property name="external-helper" type="s" access="readwrite"/>
+  <method name="get_issuer_names">
+   <arg name="names" type="as" direction="out"/>
+  </method>
+  <method name="refresh">
+   <arg name="working" type="b" direction="out"/>
+  </method>
+  <property name="ca-error" type="s" access="read"/>
+  <property name="issuer-names" type="as" access="read"/>
+  <property name="root-certs" type="a(ss)" access="read"/>
+  <property name="root-other-certs" type="a(ss)" access="read"/>
+  <property name="other-certs" type="a(ss)" access="read"/>
+  <property name="required-enroll-attributes" type="as" access="read"/>
+  <property name="required-renew-attributes" type="as" access="read"/>
+  <property name="supported-profiles" type="as" access="read"/>
+  <property name="default-profile" type="s" access="read"/>
+  <property name="root-cert-files" type="as" access="readwrite"/>
+  <property name="root-other-cert-files" type="as" access="readwrite"/>
+  <property name="other-cert-files" type="as" access="readwrite"/>
+  <property name="root-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="root-other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="other-cert-nssdbs" type="as" access="readwrite"/>
+  <property name="ca-presave-command" type="s" access="read"/>
+  <property name="ca-presave-uid" type="s" access="read"/>
+  <property name="ca-postsave-command" type="s" access="read"/>
+  <property name="ca-postsave-uid" type="s" access="read"/>
+  <property name="scep-cipher" type="s" access="readwrite"/>
+  <property name="scep-digest" type="s" access="readwrite"/>
+  <property name="scep-ca-identifier" type="s" access="readwrite"/>
+  <property name="scep-ca-capabilities" type="as" access="read"/>
+  <property name="scep-ra-cert" type="s" access="read"/>
+  <property name="scep-ca-cert" type="s" access="read"/>
+  <property name="scep-other-certs" type="s" access="read"/>
+ </interface>
+</node>
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_config_file_path ]
+$tmpdir/cas/20180327134236-3
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_nickname ]
+dogtag-ipa-renew-agent
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_is_default ]
+0
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_type ]
+EXTERNAL
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_serial ]
+None
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_location ]
+$libexecdir/dogtag-ipa-renew-agent-submit
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.get_issuer_names ]
+dbus.Array([], signature=dbus.Signature('s'))
+
+[ /org/fedorahosted/certmonger/cas/CA4: org.fedorahosted.certmonger.ca.refresh ]
+1
+
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 9977aed..67c9366 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -6,6 +6,8 @@ CLEANFILES = \
 	001-keyiread-ec/actual.err \
 	001-keyiread-rsa/actual.out \
 	001-keyiread-rsa/actual.err \
+	001-keyiread-mldsa/actual.out \
+	001-keyiread-mldsa/actual.err \
 	002-keygen/actual.out \
 	002-keygen/actual.err \
 	002-keygen-dbm/actual.out \
@@ -18,12 +20,16 @@ CLEANFILES = \
 	002-keygen-ec/actual.err \
 	002-keygen-rsa/actual.out \
 	002-keygen-rsa/actual.err \
+	002-keygen-mldsa/actual.err \
+	002-keygen-mldsa/actual.out \
 	003-csrgen/actual.out \
 	003-csrgen/actual.err \
 	003-csrgen-ec/actual.out \
 	003-csrgen-ec/actual.err \
 	003-csrgen-rsa/actual.out \
 	003-csrgen-rsa/actual.err \
+	003-csrgen-mldsa/actual.out \
+	003-csrgen-mldsa/actual.err \
 	004-selfsign/actual.out \
 	004-selfsign/actual.err \
 	004-selfsign-ec/actual.out \
@@ -145,6 +151,8 @@ EXTRA_DIST = \
 	001-keyiread-ec/expected.out.2 \
 	001-keyiread-ec/expected.out.3 \
 	001-keyiread-ec/expected.out.4 \
+	001-keyiread-mldsa/run.sh \
+	001-keyiread-mldsa/expected.out \
 	002-keygen/prequal.sh \
 	002-keygen/run.sh \
 	002-keygen/expected.out \
@@ -164,12 +172,17 @@ EXTRA_DIST = \
 	002-keygen-ec/expected.out.2 \
 	002-keygen-ec/expected.out.3 \
 	002-keygen-ec/expected.out.4 \
+	002-keygen-mldsa/prequal.sh \
+	002-keygen-mldsa/run.som \
+	002-keygen-mldsa/expected.out \
 	003-csrgen/run.sh \
 	003-csrgen/expected.out \
 	003-csrgen-rsa/run.sh \
 	003-csrgen-rsa/expected.out \
 	003-csrgen-ec/run.sh \
 	003-csrgen-ec/expected.out \
+	003-csrgen-mldsa/run.sh \
+	003-csrgen-mldsa/expected.out \
 	004-selfsign/run.sh \
 	004-selfsign/expected.out \
 	004-selfsign-rsa/run.sh \
@@ -295,6 +308,7 @@ EXTRA_DIST = \
 	027-hooks/expected.out \
 	027-hooks/run.sh \
 	028-dbus/expected.out 028-dbus/expected.out.nodsa \
+	028-dbus/expected.out.nomldsa \
 	028-dbus/entry 028-dbus/bogus-entry \
 	028-dbus/prequal.sh \
 	028-dbus/run.sh \
@@ -450,6 +464,13 @@ subdirs += \
 	004-selfsign-ec
 endif
 
+if HAVE_ML_DSA
+subdirs += \
+	001-keyiread-mldsa \
+	002-keygen-mldsa \
+	003-csrgen-mldsa
+endif
+
 check: all
 	if [ ! -e $$HOME/.rnd ] ; then \
 		openssl rand -writerand $$HOME/.rnd; \

From 2bf41b5b20eae95560adfe1453670b834b9c260d Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 15 2026 18:41:18 +0000
Subject: [PATCH 6/6] Allow requesting a ML-DSA key using a strength


I was discouraged from treating the numeric portion of ML-DSA
algorithms as a "strength" per-se but it seems like other
crypto products are doing it so I'm adding it for consistentcy.

Interally certmonger will use the full name if a key type of
ML-DSA is requested. The default will be ML-DSA-65.

While implementing this I discovered there is a public function
for identifying the ML key type in NSS given a private key blob
so I switched that out in this as well.

---

diff --git a/configure.ac b/configure.ac
index 47e31c0..dd79228 100644
--- a/configure.ac
+++ b/configure.ac
@@ -721,18 +721,17 @@ if ! ${configure_dist_target_only:-false} ; then
 		LIBSsave="$LIBS"
 		CFLAGS="$OPENSSL_CFLAGS $CFLAGS"
 		LIBS="$OPENSSL_LIBS $LIBS"
+		can_ml_dsa=true
+		PKG_CHECK_MODULES([OPENSSL],[openssl >= 3.5],,[can_ml_dsa=false])
 		AC_CHECK_LIB(crypto,EVP_PKEY_Q_keygen)
+		AC_CHECK_DECLS(EVP_PKEY_ML_DSA_65,,can_ml_dsa=false,[#include <openssl/evp.h>])
 		CFLAGS="$NSS_CFLAGS $CFLAGS"
 		LIBS="$NSS_LIBS $LIBS"
-		AC_CHECK_TYPES([CKM_ML_DSA_KEY_PAIR_GEN],,,[[#include <pkcs11t.h>]])
+		PKG_CHECK_MODULES([NSS],[nss >= 3.119],,[can_ml_dsa=false])
+		AC_CHECK_DECLS([CKM_ML_DSA_KEY_PAIR_GEN],,can_ml_dsa=false,[#include <pkcs11t.h>])
 		AC_CHECK_FUNCS(PK11_GenerateKeyPair)
-		AC_CHECK_FUNCS(NSS_OptionGet NSS_OptionSet)
 		CFLAGS="$CFLAGSsave"
 		LIBS="$LIBSsave"
-		can_ml_dsa=true
-		if test x$HAVE_CKM_ML_DSA_KEY_PAIR_GEN = xno ; then
-			can_ml_dsa=false
-		fi
 		if test x$ac_cv_lib_crypto_EVP_PKEY_Q_keygen = xno ; then
 			can_ml_dsa=false
 		fi
diff --git a/src/getcert.c b/src/getcert.c
index a835328..0771cb8 100644
--- a/src/getcert.c
+++ b/src/getcert.c
@@ -896,6 +896,7 @@ request(const char *argv0, int argc, const char **argv)
 			    && (strcasecmp(poptarg, "EC") != 0)
 #endif
 #ifdef CM_ENABLE_ML_DSA
+			    && (strcasecmp(poptarg, "ML-DSA") != 0)
 			    && (strcasecmp(poptarg, "ML-DSA-44") != 0)
 			    && (strcasecmp(poptarg, "ML-DSA-65") != 0)
 			    && (strcasecmp(poptarg, "ML-DSA-87") != 0)
@@ -912,7 +913,7 @@ request(const char *argv0, int argc, const char **argv)
 				printf(" EC");
 #endif
 #ifdef CM_ENABLE_ML_DSA
-				printf(" ML-DSA-44 ML-DA-65 ML-DSA-87");
+				printf(" ML-DSA ML-DSA-44 ML-DA-65 ML-DSA-87");
 #endif
 				printf("\n");
 				return 1;
@@ -1141,6 +1142,25 @@ request(const char *argv0, int argc, const char **argv)
 		}
 	}
 #endif
+#ifdef CM_ENABLE_ML_DSA
+	if ((keytype != NULL) && (strcasecmp(keytype, "ML-DSA") == 0)) {
+		talloc_free(keytype);
+		switch(keysize) {
+		case 44:
+			keytype = talloc_strdup(globals.tctx, "ML-DSA-44");
+			break;
+		case 65:
+			keytype = talloc_strdup(globals.tctx, "ML-DSA-65");
+			break;
+		case 87:
+			keytype = talloc_strdup(globals.tctx, "ML-DSA-87");
+			break;
+		default:
+			keytype = talloc_strdup(globals.tctx, "ML-DSA-65");
+			break;
+		}
+	}
+#endif
 	if ((dbdir != NULL) && (nickname != NULL)) {
 		param[i].key = "KEY_STORAGE";
 		param[i].value_type = cm_tdbusm_dict_s;
@@ -2711,7 +2731,10 @@ rekey_or_resubmit(const char *argv0, const char *category, int argc,
 			    && (strcasecmp(poptarg, "EC") != 0)
 #endif
 #ifdef CM_ENABLE_ML_DSA
+			    && (strcasecmp(poptarg, "ML-DSA") != 0)
+			    && (strcasecmp(poptarg, "ML-DSA-44") != 0)
 			    && (strcasecmp(poptarg, "ML-DSA-65") != 0)
+			    && (strcasecmp(poptarg, "ML-DSA-87") != 0)
 #endif
 			    ) {
 				printf(_("No support for generating \"%s\" keys.\n"),
diff --git a/src/keygen-n.c b/src/keygen-n.c
index 45b1426..800d499 100644
--- a/src/keygen-n.c
+++ b/src/keygen-n.c
@@ -36,6 +36,7 @@
 #include <keythi.h>
 #include <prerror.h>
 #include <secerr.h>
+#include <cryptohi.h>
 
 #include <talloc.h>
 
@@ -274,15 +275,17 @@ cm_keygen_n_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 		pmech = CKM_EC_KEY_PAIR_GEN;
 		break;
 #endif
-#ifdef CM_ENABLE_ML_DSA:
+#ifdef CM_ENABLE_ML_DSA
 	case cm_key_ml_dsa_44:
 		cm_requested_key_size = ML_DSA_44_PRIVATEKEY_LEN;
 		mech = CKM_ML_DSA_KEY_PAIR_GEN;
 		pmech = CKM_ML_DSA_KEY_PAIR_GEN;
+		break;
 	case cm_key_ml_dsa_65:
 		cm_requested_key_size = ML_DSA_65_PRIVATEKEY_LEN;
 		mech = CKM_ML_DSA_KEY_PAIR_GEN;
 		pmech = CKM_ML_DSA_KEY_PAIR_GEN;
+		break;
 	case cm_key_ml_dsa_87:
 		cm_requested_key_size = ML_DSA_87_PRIVATEKEY_LEN;
 		mech = CKM_ML_DSA_KEY_PAIR_GEN;
@@ -550,7 +553,7 @@ retry_gen:
 		/* no parameters to generate */
 		break;
 #endif
-#ifdef CM_ENABLE_ML_DSA:
+#ifdef CM_ENABLE_ML_DSA
 	case cm_key_ml_dsa_44:
 	case cm_key_ml_dsa_65:
 	case cm_key_ml_dsa_87:
@@ -592,20 +595,23 @@ retry_gen:
 		params = &ec_params;
 		break;
 #endif
-#ifdef CM_ENABLE_ML_DSA:
+#ifdef CM_ENABLE_ML_DSA
 	case cm_key_ml_dsa_44:
 		memset(&ml_params, 0, sizeof(ml_params));
 		ml_params = CKP_ML_DSA_44;
+		cm_log(0, "params set to 44\n");
 		params = &ml_params;
 		break;
 	case cm_key_ml_dsa_65:
 		memset(&ml_params, 0, sizeof(ml_params));
 		ml_params = CKP_ML_DSA_65;
+		cm_log(0, "params set to 65\n");
 		params = &ml_params;
 		break;
 	case cm_key_ml_dsa_87:
 		memset(&ml_params, 0, sizeof(ml_params));
 		ml_params = CKP_ML_DSA_87;
+		cm_log(0, "params set to 87\n");
 		params = &ml_params;
 		break;
 #endif
diff --git a/src/keyiread-n.c b/src/keyiread-n.c
index 7f9cdf0..254ed36 100644
--- a/src/keyiread-n.c
+++ b/src/keyiread-n.c
@@ -47,65 +47,13 @@
 #include "store-int.h"
 #include "subproc.h"
 #include "util-n.h"
+#include "prefs-n.h"
 
 #ifndef PRIVKEY_LIST_EMPTY
 #define PRIVKEY_LIST_EMPTY(l) PRIVKEY_LIST_END(PRIVKEY_LIST_HEAD(l), l)
 #endif
 
 
-/* FIXME: replace when NSS exposes the capability to get the alg */
-/* look up the an oid by string.
- *
- * The following code was ripped from NSS.
- * */
-#include <cryptohi.h>
-
-/* If SECKEY_GetParameterSet() is exported then this can be replaced */
-const char *
-cm_GetSignatureAlgorithmFromPrivateKey(SECKEYPrivateKey *privKey)
-{
-    SECItem item;
-    CK_ULONG paramSet;
-    SECStatus rv;
-
-    /* don't modify hashAlg if we aren't a DSA key */
-    if (privKey->keyType != mldsaKey) {
-	/* should not get here */
-        return "Invalid ML-DSA key";
-    }
-
-    rv = PK11_ReadRawAttribute(PK11_TypePrivKey, privKey,
-                                CKA_PARAMETER_SET, &item);
-
-    if (rv != SECSuccess) {
-        return "UNKNOWN";
-    }
-    if (item.len != sizeof (paramSet)) {
-        PORT_Free(item.data);
-        PORT_SetError(SEC_ERROR_INVALID_KEY);
-        return "UNKNOWN";
-    }
-    paramSet = *(CK_ULONG *)item.data;
-    PORT_Free(item.data);
-    switch  (paramSet) {
-        case CKP_ML_DSA_44:
-            return "ML-DSA-44";
-            break;
-        case CKP_ML_DSA_65:
-            return "ML-DSA-65";
-            break;
-        case CKP_ML_DSA_87:
-            return "ML-DSA-87";
-            break;
-        default:
-            return "UNKNOWN";
-            break;
-    }
-    return "UNKNOWN";
-}
-
-/* end FIXME */
-
 struct cm_keyiread_state {
 	struct cm_keyiread_state_pvt pvt;
 	struct cm_store_entry *entry;
@@ -577,8 +525,8 @@ cm_keyiread_n_main(int fd, struct cm_store_ca *ca, struct cm_store_entry *entry,
 			alg = "EC";
 			break;
 		case mldsaKey:
-			cm_log(3, "Key is an ML-DSA key.\n");
-			alg = cm_GetSignatureAlgorithmFromPrivateKey(keys->privkey);
+			cm_log(3, "Key is an ML-DSA key %s.\n", alg);
+			alg = cm_GetSignatureMLAlgorithmFromPrivateKey(keys->privkey);
 			break;
 		case nullKey:
 		default:
diff --git a/src/prefs-n.c b/src/prefs-n.c
index d080f0a..49e443b 100644
--- a/src/prefs-n.c
+++ b/src/prefs-n.c
@@ -19,6 +19,7 @@
 
 #include <nss.h>
 #include <nss.h>
+#include <cryptohi.h>
 #include <keyhi.h>
 #include <keythi.h>
 #include <pk11pub.h>
@@ -26,6 +27,7 @@
 
 #include "prefs.h"
 #include "prefs-n.h"
+#include "util-n.h"
 
 unsigned int
 cm_prefs_nss_sig_alg(SECKEYPrivateKey *pkey)
@@ -118,41 +120,19 @@ cm_prefs_nss_sig_alg(SECKEYPrivateKey *pkey)
 		}
 #ifdef CM_ENABLE_ML_DSA
 	case mldsaKey:
-		/* This is a simplified version of
-		 * cm_GetSignatureAlgorithmFromPrivateKey from keyiread-n.c.
-		 * Once the NSS function is exported that can be moved to
-		 * something like util-n.c and be generalized. For now I
-		 * duplicate.
-		 */
-		SECItem item;
-		CK_ULONG paramSet;
-		SECStatus rv;
+		const char *alg = cm_GetSignatureMLAlgorithmFromPrivateKey(pkey);
 
-		rv = PK11_ReadRawAttribute(PK11_TypePrivKey, pkey, CKA_PARAMETER_SET, &item);
-		if (rv != SECSuccess) {
-			return SEC_OID_UNKNOWN;
-		}
-
-		if (item.len != sizeof(paramSet)) {
-			PORT_Free(item.data);	
-			PORT_SetError(SEC_ERROR_INVALID_KEY);
-			return SEC_OID_UNKNOWN;
-		}
-		paramSet = *(CK_ULONG *)item.data;
-		PORT_Free(item.data);
-		switch (paramSet) {
-		case CKP_ML_DSA_44:
+		if (strcasecmp(alg, "ML-DSA-44") == 0) {
 			return SEC_OID_ML_DSA_44_SIGNATURE;
-			break;
-		case CKP_ML_DSA_65:
+		}
+		else if (strcasecmp(alg, "ML-DSA-65") == 0) {
 			return SEC_OID_ML_DSA_65_SIGNATURE;
-			break;
-		case CKP_ML_DSA_87:
+		}
+		else if (strcasecmp(alg, "ML-DSA-87") == 0) {
 			return SEC_OID_ML_DSA_87_SIGNATURE;
-			break;
-		default:
+		}
+		else {
 			return SEC_OID_UNKNOWN;
-			break;
 		}
 #endif
 	default:
@@ -211,3 +191,33 @@ cm_prefs_nss_dig_alg_len(void)
 		break;
 	}
 }
+
+const char *
+cm_GetSignatureMLAlgorithmFromPrivateKey(SECKEYPrivateKey *privKey)
+{
+	SECOidTag hashAlgTag = SEC_OID_UNKNOWN;
+	SECOidTag tag = SEC_GetSignatureAlgorithmOidTagByKey(privKey, NULL, hashAlgTag);
+
+	if (privKey->keyType != mldsaKey) {
+		return "Not a ML-DSA key";
+	}
+
+	if (tag == SEC_OID_UNKNOWN) {
+	   return "UNKNOWN";
+	}
+
+	switch (tag) {
+		case SEC_OID_ML_DSA_44_PUBLIC_KEY:
+			return "ML-DSA-44";
+			break;
+		case SEC_OID_ML_DSA_65_PUBLIC_KEY:
+			return "ML-DSA-65";
+			break;
+		case SEC_OID_ML_DSA_87_PUBLIC_KEY:
+			return "ML-DSA-87";
+			break;
+		default:
+			return "UNKNOWN";
+			break;
+	}
+}
diff --git a/src/prefs-n.h b/src/prefs-n.h
index dff0bc3..40461f3 100644
--- a/src/prefs-n.h
+++ b/src/prefs-n.h
@@ -22,4 +22,6 @@ unsigned int cm_prefs_nss_sig_alg(SECKEYPrivateKey *pkey);
 unsigned int cm_prefs_nss_dig_alg(void);
 unsigned int cm_prefs_nss_dig_alg_len(void);
 
+const char * cm_GetSignatureMLAlgorithmFromPrivateKey(SECKEYPrivateKey *privKey);
+
 #endif

