Skip to content
Draft
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
62 changes: 59 additions & 3 deletions .github/workflows/supplicant.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ on:
- 'src/supplicant/**'
- 'tools/hostapd/**'
- 'tools/wolfsta/**'
- 'src/macsec/**'
- 'tools/macsec/**'
- 'Makefile'
- '.github/workflows/supplicant.yml'
pull_request:
Expand All @@ -17,6 +19,8 @@ on:
- 'src/supplicant/**'
- 'tools/hostapd/**'
- 'tools/wolfsta/**'
- 'src/macsec/**'
- 'tools/macsec/**'
- 'Makefile'
- '.github/workflows/supplicant.yml'

Expand All @@ -30,6 +34,9 @@ env:
# never cache-hit). Bump to refresh.
WOLFSSL_REF: v5.9.1-stable
WOLFSSL_PREFIX: /home/runner/wolfssl-install
# Build the whole suite with MACsec/MKA on so every object is compiled once
# with a consistent flag set (CMAC/GCM/AES-key-wrap come from --enable-all).
WOLFIP_ENABLE_MACSEC: 1

jobs:
supplicant:
Expand All @@ -45,7 +52,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y build-essential autoconf automake libtool pkg-config \
hostapd dnsmasq socat iw rfkill \
hostapd wpasupplicant dnsmasq socat iw rfkill tcpdump \
libnl-3-dev libnl-genl-3-dev
# mac80211_hwsim lives in linux-modules-extra; best-effort.
sudo apt-get install -y "linux-modules-extra-$(uname -r)" || true
Expand Down Expand Up @@ -80,8 +87,10 @@ jobs:
# against the cached wolfSSL (no sudo install needed; rpath is absolute
# so the binaries resolve the lib even under sudo).

# Hard gate: in-process unit tests (no root / no radio).
- name: Supplicant unit tests
# Hard gate: in-process unit tests (no root / no radio). With
# WOLFIP_ENABLE_MACSEC=1 in env this also builds and runs the MACsec/MKA
# crypto, SecY, SA and protocol tests.
- name: Supplicant + MACsec unit tests
run: make supplicant-tests

# PEAP/MSCHAPv2 is off by default (pulls in deprecated MD4 + DES); the
Expand Down Expand Up @@ -148,5 +157,52 @@ jobs:
sudo ./tools/hostapd/run_hwsim_wolfsta_dhcp_test.sh
sudo ./tools/hostapd/run_hwsim_wolfsta_dhcp_psk_test.sh

# Best-effort: MACsec interop needs the kernel `macsec` module (in-tree
# in mainline, but not loadable on every hosted runner). Probe and skip
# cleanly. Unlike hwsim this needs no radio, only veth.
- name: Probe kernel macsec
id: macsec
run: |
if sudo modprobe macsec 2>/dev/null; then
echo "available=yes" >> "$GITHUB_OUTPUT"
else
echo "available=no" >> "$GITHUB_OUTPUT"
echo "::warning::kernel macsec module unavailable; skipping macsec interop"
fi

- name: Build MACsec SecY interop tool
if: steps.macsec.outputs.available == 'yes'
run: make build/macsec-probe

# SecY framing vs the kernel MACsec module, both directions (static SAK).
- name: MACsec SecY kernel interop
if: steps.macsec.outputs.available == 'yes'
run: sudo bash tools/macsec/run_macsec_static_test.sh

# NOTE: MKA control-plane interop (run_macsec_mka_test.sh /
# run_macsec_mka_dataplane_test.sh) needs the wolfDen wolfMKA library
# (WOLFMKA_DIR) with the L2-ICV interop fix (wolfSSL/wolfDen PR #2); it
# returns to CI once that library is available upstream.

# Hard gate: the in-process unit tests rebuilt with AddressSanitizer +
# UBSan, to catch memory / undefined-behaviour bugs in the parsers.
# Runs from a clean tree (sanitizer objects must not be reused); placed
# last so it does not disturb the normal-build interop steps above.
- name: Supplicant + MACsec unit tests (ASan + UBSan)
run: |
sudo sysctl vm.mmap_rnd_bits=28 || true
make clean
make supplicant-tests-sanitize

