Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions .github/workflows/mldsa-composite-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
name: ML-DSA Composite Configs

# Builds wolfSSH with ML-DSA on but traditional halves off, exercising the
# per-composite gates in wolfssh/internal.h in states the all-on and all-off
# builds never reach.

on:
push:
branches: [ 'master', 'main', 'release/**' ]
pull_request:
branches: [ '*' ]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build_wolfssl:
name: Build wolfSSL
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout wolfSSL
uses: actions/checkout@v6
with:
repository: wolfssl/wolfssl
ref: master
path: wolfssl

- name: Build wolfSSL
working-directory: ./wolfssl
run: |
./autogen.sh
./configure --enable-all --enable-mldsa --enable-ed25519-stream \
--enable-ed448-stream
make -j$(nproc)
sudo make install
sudo ldconfig

- name: tar build-dir
run: tar -zcf wolfssl-install.tgz /usr/local/lib/libwolfssl* /usr/local/include/wolfssl

- name: Upload built lib
uses: actions/upload-artifact@v7
with:
name: wolfssl-mldsa-composite
path: wolfssl-install.tgz
retention-days: 5

partial_config:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
timeout-minutes: 30
needs: build_wolfssl
strategy:
fail-fast: false
matrix:
include:
# Only the Ed448 half is left, so exactly one composite survives.
- name: ed448-only
defines: -DWOLFSSH_NO_ECDSA_SHA2_NISTP256 -DWOLFSSH_NO_ECDSA_SHA2_NISTP384 -DWOLFSSH_NO_ED25519
expect: ssh-mldsa87-ed448@wolfssl.com
exclude_tests: tests/api.test tests/testsuite.test
# Nothing pairs with ML-DSA, so WOLFSSH_NO_MLDSA_COMPOSITES must
# be derived rather than given.
- name: no-pairing
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
# 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
defines: -DWOLFSSH_NO_MLDSA44_ES256
configure_flags: --enable-ossh-certs
expect: ssh-mldsa44-ed25519@openssh.com ssh-mldsa65-ed25519@wolfssl.com ssh-mldsa65-es256@wolfssl.com ssh-mldsa87-ed448@wolfssl.com ssh-mldsa87-es384@wolfssl.com
# Every composite off while ML-DSA and the traditional halves stay
# on; the six gates must follow.
- name: composites-off
defines: -DWOLFSSH_NO_MLDSA_COMPOSITES
expect: ''

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The four rows cover the pairing gates well, but every one of them builds wolfSSL --enable-all, so all the wolfCrypt sub-features are on in all of them. That means no row ever reaches the state where HAVE_ED448 is set but WOLFSSH_HAVE_COMPOSITE_ED448 is not, which is precisely the state the new macro was added for, and the one where tests/unit.c currently fails to compile. A row here would have caught that.

This needs no second wolfSSL build: settings.h derives HAVE_ED448_SIGN from the absence of NO_ED448_SIGN in every wolfSSH translation unit, so a -DNO_ED448_SIGN in CPPFLAGS gives wolfSSH's compiles the sub-feature-off view while the installed library still exports the symbol. I confirmed the expect string below is what client -E actually offers in that build.

Suggested change
expect: ''
expect: ''
# A wolfCrypt sub-feature off rather than a pairing gate: HAVE_ED448
# stays set but WOLFSSH_HAVE_COMPOSITE_ED448 does not, which is the
# state that macro exists for. No second wolfSSL build is needed --
# settings.h is re-processed in every wolfSSH translation unit, so
# this suppresses HAVE_ED448_SIGN for wolfSSH's compiles while the
# installed library still exports the symbol.
- name: no-ed448-sign
defines: -DNO_ED448_SIGN
expect: ssh-mldsa44-ed25519@openssh.com ssh-mldsa44-es256@wolfssl.com ssh-mldsa65-ed25519@wolfssl.com ssh-mldsa65-es256@wolfssl.com ssh-mldsa87-es384@wolfssl.com

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I applied suggested change

