/* * * Wireless daemon for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include "src/crypto.h" #include "src/ie.h" #include "src/handshake.h" #include "src/owe.h" #include "src/mpdu.h" #include "src/auth-proto.h" struct owe_sm { struct auth_proto ap; struct handshake_state *hs; const struct l_ecc_curve *curve; struct l_ecc_scalar *private; struct l_ecc_point *public_key; uint8_t retry; uint16_t group; const unsigned int *ecc_groups; owe_tx_authenticate_func_t auth_tx; owe_tx_associate_func_t assoc_tx; void *user_data; }; static bool owe_reset(struct owe_sm *owe) { /* * Reset OWE with a different curve group and generate a new key pair */ if (owe->ecc_groups[owe->retry] == 0) return false; owe->group = owe->ecc_groups[owe->retry]; owe->curve = l_ecc_curve_get_ike_group(owe->group); if (owe->private) l_ecc_scalar_free(owe->private); if (owe->public_key) l_ecc_point_free(owe->public_key); if (!l_ecdh_generate_key_pair(owe->curve, &owe->private, &owe->public_key)) return false; return true; } static void owe_free(struct auth_proto *ap) { struct owe_sm *owe = l_container_of(ap, struct owe_sm, ap); l_ecc_scalar_free(owe->private); l_ecc_point_free(owe->public_key); l_free(owe); } static bool owe_start(struct auth_proto *ap) { struct owe_sm *owe = l_container_of(ap, struct owe_sm, ap); owe->auth_tx(owe->user_data); return true; } static int owe_rx_authenticate(struct auth_proto *ap, const uint8_t *frame, size_t frame_len) { struct owe_sm *owe = l_container_of(ap, struct owe_sm, ap); uint8_t buf[5 + L_ECC_SCALAR_MAX_BYTES]; struct iovec iov[3]; int iov_elems = 0; size_t len; /* * RFC 8110 Section 4.3 * A client wishing to do OWE MUST indicate the OWE AKM in the RSN * element portion of the 802.11 association request ... */ iov[iov_elems].iov_base = owe->hs->supplicant_ie; iov[iov_elems].iov_len = owe->hs->supplicant_ie[1] + 2; iov_elems++; /* * ... and MUST include a Diffie-Hellman Parameter element to its * 802.11 association request. */ buf[0] = IE_TYPE_EXTENSION; buf[2] = IE_TYPE_OWE_DH_PARAM - 256; l_put_le16(owe->group, buf + 3); /* group */ len = l_ecc_point_get_x(owe->public_key, buf + 5, L_ECC_SCALAR_MAX_BYTES); buf[1] = 3 + len; /* length */ iov[iov_elems].iov_base = (void *) buf; iov[iov_elems].iov_len = buf[1] + 2; iov_elems++; owe->assoc_tx(iov, iov_elems, owe->user_data); return 0; } /* * RFC 8110 Section 4.4 Post Association */ static bool owe_compute_keys(struct owe_sm *owe, const void *public_key, size_t pub_len) { struct l_ecc_scalar *shared_secret; uint8_t ss_buf[L_ECC_SCALAR_MAX_BYTES]; uint8_t prk[L_ECC_SCALAR_MAX_BYTES]; uint8_t pmk[L_ECC_SCALAR_MAX_BYTES]; uint8_t pmkid[16]; uint8_t key[L_ECC_SCALAR_MAX_BYTES + L_ECC_SCALAR_MAX_BYTES + 2]; uint8_t *ptr = key; struct iovec iov[2]; struct l_checksum *sha; struct l_ecc_point *other_public; ssize_t nbytes; enum l_checksum_type type; other_public = l_ecc_point_from_data(owe->curve, L_ECC_POINT_TYPE_COMPLIANT, public_key, pub_len); if (!other_public) { l_error("AP public key was not valid"); return false; } if (!l_ecdh_generate_shared_secret(owe->private, other_public, &shared_secret)) { l_ecc_point_free(other_public); return false; } l_ecc_point_free(other_public); nbytes = l_ecc_scalar_get_data(shared_secret, ss_buf, sizeof(ss_buf)); l_ecc_scalar_free(shared_secret); if (nbytes < 0) return false; ptr += l_ecc_point_get_x(owe->public_key, ptr, sizeof(key)); memcpy(ptr, public_key, nbytes); ptr += nbytes; l_put_le16(owe->group, ptr); ptr += 2; switch (owe->group) { case 19: type = L_CHECKSUM_SHA256; break; case 20: type = L_CHECKSUM_SHA384; break; default: goto failed; } /* prk = HKDF-extract(C | A | group, z) */ if (!hkdf_extract(type, key, ptr - key, 1, prk, ss_buf, nbytes)) goto failed; /* PMK = HKDF-expand(prk, "OWE Key Generation", n) */ if (!hkdf_expand(type, prk, nbytes, "OWE Key Generation", strlen("OWE Key Generation"), pmk, nbytes)) goto failed; sha = l_checksum_new(type); /* PMKID = Truncate-128(Hash(C | A)) */ iov[0].iov_base = key; /* first nbytes of key are owe->public_key */ iov[0].iov_len = nbytes; iov[1].iov_base = (void *) public_key; iov[1].iov_len = nbytes; l_checksum_updatev(sha, iov, 2); l_checksum_get_digest(sha, pmkid, 16); l_checksum_free(sha); handshake_state_set_pmk(owe->hs, pmk, nbytes); handshake_state_set_pmkid(owe->hs, pmkid); return true; failed: memset(ss_buf, 0, sizeof(ss_buf)); return false; } static bool owe_retry(struct owe_sm *owe) { /* retry with another group, if possible */ owe->retry++; if (!owe_reset(owe)) return false; l_debug("OWE retrying with group %u", owe->group); owe_rx_authenticate(&owe->ap, NULL, 0); return true; } static int owe_rx_associate(struct auth_proto *ap, const uint8_t *frame, size_t len) { struct owe_sm *owe = l_container_of(ap, struct owe_sm, ap); const struct mmpdu_header *mpdu = NULL; const struct mmpdu_association_response *body; struct ie_tlv_iter iter; size_t owe_dh_len = 0; const uint8_t *owe_dh = NULL; struct ie_rsn_info info; bool akm_found = false; const void *data; mpdu = mpdu_validate(frame, len); if (!mpdu) { l_error("could not process frame"); return -EBADMSG; } body = mmpdu_body(mpdu); if (L_LE16_TO_CPU(body->status_code) == MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP) { if (!owe_retry(owe)) goto owe_bad_status; return -EAGAIN; } else if (body->status_code) goto owe_bad_status; ie_tlv_iter_init(&iter, body->ies, (const uint8_t *) mpdu + len - body->ies); while (ie_tlv_iter_next(&iter)) { uint16_t tag = ie_tlv_iter_get_tag(&iter); data = ie_tlv_iter_get_data(&iter); len = ie_tlv_iter_get_length(&iter); switch (tag) { case IE_TYPE_OWE_DH_PARAM: owe_dh = data; owe_dh_len = len; break; case IE_TYPE_RSN: if (ie_parse_rsne(&iter, &info) < 0) { l_error("could not parse RSN IE"); goto invalid_ies; } /* * RFC 8110 Section 4.2 * An AP agreeing to do OWE MUST include the OWE AKM in * the RSN element portion of the 802.11 association * response. */ if (info.akm_suites != IE_RSN_AKM_SUITE_OWE) { l_error("OWE AKM not included"); goto invalid_ies; } akm_found = true; break; default: continue; } } if (!owe_dh || owe_dh_len < 34 || !akm_found) { l_error("associate response did not include proper OWE IE's"); goto invalid_ies; } if (l_get_le16(owe_dh) != owe->group) { l_error("associate response contained unsupported group %u", l_get_le16(owe_dh)); return -EBADMSG; } if (!owe_compute_keys(owe, owe_dh + 2, owe_dh_len - 2)) { l_error("could not compute OWE keys"); return -EBADMSG; } return 0; invalid_ies: return MMPDU_STATUS_CODE_INVALID_ELEMENT; owe_bad_status: return L_LE16_TO_CPU(body->status_code); } struct auth_proto *owe_sm_new(struct handshake_state *hs, owe_tx_authenticate_func_t auth, owe_tx_associate_func_t assoc, void *user_data) { struct owe_sm *owe = l_new(struct owe_sm, 1); owe->hs = hs; owe->auth_tx = auth; owe->assoc_tx = assoc; owe->user_data = user_data; owe->ecc_groups = l_ecc_curve_get_supported_ike_groups(); owe->ap.start = owe_start; owe->ap.free = owe_free; owe->ap.rx_authenticate = owe_rx_authenticate; owe->ap.rx_associate = owe_rx_associate; if (!owe_reset(owe)) { l_free(owe); return NULL; } return &owe->ap; }