diff --git a/.github/workflows/mldsa-composite-config.yml b/.github/workflows/mldsa-composite-config.yml index 45e379cff..8da3005db 100644 --- a/.github/workflows/mldsa-composite-config.yml +++ b/.github/workflows/mldsa-composite-config.yml @@ -1,6 +1,6 @@ name: ML-DSA Composite Configs -# Builds wolfSSH with ML-DSA on but traditional halves off, exercising the +# Builds wolfSSH with ML-DSA levels or traditional halves off, exercising the # per-composite gates in wolfssh/internal.h in states the all-on and all-off # builds never reach. @@ -68,6 +68,16 @@ jobs: defines: -DWOLFSSH_NO_ECDSA_SHA2_NISTP256 -DWOLFSSH_NO_ECDSA_SHA2_NISTP384 -DWOLFSSH_NO_ED25519 -DWOLFSSH_NO_MLDSA87 expect: '' exclude_tests: tests/api.test tests/testsuite.test + # No level left, so WOLFSSH_NO_MLDSA itself must be derived; + # test_MlDsaLevelsDisabled in unit.test checks that. + - name: no-levels + defines: -DWOLFSSH_NO_MLDSA44 -DWOLFSSH_NO_MLDSA65 -DWOLFSSH_NO_MLDSA87 + mldsa_off: true + expect: '' + # ML-DSA-44 alone off; its composites go, 65 and 87 stay. + - name: no-mldsa44 + defines: -DWOLFSSH_NO_MLDSA44 + expect: ssh-mldsa65-ed25519@wolfssl.com ssh-mldsa65-es256@wolfssl.com ssh-mldsa87-ed448@wolfssl.com ssh-mldsa87-es384@wolfssl.com # Sets one gate directly rather than deriving it, the rest left on # to expose a missed site. Only row with --enable-ossh-certs. - name: single-composite-gate @@ -143,14 +153,20 @@ jobs: # A crash must not pass vacuously with an empty list. echo "$out" | grep -q '^Set Key: ' keys=$(echo "$out" | sed -n 's/^Set Key: //p') - # ML-DSA itself must be on, or an empty composite list proves nothing. - echo "$keys" | grep -q 'ssh-mldsa-44' + acc=$(echo "$out" | sed -n 's/^Set Key Accepted: //p') + if [ '${{ matrix.mldsa_off }}' = 'true' ]; then + # No ML-DSA name of any kind in either table. A bare `!` does + # not trip bash -e, so fail explicitly. + if echo "$keys,$acc" | grep -q 'ssh-mldsa'; then exit 1; fi + else + # ML-DSA itself must be on, or an empty composite list proves nothing. + echo "$keys" | grep -q 'ssh-mldsa-[0-9]' + fi got=$(echo "$keys" | tr ',' '\n' | { grep -o 'ssh-mldsa[0-9][0-9]-.*' || true; } | LC_ALL=C sort -u | tr '\n' ' ' | sed 's/ $//') echo "composites offered: '$got'" test "$got" = "${{ matrix.expect }}" # Second table, gated in parallel with the host-key one. echo "$out" | grep -q '^Set Key Accepted: ' - acc=$(echo "$out" | sed -n 's/^Set Key Accepted: //p') gotAcc=$(echo "$acc" | tr ',' '\n' | { grep -o 'ssh-mldsa[0-9][0-9]-.*' || true; } | LC_ALL=C sort -u | tr '\n' ' ' | sed 's/ $//') echo "composites accepted: '$gotAcc'" test "$gotAcc" = "${{ matrix.expect }}" diff --git a/src/internal.c b/src/internal.c index 9759ca5f1..58c409d45 100644 --- a/src/internal.c +++ b/src/internal.c @@ -211,7 +211,8 @@ typedef char wolfSSH_channel_overhead_check[ HAVE_ED25519_KEY_EXPORT are all set. Disables ssh-ed25519 server and user authentication as well as the ML-DSA+Ed25519 composites. WOLFSSH_NO_MLDSA - Set when MLDSA is disabled and/or not included in wolfssl downloaded. + Set when MLDSA is disabled and/or not included in wolfssl downloaded, + or when WOLFSSH_NO_MLDSA44, 65 and 87 are all set. WOLFSSH_NO_MLDSA44 Set for ML-DSA-44. WOLFSSH_NO_MLDSA65 diff --git a/tests/unit.c b/tests/unit.c index 8dfa18669..a15b6595f 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -1031,6 +1031,12 @@ static int test_KDF(void) } +/* Checks the internal.h derivation directly, independent of any API. */ +#if defined(WOLFSSH_NO_MLDSA44) && defined(WOLFSSH_NO_MLDSA65) && \ + defined(WOLFSSH_NO_MLDSA87) && !defined(WOLFSSH_NO_MLDSA) + #error "WOLFSSH_NO_MLDSA must follow from all three levels being off" +#endif + /* Key Generation Unit Test */ #ifdef WOLFSSH_KEYGEN @@ -1369,6 +1375,25 @@ static int test_MlDsaCompositesDisabled(void) #endif /* WOLFSSH_NO_MLDSA */ +/* API behaviour with no level built; the #error above covers the derivation. */ +#if defined(WOLFSSH_NO_MLDSA44) && defined(WOLFSSH_NO_MLDSA65) && \ + defined(WOLFSSH_NO_MLDSA87) +static int test_MlDsaLevelsDisabled(void) +{ + byte dummy[1]; + int result = 0; + int sz; + + sz = wolfSSH_MakeMlDsaKey(dummy, sizeof(dummy), WOLFSSH_MLDSAKEY_44); + if (sz != WS_NOT_COMPILED) { + printf("MlDsaLevelsDisabled: MakeMlDsaKey wrong result %d\n", sz); + result = -133; + } + + return result; +} +#endif + #endif /* WOLFSSH_KEYGEN */ /* Exercises the malformed-input error paths of the WOLFSSH_FORMAT_OPENSSH @@ -13223,7 +13248,7 @@ static int test_KEY_clean_osshCert(void) } #endif /* WOLFSSH_OSSH_CERTS */ -#if !defined(WOLFSSH_NO_MLDSA) +#if !defined(WOLFSSH_NO_MLDSA) && !defined(WOLFSSH_NO_MLDSA44) /* keys/server-key-mldsa44.der - MlDsa44 OneAsymmetricKey */ static const byte unitTestMlDsaPrivKey[] = { 0x30, 0x82, 0x0a, 0x3e, 0x02, 0x01, 0x00, 0x30, @@ -13556,7 +13581,7 @@ static const byte unitTestMlDsaPrivKey[] = { 0x27, 0xfe, 0x32, 0x26, 0x3c, 0x33, 0x83, 0xda, 0x18, 0xc9 }; -#endif /* !WOLFSSH_NO_MLDSA */ +#endif /* !WOLFSSH_NO_MLDSA && !WOLFSSH_NO_MLDSA44 */ #ifndef WOLFSSH_NO_ECDSA /* P-256 DER with the OID last byte changed 0x07 -> 0x01 (secp192r1). @@ -23755,6 +23780,13 @@ int wolfSSH_UnitTest(int argc, char** argv) testResult = testResult || unitResult; #endif /* WOLFSSH_NO_MLDSA_COMPOSITES */ #endif +#if defined(WOLFSSH_NO_MLDSA44) && defined(WOLFSSH_NO_MLDSA65) && \ + defined(WOLFSSH_NO_MLDSA87) + unitResult = test_MlDsaLevelsDisabled(); + printf("MlDsaLevelsDisabled: %s\n", + (unitResult == 0 ? "SUCCESS" : "FAILED")); + testResult = testResult || unitResult; +#endif #endif /* WOLFSSH_KEYGEN */ unitResult = test_OpenSshPemNegative(); printf("OpenSshPemNegative: %s\n", diff --git a/wolfssh/internal.h b/wolfssh/internal.h index 7044912cd..7f86b58fb 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -146,6 +146,12 @@ extern "C" { #define WOLFSSH_NO_MLDSA65 #define WOLFSSH_NO_MLDSA87 #endif +/* Each composite needs its ML-DSA level, so no level leaves no ML-DSA. */ +#if defined(WOLFSSH_NO_MLDSA44) && defined(WOLFSSH_NO_MLDSA65) && \ + defined(WOLFSSH_NO_MLDSA87) + #undef WOLFSSH_NO_MLDSA + #define WOLFSSH_NO_MLDSA +#endif #ifdef NO_SHA #undef WOLFSSH_NO_SHA1