# HAVE_ED448 stays set but WOLFSSH_HAVE_COMPOSITE_ED448 does not,
# the state that macro exists for. settings.h is re-processed per
# translation unit, so no second wolfSSL build is needed.
- name: no-ed448-sign
defines: -DNO_ED448_SIGN
expect: ssh-mldsa44-ed25519@openssh.com ssh-mldsa44-es256@wolfssl.com ssh-mldsa65-ed25519@wolfssl.com ssh-mldsa65-es256@wolfssl.com ssh-mldsa87-es384@wolfssl.com
# Same for the widened WOLFSSH_NO_ED25519 gate. NO_ED25519_VERIFY
# also suppresses WOLFSSL_ED25519_PERSISTENT_SHA, a member of
# struct ed25519_key, so it is re-defined to keep the layout
# matching the installed library's.
- name: no-ed25519-verify
defines: -DNO_ED25519_VERIFY -DWOLFSSL_ED25519_PERSISTENT_SHA
expect: ssh-mldsa44-es256@wolfssl.com ssh-mldsa65-es256@wolfssl.com ssh-mldsa87-ed448@wolfssl.com ssh-mldsa87-es384@wolfssl.com
# The remaining Ed conjuncts. Both are layout-neutral, so neither
# needs a compensating define the way no-ed25519-verify does.
- name: no-ed25519-sign
defines: -DNO_ED25519_SIGN
expect: ssh-mldsa44-es256@wolfssl.com ssh-mldsa65-es256@wolfssl.com ssh-mldsa87-ed448@wolfssl.com ssh-mldsa87-es384@wolfssl.com
- name: no-ed448-key-export
defines: -DNO_ED448_KEY_EXPORT
expect: ssh-mldsa44-ed25519@openssh.com ssh-mldsa44-es256@wolfssl.com ssh-mldsa65-ed25519@wolfssl.com ssh-mldsa65-es256@wolfssl.com ssh-mldsa87-es384@wolfssl.com
# The last four conjuncts, so all eight the two derivations rest on
# are covered. NO_ED448_VERIFY needs the same layout compensation
# as no-ed25519-verify, since the wolfSSL build enables both
# streams rather than leaving either to --enable-all.
- name: no-ed448-verify
defines: -DNO_ED448_VERIFY -DWOLFSSL_ED448_PERSISTENT_SHA
expect: ssh-mldsa44-ed25519@openssh.com ssh-mldsa44-es256@wolfssl.com ssh-mldsa65-ed25519@wolfssl.com ssh-mldsa65-es256@wolfssl.com ssh-mldsa87-es384@wolfssl.com
- name: no-ed448-key-import
defines: -DNO_ED448_KEY_IMPORT
expect: ssh-mldsa44-ed25519@openssh.com ssh-mldsa44-es256@wolfssl.com ssh-mldsa65-ed25519@wolfssl.com ssh-mldsa65-es256@wolfssl.com ssh-mldsa87-es384@wolfssl.com
- name: no-ed25519-key-import
defines: -DNO_ED25519_KEY_IMPORT
expect: ssh-mldsa44-es256@wolfssl.com ssh-mldsa65-es256@wolfssl.com ssh-mldsa87-ed448@wolfssl.com ssh-mldsa87-es384@wolfssl.com
- name: no-ed25519-key-export
defines: -DNO_ED25519_KEY_EXPORT
expect: ssh-mldsa44-es256@wolfssl.com ssh-mldsa65-es256@wolfssl.com ssh-mldsa87-ed448@wolfssl.com ssh-mldsa87-es384@wolfssl.com
steps:
- name: Checkout wolfSSH
uses: actions/checkout@v6

- name: Download wolfSSL
uses: actions/download-artifact@v8
with:
name: wolfssl-mldsa-composite

- name: Install wolfSSL
run: |
sudo tar -zxf wolfssl-install.tgz -C /
sudo ldconfig

- name: Build wolfSSH
run: |
./autogen.sh
./configure --enable-all ${{ matrix.configure_flags }} CPPFLAGS="${{ matrix.defines }}"
make -j$(nproc)

# The -E list must hold exactly the composites the gates leave on.
- name: Check negotiated composites
run: |
out=$(./examples/client/client -E -u "${USER:-runner}" 2>&1)
# 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'
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 }}"

# api and testsuite are the only suites needing the NISTP256 sample
# keys and certs. Subtract them from the TESTS the Makefile computes,
# so suites added later still run.
- name: Run tests
run: |
if [ -n '${{ matrix.exclude_tests }}' ]; then
printf 'include Makefile\nprint-tests:\n\t@echo $(TESTS)\n' \
> "$RUNNER_TEMP/print-tests.mk"
excl=""
for t in ${{ matrix.exclude_tests }}; do
excl="$excl -e $t"
done
keep=$(make -s -f "$RUNNER_TEMP/print-tests.mk" print-tests \
| tr ' ' '\n' \
| grep -vxF $excl \
| tr '\n' ' ')
echo "running: $keep"
test -n "$keep"
timeout -k 30 1200 make check TESTS="$keep"
else
timeout -k 30 1200 make check
fi