# Hard gate: short libFuzzer smoke over the SecY receive path. Needs
# clang for -fsanitize=fuzzer.
- name: MACsec SecY fuzz smoke
run: |
if ! command -v clang >/dev/null; then
echo "::warning::clang unavailable; skipping fuzz smoke"; exit 0
fi
make macsec-fuzz
./build/fuzz-macsec-secy -max_total_time=30 -rss_limit_mb=4096

# NOTE: P-521 hunt-and-peck (run_hwsim_sae_softmac_g21_test.sh) has an
# open hostapd interop gap - kept as a make target, not yet in CI.
113 changes: 112 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ WOLFIP_ENABLE_PEAP_MSCHAPV2 ?= 0
WOLFIP_ENABLE_SAE ?= 1
WOLFIP_ENABLE_SAE_H2E ?= 1
WOLFIP_ENABLE_SAE_HNP ?= 1
WOLFIP_ENABLE_MACSEC ?= 0

ifneq ($(WOLFSSL_PREFIX),)
WOLFSSL_CFLAGS := -I$(WOLFSSL_PREFIX)/include
Expand Down Expand Up @@ -335,7 +336,40 @@ $(error WOLFIP_ENABLE_SAE_H2E=1 requires WOLFIP_ENABLE_SAE=1)
endif
endif

SUPPLICANT_OBJ := $(patsubst src/%.c,build/%.o,$(SUPPLICANT_SRC))
# MACsec / MKA (IEEE 802.1AE + 802.1X-2010). Standalone module under
# src/macsec/ that reuses the supplicant crypto layer (AES key wrap, secure
# zero). Requires wolfSSL built with --enable-cmac (WOLFSSL_CMAC).
# The software SecY (macsec_crypto/macsec_secy/macsec_sa) is always built with
# MACsec. The MKA control plane is provided by the wolfDen wolfMKA library
# (https://github.com/wolfSSL/wolfDen/tree/main/mka) from a local clone (not a
# submodule): set WOLFMKA_DIR to build it. Without it, only the SecY data plane
# (usable with a static SAK) is built.
WOLFMKA_DIR ?=
WOLFMKA_OBJ :=
WOLFIP_HAVE_WOLFMKA :=

ifeq ($(WOLFIP_ENABLE_MACSEC),1)
SUPPLICANT_SRC += src/macsec/macsec_crypto.c \
src/macsec/macsec_secy.c \
src/macsec/macsec_sa.c
CFLAGS += -DWOLFIP_ENABLE_MACSEC=1 -Isrc/macsec
ifneq ($(WOLFMKA_DIR),)
ifeq ($(wildcard $(WOLFMKA_DIR)/include/wolfmka/mka_kay.h),)
$(error WOLFMKA_DIR is set but does not point at a wolfMKA clone (got '$(WOLFMKA_DIR)'))
endif
WOLFIP_HAVE_WOLFMKA := 1
SUPPLICANT_SRC += src/macsec/mka_wolfmka.c
# WOLFMKA_ICV_L2_ADDR makes wolfMKA include the L2 addresses (DA||SA||EtherType)
# in the MKA ICV, matching wpa_supplicant / the Linux kernel (802.1X-2010
# 11.11.3); required to interoperate with them (wolfSSL/wolfDen PR #2).
CFLAGS += -DWOLFIP_MKA_WOLFMKA=1 -DWOLFMKA_ICV_L2_ADDR -I$(WOLFMKA_DIR)/include
# wolfMKA library objects live outside src/, compiled to build/wolfmka/.
WOLFMKA_OBJ := build/wolfmka/mka_crypto.o build/wolfmka/mka_kay.o \
build/wolfmka/mka_pdu.o build/wolfmka/mka_status.o
endif
endif

SUPPLICANT_OBJ := $(patsubst src/%.c,build/%.o,$(SUPPLICANT_SRC)) $(WOLFMKA_OBJ)

