void fix_wpa(nas_t *nas, nas_sta_t *sta, char *key, int len) { eapol_header_t eapol; eapol_wpa_key_header_t wpa_key; unsigned char buffer[sizeof(eapol) + sizeof(wpa_key)]; wpa_t *wpa = (wpa_t *)nas->wpa; dbg(nas, "use MPPE recv key as PMK"); /* Use the RADIUS key for a PMK. */ if (len > PMK_LEN) len = PMK_LEN; memcpy(sta->suppl.pmk, key, len); sta->suppl.pmk_len = len; /* generate pmkid */ if (sta->mode & WPA2) { nas_wpa_calc_pmkid(wpa, sta); if (sta->flags & STA_FLAG_PRE_AUTH) { /* Once Pre authed don't worry about this variable */ sta->flags &= ~STA_FLAG_PRE_AUTH; return; } } /* Fake enough of an EAPOL key message to make WPA happy. */ memcpy(&eapol.eth.ether_shost, &sta->ea, ETHER_ADDR_LEN); memcpy(&eapol.eth.ether_dhost, &nas->ea, ETHER_ADDR_LEN); eapol.eth.ether_type = htons(ETHER_TYPE_802_1X); eapol.version = sta->eapol_version; eapol.type = EAPOL_KEY; eapol.length = EAPOL_WPA_KEY_LEN; memset(&wpa_key, 0, sizeof(wpa_key)); if (sta->mode & (WPA2_PSK | WPA2)) wpa_key.type = EAPOL_WPA2_KEY; else wpa_key.type = EAPOL_WPA_KEY; wpa_key.key_info = htons(WPA_KEY_PAIRWISE | WPA_KEY_REQ); /* Put the message pieces together. (They probably were together, * but it's not valid to assume so. */ memcpy(buffer, &eapol, sizeof(eapol)); memcpy(&buffer[offsetof(eapol_header_t, body)], &wpa_key, sizeof(wpa_key)); /* WPA state should be okay now to start 4-way handshake. */ if (process_wpa(wpa, (eapol_header_t *)buffer, sta)) { cleanup_sta(nas, sta, DOT11_RC_8021X_AUTH_FAIL, 0); } }
void eapol_sup_dispatch(nas_t *nas, eapol_header_t *eapol) { nas_sta_t *sta; #ifdef BCMDBG char eabuf[ETHER_ADDR_STR_LEN]; #endif if (!eapol) { dbg(nas, "Missing EAPOL header"); return; } sta = lookup_sta(nas, (struct ether_addr *) eapol->eth.ether_shost, SEARCH_ENTER); if (!sta) { dbg(nas, "No STA struct available"); return; } dbg(nas, "%s message from %s", eapol_msg_type_name(eapol->type), ether_etoa((uchar *)&sta->ea, eabuf)); if (eapol->version < sta->eapol_version) { dbg(nas, "EAPOL version %d packet received, current version is %d", eapol->version, sta->eapol_version); } /* If this is a WPA key pkt then process it accordingly */ if ((eapol->type == EAPOL_KEY) && (CHECK_EAPOL_KEY(eapol->body[0]))) { /* Expect to do this only for WPA_PSK or for WPA either very * early or after RADIUS acceptance. */ if ((CHECK_NAS(sta->mode)) && ((sta->pae.state == AUTHENTICATED) || (sta->pae.state == INITIALIZE))) { /* process WPA key pkt */ if (process_sup_wpa(nas->wpa, eapol, sta)) { /* Something wrong in WPA. Lose this pae. */ cleanup_sta(nas, sta, DOT11_RC_8021X_AUTH_FAIL, 0); } return; } } err(nas, "unknown EAPOL type %d", eapol->type); }
/* Proxy EAP packet from RADIUS server to PAE */ void radius_dispatch(nas_t *nas, radius_header_t *response) { nas_sta_t *sta = &nas->sta[response->id % MAX_SUPPLICANTS]; radius_header_t *request; int left, type, length = 0, index, authenticated = 0; unsigned char buf[16], *cur; eapol_header_t eapol; eap_header_t *eap = NULL; unsigned int vendor, ssnto; unsigned char *mppe_send = NULL, *mppe_recv = NULL, *mppe_key; struct iovec frags[RADIUS_MAX_ATTRIBUTES]; int nfrags = 0; #ifdef BCMDBG char eabuf[ETHER_ADDR_STR_LEN]; #endif /* The STA could have been toss during the wait. */ if (!sta->used) return; request = sta->pae.radius.request; if (!request || request->id != response->id) { dbg(nas, "bogus RADIUS packet response->id=%d request->id=%d", response->id, request->id); return; } /* Parse attributes */ left = ntohs(response->length) - RADIUS_HEADER_LEN; cur = response->attributes; while (left >= 2) { int attribute_error = 0; type = *cur++; length = *cur++ - 2; left -= 2; /* Bad attribute length */ if (length > left) { dbg(nas, "bad attribute length %d", length); break; } switch (type) { case RD_TP_MESSAGE_AUTHENTICATOR: if (length < 16) { dbg(nas, "bad signature length %d", length); attribute_error = 1; break; } /* Validate HMAC-MD5 checksum */ memcpy(buf, cur, 16); memset(cur, 0, 16); memcpy(response->vector, request->vector, 16); /* Calculate HMAC-MD5 checksum with request vector and null signature */ hmac_md5((unsigned char *) response, ntohs(response->length), nas->secret.data, nas->secret.length, cur); if ((authenticated = !memcmp(buf, cur, 16)) == 0) { dbg(nas, "Invalid signature"); attribute_error = 1; } break; case RD_TP_STATE: /* Preserve server state unmodified */ sta->pae.radius.state.length = length; if (sta->pae.radius.state.data) free(sta->pae.radius.state.data); sta->pae.radius.state.length = length; if (!(sta->pae.radius.state.data = malloc(sta->pae.radius.state.length))) { perror("malloc"); attribute_error = 1; } else memcpy(sta->pae.radius.state.data, cur, sta->pae.radius.state.length); break; case RD_TP_EAP_MESSAGE: /* Initialize EAPOL header */ if (!nfrags) { memcpy(&eapol.eth.ether_dhost, &sta->ea, ETHER_ADDR_LEN); memcpy(&eapol.eth.ether_shost, &nas->ea, ETHER_ADDR_LEN); #ifdef BCMWPA2 if (sta->flags & STA_FLAG_PRE_AUTH) eapol.eth.ether_type = htons(ETHER_TYPE_802_1X_PREAUTH); else #endif eapol.eth.ether_type = htons(ETHER_TYPE_802_1X); eapol.version = sta->eapol_version; eapol.type = EAP_PACKET; eapol.length = htons(0); eap = (eap_header_t *) cur; frags[nfrags].iov_base = (caddr_t) &eapol; frags[nfrags].iov_len = EAPOL_HEADER_LEN; nfrags++; /* Set up internal flags */ if (eap->code == EAP_SUCCESS) sta->pae.flags |= PAE_FLAG_EAP_SUCCESS; } /* Gather fragmented EAP messages */ if (nfrags < ARRAYSIZE(frags)) { eapol.length = htons(ntohs(eapol.length) + length); frags[nfrags].iov_base = (caddr_t) cur; frags[nfrags].iov_len = length; nfrags++; } break; case RD_TP_VENDOR_SPECIFIC: if (length < 6) { dbg(nas, "bad vendor attribute length %d", length); attribute_error = 1; break; } memcpy(&vendor, cur, 4); vendor = ntohl(vendor); cur += 4; type = *cur++; length = *cur++ - 2; left -= 6; /* Bad attribute length */ if (length > left) { dbg(nas, "bad vendor attribute length %d", length); attribute_error = 1; break; } /* Parse vendor-specific attributes */ switch (vendor) { case RD_VENDOR_MICROSOFT: switch (type) { case RD_MS_MPPE_SEND: case RD_MS_MPPE_RECV: if (response->code != RADIUS_ACCESS_ACCEPT) { dbg(nas, "ignore MS-MPPE-Key in non" " RADIUS_ACCESS_ACCEPT packet"); break; } /* Key length (minus salt) must be a multiple of 16 and * greater than 32 */ if ((length - 2) % 16 || (length - 2) <= 32) { dbg(nas, "bad MS-MPPE-Key length %d", length); attribute_error = 1; break; } /* Allocate key */ if (!(mppe_key = malloc(length - 2))) { perror("malloc"); attribute_error = 1; break; } /* Decrypt key */ memcpy(mppe_key, &cur[2], length - 2); mppe_crypt(cur, mppe_key, length - 2, nas->secret.data, nas->secret.length, request->vector, 0); /* Set key pointers */ if (type == RD_MS_MPPE_SEND) mppe_send = mppe_key; else mppe_recv = mppe_key; break; } break; default: dbg(nas, "unknown vendor attribute = %d", vendor); dbg(nas, " vendor type = %d", type); dbg(nas, " attribute string = %s", cur); break; } break; case RD_TP_SESSION_TIMEOUT: if (response->code != RADIUS_ACCESS_ACCEPT) break; if (length < 4) { dbg(nas, "bad session timeout attribute length %d", length); attribute_error = 1; break; } memcpy(&ssnto, cur, 4); sta->pae.ssnto = ntohl(ssnto); dbg(nas, "session timeout in %d seconds", sta->pae.ssnto); break; default: /* Ignore all other attributes */ break; } /* Don't go on looking if something already went wrong. */ if (attribute_error) goto done; left -= length; cur += length; } if (!authenticated && response->code != RADIUS_ACCESS_REJECT) { dbg(nas, "missing signature"); goto done; } if (eap) sta->pae.id = eap->id; if (eap && (eap->code != EAP_SUCCESS || response->code != RADIUS_ACCESS_ACCEPT) && (eap->code != EAP_FAILURE || response->code != RADIUS_ACCESS_REJECT) && nfrags) { #ifdef BCMWPA2 if (sta->flags & STA_FLAG_PRE_AUTH) nas_preauth_send_packet(nas, frags, nfrags); else #endif nas_eapol_send_packet(nas, frags, nfrags); } /* RADIUS event */ switch (response->code) { case RADIUS_ACCESS_ACCEPT: /* Check for EAP-Success before allowing complete access */ if (!(sta->pae.flags & PAE_FLAG_EAP_SUCCESS)) { dbg(nas, "Radius success without EAP success?!"); pae_state(nas, sta, HELD); dbg(nas, "deauthenticating %s", ether_etoa((uint8 *)&sta->ea, eabuf)); nas_deauthorize(nas, &sta->ea); goto done; } dbg(nas, "Access Accept"); pae_state(nas, sta, AUTHENTICATED); /* overwrite session timeout with global setting */ if (!sta->pae.ssnto || sta->pae.ssnto > nas->ssn_to) sta->pae.ssnto = nas->ssn_to; /* WPA-mode needs to do the 4-way handshake here instead. */ if (CHECK_WPA(sta->mode) && mppe_recv) { fix_wpa(nas, sta, (char *)&mppe_recv[1], (int)mppe_recv[0]); break; } /* Plump the keys to driver and send them to peer as well */ if (mppe_recv) { /* Cobble a multicast key if there isn't one yet. */ if (!(nas->flags & NAS_FLAG_GTK_PLUMBED)) { nas->wpa->gtk_index = GTK_INDEX_1; if (nas->wpa->gtk_len == 0) nas->wpa->gtk_len = WEP128_KEY_SIZE; nas_rand128(nas->wpa->gtk); if (nas_set_key(nas, NULL, nas->wpa->gtk, nas->wpa->gtk_len, nas->wpa->gtk_index, 1, 0, 0) < 0) { err(nas, "invalid multicast key"); nas_handle_error(nas, 1); } nas->flags |= NAS_FLAG_GTK_PLUMBED; } sta->rc4keysec = -1; sta->rc4keyusec = -1; /* Send multicast key */ index = nas->wpa->gtk_index; length = nas->wpa->gtk_len; if (mppe_send) eapol_key(nas, sta, &mppe_send[1], mppe_send[0], &mppe_recv[1], mppe_recv[0], nas->wpa->gtk, length, index, 0); else eapol_key(nas, sta, NULL, 0, &mppe_recv[1], mppe_recv[0], nas->wpa->gtk, length, index, 0); /* MS-MPPE-Recv-Key is MS-MPPE-Send-Key on the Suppl */ index = DOT11_MAX_DEFAULT_KEYS - 1; length = WEP128_KEY_SIZE; if (nas_set_key(nas, &sta->ea, &mppe_recv[1], length, index, 1, 0, 0) < 0) { dbg(nas, "unicast key rejected by driver, assuming too many" " associated STAs"); cleanup_sta(nas, sta, DOT11_RC_BUSY, 0); } /* Set unicast key index */ if (mppe_send) eapol_key(nas, sta, &mppe_send[1], mppe_send[0], NULL, 0, NULL, length, index, 1); else eapol_key(nas, sta, NULL, 0, NULL, 0, NULL, length, index, 1); dbg(nas, "authorize %s (802.1x)", ether_etoa((uint8 *)&sta->ea, eabuf)); nas_authorize(nas, &sta->ea); } break; case RADIUS_ACCESS_REJECT: dbg(nas, "Access Reject"); pae_state(nas, sta, HELD); dbg(nas, "deauthenticating %s", ether_etoa((uint8 *)&sta->ea, eabuf)); nas_deauthorize(nas, &sta->ea); sta->pae.ssnto = 0; break; case RADIUS_ACCESS_CHALLENGE: dbg(nas, "Access Challenge"); break; default: dbg(nas, "unknown RADIUS code %d", response->code); break; } done: if (mppe_send) free(mppe_send); if (mppe_recv) free(mppe_recv); free(request); sta->pae.radius.request = NULL; }
void driver_message_dispatch(nas_t *nas, bcm_event_t *dpkt) { wl_event_msg_t *event = &(dpkt->event); int type = ntohl(event->event_type); uint8 *addr = (uint8 *)&(event->addr); nas_sta_t *sta; #ifdef BCMDBG char eabuf[ETHER_ADDR_STR_LEN]; #endif /* !!!THESE ARE THE MESSAGES WE CARE!!! */ dbg(nas, "received event of type : %d\n", type); switch (type) { case WLC_E_LINK: /* WLC_E_LINK evnet on WDS is use for trigger nas_start */ if (nas->flags & NAS_FLAG_WDS) return; /* authenticator is not interested in LINK event */ if (nas->flags & NAS_FLAG_AUTHENTICATOR) return; /* and the supplicant is only interested in link up */ if (!(ntohs(event->flags) & WLC_EVENT_MSG_LINK)) { return; } case WLC_E_ASSOC_IND: case WLC_E_REASSOC_IND: case WLC_E_DISASSOC_IND: case WLC_E_DEAUTH_IND: case WLC_E_MIC_ERROR: break; default: /* quietly discard unwanted events */ return; } dbg(nas, "start"); dbg(nas, "driver %s message received for %s", driver_msg_name(type), ether_etoa(addr, eabuf)); /* Look for the STA struct, but don't create one if the goal is * to remove it. */ sta = lookup_sta(nas, (struct ether_addr *)addr, (type == WLC_E_DISASSOC_IND || type == WLC_E_DEAUTH_IND || type == WLC_E_MIC_ERROR) ? SEARCH_ONLY : SEARCH_ENTER); switch (type) { case WLC_E_LINK: case WLC_E_ASSOC_IND: case WLC_E_REASSOC_IND: if (!(CHECK_NAS(nas->mode))) { dbg(nas, "Unexpected driver %s message in mode %d", driver_msg_name(type), nas->mode); return; } if (wpa_driver_assoc_msg(nas->wpa, dpkt, sta) == 0) break; /* clean-up stuff if there was a problem. */ if (sta) cleanup_sta(nas, sta, 0, 1); break; case WLC_E_DISASSOC_IND: case WLC_E_DEAUTH_IND: if (wpa_driver_disassoc_msg(nas->wpa, dpkt, sta) == 0) break; /* clean-up stuff if there was a problem. */ if (sta) cleanup_sta(nas, sta, 0, 1); break; case WLC_E_MIC_ERROR: if ((ntohs(event->flags) & WLC_EVENT_MSG_GROUP) && (nas->flags & NAS_FLAG_AUTHENTICATOR)) { /* flags - authenticator and supplicant are mutually exclusive. */ assert(!(nas->flags & NAS_FLAG_SUPPLICANT)); dbg(nas, "Group addressed MIC error notification received by AP"); return; } dbg(nas, "%scast MIC error notification received - %sknown STA", ((ntohs(event->flags) & WLC_EVENT_MSG_GROUP) ? "multi" : "uni"), (!sta ? "un" : "")); /* ignore errors from STAs we don't know about. */ if (sta) wpa_mic_error(nas->wpa, sta, TRUE); break; default: dbg(nas, "Tossing unexpected event #%u", type); } dbg(nas, "done"); }
static void eapol_dispatch_ex(nas_t *nas, eapol_header_t *eapol, int preauth, int bytes) { nas_sta_t *sta; eap_header_t *eap; #ifdef BCMDBG char eabuf[ETHER_ADDR_STR_LEN]; #endif /* Validate EAPOL version */ if (!eapol) { dbg(nas, "Missing EAPOL header"); return; } #ifdef NAS_GTK_PER_STA if (ETHER_ISNULLADDR(eapol->eth.ether_shost) && (eapol->type == 0xFF)) { wpa_set_gtk_per_sta(nas->wpa, eapol->body[0]); return; } #endif sta = lookup_sta(nas, (struct ether_addr *) eapol->eth.ether_shost, SEARCH_ENTER); if (!sta) { dbg(nas, "no STA struct available"); return; } if (sta->pae.state == HELD) { dbg(nas, "nothing done since in HELD state"); return; } dbg(nas, "%s message from %s", eapol_msg_type_name(eapol->type), ether_etoa((uchar *)&sta->ea, eabuf)); if (eapol->version < sta->eapol_version) { dbg(nas, "EAPOL version %d packet received, current version is %d", eapol->version, sta->eapol_version); } if (!preauth) { /* If this is a WPA key pkt then process it accordingly */ if ((eapol->type == EAPOL_KEY) && (CHECK_EAPOL_KEY(eapol->body[0]))) { /* * Expect to do this only for WPA_PSK or for WPA either very * early or after RADIUS acceptance. */ if ((CHECK_NAS(sta->mode)) && ((sta->pae.state == AUTHENTICATED) || (sta->pae.state == INITIALIZE))) { /* process WPA key pkt */ if (process_wpa(nas->wpa, eapol, sta)) { /* Something wrong in WPA. Lose this sta. */ cleanup_sta(nas, sta, DOT11_RC_8021X_AUTH_FAIL, 0); } return; } /* return; */ } /* break out if STA is only PSK */ if (CHECK_PSK(sta->mode)) return; sta->flags &= ~STA_FLAG_PRE_AUTH; } /* * instead of passing the state variable saying whether this data is from a pre auth * sta or from a normal store the variable in the sta struct and * would be reset once the message is handled ... state variable makes sense * only for this one message. */ else { sta->flags |= STA_FLAG_PRE_AUTH; sta->mode = WPA2; sta->eapol_version = WPA2_EAPOL_VERSION; } /* EAPOL event : Radius support */ switch (eapol->type) { case EAP_PACKET: dbg(nas, "EAP Packet.Preauth=%d", preauth); if (ntohs(eapol->length) >= (EAP_HEADER_LEN + 1)) { eap = (eap_header_t *) eapol->body; dbg(nas, "STA State=%d EAP Packet Type=%d Id=%d code=%d", sta->pae.state, eap->type, eap->id, eap->code); switch (eap->type) { case EAP_IDENTITY: /* Bogus packet */ if (eap->id != sta->pae.id || eap->code != EAP_RESPONSE) { dbg(nas, "bogus EAP packet id %d code %d, expected %d", eap->id, eap->code, sta->pae.id); break; } if (sta->pae.state == AUTHENTICATING) { dbg(nas, "NAS currently authenticating STA. " "Tossing packet id=%d code=%d.", eap->id, eap->code); break; } /* * Do eap length checking to prevent attacker responds to * an EAP Identity Request with an EAP Identity Response, * whose Message Length value is larger than the actual * EAP packet, nas sends out a Radius message with more * data than contained in the EAP Identity Response. That * cause nas over-reads the heap memory when forming * the Radius message. An attack may result in exposure * of sensitive data from the heap or may allow to cause * a denial-of-service. */ if (ntohs(eap->length) > bytes - EAPOL_HEADER_LEN) { dbg(nas, "Invalid EAP length %d, received %d.", ntohs(eap->length), bytes - EAPOL_HEADER_LEN); break; } /* Record identity */ if (ntohs(eap->length) > (EAP_HEADER_LEN + 1)) { if (sta->pae.radius.username.data) free(sta->pae.radius.username.data); sta->pae.radius.username.length = ntohs(eap->length) - EAP_HEADER_LEN - 1; if (!(sta->pae.radius.username.data = malloc(sta->pae.radius.username.length))) { perror("malloc"); return; } else memcpy(sta->pae.radius.username.data, eap->data, sta->pae.radius.username.length); } pae_state(nas, sta, AUTHENTICATING); /* Fall through */ default: /* Bogus packet */ /* Forward to authentication server */ RADIUS_FORWARD(nas, sta, eap); break; } } break; case EAPOL_START: dbg(nas, "Start"); sta->pae.id = 0; /* reset counter */ switch (sta->pae.state) { case AUTHENTICATING: pae_state(nas, sta, ABORTING); /* Fall through */ default: pae_state(nas, sta, CONNECTING); break; } sta->suppl.state = sta->suppl.retry_state = WPA_PTKSTART; break; case EAPOL_LOGOFF: dbg(nas, "Logoff"); switch (sta->pae.state) { default: dbg(nas, "Unexpected pae state %d", sta->pae.state); /* Fall through */ case AUTHENTICATING: pae_state(nas, sta, ABORTING); /* Fall through */ case CONNECTING: case AUTHENTICATED: pae_state(nas, sta, DISCONNECTED); dbg(nas, "deauthenticating %s", ether_etoa((uchar *)&sta->ea, eabuf)); nas_deauthorize(nas, &sta->ea); break; } break; case EAPOL_KEY: dbg(nas, "Key"); break; case EAPOL_ASF: dbg(nas, "Encapsulated ASF Alert"); break; default: dbg(nas, "unknown EAPOL type %d", eapol->type); break; } }