- name: Print test logs on failure
if: failure()
run: |
echo "=== test-suite.log ==="
cat test-suite.log || true
for f in tests/*.log apps/wolfsshd/test/*.log; do
echo "=== $f ==="; cat "$f" || true
done
24 changes: 19 additions & 5 deletions apps/wolfssh-options.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,27 @@ int main(void)
#ifndef WOLFSSH_NO_MLDSA
printf("MLDSA\n");
#endif
/* Same guard as cannedKeyAlgoNamesHostKey in src/internal.c: composites
* as a whole can be compiled out, and this one needs the ECDSA half. */
#if !defined(WOLFSSH_NO_MLDSA_COMPOSITES) && \
!defined(WOLFSSH_NO_MLDSA87) && \
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384) && !defined(NO_SHA512)
#ifndef WOLFSSH_NO_MLDSA_COMPOSITES
printf("MLDSA_COMPOSITES\n");
#endif
#ifndef WOLFSSH_NO_MLDSA44_ES256
printf("MLDSA44_ES256\n");
#endif
#ifndef WOLFSSH_NO_MLDSA65_ES256
printf("MLDSA65_ES256\n");
#endif
#ifndef WOLFSSH_NO_MLDSA87_ES384
printf("MLDSA87_ES384\n");
#endif
#ifndef WOLFSSH_NO_MLDSA44_ED25519
printf("MLDSA44_ED25519\n");
#endif
#ifndef WOLFSSH_NO_MLDSA65_ED25519
printf("MLDSA65_ED25519\n");
#endif
#ifndef WOLFSSH_NO_MLDSA87_ED448
printf("MLDSA87_ED448\n");
#endif

/* Applications. */
#ifdef WOLFSSH_SSHCLIENT
Expand Down
28 changes: 12 additions & 16 deletions apps/wolfsshd/test/test_configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -3082,26 +3082,22 @@ static int test_CheckAuthKeysLineTypes(void)
#endif
#endif
#endif
#if !defined(WOLFSSH_NO_MLDSA44) && !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256)
#ifndef WOLFSSH_NO_MLDSA44_ES256
"ssh-mldsa44-es256@wolfssl.com",
#endif
#if !defined(WOLFSSH_NO_MLDSA65) && \
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) && !defined(NO_SHA512)
#ifndef WOLFSSH_NO_MLDSA65_ES256
"ssh-mldsa65-es256@wolfssl.com",
#endif
#if !defined(WOLFSSH_NO_MLDSA87) && \
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384) && !defined(NO_SHA512)
#ifndef WOLFSSH_NO_MLDSA87_ES384
"ssh-mldsa87-es384@wolfssl.com",
#endif
#if !defined(WOLFSSH_NO_MLDSA44) && !defined(WOLFSSH_NO_ED25519) && \
!defined(NO_SHA512)
#ifndef WOLFSSH_NO_MLDSA44_ED25519
"ssh-mldsa44-ed25519@openssh.com",
#endif
#if !defined(WOLFSSH_NO_MLDSA65) && !defined(WOLFSSH_NO_ED25519) && \
!defined(NO_SHA512)
#ifndef WOLFSSH_NO_MLDSA65_ED25519
"ssh-mldsa65-ed25519@wolfssl.com",
#endif
#if !defined(WOLFSSH_NO_MLDSA87) && defined(HAVE_ED448)
#ifndef WOLFSSH_NO_MLDSA87_ED448
"ssh-mldsa87-ed448@wolfssl.com",
#endif
};
Expand Down Expand Up @@ -3264,29 +3260,29 @@ static int test_CheckAuthKeysLineMaxSz(void)