# Header-dependency tracking for the supplicant + its host glue. Without
# this, editing a supplicant header (e.g. a struct in supplicant.h) would
Expand All @@ -351,6 +385,17 @@ build/supplicant/%.o: src/supplicant/%.c
@echo "[CC] $<"
@$(CC) $(CFLAGS) $(WOLFSSL_CFLAGS) $(NL80211_CFLAGS) -MMD -MP -Isrc/supplicant -c $< -o $@

build/macsec/%.o: src/macsec/%.c
@mkdir -p `dirname $@` || true
@echo "[CC] $<"
@$(CC) $(CFLAGS) $(WOLFSSL_CFLAGS) -MMD -MP -Isrc/supplicant -Isrc/macsec -c $< -o $@

# wolfMKA library sources from the local clone ($(WOLFMKA_DIR)/src).
build/wolfmka/%.o: $(WOLFMKA_DIR)/src/%.c
@mkdir -p build/wolfmka || true
@echo "[CC] $<"
@$(CC) $(CFLAGS) $(WOLFSSL_CFLAGS) -I$(WOLFMKA_DIR)/include -c $< -o $@

# WOLFSSL_LIBS / WOLFSSL_CFLAGS may already be set above when
# WOLFSSL_PREFIX is provided. Otherwise default to pkg-config detection
# and a plain -lwolfssl fallback.
Expand All @@ -368,6 +413,59 @@ build/test-wpa-crypto: $(SUPPLICANT_OBJ) build/supplicant/test_wpa_crypto.o
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(WOLFSSL_LIBS) $(END_GROUP)

build/test-macsec-crypto: $(SUPPLICANT_OBJ) build/macsec/test_macsec_crypto.o
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(WOLFSSL_LIBS) $(END_GROUP)

build/test-macsec-secy: $(SUPPLICANT_OBJ) build/macsec/test_macsec_secy.o
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(WOLFSSL_LIBS) $(END_GROUP)

build/test-macsec-sa: $(SUPPLICANT_OBJ) build/macsec/test_macsec_sa.o
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(WOLFSSL_LIBS) $(END_GROUP)

# macsec_probe: byte-level SecY cross-check tool used by the kernel-macsec
# interop scripts (tools/macsec/run_macsec_static_test.sh).
build/tools/macsec_probe.o: tools/macsec/macsec_probe.c
@mkdir -p build/tools || true
@echo "[CC] $<"
@$(CC) $(CFLAGS) $(WOLFSSL_CFLAGS) -Isrc/supplicant -Isrc/macsec -c $< -o $@

build/macsec-probe: $(SUPPLICANT_OBJ) build/tools/macsec_probe.o
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(WOLFSSL_LIBS) $(END_GROUP)

# macsec_sta: AF_PACKET host harness that runs the wolfMKA participant for
# interop against wpa_supplicant (run_macsec_mka_test.sh). Linux only; requires
# WOLFMKA_DIR (the wolfMKA library provides the MKA control plane).
build/tools/macsec_sta.o: tools/macsec/macsec_sta.c
@test -n "$(WOLFMKA_DIR)" || { echo "macsec-sta requires WOLFMKA_DIR=<wolfMKA clone>"; exit 1; }
@mkdir -p build/tools || true
@echo "[CC] $<"
@$(CC) $(CFLAGS) $(WOLFSSL_CFLAGS) -Isrc/supplicant -Isrc/macsec -I$(WOLFMKA_DIR)/include -c $< -o $@

build/macsec-sta: $(SUPPLICANT_OBJ) build/tools/macsec_sta.o
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(WOLFSSL_LIBS) $(END_GROUP)

# libFuzzer harness for the network-facing SecY receive path. Needs clang
# (-fsanitize=fuzzer); the macsec sources are compiled straight into the
# harness so the instrumentation is consistent. Not built by default. Requires
# a CMAC-enabled wolfSSL (WOLFSSL_CFLAGS/LIBS).
FUZZ_CC ?= clang
FUZZ_FLAGS ?= -fsanitize=fuzzer,address,undefined -fno-sanitize-recover=all -g -O1
FUZZ_MACSEC_SRC := src/macsec/macsec_crypto.c src/macsec/macsec_secy.c \
src/macsec/macsec_sa.c src/supplicant/wpa_crypto.c

