Skip to content

Commit f28b497

Browse files
committed
wolfSupplicant: real-authenticator interop harness (hostapd + mac80211_hwsim) for PSK, EAP-TLS, PEAP, SAE
1 parent 780bc37 commit f28b497

15 files changed

Lines changed: 2376 additions & 0 deletions
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
/* test_supplicant_hostapd.c
2+
*
3+
* Copyright (C) 2006-2025 wolfSSL Inc.
4+
*
5+
* Real-authenticator interop test. Drives the wolfIP supplicant over a
6+
* Linux TAP device against a hostapd-in-wired-mode EAP server. Validates
7+
* EAP-TLS framing, identity exchange, TLS handshake, fragmentation, and
8+
* EAP-Success against a non-wolfSSL implementation of the authenticator.
9+
*
10+
* Usage:
11+
* sudo ./test-supplicant-hostapd <ifname>
12+
*
13+
* The TAP is expected to be already created and brought up
14+
* (tools/hostapd/run_hostapd_test.sh does this). The hostapd EAP server
15+
* is also expected to be running and bound to the same TAP.
16+
*
17+
* Success criterion: the supplicant transitions to SUPP_STATE_4WAY_M1_WAIT
18+
* (i.e. EAP-Success was received and the MSK-derived PMK is installed).
19+
* Wired hostapd does NOT perform the 4-way handshake - that's already
20+
* validated against the in-process AP in test-supplicant-eap-tls.
21+
*/
22+
23+
#include <stdio.h>
24+
#include <stdlib.h>
25+
#include <string.h>
26+
#include <stdint.h>
27+
#include <errno.h>
28+
#include <unistd.h>
29+
#include <fcntl.h>
30+
#include <time.h>
31+
#include <sys/socket.h>
32+
#include <sys/ioctl.h>
33+
#include <net/if.h>
34+
#include <netinet/in.h>
35+
#include <netpacket/packet.h>
36+
#include <linux/if_ether.h>
37+
38+
#include <wolfssl/options.h>
39+
#include <wolfssl/ssl.h>
40+
41+
#include "supplicant.h"
42+
#include "eapol.h"
43+
#include "rsn_ie.h"
44+
#include "test_eap_certs.h"
45+
46+
#define EAPOL_ETH_TYPE 0x888EU
47+
48+
/* PAE group address: where the supplicant addresses outgoing EAPOL
49+
* frames in wired/bridge environments per IEEE 802.1X-2010 7.8. */
50+
static const uint8_t PAE_GROUP_MAC[6] = {0x01,0x80,0xC2,0x00,0x00,0x03};
51+
52+
struct host_ctx {
53+
int sock;
54+
int ifindex;
55+
uint8_t local_mac[6];
56+
};
57+
58+
static struct host_ctx HCTX;
59+
60+
/* ---- transport callbacks bridging supplicant to the raw socket ---- */
61+
62+
static int hostapd_send_eapol(void *ctx, const uint8_t *frame, size_t len)
63+
{
64+
struct host_ctx *h = (struct host_ctx *)ctx;
65+
uint8_t eth[1600];
66+
struct sockaddr_ll sll;
67+
ssize_t sent;
68+
69+
if (len + 14 > sizeof(eth)) return -1;
70+
memcpy(&eth[0], PAE_GROUP_MAC, 6);
71+
memcpy(&eth[6], h->local_mac, 6);
72+
eth[12] = (uint8_t)(EAPOL_ETH_TYPE >> 8);
73+
eth[13] = (uint8_t)(EAPOL_ETH_TYPE & 0xFFU);
74+
memcpy(&eth[14], frame, len);
75+
76+
memset(&sll, 0, sizeof(sll));
77+
sll.sll_family = AF_PACKET;
78+
sll.sll_ifindex = h->ifindex;
79+
sll.sll_halen = 6;
80+
memcpy(sll.sll_addr, PAE_GROUP_MAC, 6);
81+
82+
sent = sendto(h->sock, eth, len + 14, 0,
83+
(struct sockaddr *)&sll, sizeof(sll));
84+
if (sent < 0) {
85+
fprintf(stderr, "sendto: %s\n", strerror(errno));
86+
return -1;
87+
}
88+
return 0;
89+
}
90+
91+
static int hostapd_install_key(void *ctx, wolfip_supplicant_keytype_t kt,
92+
uint8_t idx, const uint8_t *k, size_t l)
93+
{
94+
(void)ctx; (void)kt; (void)idx; (void)k; (void)l;
95+
/* No 4-way runs against wired hostapd, so install_key isn't expected
96+
* to fire here. Accept defensively. */
97+
return 0;
98+
}
99+
100+
/* ---- raw socket open + interface lookup ---- */
101+
102+
static int open_raw_socket(const char *ifname, struct host_ctx *h)
103+
{
104+
struct ifreq ifr;
105+
struct sockaddr_ll sll;
106+
int s;
107+
108+
s = socket(AF_PACKET, SOCK_RAW, htons(EAPOL_ETH_TYPE));
109+
if (s < 0) {
110+
fprintf(stderr, "socket(AF_PACKET): %s (need root)\n", strerror(errno));
111+
return -1;
112+
}
113+
memset(&ifr, 0, sizeof(ifr));
114+
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
115+
if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
116+
fprintf(stderr, "SIOCGIFINDEX(%s): %s\n", ifname, strerror(errno));
117+
close(s);
118+
return -1;
119+
}
120+
h->ifindex = ifr.ifr_ifindex;
121+
122+
/* Use a fixed locally-administered MAC for the supplicant. The
123+
* actual TAP MAC is irrelevant since we build the Ethernet header
124+
* ourselves with SOCK_RAW. */
125+
h->local_mac[0] = 0x02; h->local_mac[1] = 0x00; h->local_mac[2] = 0x00;
126+
h->local_mac[3] = 0x00; h->local_mac[4] = 0x00; h->local_mac[5] = 0x22;
127+
128+
memset(&sll, 0, sizeof(sll));
129+
sll.sll_family = AF_PACKET;
130+
sll.sll_protocol = htons(EAPOL_ETH_TYPE);
131+
sll.sll_ifindex = h->ifindex;
132+
if (bind(s, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
133+
fprintf(stderr, "bind: %s\n", strerror(errno));
134+
close(s);
135+
return -1;
136+
}
137+
h->sock = s;
138+
return 0;
139+
}
140+
141+
/* ---- main test driver ---- */
142+
143+
static uint64_t now_ms(void)
144+
{
145+
struct timespec ts;
146+
clock_gettime(CLOCK_MONOTONIC, &ts);
147+
return (uint64_t)ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000ULL;
148+
}
149+
150+
int main(int argc, char **argv)
151+
{
152+
const char *ifname = (argc > 1) ? argv[1] : "wolfip-eap0";
153+
struct eap_test_creds creds;
154+
struct wolfip_supplicant_cfg cfg;
155+
struct wolfip_supplicant *supp = NULL;
156+
uint8_t rsn[64]; size_t rsn_len;
157+
uint8_t rxbuf[1600];
158+
uint64_t deadline;
159+
int rc = 1;
160+
161+
setvbuf(stdout, NULL, _IONBF, 0);
162+
printf("wolfIP supplicant <-> hostapd interop on '%s'\n", ifname);
163+
164+
if (eap_test_load_creds(&creds) != 0) {
165+
fprintf(stderr, "failed to load test certs\n");
166+
return 1;
167+
}
168+
if (rsn_ie_build_wpa2_psk(rsn, sizeof(rsn), &rsn_len) != 0) {
169+
fprintf(stderr, "rsn_ie_build\n");
170+
return 1;
171+
}
172+
173+
if (open_raw_socket(ifname, &HCTX) != 0) {
174+
return 1;
175+
}
176+
printf("AF_PACKET bound to %s (ifindex=%d, SA=%02x:%02x:%02x:%02x:%02x:%02x)\n",
177+
ifname, HCTX.ifindex,
178+
HCTX.local_mac[0], HCTX.local_mac[1], HCTX.local_mac[2],
179+
HCTX.local_mac[3], HCTX.local_mac[4], HCTX.local_mac[5]);
180+
181+
/* Configure supplicant for EAP-TLS, identity matching eap_users. */
182+
memset(&cfg, 0, sizeof(cfg));
183+
cfg.ssid = "wolfIP-Interop"; cfg.ssid_len = strlen(cfg.ssid);
184+
cfg.auth_mode = WOLFIP_AUTH_EAP_TLS;
185+
cfg.identity = "alice@wolfip.local";
186+
cfg.identity_len = strlen(cfg.identity);
187+
/* AP MAC = PAE group; STA MAC = our raw-socket MAC. Used only in PTK
188+
* derivation; wired hostapd never runs the 4-way so values are
189+
* effectively unused, but the supplicant still requires them. */
190+
memcpy(cfg.ap_mac, PAE_GROUP_MAC, 6);
191+
memcpy(cfg.sta_mac, HCTX.local_mac, 6);
192+
cfg.ap_rsn_ie = rsn; cfg.ap_rsn_ie_len = rsn_len;
193+
cfg.eap_tls.ca = creds.ca; cfg.eap_tls.ca_len = creds.ca_len;
194+
cfg.eap_tls.ca_format = WOLFIP_EAP_TLS_FMT_DER;
195+
cfg.eap_tls.client_cert = creds.cli_cert;
196+
cfg.eap_tls.client_cert_len = creds.cli_cert_len;
197+
cfg.eap_tls.client_cert_format = WOLFIP_EAP_TLS_FMT_DER;
198+
cfg.eap_tls.client_key = creds.cli_key;
199+
cfg.eap_tls.client_key_len = creds.cli_key_len;
200+
cfg.eap_tls.client_key_format = WOLFIP_EAP_TLS_FMT_DER;
201+
cfg.eap_tls.tls_version_pin = 1; /* hostapd's default is TLS 1.2 */
202+
cfg.eap_tls.server_name_pin = NULL;/* hostapd cert CN = test issuer
203+
* dependent; skip pinning */
204+
cfg.ops.send_eapol = hostapd_send_eapol;
205+
cfg.ops.install_key = hostapd_install_key;
206+
cfg.ops.ctx = &HCTX;
207+
208+
supp = wolfip_supplicant_new(&cfg);
209+
if (supp == NULL) {
210+
fprintf(stderr, "supplicant_new failed\n");
211+
close(HCTX.sock); return 1;
212+
}
213+
if (wolfip_supplicant_kick(supp, now_ms()) != 0) {
214+
fprintf(stderr, "kick failed\n");
215+
wolfip_supplicant_free(supp); close(HCTX.sock); return 1;
216+
}
217+
printf("supplicant kicked, awaiting hostapd EAP-Request/Identity\n");
218+
219+
/* Drive for up to 10 seconds. */
220+
deadline = now_ms() + 10000;
221+
while (now_ms() < deadline) {
222+
struct timeval tv = {0, 100000}; /* 100 ms */
223+
fd_set rfds;
224+
int sel;
225+
FD_ZERO(&rfds);
226+
FD_SET(HCTX.sock, &rfds);
227+
sel = select(HCTX.sock + 1, &rfds, NULL, NULL, &tv);
228+
if (sel < 0) {
229+
if (errno == EINTR) continue;
230+
fprintf(stderr, "select: %s\n", strerror(errno));
231+
break;
232+
}
233+
if (sel > 0 && FD_ISSET(HCTX.sock, &rfds)) {
234+
ssize_t n = recv(HCTX.sock, rxbuf, sizeof(rxbuf), 0);
235+
if (n < 14) continue;
236+
/* Skip our own outbound echo (some kernels deliver). */
237+
if (memcmp(&rxbuf[6], HCTX.local_mac, 6) == 0) continue;
238+
/* Hand 802.1X body up to supplicant. */
239+
(void)wolfip_supplicant_rx(supp, rxbuf + 14, (size_t)(n - 14),
240+
now_ms());
241+
}
242+
wolfip_supplicant_tick(supp, now_ms());
243+
244+
if (wolfip_supplicant_state(supp) == SUPP_STATE_4WAY_M1_WAIT) {
245+
printf("EAP-Success received from hostapd; supplicant has PMK\n");
246+
rc = 0;
247+
break;
248+
}
249+
if (wolfip_supplicant_state(supp) == SUPP_STATE_FAILED) {
250+
fprintf(stderr, "supplicant entered FAILED state\n");
251+
rc = 1;
252+
break;
253+
}
254+
}
255+
if (rc != 0
256+
&& wolfip_supplicant_state(supp) != SUPP_STATE_4WAY_M1_WAIT) {
257+
fprintf(stderr, "timeout: state=%d after %lums (no EAP-Success)\n",
258+
(int)wolfip_supplicant_state(supp),
259+
(unsigned long)(now_ms() - (deadline - 10000)));
260+
}
261+
262+
wolfip_supplicant_free(supp);
263+
close(HCTX.sock);
264+
return rc;
265+
}

0 commit comments

Comments
 (0)