249 lines
5.6 KiB
C
249 lines
5.6 KiB
C
/*
|
|
*
|
|
* 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 <config.h>
|
|
#endif
|
|
|
|
#include <ell/ell.h>
|
|
|
|
#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)
|
|
{
|
|
if (owe->hs->force_default_owe_group) {
|
|
if (owe->retry != 0) {
|
|
l_warn("Forced default OWE group but was rejected!");
|
|
return false;
|
|
}
|
|
|
|
l_debug("Forcing default OWE group 19");
|
|
|
|
owe->retry++;
|
|
owe->group = 19;
|
|
|
|
goto get_curve;
|
|
}
|
|
|
|
/*
|
|
* 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];
|
|
|
|
get_curve:
|
|
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;
|
|
}
|