#if !defined(WOLFSSH_NO_MLDSA87)
keySz = WC_MLDSA_87_PUB_KEY_SIZE + COMPOSITE_MAX_TRAD_PUB_SZ;
#if !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384) && !defined(NO_SHA512)
#ifndef WOLFSSH_NO_MLDSA87_ES384
type = "ssh-mldsa87-es384@wolfssl.com";
#elif defined(HAVE_ED448)
#elif !defined(WOLFSSH_NO_MLDSA87_ED448)
type = "ssh-mldsa87-ed448@wolfssl.com";
#else
type = "ssh-mldsa-87";
keySz = WC_MLDSA_87_PUB_KEY_SIZE;
#endif
#elif !defined(WOLFSSH_NO_MLDSA65)
keySz = WC_MLDSA_65_PUB_KEY_SIZE + COMPOSITE_MAX_TRAD_PUB_SZ;
#if !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) && !defined(NO_SHA512)
#ifndef WOLFSSH_NO_MLDSA65_ES256
type = "ssh-mldsa65-es256@wolfssl.com";
#elif !defined(WOLFSSH_NO_ED25519) && !defined(NO_SHA512)
#elif !defined(WOLFSSH_NO_MLDSA65_ED25519)
type = "ssh-mldsa65-ed25519@wolfssl.com";
#else
type = "ssh-mldsa-65";
keySz = WC_MLDSA_65_PUB_KEY_SIZE;
#endif
#else
keySz = WC_MLDSA_44_PUB_KEY_SIZE + COMPOSITE_MAX_TRAD_PUB_SZ;
#if !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256)
#ifndef WOLFSSH_NO_MLDSA44_ES256
type = "ssh-mldsa44-es256@wolfssl.com";
#elif !defined(WOLFSSH_NO_ED25519) && !defined(NO_SHA512)
#elif !defined(WOLFSSH_NO_MLDSA44_ED25519)
type = "ssh-mldsa44-ed25519@openssh.com";
#else
type = "ssh-mldsa-44";
Expand Down
5 changes: 4 additions & 1 deletion examples/client/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,10 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args)
printf("\t%d: %s\n", idx, current);
}
} while (current != NULL);
printf("Set Key: %s\n\n", wolfSSH_GetAlgoListKey(ssh));
printf("Set Key: %s\n", wolfSSH_GetAlgoListKey(ssh));
/* Separate table from the host key list; both must gate alike. */
printf("Set Key Accepted: %s\n\n",
wolfSSH_GetAlgoListKeyAccepted(ssh));

idx = 0;
printf("Cipher:\n");
Expand Down
21 changes: 7 additions & 14 deletions examples/echoserver/echoserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -2440,39 +2440,32 @@ typedef struct {
const char* label;
} MlDsaCompositeEntry;

/* NULL-terminated so the table is never empty if composites are compiled
* out or ECDSA and Ed25519/Ed448 are both disabled */
/* NULL-terminated so the table is never empty. */
static const MlDsaCompositeEntry mldsaCompositeEntries[] = {
#ifndef WOLFSSH_NO_MLDSA_COMPOSITES
#if !defined(WOLFSSH_NO_MLDSA44) && !defined(WOLFSSH_NO_ED25519) && \
!defined(NO_SHA512)
#ifndef WOLFSSH_NO_MLDSA44_ED25519
{ "mldsa44-ed25519", "./keys/server-key-mldsa44ed25519",
"ML-DSA-44+Ed25519" },
#endif
#if !defined(WOLFSSH_NO_MLDSA44) && !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256)
#ifndef WOLFSSH_NO_MLDSA44_ES256
{ "mldsa44-es256", "./keys/server-key-mldsa44es256",
"ML-DSA-44+ES256" },
#endif
#if !defined(WOLFSSH_NO_MLDSA65) && !defined(WOLFSSH_NO_ED25519) && \
!defined(NO_SHA512)
#ifndef WOLFSSH_NO_MLDSA65_ED25519
{ "mldsa65-ed25519", "./keys/server-key-mldsa65ed25519",
"ML-DSA-65+Ed25519" },
#endif
#if !defined(WOLFSSH_NO_MLDSA65) && \
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) && !defined(NO_SHA512)
#ifndef WOLFSSH_NO_MLDSA65_ES256
{ "mldsa65-es256", "./keys/server-key-mldsa65es256",
"ML-DSA-65+ES256" },
#endif
#if !defined(WOLFSSH_NO_MLDSA87) && defined(HAVE_ED448)
#ifndef WOLFSSH_NO_MLDSA87_ED448
{ "mldsa87-ed448", "./keys/server-key-mldsa87ed448",
"ML-DSA-87+Ed448" },
#endif
#if !defined(WOLFSSH_NO_MLDSA87) && \
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384) && !defined(NO_SHA512)
#ifndef WOLFSSH_NO_MLDSA87_ES384
{ "mldsa87-es384", "./keys/server-key-mldsa87es384",
"ML-DSA-87+ES384" },
#endif
#endif /* !WOLFSSH_NO_MLDSA_COMPOSITES */
{ NULL, NULL, NULL }
};

Expand Down
Loading
Loading