/* * * 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 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; }; 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_from_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; } void owe_sm_free(struct owe_sm *owe) { l_ecc_scalar_free(owe->private); l_ecc_point_free(owe->public_key); l_free(owe); } void owe_build_dh_ie(struct owe_sm *owe, uint8_t *buf, size_t *len_out) { /* * A client wishing to do OWE ... 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_out = l_ecc_point_get_x(owe->public_key, buf + 5, L_ECC_SCALAR_MAX_BYTES); buf[1] = 3 + *len_out; /* length */ *len_out += 5; } /* * 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", 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; } bool owe_next_group(struct owe_sm *owe) { /* retry with another group, if possible */ owe->retry++; if (!owe_reset(owe)) return false; return true; } int owe_process_dh_ie(struct owe_sm *owe, const uint8_t *dh, size_t len) { if (!dh || len < 34) { l_error("associate response did not include proper OWE IE's"); goto invalid_ies; } if (l_get_le16(dh) != owe->group) { l_error("associate response contained unsupported group %u", l_get_le16(dh)); return -EBADMSG; } if (!owe_compute_keys(owe, dh + 2, len - 2)) { l_error("could not compute OWE keys"); return -EBADMSG; } return 0; invalid_ies: return MMPDU_STATUS_CODE_INVALID_ELEMENT; } struct owe_sm *owe_sm_new(struct handshake_state *hs) { struct owe_sm *owe = l_new(struct owe_sm, 1); owe->hs = hs; owe->ecc_groups = l_ecc_supported_ike_groups(); if (!owe_reset(owe)) { l_free(owe); return NULL; } return owe; }