diff --git a/certs/mldsa/mldsa44-cert.der b/certs/mldsa/mldsa44-cert.der new file mode 100644 index 00000000..d40ff06e Binary files /dev/null and b/certs/mldsa/mldsa44-cert.der differ diff --git a/certs/mldsa/mldsa44-cert.pem b/certs/mldsa/mldsa44-cert.pem new file mode 100644 index 00000000..5d175f3b --- /dev/null +++ b/certs/mldsa/mldsa44-cert.pem @@ -0,0 +1,88 @@ +-----BEGIN CERTIFICATE----- +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 +EQsuP3jUjzIWSYC4yxQe0LCGEC4EgtNlcGJjmHLv4dwMDbi8RwM/Wy3gGemMbIWo +Gy1dMNNKH6rdg5Czy00v7+EYSlNUlDglYJo8161r2k0queoQ7n8tPtatyoyLbluK +otdL2hBd+ewuUMqe+2zgApTAOGplfu+rDvwnw1aCmxAAhJlF7FFWRHTF5Ag4labX +3XJ9SBeWXXbD01LP0tYx59WsVY+aZ3CNBCmVlc4oepkwxi602WnyLvDpYzEKp3LY +x+scuPvU9nMuvP1S/mqf5guG5uPd9gPRpkI33raDPDU9tw7aPq7r6h/uTvJRK3Yz +IUqBaMfadVWdmo9Rm0TzWJdfj74bIAWwCR9M3y9HaRiv6keawqbq1VOsqWIFgzQy +C2W4Ni8+dh9LycKHJaVTj95baJRkFS+nIoZF5nwoJysPBinT7ZZPawciOhaZykue +agG5372BYYvCMrt40zmULlbDTbU+3loDiX/2HVqkQ7M5o6gSzThhN9O+V6sISSEH +LTM9aG1zep7Bz+v1DBgtMkFPXIKJlafS3voZPj9ITVNWWGB6laTCx8gAAQUGCihb +ZWeDhIi2u8fb4OzzAAAAAAAAAAAAAAAAAAAAAAAAAA0bKj0= +-----END CERTIFICATE----- diff --git a/certs/mldsa/mldsa44-key.pem b/certs/mldsa/mldsa44-key.pem new file mode 100644 index 00000000..61790305 --- /dev/null +++ b/certs/mldsa/mldsa44-key.pem @@ -0,0 +1,57 @@ +-----BEGIN PRIVATE KEY----- +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 +-----END PRIVATE KEY----- diff --git a/certs/mldsa/mldsa44-keyPub.der b/certs/mldsa/mldsa44-keyPub.der new file mode 100644 index 00000000..83b7f8b4 Binary files /dev/null and b/certs/mldsa/mldsa44-keyPub.der differ diff --git a/certs/mldsa/mldsa44_pub-spki.der b/certs/mldsa/mldsa44_pub-spki.der new file mode 100644 index 00000000..15858905 Binary files /dev/null and b/certs/mldsa/mldsa44_pub-spki.der differ diff --git a/certs/mldsa/mldsa44_seed-only.der b/certs/mldsa/mldsa44_seed-only.der new file mode 100644 index 00000000..82d0a738 Binary files /dev/null and b/certs/mldsa/mldsa44_seed-only.der differ diff --git a/certs/mldsa/mldsa65_pub-spki.der b/certs/mldsa/mldsa65_pub-spki.der new file mode 100644 index 00000000..02381d7b Binary files /dev/null and b/certs/mldsa/mldsa65_pub-spki.der differ diff --git a/certs/mldsa/mldsa65_seed-only.der b/certs/mldsa/mldsa65_seed-only.der new file mode 100644 index 00000000..f7e0ba69 Binary files /dev/null and b/certs/mldsa/mldsa65_seed-only.der differ diff --git a/certs/mldsa/mldsa87_pub-spki.der b/certs/mldsa/mldsa87_pub-spki.der new file mode 100644 index 00000000..517c3cc9 Binary files /dev/null and b/certs/mldsa/mldsa87_pub-spki.der differ diff --git a/certs/mldsa/mldsa87_seed-only.der b/certs/mldsa/mldsa87_seed-only.der new file mode 100644 index 00000000..6ec75512 Binary files /dev/null and b/certs/mldsa/mldsa87_seed-only.der differ diff --git a/certs/renew.sh b/certs/renew.sh index ad0363bf..dedd3445 100755 --- a/certs/renew.sh +++ b/certs/renew.sh @@ -41,6 +41,19 @@ cp $CERTS_DIR/intermediate/ca-int-cert.pem . cp $CERTS_DIR/intermediate/ca-int2-cert.pem . cp $CERTS_DIR/intermediate/client-int-cert.pem . +echo "Copy over ML-DSA keys and certs" +cp $CERTS_DIR/mldsa/mldsa44-cert.pem ./mldsa/. +cp $CERTS_DIR/mldsa/mldsa44-cert.der ./mldsa/. +cp $CERTS_DIR/mldsa/mldsa44-key.pem ./mldsa/. +cp $CERTS_DIR/mldsa/mldsa44_pub-spki.der ./mldsa/. +cp $CERTS_DIR/mldsa/mldsa44_seed-only.der ./mldsa/. +cp $CERTS_DIR/mldsa/mldsa65_pub-spki.der ./mldsa/. +cp $CERTS_DIR/mldsa/mldsa65_seed-only.der ./mldsa/. +cp $CERTS_DIR/mldsa/mldsa87_pub-spki.der ./mldsa/. +cp $CERTS_DIR/mldsa/mldsa87_seed-only.der ./mldsa/. +openssl x509 -in ./mldsa/mldsa44-cert.der -inform der -noout -pubkey | \ + openssl pkey -pubin -outform der -out ./mldsa/mldsa44-keyPub.der + # client is decoupled from wolfSSL example client #echo "Additional update of client example source code" #cp $CERTS_DIR/../examples/client/client.c ../src/client/client.c diff --git a/src/pkcs/clu_pkcs8.c b/src/pkcs/clu_pkcs8.c index 9c4c7049..eeb5bbad 100644 --- a/src/pkcs/clu_pkcs8.c +++ b/src/pkcs/clu_pkcs8.c @@ -288,7 +288,7 @@ int wolfCLU_PKCS8(int argc, char** argv) WOLFCLU_LOG(WOLFCLU_E0, "Error writing from private key to pem"); ret = WOLFCLU_FATAL_ERROR; - } + } } } diff --git a/src/pkey/clu_pkey.c b/src/pkey/clu_pkey.c index f171d05c..fe17a79a 100644 --- a/src/pkey/clu_pkey.c +++ b/src/pkey/clu_pkey.c @@ -56,6 +56,86 @@ static void wolfCLU_pKeyHelp(void) } +#ifdef HAVE_DILITHIUM +/* Get the DER encoding of an ML-DSA private key + * returns the size of *out on success + */ +static int _MLDSApKeyToPriKey(WOLFSSL_EVP_PKEY* pkey, unsigned char** out) +{ + int derSz; + + if (pkey == NULL || out == NULL) { + return BAD_FUNC_ARG; + } + + derSz = wolfSSL_i2d_PrivateKey(pkey, out); + if (derSz <= 0) { + wolfCLU_LogError("Unable to get DER from ML-DSA private key"); + return WOLFCLU_FATAL_ERROR; + } + return derSz; +} + + +/* Get the DER encoding of an ML-DSA public key + * returns the size of *out on success + */ +static int _MLDSApKeyToPubKey(WOLFSSL_EVP_PKEY* pkey, unsigned char** out) +{ + int derSz; + + if (pkey == NULL || out == NULL) { + return BAD_FUNC_ARG; + } + + derSz = wolfSSL_i2d_PUBKEY(pkey, out); + if (derSz <= 0) { + wolfCLU_LogError("Unable to get DER from ML-DSA public key"); + return WOLFCLU_FATAL_ERROR; + } + return derSz; +} + + +/* Write an ML-DSA key out as PEM. + * return WOLFSSL_SUCCESS on success + */ +static int _MLDSApKeyPEMtoKey(WOLFSSL_BIO* bio, WOLFSSL_EVP_PKEY* pkey, + int isPrivate) +{ + unsigned char* der = NULL; + int derSz; + int ret = WOLFCLU_FAILURE; + + if (isPrivate) { + derSz = _MLDSApKeyToPriKey(pkey, &der); + } + else { + derSz = _MLDSApKeyToPubKey(pkey, &der); + } + + if (derSz > 0) { + if (isPrivate) { + ret = wolfCLU_printDerPriKey(bio, der, derSz, + PKCS8_PRIVATEKEY_TYPE); + } + else { + ret = wolfCLU_printDerPubKey(bio, der, derSz); + } + } + + if (der != NULL) { + if (isPrivate) { + wolfCLU_ForceZero(der, derSz); + } + XFREE(der, HEAP_HINT, DYNAMIC_TYPE_OPENSSL); + } + + return (ret == WOLFCLU_SUCCESS) ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE; +} +#endif /* HAVE_DILITHIUM */ + + /* helper function for ECC EVP_PKEY * return WOLFSSL_SUCCESS on success */ static int _ECCpKeyPEMtoKey(WOLFSSL_BIO* bio, WOLFSSL_EVP_PKEY* pkey, @@ -135,7 +215,11 @@ static int wolfCLU_pKeyPEMtoPubKey(WOLFSSL_BIO* bio, WOLFSSL_EVP_PKEY* pkey) case EVP_PKEY_EC: ret = _ECCpKeyPEMtoKey(bio, pkey, 0); break; - + #ifdef HAVE_DILITHIUM + case EVP_PKEY_DILITHIUM: + ret = _MLDSApKeyPEMtoKey(bio, pkey, 0); + break; + #endif case EVP_PKEY_DSA: FALL_THROUGH; default: @@ -244,7 +328,11 @@ int wolfCLU_pKeyPEMtoPriKey(WOLFSSL_BIO* bio, WOLFSSL_EVP_PKEY* pkey) case EVP_PKEY_EC: ret = _ECCpKeyPEMtoKey(bio, pkey, 1); break; - + #ifdef HAVE_DILITHIUM + case EVP_PKEY_DILITHIUM: + ret = _MLDSApKeyPEMtoKey(bio, pkey, 1); + break; + #endif case EVP_PKEY_DSA: FALL_THROUGH; default: @@ -369,6 +457,12 @@ int wolfCLU_pKeytoPubKey(WOLFSSL_EVP_PKEY* pkey, unsigned char** out) ret = wolfCLU_pKeyToKeyECC(pkey, out, 0); break; + #ifdef HAVE_DILITHIUM + case EVP_PKEY_DILITHIUM: + ret = _MLDSApKeyToPubKey(pkey, out); + break; + #endif + default: wolfCLU_LogError("unknown / unsupported key type"); ret = USER_INPUT_ERROR; @@ -402,6 +496,12 @@ int wolfCLU_pKeytoPriKey(WOLFSSL_EVP_PKEY* pkey, unsigned char** out) ret = wolfCLU_pKeyToKeyECC(pkey, out, 1); break; + #ifdef HAVE_DILITHIUM + case EVP_PKEY_DILITHIUM: + ret = _MLDSApKeyToPriKey(pkey, out); + break; + #endif + default: wolfCLU_LogError("unknown / unsupported key type"); ret = USER_INPUT_ERROR; diff --git a/tests/pkcs/pkcs8-test.py b/tests/pkcs/pkcs8-test.py index 2a23aa3d..67a64d48 100644 --- a/tests/pkcs/pkcs8-test.py +++ b/tests/pkcs/pkcs8-test.py @@ -10,6 +10,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, is_fips, run_wolfssl, test_main +ML_DSA_SETS = (44, 65, 87) class Pkcs8Test(unittest.TestCase): @@ -117,5 +118,88 @@ def test_fail_wrong_format(self): self.assertNotEqual(r.returncode, 0) +class Pkcs8MlDsaTest(unittest.TestCase): + """ML-DSA PEM<->DER conversion through `wolfssl pkcs8`.""" + + @classmethod + def setUpClass(cls): + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + r = run_wolfssl("pkcs8", "-in", + os.path.join(CERTS_DIR, "server-key.pem")) + if "Recompile wolfSSL with PKCS8 support" in (r.stdout + r.stderr): + raise unittest.SkipTest("PKCS8 support not compiled in") + + r = run_wolfssl("genkey", "ml-dsa", "-level", "2", "-out", + "mldsa8-probe", "-output", "keypair", "-outform", "pem") + for path in ("mldsa8-probe.priv", "mldsa8-probe.pub"): + if os.path.exists(path): + os.remove(path) + if r.returncode != 0 or "not enabled" in (r.stdout + r.stderr): + raise unittest.SkipTest("ML-DSA support not compiled in") + + def _cleanup(self, *files): + for f in files: + self.addCleanup(lambda p=f: os.remove(p) + if os.path.exists(p) else None) + + def _file_matrix(self): + """Yield (path, stem) for the seed-form private key of each ML-DSA + parameter set. stem names output files.""" + for n in ML_DSA_SETS: + path = os.path.join(CERTS_DIR, "mldsa", "mldsa%d_seed-only.der" % n) + if not os.path.isfile(path): + self.fail("%s not found" % path) + yield path, "pkcs8-mldsa%d-priv" % n + + def _assert_der(self, path): + """A DER output must not be PEM text that slipped through.""" + with open(path, "rb") as f: + head = f.read(1) + self.assertEqual(head, b"\x30", + "%s is not DER (expected a SEQUENCE tag)" % path) + + def test_round_trip_private(self): + """ML-DSA private key survives DER->PEM->DER at every level.""" + for src, stem in self._file_matrix(): + with self.subTest(key=src): + pem, der = stem + ".pem", stem + ".der" + self._cleanup(pem, der) + + r = run_wolfssl("pkcs8", "-in", src, "-inform", "der", + "-outform", "pem", "-out", pem) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("pkcs8", "-in", pem, "-inform", "pem", + "-outform", "der", "-out", der) + self.assertEqual(r.returncode, 0, r.stderr) + self._assert_der(der) + + self.assertTrue(filecmp.cmp(src, der, shallow=False), + "%s changed over DER->PEM->DER" % src) + + def test_der_matches_pkey(self): + """ML-DSA DER from pkcs8 and from pkey are identical.""" + for src, stem in self._file_matrix(): + with self.subTest(key=src): + der8, der_pkey = stem + ".pkcs8.der", stem + ".pkey.der" + self._cleanup(der8, der_pkey) + + r = run_wolfssl("pkcs8", "-in", src, "-inform", "der", + "-outform", "der", "-out", der8) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("pkey", "-in", src, "-inform", "der", + "-outform", "der", "-out", der_pkey) + self.assertEqual(r.returncode, 0, r.stderr) + + self.assertTrue(filecmp.cmp(der8, der_pkey, shallow=False), + "pkcs8 and pkey disagree on the ML-DSA DER") + + if __name__ == "__main__": test_main() diff --git a/tests/pkey/pkey-test.py b/tests/pkey/pkey-test.py index 91b528d1..e8bb1d73 100644 --- a/tests/pkey/pkey-test.py +++ b/tests/pkey/pkey-test.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 """pkey tests for wolfCLU.""" +import base64 +import filecmp import os import sys import unittest @@ -8,6 +10,8 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from wolfclu_test import CERTS_DIR, run_wolfssl, test_main +ML_DSA_SETS = (44, 65, 87) + ECC_PUBKEY_PEM = """\ -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuzOsTCdQSsZKpQTDPN6fNttyLc6U @@ -108,5 +112,152 @@ def test_out_to_file(self): self.assertIn("BEGIN PUBLIC KEY", f.read()) +class PkeyMlDsaTest(unittest.TestCase): + """ML-DSA PEM<->DER conversion through `wolfssl pkey`.""" + + @classmethod + def setUpClass(cls): + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + r = run_wolfssl("genkey", "ml-dsa", "-level", "2", "-out", + "mldsa-probe", "-output", "keypair", "-outform", "pem") + for path in ("mldsa-probe.priv", "mldsa-probe.pub"): + if os.path.exists(path): + os.remove(path) + if r.returncode != 0 or "not enabled" in (r.stdout + r.stderr): + raise unittest.SkipTest("ML-DSA support not compiled in") + + def _cleanup(self, *files): + for f in files: + self.addCleanup(lambda p=f: os.remove(p) + if os.path.exists(p) else None) + + def _genkey(self, level, outform="pem"): + """Generate an ML-DSA keypair, returning the paths as a tuple: + (private, public).""" + stem = "mldsa-l%d" % level + priv, pub = stem + ".priv", stem + ".pub" + self._cleanup(priv, pub) + + r = run_wolfssl("genkey", "ml-dsa", "-level", str(level), "-out", stem, + "-output", "keypair", "-outform", outform) + self.assertEqual(r.returncode, 0, r.stderr) + return priv, pub + + def _assert_der(self, path): + """A DER output must not be PEM text that slipped through.""" + with open(path, "rb") as f: + head = f.read(1) + self.assertEqual(head, b"\x30", + "%s is not DER (expected a SEQUENCE tag)" % path) + + def _pem_to_der(self, path): + """Decode a PUBLIC KEY PEM file to its DER bytes.""" + with open(path, "r") as f: + lines = f.read().splitlines() + self.assertEqual(lines[0], "-----BEGIN PUBLIC KEY-----") + return base64.b64decode("".join(l for l in lines + if not l.startswith("-----"))) + + def _file_matrix(self): + """Yield (path, stem, pubin) for the seed-form private key and SPKI + public key of each ML-DSA parameter set. stem names output files.""" + for n in ML_DSA_SETS: + for name, kind, pubin in ( + ("mldsa%d_seed-only.der" % n, "priv", ()), + ("mldsa%d_pub-spki.der" % n, "pub", ("-pubin",))): + path = os.path.join(CERTS_DIR, "mldsa", name) + if not os.path.isfile(path): + self.fail("%s not found" % path) + yield path, "pkey-mldsa%d-%s" % (n, kind), pubin + + def test_round_trip(self): + """ML-DSA private and public keys survive DER->PEM->DER.""" + for src, stem, pubin in self._file_matrix(): + with self.subTest(key=src): + pem, der = stem + ".pem", stem + ".der" + self._cleanup(pem, der) + + r = run_wolfssl("pkey", *pubin, "-in", src, "-inform", "der", + "-outform", "pem", "-out", pem) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("pkey", *pubin, "-in", pem, "-inform", "pem", + "-outform", "der", "-out", der) + self.assertEqual(r.returncode, 0, r.stderr) + self._assert_der(der) + + self.assertTrue(filecmp.cmp(src, der, shallow=False), + "%s changed over DER->PEM->DER" % src) + + def test_pem_to_pem_is_identity(self): + """ML-DSA PEM->PEM leaves the key byte-identical.""" + src = os.path.join(CERTS_DIR, "mldsa", "mldsa44-key.pem") + if not os.path.isfile(src): + self.fail("%s not found" % src) + out = "pkey-mldsa44-key.pem" + self._cleanup(out) + + r = run_wolfssl("pkey", "-in", src, "-inform", "pem", + "-outform", "pem", "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(filecmp.cmp(src, out, shallow=False), + "%s changed over PEM->PEM" % src) + + def test_pubout_from_private_key(self): + """ML-DSA -pubout only outputs the public key.""" + priv = os.path.join(CERTS_DIR, "mldsa", "mldsa44-key.pem") + pub = os.path.join(CERTS_DIR, "mldsa", "mldsa44-keyPub.der") + out = "pkey-mldsa44-pubout.der" + self._cleanup(out) + + r = run_wolfssl("pkey", "-in", priv, "-inform", "pem", "-pubout", + "-outform", "der", "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(filecmp.cmp(pub, out, shallow=False), + "-pubout did not produce the matching public key") + + def test_pubout_and_pubin_pem(self): + """ML-DSA -pubout and -pubin PEM->PEM output the public key.""" + priv = os.path.join(CERTS_DIR, "mldsa", "mldsa44-key.pem") + with open(os.path.join(CERTS_DIR, "mldsa", "mldsa44-keyPub.der"), + "rb") as f: + pub = f.read() + pubout, pubin = "pkey-mldsa44-pubout.pem", "pkey-mldsa44-pubin.pem" + self._cleanup(pubout, pubin) + + r = run_wolfssl("pkey", "-in", priv, "-pubout", "-out", pubout) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(self._pem_to_der(pubout), pub, + "-pubout did not produce the matching public key") + + r = run_wolfssl("pkey", "-pubin", "-in", pubout, "-out", pubin) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(self._pem_to_der(pubin), pub, + "-pubin changed the public key") + + def test_genkey_round_trip(self): + """A genkey ML-DSA private key survives DER->PEM->DER.""" + priv, _ = self._genkey(2, "der") + pem, der = priv + ".pem", priv + ".der" + self._cleanup(pem, der) + + r = run_wolfssl("pkey", "-in", priv, "-inform", "der", + "-outform", "pem", "-out", pem) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("pkey", "-in", pem, "-inform", "pem", + "-outform", "der", "-out", der) + self.assertEqual(r.returncode, 0, r.stderr) + self._assert_der(der) + + self.assertTrue(filecmp.cmp(priv, der, shallow=False), + "%s changed over DER->PEM->DER" % priv) + + if __name__ == "__main__": test_main() diff --git a/tests/x509/x509-process-test.py b/tests/x509/x509-process-test.py index 3e63ed86..edbfe116 100644 --- a/tests/x509/x509-process-test.py +++ b/tests/x509/x509-process-test.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 """Tests for wolfssl x509 processing (converted from x509-process-test.sh).""" +import filecmp import os +import re import shutil import subprocess import sys @@ -13,6 +15,13 @@ TESTS_X509_DIR = os.path.dirname(os.path.abspath(__file__)) HAS_OPENSSL = shutil.which("openssl") is not None +# ML-DSA-44 public key and signature sizes, from FIPS 204. +ML_DSA_44_PUB_SZ = 1312 +ML_DSA_44_SIG_SZ = 2420 + +# One line of a printed hex block, e.g. "A4:2A:BB:...:78:" or "...:F0:18". +_HEX_LINE = re.compile(r"(?:[0-9A-Fa-f]{2}:)*[0-9A-Fa-f]{2}:?\Z") + def _check_cert_signature(cert_path, digest, inform="PEM"): """Use OpenSSL to verify the signature on a self-signed certificate. @@ -84,6 +93,27 @@ def _cleanup(*files): os.remove(f) +def _hex_block(lines, start): + """Return the bytes of the hex block printed directly under lines[start]. + + Stops at the first line that is not colon separated hex, so a block that + was cut short is reported as a short byte count rather than skipped. + """ + out = [] + for line in lines[start + 1:]: + text = line.strip() + if not _HEX_LINE.match(text): + break + out.extend(b for b in text.split(":") if b) + return out + + +def _find_line(lines, text): + """Index of the only line containing 'text', -1 when absent.""" + hits = [i for i, l in enumerate(lines) if text in l] + return hits[0] if len(hits) == 1 else -1 + + class TestX509ProcessValid(unittest.TestCase): """run1: valid PEM/DER format conversions and combined file handling.""" @@ -532,5 +562,164 @@ def test_modulus_noout(self): "{}".format(r.returncode)) +class TestX509MlDsaText(unittest.TestCase): + """x509 -text on a pure ML-DSA-44 certificate.""" + + CERT_PEM = os.path.join(CERTS_DIR, "mldsa", "mldsa44-cert.pem") + CERT_DER = os.path.join(CERTS_DIR, "mldsa", "mldsa44-cert.der") + PUB_DER = os.path.join(CERTS_DIR, "mldsa", "mldsa44-keyPub.der") + + @classmethod + def setUpClass(cls): + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + r = run_wolfssl("genkey", "ml-dsa", "-level", "2", "-out", + "mldsa509-probe", "-output", "keypair", + "-outform", "pem") + _cleanup("mldsa509-probe.priv", "mldsa509-probe.pub") + if r.returncode != 0 or "not enabled" in (r.stdout + r.stderr): + raise unittest.SkipTest("ML-DSA support not compiled in") + + def _text(self, *extra): + r = run_wolfssl("x509", "-in", self.CERT_PEM, "-text", "-noout", *extra) + self.assertEqual(r.returncode, 0, r.stderr) + return r.stdout + + def test_names_parameter_set(self): + """The parameter set is named in the key and both algorithm slots.""" + out = self._text() + self.assertEqual(out.count("Signature Algorithm: ML-DSA 44"), 2, + "expected both the tbs and the outer algorithm") + self.assertIn("Public Key Algorithm: ML-DSA 44", out) + self.assertIn("ML-DSA 44 Public-Key:", out) + + def test_prints_whole_key_and_signature(self): + """The key and signature print in full, at their FIPS 204 sizes.""" + lines = self._text().splitlines() + + pub = _find_line(lines, "pub:") + self.assertNotEqual(pub, -1, "no 'pub:' block") + self.assertEqual(len(_hex_block(lines, pub)), ML_DSA_44_PUB_SZ) + + sig = [i for i, l in enumerate(lines) + if l.strip().startswith("Signature Algorithm:")] + self.assertEqual(len(sig), 2, "expected two algorithm lines") + self.assertEqual(len(_hex_block(lines, sig[-1])), ML_DSA_44_SIG_SZ) + + def test_der_text_matches_pem(self): + """The DER form of the cert prints identically to the PEM form.""" + r = run_wolfssl("x509", "-inform", "der", "-text", "-noout", + "-in", self.CERT_DER) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout, self._text(), "DER/PEM text mismatch") + + def test_pubkey_matches_cert_key(self): + """-pubkey prints the cert's SPKI.""" + out, der = "mldsa44-spki.pem", "mldsa44-spki.der" + self.addCleanup(lambda: _cleanup(out, der)) + + r = run_wolfssl("x509", "-in", self.CERT_PEM, "-pubkey", "-noout", + "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + with open(out) as f: + self.assertIn("BEGIN PUBLIC KEY", f.read()) + + r = run_wolfssl("pkey", "-pubin", "-in", out, "-inform", "pem", + "-outform", "der", "-out", der) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertTrue(filecmp.cmp(self.PUB_DER, der, shallow=False), + "-pubkey did not match the cert's public key") + + +class TestX509MlDsaDualAlgText(unittest.TestCase): + """x509 -text on chimera certs with ML-DSA-44 as the alternative alg.""" + + # Primary alg : ECDSA + # Secondary alg: ML-DSA-44 + CERTS = (os.path.join(CERTS_DIR, "ca-chimera-cert.pem"), + os.path.join(CERTS_DIR, "server-chimera-cert.pem")) + + ALT_PUB = "X509v3 Subject Alternative Public Key Info" + ALT_ALG = "X509v3 Alternative Signature Algorithm" + ALT_SIG = "X509v3 Alternative Signature Value" + + @classmethod + def setUpClass(cls): + config_log = os.path.join(".", "config.log") + if os.path.isfile(config_log): + with open(config_log, "r") as f: + if "disable-filesystem" in f.read(): + raise unittest.SkipTest("filesystem support disabled") + + # '-altextend' is only built with WOLFSSL_DUAL_ALG_CERTS and + # HAVE_DILITHIUM, the same pair that gates printing the alt extensions. + r = run_wolfssl("ca", "-help") + if "altextend" not in r.stdout + r.stderr: + raise unittest.SkipTest("altextend not available") + + def _text(self, cert): + r = run_wolfssl("x509", "-in", cert, "-text", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + return r.stdout + + def test_alt_extensions_are_decoded(self): + """ECDSA, then the ML-DSA 44 alt extensions, then ECDSA again.""" + expected = ("Signature Algorithm: sha256WithECDSA", + "Public Key Algorithm: id-ecPublicKey", + self.ALT_PUB, "ML-DSA 44 Public-Key:", + self.ALT_ALG, "ML-DSA 44", + self.ALT_SIG, + "Signature Algorithm: sha256WithECDSA") + for cert in self.CERTS: + with self.subTest(cert=cert): + lines = self._text(cert).splitlines() + prev = -1 + for text in expected: + i = next((j for j, l in enumerate(lines) + if j > prev and text in l), -1) + self.assertNotEqual( + i, -1, "'{}' missing or out of order".format(text)) + prev = i + + def test_alt_key_and_signature_are_whole(self): + """The alternative key and signature print at their full sizes.""" + for cert in self.CERTS: + with self.subTest(cert=cert): + lines = self._text(cert).splitlines() + + # The alt key's own "pub:" header, not the EC key's. + start = _find_line(lines, self.ALT_PUB) + self.assertNotEqual(start, -1, "no alternative key info") + pub = next((i for i, l in enumerate(lines) + if i > start and l.strip() == "pub:"), -1) + self.assertNotEqual(pub, -1, "no alternative 'pub:' block") + self.assertEqual(len(_hex_block(lines, pub)), ML_DSA_44_PUB_SZ) + + sig = _find_line(lines, self.ALT_SIG) + self.assertNotEqual(sig, -1, "no alternative signature value") + self.assertEqual(len(_hex_block(lines, sig)), ML_DSA_44_SIG_SZ) + + def test_der_text_matches_pem(self): + """The alternative extensions survive a round trip through DER.""" + for i, cert in enumerate(self.CERTS): + with self.subTest(cert=cert): + der = "x509-chimera-%d.der" % i + self.addCleanup(lambda p=der: _cleanup(p)) + + r = run_wolfssl("x509", "-in", cert, "-outform", "der", + "-out", der) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("x509", "-inform", "der", "-in", der, + "-text", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(r.stdout, self._text(cert), + "DER/PEM text mismatch") + + if __name__ == "__main__": test_main()