From d86812ba4b081082d935ff6c512215f4f10e69dd Mon Sep 17 00:00:00 2001 From: sebastian-carpenter Date: Tue, 29 Sep 2026 14:44:26 -0600 Subject: [PATCH] mldsa support for pkey, pkcs8, x509 --- certs/mldsa/mldsa44-cert.der | Bin 0 -> 4115 bytes certs/mldsa/mldsa44-cert.pem | 88 ++++++++++++++ certs/mldsa/mldsa44-key.pem | 57 +++++++++ certs/mldsa/mldsa44-keyPub.der | Bin 0 -> 1334 bytes certs/mldsa/mldsa44_pub-spki.der | Bin 0 -> 1334 bytes certs/mldsa/mldsa44_seed-only.der | Bin 0 -> 54 bytes certs/mldsa/mldsa65_pub-spki.der | Bin 0 -> 1974 bytes certs/mldsa/mldsa65_seed-only.der | Bin 0 -> 54 bytes certs/mldsa/mldsa87_pub-spki.der | Bin 0 -> 2614 bytes certs/mldsa/mldsa87_seed-only.der | Bin 0 -> 54 bytes certs/renew.sh | 13 ++ src/pkcs/clu_pkcs8.c | 2 +- src/pkey/clu_pkey.c | 104 +++++++++++++++- tests/pkcs/pkcs8-test.py | 84 +++++++++++++ tests/pkey/pkey-test.py | 151 ++++++++++++++++++++++++ tests/x509/x509-process-test.py | 189 ++++++++++++++++++++++++++++++ 16 files changed, 685 insertions(+), 3 deletions(-) create mode 100644 certs/mldsa/mldsa44-cert.der create mode 100644 certs/mldsa/mldsa44-cert.pem create mode 100644 certs/mldsa/mldsa44-key.pem create mode 100644 certs/mldsa/mldsa44-keyPub.der create mode 100644 certs/mldsa/mldsa44_pub-spki.der create mode 100644 certs/mldsa/mldsa44_seed-only.der create mode 100644 certs/mldsa/mldsa65_pub-spki.der create mode 100644 certs/mldsa/mldsa65_seed-only.der create mode 100644 certs/mldsa/mldsa87_pub-spki.der create mode 100644 certs/mldsa/mldsa87_seed-only.der diff --git a/certs/mldsa/mldsa44-cert.der b/certs/mldsa/mldsa44-cert.der new file mode 100644 index 0000000000000000000000000000000000000000..d40ff06ee0e4e404776a90c01ee059f043c68d9a GIT binary patch literal 4115 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 HmP_(qD#n~i literal 0 HcmV?d00001 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----- +MIIKPgIBADALBglghkgBZQMEAxEEggoqMIIKJgQg4nkpgTfvQgkEqtREw9UOaEyK +0HGTAdDfaz69vCRlHMEEggoA+SQwPO6mvA7iZs++HSARvkZZc0ICcRYU9oB8RRuE +Ah5aj1fuQ0sM8eYPXZUu58FYFZTo13OWzI34F/vwZtX6qXzNB/N3QTaGwft4JYZL +yIzniSlF2kkSwFaADEDpTMU3tjjVKdThyYeZh+hwIFPPh7MxPmgEw6kQPH9xg1Sq +Fk1bAmYcOS3YpGzDIBGQlhHREi0UGUYByWwjpyFkNGJiKJAgyBALF2lclhAThETD +hkygQIzaFgxjkmRiCHAjkzAYwixKSAWEMgRRFBFKQoRkwIlZtI0MRAQayAzZFgLI +yA0aESVJtlEiBmwahwjCFG7akCjSokBiOCzMFkIcQmgjhUwKRVEUhgnCxI2JSE0j +yShAyG0INRIDSSFkKITjRFEUBGTbOG4BMybaKArLCAISRy7QMiRbCCDRJA0UE0oB +F4rSmAwaEo5RtEgiBwKkNiYZFC0ZIXIitS0IlwkMgg2ARFCEmCTkxJEUJwgkhzHi +NmggEw4Rl4WClCSkEGIgp1BbMGZBAi7bCCaMKA4KQQSgti1bgGQTsSBRoEHBgg0A +GQDKAHDZJGCahjHSBBGLoJBDApIjyYTYBGnCAIaIlECUxInAGGlMtoiCpowMQgiB +yGTRwpFASIBahBAKMAULuETIhAWMxgVaOHBiJEIBB2gQllAcE2EQqRFSKEQDEomT +BImYEohEhIgipiUiQgRDpGGkqIQZkhBZJC6ESIZMtmGQQmgZRA2gwIWABjHCOAwk +FoLEAi5EgmwKNWBhlI2awGAjQWhTwmyURkgMk4xQRmkDEhCQJEiSKAlMMIwhlBFg +wgkRgkUAGTLiIlAglozAgAHktIhZACWagImUMihTACIkFgpKqEgaGGFRtGlYIoaT +wERQuIgRAIkENSQiFAXQwAAYEEWhIDEMuS0JJQ2awA0SRABDwoyTsgAAqYmiJGik +FiERRwaiAERTMCrZwE3DginjtGnTNi0jpGHZtA1UQCCERETIkkjIBGFMJpADCSrS +EiYMOUDACEpYMDIhIyIkyGhkqERDRgUkM5ELwYxRMgkKgmAZAjBiAEoSEYUACG2J +hjHRSJBSACUaIFJjEDHECITCSGniNBGkqAAhR4HQkHAkky1RkGABlw0IRiwiFW2L +IAkjsGzhNFLQAmHEAo3cAG3YgnDMxETiRoAZAJKbAk4JKYyEMAgbyEjQlC0QhBFk +Rkpg+PqdjkVKnCvgBltVsxYdtdtcHhbcMBWrz0NnqfOA3IAMWTP/JXyNk470oPPB +gF1W3bLmgNXyHhGRNyCGC8wlVwN+Ax+D4P27y5PIHS1bMZObrpmnUQTh7l/yOHQm +iIH9n77yXTTtUF6B3w5VC9QSWOpq6nGM2e1Zp1dQbq2+BcDkD0/N4gSDxdfEbNN8 +LpmmFlHCSKJSQ9AOkbYnkgb5YEEy+M9MMfxjA6FBUWpHc+omMWq9rXjKqslXTw9U +lE42xvn2Pj/GRtsOJjKBHa9jmANyYdy5PZlTjDMXEuCH8aPcmdkpS6z39OQxG3Em +bDq2YVQ/v3YIhveTXQTxHArqz3QNUKuu9P5x3uZ6k67MeLY53j7mNBzkIKisiEUo +sqMkCGs83GN1N2gK9OxjXk3twpJWWoftWhkaWlJEQrjcpxAXFsCfOHXzneIINgTb +9oBE90yiaWNDXq0AB35q7UcoFYNcscdDhn4t8an48hXX14PZ74BbC+yQcNGGvMqH +db7FNmgSUvcnyePoZIEbugVXf8fely4Sj6pattYAM9a8WRRQzNfR/UZB731tVcLU +f6bKHhV6PM4GJHR4tgunz3NcjNr/JBR02hFyXVe0348cjfD+JoDj4wEhpGv4a+y1 +8WJP4pHAcklrLkdyVcjcsiXVCflBb9bcoF2nOMCmpUsnOv4XrOXsR5Nk+fKjJR9c +J/PqDHMit2RUZA0VwVNGuuqtjFIWCTpdWi6SSlF0YvIzZdLW5OyITBzmDFo1LwGX +zigiukm3iiob7GG65CM9fb8qzJzLNrlSa4W02q9rbG5FMxNfARiskYRjHbhNqdGj +9/C/9GpWvAPMLIVNcnx9Udv/mBYtBiBSa9UY/fx1g0fadESZp3mmHDm3qJOWyclV +fP6s8tL+kmohuzKIZcYj12sJ6xDpaBArZZMdwYkMoQnWA8Hy8IlDjwcwXQi+IYZ5 +fbf22+/fZD3ARqxffdzw1p9Y1LS6V6skRlq2o50DihEpBg4cuO9JOPFc7tnbA8s0 +4VKGgop4l4gm4fs+gWVxJNVCVFiQ0Pn4mQgP3FxvXp6gdJe4HC7xknAd/h0DQqRk +fwU3TIRU/w8QO38ugDIxxMGC33WL0sqhUP59NIK+WxM5/3RVugTFkZ0yow4RNULZ +jcb/1mArz46DhAJILgld9j2iK0WlyBwmY8knHKnJQ8eJZ5XdWHZGwUf4C745kagk +VIyKOJjMOUcgw04DWDd44lcqbjhsk0X7MIHJhu5p1jRhUPXgKxgsGQFPBB2gSrZV +IGSk9V2T9tl/J4dpzP0qJagsPqDj89ZsO7Zks9/tdEZx4fosB9BR3CR2D1FPpXtX +WwGGxa8LNdFDo1KXHyZpeCQbUVrw8s6KwlOb/andwa63Cb25luQ6ADDn8QwqbakA +pxFRvBCqdCNfpM0gu6LX6jrwcvSw32ppQ7mZA4F+NLXxtVrqo64wgQ7Wqur70jNy +W2Zj+6eZ4qQp+bfzqQTOdy08cc/24eqYPhHoNSUnRxJRrbwKNrvjTj0bBNP7TBey +DdWddNh7GUpT8zdu58P82wIh8Zb70Y3y0Bk1zidgzTIl6k6545CFDhRqw0IhSbHu +Ai2HjMP+5wgDXuDvfdrwYGY2ObuMwkhLFvGRmWFgRaWVQpxs++dxRVy/+G2oYo/M +F3CQpcJXyN6wsEd4KX8dhgN1JIatWb3uYMM/jpD7pE0PyaCmYU7RcGqqpMLjloYN +tS/27fqnaL8quJMuVwUa4aCe7mv8cjEPTY4/2qdKWzdedLF4rpN7Ml+VSEaMZQ+I +d+HbmoMnh7oFm4uao+qFIkFMzsydfajJhEXTqUfI/AlmepQIffYaim4We5ta+YZV +QjN+YZhLIfMB6kg+lNseG7J9Uad7+cxGrYXbcviZ5EmVL8h4JzG7vJ+A0+8J137i +XmRKi/V7BZWr2yV3La/3sIirYJxtWXmMmAVcQgycjVX8+G8gahCKhijCb1mz4JjR +V5G/yg6mjsxJLWqxZPM3IRWTUelHzZ14rEq9YLFY1vh6KDdkUP/5YOSdqdTTmQjt +MfNu3DTMNTLsi9xtyDcjd1hFfQNNPPXarGQKCoVRavLbPI5JOGuEyvbQxH9Ou3IO +hdqCSg2wiur7fCLhMF3q4SdU0I+0qwqiIUVRlZH6rKisAJ+BEwAO14uDLH0R1Bi9 +TCDPYU8VNQrV3nb8YH5onpylVWvU2y9LUgpqete3oN1r+Q== +-----END PRIVATE KEY----- diff --git a/certs/mldsa/mldsa44-keyPub.der b/certs/mldsa/mldsa44-keyPub.der new file mode 100644 index 0000000000000000000000000000000000000000..83b7f8b4bed98610c0a5e2990eb789eac1c42bae GIT binary patch literal 1334 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 YG@x{Cx@jNf+5q}c3h1Ep=dNHGTHX*A8UO$Q literal 0 HcmV?d00001 diff --git a/certs/mldsa/mldsa87_seed-only.der b/certs/mldsa/mldsa87_seed-only.der new file mode 100644 index 0000000000000000000000000000000000000000..6ec75512b722643297a10afd19693e77cf6433d0 GIT binary patch literal 54 zcmV-60LlL_Gy(wtFbf6=V1`HmWdj5Q69giFAkBazxW`NM>Ue1RqFo5Yc9qO$J?P$P M74R&L%dm%#Pq*w9F8}}l literal 0 HcmV?d00001 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()