build/fuzz-macsec-secy: src/macsec/fuzz_macsec_secy.c $(FUZZ_MACSEC_SRC)
@mkdir -p build || true
@echo "[FUZZ] $@"
@$(FUZZ_CC) $(FUZZ_FLAGS) $(WOLFSSL_CFLAGS) -Isrc/supplicant -Isrc/macsec \
$(^) -o $@ $(WOLFSSL_LIBS)

macsec-fuzz: build/fuzz-macsec-secy

build/test-supplicant-4way: $(SUPPLICANT_OBJ) build/supplicant/test_supplicant_4way.o
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(WOLFSSL_LIBS) $(END_GROUP)
Expand Down Expand Up @@ -535,10 +633,23 @@ endif
ifeq ($(WOLFIP_ENABLE_SAE),1)
SUPPLICANT_TEST_BINS += build/test-sae-crypto build/test-supplicant-sae
endif
ifeq ($(WOLFIP_ENABLE_MACSEC),1)
SUPPLICANT_TEST_BINS += build/test-macsec-crypto build/test-macsec-secy \
build/test-macsec-sa
endif

supplicant-tests: $(SUPPLICANT_TEST_BINS)
@for t in $(SUPPLICANT_TEST_BINS); do echo "==> $$t"; $$t || exit 1; done

# The same in-process unit tests built with AddressSanitizer + UBSan, to catch
# memory / undefined-behaviour bugs in the parsers. Target-specific flags flow
# to the object + link rules of the prerequisite bins. Run from a clean tree
# (sanitizer objects must not be reused by a normal build).
supplicant-tests-sanitize: CFLAGS += -fsanitize=address,undefined \
-fno-sanitize-recover=all -g
supplicant-tests-sanitize: LDFLAGS += -fsanitize=address,undefined
supplicant-tests-sanitize: supplicant-tests

# Real-authenticator interop tests. Both require hostapd installed and
# root (veth pair + AF_PACKET raw socket). Not part of supplicant-tests
# because of those constraints.
Expand Down
67 changes: 67 additions & 0 deletions src/macsec/fuzz_macsec_secy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* fuzz_macsec_secy.c
*
* Copyright (C) 2006-2025 wolfSSL Inc.
*
* This file is part of wolfIP.
*
* wolfIP 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 2 of the License, or
* (at your option) any later version.
*
* wolfIP is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/

/* libFuzzer harness for the 802.1AE SecY receive path. Feeds arbitrary bytes
* to macsec_sectag_parse and macsec_validate: GCM authentication rejects
* random frames, but the SecTAG parse and the length / header / offset bounds
* math that run before decryption are fully exercised. Each confidentiality
* offset (0/30/50) is tried. Build: make WOLFIP_ENABLE_MACSEC=1 build/fuzz-macsec-secy */

#include <stdint.h>
#include <string.h>

#include "macsec_secy.h"

static const uint8_t FZ_SAK[16] = {
0xad,0x7a,0x2b,0xd0,0x3e,0xac,0x83,0x5a,
0x6f,0x62,0x0f,0xdc,0xb5,0x06,0xb3,0x45
};
static const uint8_t FZ_SCI[8] = { 0x02,0,0,0,0,0x22,0x00,0x01 };

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
struct macsec_sectag tag;
struct macsec_validate_params vp;
uint8_t out[2048];
size_t out_len;
size_t i;
static const size_t offsets[3] = { 0U, 30U, 50U };

if (size > 1600) {
return 0;
}

/* SecTAG parser on the raw bytes (starting at the MACsec EtherType). */
(void)macsec_sectag_parse(data, size, &tag);

/* Full validate path for each configured confidentiality offset. */
for (i = 0; i < 3; i++) {
memset(&vp, 0, sizeof(vp));
vp.sak = FZ_SAK;
vp.sak_len = sizeof(FZ_SAK);
vp.sci = FZ_SCI;
vp.conf_offset = offsets[i];
out_len = 0;
(void)macsec_validate(&vp, data, size, out, sizeof(out), &out_len,
&tag);
}
return 0;
}
Loading
Loading