/** * process_ampe_frame - process an ampe frame * @frame: The full frame * @len: The full frame length * @me: The MAC address of the local interface * @cookie: Opaque cookie that will be returned to the caller along with * frames to be transmitted. * * Returns 0 unless something really horrible happened. In other words, even * the frame could not be processed or it was corrupted, the function still * returns 0. */ int process_ampe_frame(struct ieee80211_mgmt_frame *mgmt, int len, unsigned char *me, void *cookie) { struct info_elems elems; struct info_elems our_elems; unsigned char ftype; struct candidate *cand = NULL; enum plink_event event; unsigned char ie_len = 0; unsigned short plid = 0, llid = 0; unsigned char *ies; unsigned short ies_len; #define FAKE_LOSS_PROBABILITY 0 #if (FAKE_LOSS_PROBABILITY > 0) do { unsigned short dice; dice = RAND_bytes((unsigned char *) &dice, sizeof(dice)); if ((dice % 100) < FAKE_LOSS_PROBABILITY) { sae_debug(AMPE_DEBUG_FSM, "Frame dropped\n"); return 0; } } while (0); #endif /* management header, category, action code, mesh id and peering mgmt*/ if (len < 24 + 1 + 1 + 2 + 2) return 0; //if (is_multicast_ether_addr(mgmt->da)) { // sae_debug(AMPE_DEBUG_FSM, "Mesh plink: ignore frame to multicast address"); // return 0; //} ies = start_of_ies(mgmt, len, &ies_len); parse_ies(ies, ies_len, &elems); if (!elems.mesh_peering) { // || !elems.rsn) { sae_debug(AMPE_DEBUG_FSM, "Mesh plink: missing necessary peer link ie\n"); return 0; } ftype = mgmt->action.action_code; ie_len = elems.mesh_peering_len; if ((ftype == PLINK_OPEN && ie_len != 20) || (ftype == PLINK_CONFIRM && ie_len != 22) || (ftype == PLINK_CLOSE && ie_len != 22 && ie_len != 24)) { sae_debug(AMPE_DEBUG_FSM, "Mesh plink: incorrect plink ie length %d %d\n", ftype, ie_len); return 0; } if (ftype != PLINK_CLOSE && (!elems.mesh_id || !elems.mesh_config)) { sae_debug(AMPE_DEBUG_FSM, "Mesh plink: missing necessary ie %p %p\n", elems.mesh_id, elems.mesh_config); return 0; } /* Note the lines below are correct, the llid in the frame is the plid * from the point of view of this host. */ memcpy(&plid, PLINK_GET_LLID(elems.mesh_peering), 2); if (ftype == PLINK_CONFIRM || (ftype == PLINK_CLOSE && ie_len == 10)) memcpy(&llid, PLINK_GET_PLID(elems.mesh_peering), 2); /* match BSSBasicRateSet*/ parse_ies(sta_fixed_ies, sta_fixed_ies_len, &our_elems); if (get_basic_rates(&our_elems) != get_basic_rates(&elems)) { sae_debug(AMPE_DEBUG_FSM, "mesh plink: mismatched BSSBasicRateSet!\n"); return 0; } /* "1" here means only get peers in SAE_ACCEPTED */ if ((cand = find_peer(mgmt->sa, 1)) == NULL) { sae_debug(AMPE_DEBUG_FSM, "Mesh plink: plink open from unauthed peer "MACSTR"\n", MAC2STR(mgmt->sa)); return 0; } if (cand->my_lid == 0) peer_ampe_init(&e_conf, cand, me, cookie); if (elems.sup_rates) { memcpy(cand->sup_rates, elems.sup_rates, elems.sup_rates_len); cand->sup_rates_len = elems.sup_rates_len; if (elems.ext_rates) { memcpy(cand->sup_rates + elems.sup_rates_len, elems.ext_rates, elems.ext_rates_len); cand->sup_rates_len += elems.ext_rates_len; } } check_frame_protection(cand, mgmt, len, &elems); cand->cookie = cookie; if (cand->link_state == PLINK_BLOCKED) { return 0; } /* Now we will figure out the appropriate event... */ event = PLINK_UNDEFINED; // if (ftype != PLINK_CLOSE && (!mesh_matches_local(&elems, sdata))) { if (ftype != PLINK_CLOSE) { switch (ftype) { case PLINK_OPEN: event = OPN_RJCT; break; case PLINK_CONFIRM: event = CNF_RJCT; break; case PLINK_CLOSE: break; } } switch (ftype) { case PLINK_OPEN: if (!plink_free_count() || (cand->peer_lid && cand->peer_lid != plid)) event = OPN_IGNR; else { cand->peer_lid = plid; event = OPN_ACPT; } break; case PLINK_CONFIRM: if (!plink_free_count() || (cand->my_lid != llid || cand->peer_lid != plid)) event = CNF_IGNR; else event = CNF_ACPT; break; case PLINK_CLOSE: if (cand->link_state == PLINK_ESTAB) /* Do not check for llid or plid. This does not * follow the standard but since multiple plinks * per cand are not supported, it is necessary in * order to avoid a livelock when MP A sees an * establish peer link to MP B but MP B does not * see it. This can be caused by a timeout in * B's peer link establishment or B beign * restarted. */ event = CLS_ACPT; else if (cand->peer_lid != plid) event = CLS_IGNR; else if (ie_len == 7 && cand->my_lid != llid) event = CLS_IGNR; else event = CLS_ACPT; break; default: sae_debug(AMPE_DEBUG_FSM, "Mesh plink: unknown frame subtype\n"); return 0; } sae_debug(AMPE_DEBUG_FSM, "Mesh plink (peer, state, llid, plid, event): " MACSTR " %s %d %d %d\n", MAC2STR(mgmt->sa), mplstates[cand->link_state], le16toh(cand->my_lid), le16toh(cand->peer_lid), event); fsm_step(cand, event); return 0; }
static int check_frame_protection(struct candidate *cand, struct ieee80211_mgmt_frame *mgmt, int len, struct info_elems *elems) { unsigned char *clear_ampe_ie; struct info_elems ies_parsed; struct mesh_node *mesh = cand->conf->mesh; unsigned short ampe_ie_len, cat_to_mic_len; int r; unsigned int* key_expiration_p; u8 ftype = mgmt->action.action_code; u8 *gtkdata, *igtkdata; assert(len && cand && mgmt); if (!elems->mic || elems->mic_len != MIC_IE_BODY_SIZE) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: invalid MIC\n"); return -1; } /* * ampe_ie_len is the length of the ciphertext (the encrypted * AMPE IE) and it needs to be inferred from the total frame * size */ ampe_ie_len = len - (elems->mic + elems->mic_len - (unsigned char *)mgmt); /* expect at least MGTK + RSC + expiry for open/confirm */ if (ftype != PLINK_CLOSE && ampe_ie_len < 2 + sizeof(struct ampe_ie) + 16 + 8 + 4) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: AMPE IE too small\n"); return -1; } /* if PMF, then we also need IGTKData */ if (mesh->conf->pmf) { if (ampe_ie_len < 2 + sizeof(struct ampe_ie) + 16 + 8 + 4 + 2 + 6 + 16 /* IGTKData */) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: AMPE IE missing IGTK\n"); return -1; } } clear_ampe_ie = malloc(ampe_ie_len); if (!clear_ampe_ie) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: out of memory\n"); return -1; } /* * cat_to_mic_len is the length of the contents of the frame * from the category (inclusive) to the mic (exclusive) */ cat_to_mic_len = elems->mic - 2 - (unsigned char *) &mgmt->action; r = siv_decrypt(&cand->sivctx, elems->mic + elems->mic_len, clear_ampe_ie, ampe_ie_len, elems->mic, 3, cand->peer_mac, ETH_ALEN, cand->my_mac, ETH_ALEN, &mgmt->action, cat_to_mic_len); sae_debug(AMPE_DEBUG_KEYS, "Checking protection to " MACSTR " from " MACSTR "\n", MAC2STR(cand->my_mac), MAC2STR(cand->peer_mac)); sae_debug(AMPE_DEBUG_KEYS, "Len checking cat-to-mic len:%d ampe ie full length: %d\n", cat_to_mic_len, ampe_ie_len); sae_hexdump(AMPE_DEBUG_KEYS, "SIV- Got AAD[3]: ", (unsigned char *) &mgmt->action, cat_to_mic_len); if (r != 1) { sae_debug(AMPE_DEBUG_KEYS, "Protection check failed\n"); free(clear_ampe_ie); return -1; } if (ampe_ie_len != clear_ampe_ie[1] + 2) { sae_debug(AMPE_DEBUG_KEYS, "AMPE -Invalid length (expected %d, got %d)\n", ampe_ie_len, clear_ampe_ie[1] + 2); free(clear_ampe_ie); return -1; } sae_hexdump(AMPE_DEBUG_KEYS, "AMPE IE: ", clear_ampe_ie, ampe_ie_len); parse_ies(clear_ampe_ie, ampe_ie_len, &ies_parsed); if (memcmp(ies_parsed.ampe->peer_nonce, null_nonce, 32) != 0 && memcmp(ies_parsed.ampe->peer_nonce, cand->my_nonce, 32) != 0) { sae_hexdump(AMPE_DEBUG_KEYS, "IE peer_nonce ", ies_parsed.ampe->peer_nonce, 32); sae_debug(AMPE_DEBUG_KEYS, "Unexpected nonce\n"); free(clear_ampe_ie); return -1; } memcpy(cand->peer_nonce, ies_parsed.ampe->local_nonce, 32); gtkdata = ies_parsed.ampe->variable; memcpy(cand->mgtk, gtkdata, sizeof(cand->mgtk)); sae_hexdump(AMPE_DEBUG_KEYS, "Received mgtk: ", cand->mgtk, sizeof(cand->mgtk)); key_expiration_p = (unsigned int *) (gtkdata + 16 + 8); cand->mgtk_expiration = le32toh(*key_expiration_p); igtkdata = gtkdata + 16 + 8 + 4; if (mesh->conf->pmf) { cand->igtk_keyid = le16toh(*(u16 *) igtkdata); igtkdata += 2 + 6; memcpy(cand->igtk, igtkdata, 16); } free(clear_ampe_ie); return -1; #undef MIC_IE_BODY_SIZE }
static int check_frame_protection(struct candidate *cand, struct ieee80211_mgmt_frame *mgmt, int len, struct info_elems *elems) { unsigned char *clear_ampe_ie; struct info_elems ies_parsed; unsigned short ampe_ie_len, cat_to_mic_len; int r; unsigned int* key_expiration_p; assert(len && cand && mgmt); clear_ampe_ie = malloc(sizeof(struct ampe_ie) + 2); if (!clear_ampe_ie) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: out of memory\n"); return -1; } if (!elems->mic || elems->mic_len != MIC_IE_BODY_SIZE) { sae_debug(AMPE_DEBUG_KEYS, "Verify frame: invalid MIC\n"); free(clear_ampe_ie); return -1; } /* * ampe_ie_len is the length of the ciphertext (the encrypted * AMPE IE) and it needs to be inferred from the total frame * size */ ampe_ie_len = len - (elems->mic + elems->mic_len - (unsigned char *)mgmt); /* * cat_to_mic_len is the length of the contents of the frame * from the category (inclusive) to the mic (exclusive) */ cat_to_mic_len = elems->mic - 2 - (unsigned char *) &mgmt->action; r = siv_decrypt(&cand->sivctx, elems->mic + elems->mic_len, clear_ampe_ie, ampe_ie_len, elems->mic, 3, cand->peer_mac, ETH_ALEN, cand->my_mac, ETH_ALEN, &mgmt->action, cat_to_mic_len); sae_debug(AMPE_DEBUG_KEYS, "Checking protection to " MACSTR " from " MACSTR "\n", MAC2STR(cand->my_mac), MAC2STR(cand->peer_mac)); sae_debug(AMPE_DEBUG_KEYS, "Len checking cat-to-mic len:%d ampe ie full length: %d\n", cat_to_mic_len, ampe_ie_len); sae_hexdump(AMPE_DEBUG_KEYS, "SIV- Got AAD[3]: ", (unsigned char *) &mgmt->action, cat_to_mic_len); if (r != 1) { sae_debug(AMPE_DEBUG_KEYS, "Protection check failed\n"); free(clear_ampe_ie); return -1; } sae_hexdump(AMPE_DEBUG_KEYS, "AMPE IE: ", clear_ampe_ie, ampe_ie_len); parse_ies(clear_ampe_ie, ampe_ie_len, &ies_parsed); if (memcmp(ies_parsed.ampe->peer_nonce, null_nonce, 32) != 0 && memcmp(ies_parsed.ampe->peer_nonce, cand->my_nonce, 32) != 0) { sae_hexdump(AMPE_DEBUG_KEYS, "IE peer_nonce ", ies_parsed.ampe->peer_nonce, 32); sae_debug(AMPE_DEBUG_KEYS, "Unexpected nonce\n"); free(clear_ampe_ie); return -1; } memcpy(cand->peer_nonce, ies_parsed.ampe->local_nonce, 32); memcpy(cand->mgtk, ies_parsed.ampe->mgtk, sizeof(cand->mgtk)); sae_hexdump(AMPE_DEBUG_KEYS, "Received mgtk: ", cand->mgtk, sizeof(cand->mgtk)); key_expiration_p = (unsigned int *)ies_parsed.ampe->key_expiration; cand->mgtk_expiration = le32toh(*key_expiration_p); free(clear_ampe_ie); return -1; #undef MIC_IE_BODY_SIZE }
/** * process_ampe_frame - process an ampe frame * @frame: The full frame * @len: The full frame length * @me: The MAC address of the local interface * @cookie: Opaque cookie that will be returned to the caller along with * frames to be transmitted. * * Returns 0 unless something really horrible happened. In other words, even * the frame could not be processed or it was corrupted, the function still * returns 0. */ int process_ampe_frame( struct ieee80211_mgmt_frame *mgmt, int len, unsigned char *me, void *cookie) { struct info_elems elems; struct info_elems our_elems; unsigned char ftype; struct candidate *cand = NULL; enum plink_event event; unsigned char ie_len = 0; unsigned short plid = 0, llid = 0; unsigned char *ies; unsigned short ies_len; size_t pmkid_len; #define FAKE_LOSS_PROBABILITY 0 #if (FAKE_LOSS_PROBABILITY > 0) do { unsigned short dice; dice = RAND_bytes((unsigned char *)&dice, sizeof(dice)); if ((dice % 100) < FAKE_LOSS_PROBABILITY) { sae_debug(AMPE_DEBUG_FSM, "Frame dropped\n"); return 0; } } while (0); #endif /* management header, category, action code, mesh id and peering mgmt*/ if (len < 24 + 1 + 1 + 2 + 2) return 0; ies = start_of_ies(mgmt, len, &ies_len); parse_ies(ies, ies_len, &elems); if (!elems.mesh_peering) { sae_debug(AMPE_DEBUG_FSM, "Mesh plink: missing necessary peer link ie\n"); return 0; } ftype = mgmt->action.action_code; ie_len = elems.mesh_peering_len; pmkid_len = ampe_conf.mesh->conf->is_secure ? sizeof(cand->pmkid) : 0; if ((ftype == PLINK_OPEN && ie_len != 4 + pmkid_len) || (ftype == PLINK_CONFIRM && ie_len != 6 + pmkid_len) || (ftype == PLINK_CLOSE && ie_len != 6 + pmkid_len && ie_len != 8 + pmkid_len)) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: incorrect plink ie length %d %d\n", ftype, ie_len); return 0; } if (ftype != PLINK_CLOSE && (!elems.mesh_id || !elems.mesh_config)) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: missing necessary ie %p %p\n", elems.mesh_id, elems.mesh_config); return 0; } /* Note the lines below are correct, the llid in the frame is the plid * from the point of view of this host. */ memcpy(&plid, PLINK_GET_LLID(elems.mesh_peering), 2); if (ftype == PLINK_CONFIRM || (ftype == PLINK_CLOSE && ie_len == 10)) memcpy(&llid, PLINK_GET_PLID(elems.mesh_peering), 2); /* match BSSBasicRateSet*/ parse_ies(sta_fixed_ies, sta_fixed_ies_len, &our_elems); if (ftype != PLINK_CLOSE && get_basic_rates(&our_elems) != get_basic_rates(&elems)) { sae_debug(AMPE_DEBUG_FSM, "mesh plink: mismatched BSSBasicRateSet!\n"); return 0; } /* require authed peers if secure mesh */ if (ampe_conf.mesh->conf->is_secure) { /* "1" here means only get peers in SAE_ACCEPTED */ if ((cand = find_peer(mgmt->sa, 1)) == NULL) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: plink open from unauthed peer " MACSTR "\n", MAC2STR(mgmt->sa)); return 0; } } else { /* * In open mesh, there's no auth stage, so we create the station * when the first mgmt frame or beacon is received. Do that now * if we haven't already and this is a plink open frame. */ cand = find_peer(mgmt->sa, 0); if (!cand) { if (ftype != PLINK_OPEN) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: ignoring non-open frame from neighbor " MACSTR "\n", MAC2STR(mgmt->sa)); return 0; } cand = create_candidate(mgmt->sa, me, 0, cookie); if (!cand) { sae_debug( AMPE_DEBUG_FSM, "Mesh plink: could not create new peer " MACSTR "\n", MAC2STR(mgmt->sa)); return 0; } } } if (cand->my_lid == 0) peer_ampe_init(&e_conf, cand, cookie); ampe_set_peer_ies(cand, &elems); if (!protection_is_valid(cand, mgmt, len, &elems)) return 0; cand->cookie = cookie; if (cand->link_state == PLINK_BLOCKED) { return 0; } /* Now we will figure out the appropriate event... */ event = PLINK_UNDEFINED; switch (ftype) { case PLINK_OPEN: if (!matches_local(ampe_conf.mesh, cand, &elems)) event = OPN_RJCT; else if (!plink_free_count(ampe_conf.mesh)) { log_reject(cand, "no free peer links"); event = REQ_RJCT; } else if (cand->peer_lid && cand->peer_lid != plid) { log_reject(cand, "invalid peer link id"); event = REQ_RJCT; } else { cand->peer_lid = plid; event = OPN_ACPT; } break; case PLINK_CONFIRM: if (!matches_local(ampe_conf.mesh, cand, &elems)) event = CNF_RJCT; else if (!plink_free_count(ampe_conf.mesh)) { log_reject(cand, "no free peer links"); event = REQ_RJCT; } else if (cand->my_lid != llid) { log_reject(cand, "invalid local link id"); event = REQ_RJCT; } else if (cand->peer_lid != plid) { log_reject(cand, "invalid peer link id"); event = REQ_RJCT; } else event = CNF_ACPT; break; case PLINK_CLOSE: if (cand->link_state == PLINK_ESTAB) /* Do not check for llid or plid. This does not * follow the standard but since multiple plinks * per cand are not supported, it is necessary in * order to avoid a livelock when MP A sees an * establish peer link to MP B but MP B does not * see it. This can be caused by a timeout in * B's peer link establishment or B beign * restarted. */ event = CLS_ACPT; else if (cand->peer_lid != plid) event = CLS_IGNR; else if (ie_len == 7 && cand->my_lid != llid) event = CLS_IGNR; else event = CLS_ACPT; break; default: sae_debug(AMPE_DEBUG_FSM, "Mesh plink: unknown frame subtype\n"); return 0; } sae_debug( AMPE_DEBUG_FSM, "Mesh plink peer=" MACSTR " state=%s llid=%d plid=%d event=%s\n", MAC2STR(mgmt->sa), mpl_states[cand->link_state], le16toh(cand->my_lid), le16toh(cand->peer_lid), mpl_events[event]); fsm_step(cand, event); return 0; }