mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-11-10 14:09:22 +01:00
840 lines
20 KiB
C
840 lines
20 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 <stdio.h>
|
|
|
|
#include <ell/ell.h>
|
|
|
|
#include "ell/useful.h"
|
|
#include "src/missing.h"
|
|
#include "src/eap.h"
|
|
#include "src/eap-private.h"
|
|
#include "src/crypto.h"
|
|
#include "src/util.h"
|
|
|
|
#define EAP_PWD_GROUP_DESC 19
|
|
#define EAP_PWD_RAND_FN 0x01
|
|
#define EAP_PWD_PRF 0x01
|
|
|
|
/* EAP header + PWD-Exch */
|
|
#define EAP_PWD_HDR_LEN 6
|
|
#define EAP_PWD_L_BIT (1 << 7)
|
|
#define EAP_PWD_M_BIT (1 << 6)
|
|
|
|
enum eap_pwd_prep {
|
|
EAP_PWD_PREP_NONE = 0x00,
|
|
EAP_PWD_PREP_MS = 0x01,
|
|
EAP_PWD_PREP_SASL = 0x02
|
|
};
|
|
|
|
enum eap_pwd_exch {
|
|
EAP_PWD_EXCH_RESERVED = 0,
|
|
EAP_PWD_EXCH_ID,
|
|
EAP_PWD_EXCH_COMMIT,
|
|
EAP_PWD_EXCH_CONFIRM
|
|
};
|
|
|
|
enum eap_pwd_state {
|
|
EAP_PWD_STATE_INIT = 0,
|
|
EAP_PWD_STATE_ID,
|
|
EAP_PWD_STATE_COMMIT,
|
|
EAP_PWD_STATE_CONFIRM
|
|
};
|
|
|
|
struct eap_pwd_handle {
|
|
enum eap_pwd_state state;
|
|
enum eap_pwd_prep prep;
|
|
char *identity;
|
|
char *password;
|
|
const struct l_ecc_curve *curve;
|
|
struct l_ecc_point *pwe;
|
|
struct l_ecc_point *element_s;
|
|
struct l_ecc_point *element_p;
|
|
uint32_t ciphersuite;
|
|
struct l_ecc_scalar *scalar_s;
|
|
struct l_ecc_scalar *scalar_p;
|
|
struct l_ecc_scalar *p_rand;
|
|
uint8_t *rx_frag_buf;
|
|
uint16_t rx_frag_total;
|
|
uint16_t rx_frag_count;
|
|
uint8_t *tx_frag_buf;
|
|
uint8_t *tx_frag_pos;
|
|
uint16_t tx_frag_remaining;
|
|
};
|
|
|
|
/* RFC 5931, Section 2.5 - Key Derivation Function */
|
|
static bool kdf(uint8_t *key, size_t key_len, const char *label,
|
|
size_t label_len, void *out, size_t olen)
|
|
{
|
|
struct l_checksum *hmac;
|
|
struct iovec iov[4];
|
|
uint16_t ibuf, i = 1;
|
|
uint16_t L = L_CPU_TO_BE16(olen * 8);
|
|
size_t len = 0;
|
|
|
|
while (len < olen) {
|
|
int iov_pos = 0;
|
|
|
|
hmac = l_checksum_new_hmac(L_CHECKSUM_SHA256, key, key_len);
|
|
if (!hmac)
|
|
return false;
|
|
|
|
/* PRF(key, K(i - 1) | i | label | L) */
|
|
if (i > 1) {
|
|
iov[iov_pos].iov_base = out + len - 32;
|
|
iov[iov_pos++].iov_len = 32;
|
|
}
|
|
|
|
ibuf = L_CPU_TO_BE16(i);
|
|
iov[iov_pos].iov_base = (void *) &ibuf;
|
|
iov[iov_pos++].iov_len = 2;
|
|
iov[iov_pos].iov_base = (void *)label;
|
|
iov[iov_pos++].iov_len = label_len;
|
|
iov[iov_pos].iov_base = &L;
|
|
iov[iov_pos++].iov_len = 2;
|
|
|
|
if (!l_checksum_updatev(hmac, iov, iov_pos)) {
|
|
l_checksum_free(hmac);
|
|
return false;
|
|
}
|
|
|
|
l_checksum_get_digest(hmac, out + len, minsize(olen - len, 32));
|
|
l_checksum_free(hmac);
|
|
|
|
len += 32;
|
|
i++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool eap_pwd_reset_state(struct eap_state *eap)
|
|
{
|
|
struct eap_pwd_handle *pwd = eap_get_data(eap);
|
|
|
|
pwd->state = EAP_PWD_STATE_INIT;
|
|
|
|
l_free(pwd->tx_frag_buf);
|
|
pwd->tx_frag_buf = NULL;
|
|
pwd->tx_frag_pos = NULL;
|
|
pwd->tx_frag_remaining = 0;
|
|
|
|
l_free(pwd->rx_frag_buf);
|
|
pwd->rx_frag_buf = NULL;
|
|
pwd->rx_frag_count = 0;
|
|
pwd->rx_frag_total = 0;
|
|
|
|
pwd->prep = EAP_PWD_PREP_NONE;
|
|
pwd->ciphersuite = 0;
|
|
|
|
l_ecc_point_free(pwd->pwe);
|
|
pwd->pwe = NULL;
|
|
l_ecc_point_free(pwd->element_p);
|
|
pwd->element_p = NULL;
|
|
l_ecc_point_free(pwd->element_s);
|
|
pwd->element_s = NULL;
|
|
l_ecc_scalar_free(pwd->scalar_p);
|
|
pwd->scalar_p = NULL;
|
|
l_ecc_scalar_free(pwd->scalar_s);
|
|
pwd->scalar_s = NULL;
|
|
l_ecc_scalar_free(pwd->p_rand);
|
|
pwd->p_rand = NULL;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void eap_pwd_free(struct eap_state *eap)
|
|
{
|
|
struct eap_pwd_handle *pwd = eap_get_data(eap);
|
|
|
|
eap_pwd_reset_state(eap);
|
|
l_free(pwd->identity);
|
|
|
|
if (pwd->password) {
|
|
explicit_bzero(pwd->password, strlen(pwd->password));
|
|
l_free(pwd->password);
|
|
}
|
|
|
|
l_free(pwd);
|
|
|
|
eap_set_data(eap, NULL);
|
|
}
|
|
|
|
static void eap_pwd_send_response(struct eap_state *eap,
|
|
uint8_t *pkt, size_t len)
|
|
{
|
|
struct eap_pwd_handle *pwd = eap_get_data(eap);
|
|
size_t mtu = eap_get_mtu(eap);
|
|
uint8_t frag[mtu];
|
|
uint8_t *pos = frag;
|
|
/* first fragment data bytes (mtu - header - Total-Length) */
|
|
uint16_t send_bytes = mtu - EAP_PWD_HDR_LEN - 2;
|
|
|
|
/* packet will fit within mtu */
|
|
if (len <= mtu) {
|
|
eap_method_respond(eap, pkt, len);
|
|
return;
|
|
}
|
|
|
|
if (pwd->tx_frag_buf) {
|
|
l_error("already processing fragment, cannot send response");
|
|
return;
|
|
}
|
|
|
|
/* header */
|
|
memcpy(pos, pkt, 5);
|
|
pos += 5;
|
|
/* PWD-Exch, first frag, so L and M are both set */
|
|
*pos++ = pwd->state | EAP_PWD_L_BIT | EAP_PWD_M_BIT;
|
|
/* Total-Length */
|
|
l_put_be16((uint16_t)len, pos);
|
|
pos += 2;
|
|
/* copy packet data bytes */
|
|
memcpy(pos, pkt + EAP_PWD_HDR_LEN, send_bytes);
|
|
|
|
pwd->tx_frag_remaining = len - EAP_PWD_HDR_LEN - send_bytes;
|
|
|
|
l_info("sending initial fragment, %zu bytes", mtu);
|
|
|
|
eap_method_respond(eap, frag, mtu);
|
|
|
|
/* alloc/copy remainder of packet to frag buf */
|
|
pwd->tx_frag_buf = l_malloc(pwd->tx_frag_remaining);
|
|
|
|
memcpy(pwd->tx_frag_buf, pkt + EAP_PWD_HDR_LEN + send_bytes,
|
|
pwd->tx_frag_remaining);
|
|
|
|
pwd->tx_frag_pos = pwd->tx_frag_buf;
|
|
}
|
|
|
|
static void eap_pwd_handle_id(struct eap_state *eap,
|
|
const uint8_t *pkt, size_t len)
|
|
{
|
|
struct eap_pwd_handle *pwd = eap_get_data(eap);
|
|
uint16_t group;
|
|
uint8_t rand_fn;
|
|
uint8_t prf;
|
|
uint32_t token;
|
|
uint8_t counter = 0;
|
|
uint8_t resp[15 + strlen(pwd->identity)];
|
|
uint8_t *pos;
|
|
uint8_t pwd_seed[32];
|
|
uint8_t pwd_value[L_ECC_SCALAR_MAX_BYTES]; /* used as X value */
|
|
size_t nbytes;
|
|
bool found = false;
|
|
|
|
/*
|
|
* Group desc (2) + Random func (1) + prf (1) + token (4) + prep (1) +
|
|
* Identity (at least 1 byte)
|
|
*/
|
|
if (len < 9) {
|
|
l_error("bad packet length");
|
|
goto error;
|
|
}
|
|
|
|
if (pwd->state != EAP_PWD_STATE_INIT) {
|
|
l_error("received ID request in invalid state");
|
|
goto error;
|
|
}
|
|
|
|
pwd->state = EAP_PWD_STATE_ID;
|
|
|
|
group = l_get_be16(pkt);
|
|
|
|
pwd->curve = l_ecc_curve_get_ike_group(group);
|
|
if (!pwd->curve) {
|
|
l_error("group %d not supported", group);
|
|
goto error;
|
|
}
|
|
|
|
rand_fn = pkt[2];
|
|
if (rand_fn != EAP_PWD_RAND_FN) {
|
|
l_error("rand_fn %d not supported", rand_fn);
|
|
goto error;
|
|
}
|
|
|
|
prf = pkt[3];
|
|
if (prf != EAP_PWD_PRF) {
|
|
l_error("PRF function %d not supported", prf);
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* RFC 5931 Section 3.2.1
|
|
* The Group Description, Random Function, and PRF together, and in that
|
|
* order, comprise the Ciphersuite...
|
|
*/
|
|
pwd->ciphersuite = l_get_u32(pkt);
|
|
token = l_get_u32(pkt + 4);
|
|
pwd->prep = pkt[8];
|
|
|
|
if (pwd->prep != EAP_PWD_PREP_NONE) {
|
|
/*
|
|
* TODO: Support other PW prep types
|
|
*/
|
|
l_error("prep type %d not currently supported", pwd->prep);
|
|
goto error;
|
|
}
|
|
|
|
nbytes = l_ecc_curve_get_scalar_bytes(pwd->curve);
|
|
|
|
while (counter < 20) {
|
|
struct l_ecc_point *pwe = NULL;
|
|
|
|
counter++;
|
|
|
|
/* pwd-seed = H(token|peer-ID|server-ID|password|counter) */
|
|
hkdf_extract(L_CHECKSUM_SHA256, NULL, 0, 5, pwd_seed, &token, 4,
|
|
pwd->identity, strlen(pwd->identity), pkt + 9,
|
|
len - 9, pwd->password, strlen(pwd->password),
|
|
&counter, (size_t) 1);
|
|
|
|
/*
|
|
* pwd-value = KDF(pwd-seed, "EAP-pwd Hunting And Pecking",
|
|
* len(p))
|
|
*/
|
|
kdf(pwd_seed, 32, "EAP-pwd Hunting And Pecking",
|
|
strlen("EAP-pwd Hunting And Pecking"),
|
|
pwd_value, nbytes);
|
|
|
|
if (!(pwd_seed[31] & 1))
|
|
pwe = l_ecc_point_from_data(pwd->curve,
|
|
L_ECC_POINT_TYPE_COMPRESSED_BIT1,
|
|
pwd_value, nbytes);
|
|
else
|
|
pwe = l_ecc_point_from_data(pwd->curve,
|
|
L_ECC_POINT_TYPE_COMPRESSED_BIT0,
|
|
pwd_value, nbytes);
|
|
|
|
if (!pwe)
|
|
continue;
|
|
|
|
if (!found) {
|
|
found = true;
|
|
pwd->pwe = pwe;
|
|
} else
|
|
l_ecc_point_free(pwe);
|
|
}
|
|
|
|
explicit_bzero(pwd_seed, sizeof(pwd_seed));
|
|
explicit_bzero(pwd_value, sizeof(pwd_value));
|
|
|
|
pos = resp + 5; /* header */
|
|
*pos++ = EAP_PWD_EXCH_ID;
|
|
l_put_be16(group, pos);
|
|
pos += 2;
|
|
*pos++ = rand_fn;
|
|
*pos++ = prf;
|
|
l_put_u32(token, pos);
|
|
pos += 4;
|
|
*pos++ = pwd->prep;
|
|
memcpy(pos, pwd->identity, strlen(pwd->identity));
|
|
pos += strlen(pwd->identity);
|
|
|
|
eap_pwd_send_response(eap, resp, pos - resp);
|
|
|
|
return;
|
|
|
|
error:
|
|
eap_method_error(eap);
|
|
}
|
|
|
|
static void eap_pwd_handle_commit(struct eap_state *eap,
|
|
const uint8_t *pkt, size_t len)
|
|
{
|
|
struct eap_pwd_handle *pwd = eap_get_data(eap);
|
|
uint8_t resp[L_ECC_POINT_MAX_BYTES + L_ECC_SCALAR_MAX_BYTES + 6];
|
|
uint8_t *pos;
|
|
struct l_ecc_scalar *p_mask;
|
|
struct l_ecc_scalar *order;
|
|
size_t nbytes = l_ecc_curve_get_scalar_bytes(pwd->curve);
|
|
|
|
/* [Element (nbytes * 2)][Scalar (nbytes)] */
|
|
if (len != nbytes + nbytes * 2) {
|
|
l_error("bad packet length, expected %zu, got %zu",
|
|
nbytes + nbytes * 2, len);
|
|
goto error;
|
|
}
|
|
|
|
if (pwd->state != EAP_PWD_STATE_ID) {
|
|
l_error("received commit request in invalid state");
|
|
goto error;
|
|
}
|
|
|
|
pwd->state = EAP_PWD_STATE_COMMIT;
|
|
|
|
/*
|
|
* Commit contains Element_S (nbytes * 2) then Scalar_s (nbytes)
|
|
*/
|
|
pwd->element_s = l_ecc_point_from_data(pwd->curve,
|
|
L_ECC_POINT_TYPE_FULL,
|
|
pkt, nbytes * 2);
|
|
if (!pwd->element_s) {
|
|
l_error("Server sent invalid Element_S during commit");
|
|
goto error;
|
|
}
|
|
|
|
pwd->scalar_s = l_ecc_scalar_new(pwd->curve, pkt + nbytes * 2, nbytes);
|
|
if (!pwd->scalar_s) {
|
|
l_error("Server sent invalid Scalar_S during commit");
|
|
goto error;
|
|
}
|
|
|
|
pwd->p_rand = l_ecc_scalar_new_random(pwd->curve);
|
|
p_mask = l_ecc_scalar_new_random(pwd->curve);
|
|
pwd->scalar_p = l_ecc_scalar_new(pwd->curve, NULL, 0);
|
|
|
|
order = l_ecc_curve_get_order(pwd->curve);
|
|
|
|
l_ecc_scalar_add(pwd->scalar_p, pwd->p_rand, p_mask, order);
|
|
|
|
l_ecc_scalar_free(order);
|
|
|
|
pwd->element_p = l_ecc_point_new(pwd->curve);
|
|
/* p_mask * PWE */
|
|
l_ecc_point_multiply(pwd->element_p, p_mask, pwd->pwe);
|
|
|
|
l_ecc_scalar_free(p_mask);
|
|
|
|
/* inv(p_mask * PWE) */
|
|
l_ecc_point_inverse(pwd->element_p);
|
|
|
|
/* send element_p and scalar_p */
|
|
pos = resp + 5; /* header */
|
|
*pos++ = EAP_PWD_EXCH_COMMIT;
|
|
pos += l_ecc_point_get_data(pwd->element_p, pos, nbytes * 2);
|
|
pos += l_ecc_scalar_get_data(pwd->scalar_p, pos, nbytes);
|
|
|
|
eap_pwd_send_response(eap, resp, pos - resp);
|
|
|
|
return;
|
|
|
|
error:
|
|
eap_method_error(eap);
|
|
}
|
|
|
|
static void eap_pwd_handle_confirm(struct eap_state *eap,
|
|
const uint8_t *pkt, size_t len)
|
|
{
|
|
struct eap_pwd_handle *pwd = eap_get_data(eap);
|
|
struct l_ecc_point *kp;
|
|
uint8_t resp[38];
|
|
uint8_t *pos;
|
|
uint8_t confirm_s[32];
|
|
uint8_t confirm_p[32];
|
|
uint8_t expected_confirm_s[32];
|
|
uint8_t mk[32];
|
|
uint8_t msk_emsk[128], session_id[33];
|
|
/* buffers used for the final hash */
|
|
uint8_t kpx[L_ECC_SCALAR_MAX_BYTES];
|
|
uint8_t scalar_s[L_ECC_SCALAR_MAX_BYTES];
|
|
uint8_t scalar_p[L_ECC_SCALAR_MAX_BYTES];
|
|
uint8_t element_s[L_ECC_POINT_MAX_BYTES];
|
|
uint8_t element_p[L_ECC_POINT_MAX_BYTES];
|
|
ssize_t plen, clen;
|
|
|
|
if (len != 32) {
|
|
l_error("bad packet length");
|
|
goto error;
|
|
}
|
|
|
|
if (pwd->state != EAP_PWD_STATE_COMMIT) {
|
|
l_error("received confirm request in invalid state");
|
|
goto error;
|
|
}
|
|
|
|
pwd->state = EAP_PWD_STATE_CONFIRM;
|
|
|
|
memcpy(confirm_s, pkt, 32);
|
|
|
|
kp = l_ecc_point_new(pwd->curve);
|
|
|
|
/* compute KP = (p_rand * (Scalar_S * PWE + Element_S)) */
|
|
l_ecc_point_multiply(kp, pwd->scalar_s, pwd->pwe);
|
|
|
|
l_ecc_point_add(kp, kp, pwd->element_s);
|
|
|
|
l_ecc_point_multiply(kp, pwd->p_rand, kp);
|
|
|
|
/*
|
|
* We just need to store clen/plen once. Since all these buffers are
|
|
* created with enough bytes in mind we know these won't fail. Also, all
|
|
* scalar/point objects were created with the same curve, so it can be
|
|
* safe to assume the return values will not change from what clen/plen
|
|
* already are.
|
|
*/
|
|
clen = l_ecc_point_get_x(kp, kpx, sizeof(kpx));
|
|
if (clen < 0)
|
|
goto invalid_point;
|
|
|
|
plen = l_ecc_point_get_data(pwd->element_s, element_s,
|
|
sizeof(element_s));
|
|
if (plen < 0)
|
|
goto invalid_point;
|
|
|
|
if (l_ecc_point_get_data(pwd->element_p, element_p,
|
|
sizeof(element_p)) < 0)
|
|
goto invalid_point;
|
|
|
|
if (l_ecc_scalar_get_data(pwd->scalar_s, scalar_s,
|
|
sizeof(scalar_s)) < 0)
|
|
goto invalid_point;
|
|
|
|
if (l_ecc_scalar_get_data(pwd->scalar_p, scalar_p,
|
|
sizeof(scalar_p)) < 0)
|
|
goto invalid_point;
|
|
|
|
l_ecc_point_free(kp);
|
|
|
|
/*
|
|
* compute Confirm_P = H(kp | Element_P | Scalar_P |
|
|
* Element_S | Scalar_S | Ciphersuite)
|
|
*/
|
|
hkdf_extract(L_CHECKSUM_SHA256, NULL, 0, 6, confirm_p, kpx, clen,
|
|
element_p, plen, scalar_p, clen, element_s,
|
|
plen, scalar_s, clen, &pwd->ciphersuite,
|
|
(size_t) 4);
|
|
|
|
hkdf_extract(L_CHECKSUM_SHA256, NULL, 0, 6, expected_confirm_s, kpx,
|
|
clen, element_s, plen, scalar_s, clen,
|
|
element_p, plen, scalar_p, clen,
|
|
&pwd->ciphersuite, (size_t) 4);
|
|
|
|
if (memcmp(confirm_s, expected_confirm_s, 32)) {
|
|
l_error("Confirm_S did not verify");
|
|
goto error;
|
|
}
|
|
|
|
pos = resp + 5; /* header */
|
|
*pos++ = EAP_PWD_EXCH_CONFIRM;
|
|
memcpy(pos, confirm_p, 32);
|
|
pos += 32;
|
|
|
|
/* derive MK = H(kp | Confirm_P | Confirm_S ) */
|
|
hkdf_extract(L_CHECKSUM_SHA256, NULL, 0, 3, mk, kpx, clen, confirm_p,
|
|
(size_t) 32, confirm_s, (size_t) 32);
|
|
|
|
eap_pwd_send_response(eap, resp, pos - resp);
|
|
|
|
eap_method_success(eap);
|
|
|
|
session_id[0] = 52;
|
|
hkdf_extract(L_CHECKSUM_SHA256, NULL, 0, 3, session_id + 1,
|
|
&pwd->ciphersuite, (size_t) 4, scalar_p, clen,
|
|
scalar_s, clen);
|
|
|
|
kdf(mk, 32, (const char *) session_id, 33, msk_emsk, 128);
|
|
eap_set_key_material(eap, msk_emsk, 64, msk_emsk + 64, 64, NULL, 0,
|
|
session_id, sizeof(session_id));
|
|
|
|
explicit_bzero(mk, sizeof(mk));
|
|
explicit_bzero(msk_emsk, sizeof(msk_emsk));
|
|
explicit_bzero(kpx, sizeof(kpx));
|
|
|
|
return;
|
|
|
|
invalid_point:
|
|
l_ecc_point_free(kp);
|
|
|
|
l_error("invalid point during confirm exchange");
|
|
error:
|
|
explicit_bzero(kpx, sizeof(kpx));
|
|
|
|
eap_method_error(eap);
|
|
}
|
|
|
|
static void eap_pwd_process(struct eap_state *eap,
|
|
const uint8_t *pkt, size_t len)
|
|
{
|
|
uint8_t pwd_exch = bit_field(pkt[0], 0, 6);
|
|
|
|
if (len < 1)
|
|
return;
|
|
|
|
switch (pwd_exch) {
|
|
case EAP_PWD_EXCH_ID:
|
|
eap_pwd_handle_id(eap, pkt + 1, len - 1);
|
|
break;
|
|
case EAP_PWD_EXCH_COMMIT:
|
|
eap_pwd_handle_commit(eap, pkt + 1, len - 1);
|
|
break;
|
|
case EAP_PWD_EXCH_CONFIRM:
|
|
eap_pwd_handle_confirm(eap, pkt + 1, len - 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void eap_pwd_send_ack(struct eap_state *eap)
|
|
{
|
|
struct eap_pwd_handle *pwd = eap_get_data(eap);
|
|
uint8_t buf[6];
|
|
|
|
buf[5] = pwd->state + 1;
|
|
|
|
eap_method_respond(eap, buf, 6);
|
|
}
|
|
|
|
#define FRAG_BYTES(mtu, remaining) \
|
|
((mtu - EAP_PWD_HDR_LEN) < remaining) ? (mtu - EAP_PWD_HDR_LEN) : \
|
|
remaining
|
|
|
|
static void eap_pwd_handle_request(struct eap_state *eap,
|
|
const uint8_t *pkt, size_t len)
|
|
{
|
|
struct eap_pwd_handle *pwd = eap_get_data(eap);
|
|
uint8_t len_bit = false;
|
|
uint8_t more_bit = false;
|
|
|
|
/* ACK from tx fragment, send next fragment */
|
|
if (len == 1 && pwd->tx_frag_buf) {
|
|
size_t mtu = eap_get_mtu(eap);
|
|
uint8_t frag[mtu];
|
|
uint8_t *pos = frag;
|
|
uint16_t frag_bytes = FRAG_BYTES(mtu, pwd->tx_frag_remaining);
|
|
|
|
pos += 5; /* header */
|
|
*pos = pwd->state;
|
|
|
|
/* more fragments coming, set M bit */
|
|
if (frag_bytes < pwd->tx_frag_remaining)
|
|
*pos |= EAP_PWD_M_BIT;
|
|
|
|
pos++;
|
|
|
|
memcpy(pos, pwd->tx_frag_pos, frag_bytes);
|
|
|
|
pwd->tx_frag_pos += frag_bytes;
|
|
pwd->tx_frag_remaining -= frag_bytes;
|
|
|
|
l_info("sending fragment, %d bytes",
|
|
frag_bytes + EAP_PWD_HDR_LEN);
|
|
|
|
eap_method_respond(eap, frag, frag_bytes + EAP_PWD_HDR_LEN);
|
|
|
|
if (!pwd->tx_frag_remaining) {
|
|
/* done sending fragments, free */
|
|
l_free(pwd->tx_frag_buf);
|
|
pwd->tx_frag_buf = NULL;
|
|
pwd->tx_frag_pos = NULL;
|
|
pwd->tx_frag_remaining = 0;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (pwd->tx_frag_buf) {
|
|
l_error("received packet while waiting for ACK!");
|
|
return;
|
|
}
|
|
|
|
if (len < 1) {
|
|
l_error("packet is too small");
|
|
return;
|
|
}
|
|
|
|
/* set if Total-Length parameter is include (i.e. first fragment) */
|
|
len_bit = test_bit(pkt, 7);
|
|
/* set on all but the last fragment */
|
|
more_bit = test_bit(pkt, 6);
|
|
|
|
/* first rx fragment */
|
|
if (len_bit) {
|
|
if (len < 3) {
|
|
l_error("malformed packet");
|
|
return;
|
|
}
|
|
|
|
/* remove length of Total-Length parameter (2) */
|
|
pwd->rx_frag_total = l_get_be16(pkt + 1) - 2;
|
|
pwd->rx_frag_buf = l_malloc(pwd->rx_frag_total);
|
|
|
|
/* skip copying Total-Length for easier processing later */
|
|
pwd->rx_frag_buf[0] = pkt[0];
|
|
memcpy(pwd->rx_frag_buf + 1, pkt + 3, len - 2);
|
|
|
|
pwd->rx_frag_count = len - 2;
|
|
|
|
l_info("received first fragment, %d total bytes",
|
|
pwd->rx_frag_total);
|
|
|
|
eap_pwd_send_ack(eap);
|
|
|
|
return;
|
|
}
|
|
|
|
/* more rx fragments */
|
|
if (pwd->rx_frag_buf) {
|
|
/* continue building packet (not including PWD-Exch byte) */
|
|
memcpy(pwd->rx_frag_buf + pwd->rx_frag_count, pkt + 1, len - 1);
|
|
pwd->rx_frag_count += (len - 1);
|
|
|
|
l_info("received another fragment, %zu bytes", len);
|
|
|
|
/* more fragments coming */
|
|
if (more_bit) {
|
|
eap_pwd_send_ack(eap);
|
|
return;
|
|
}
|
|
|
|
if (pwd->rx_frag_count != pwd->rx_frag_total) {
|
|
l_error("fragment length mismatch");
|
|
return;
|
|
}
|
|
|
|
/* this was the last fragment, process */
|
|
eap_pwd_process(eap, pwd->rx_frag_buf, pwd->rx_frag_total);
|
|
|
|
l_free(pwd->rx_frag_buf);
|
|
pwd->rx_frag_buf = NULL;
|
|
pwd->rx_frag_count = 0;
|
|
pwd->rx_frag_total = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
/* no fragmentation, process normally */
|
|
eap_pwd_process(eap, pkt, len);
|
|
}
|
|
|
|
static int eap_pwd_check_settings(struct l_settings *settings,
|
|
struct l_queue *secrets,
|
|
const char *prefix,
|
|
struct l_queue **out_missing)
|
|
{
|
|
const struct eap_secret_info *secret;
|
|
char identity_key[72];
|
|
char password_key[72];
|
|
|
|
L_AUTO_FREE_VAR(char *, identity);
|
|
L_AUTO_FREE_VAR(char *, password) = NULL;
|
|
|
|
snprintf(identity_key, sizeof(identity_key), "%sIdentity", prefix);
|
|
snprintf(password_key, sizeof(password_key), "%sPassword", prefix);
|
|
|
|
identity = l_settings_get_string(settings, "Security", identity_key);
|
|
|
|
if (!identity) {
|
|
secret = l_queue_find(secrets, eap_secret_info_match,
|
|
identity_key);
|
|
if (secret)
|
|
return 0;
|
|
|
|
eap_append_secret(out_missing, EAP_SECRET_REMOTE_USER_PASSWORD,
|
|
identity_key, password_key, NULL,
|
|
EAP_CACHE_TEMPORARY);
|
|
|
|
return 0;
|
|
}
|
|
|
|
password = l_settings_get_string(settings, "Security", password_key);
|
|
|
|
if (!password) {
|
|
secret = l_queue_find(secrets, eap_secret_info_match,
|
|
password_key);
|
|
if (secret)
|
|
return 0;
|
|
|
|
eap_append_secret(out_missing, EAP_SECRET_REMOTE_PASSWORD,
|
|
password_key, NULL, identity,
|
|
EAP_CACHE_TEMPORARY);
|
|
} else
|
|
explicit_bzero(password, strlen(password));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool eap_pwd_load_settings(struct eap_state *eap,
|
|
struct l_settings *settings,
|
|
const char *prefix)
|
|
{
|
|
struct eap_pwd_handle *pwd;
|
|
char setting_key[72];
|
|
|
|
pwd = l_new(struct eap_pwd_handle, 1);
|
|
|
|
pwd->state = EAP_PWD_STATE_INIT;
|
|
|
|
snprintf(setting_key, sizeof(setting_key), "%sIdentity", prefix);
|
|
pwd->identity = l_settings_get_string(settings, "Security",
|
|
setting_key);
|
|
|
|
if (!pwd->identity) {
|
|
l_error("'%s' setting is missing", setting_key);
|
|
goto error;
|
|
}
|
|
|
|
snprintf(setting_key, sizeof(setting_key), "%sPassword", prefix);
|
|
pwd->password = l_settings_get_string(settings, "Security",
|
|
setting_key);
|
|
|
|
if (!pwd->password) {
|
|
snprintf(setting_key, sizeof(setting_key), "%sPassword",
|
|
prefix);
|
|
l_error("'%s' setting is missing", setting_key);
|
|
goto error;
|
|
}
|
|
|
|
eap_set_data(eap, pwd);
|
|
|
|
return true;
|
|
|
|
error:
|
|
if (pwd->password) {
|
|
explicit_bzero(pwd->password, strlen(pwd->password));
|
|
l_free(pwd->password);
|
|
}
|
|
|
|
l_free(pwd->identity);
|
|
l_free(pwd);
|
|
|
|
return false;
|
|
}
|
|
|
|
static struct eap_method eap_pwd = {
|
|
.request_type = EAP_TYPE_PWD,
|
|
.exports_msk = true,
|
|
.name = "PWD",
|
|
.free = eap_pwd_free,
|
|
.handle_request = eap_pwd_handle_request,
|
|
.check_settings = eap_pwd_check_settings,
|
|
.load_settings = eap_pwd_load_settings,
|
|
.reset_state = eap_pwd_reset_state,
|
|
};
|
|
|
|
static int eap_pwd_init(void)
|
|
{
|
|
l_debug("");
|
|
return eap_register_method(&eap_pwd);
|
|
}
|
|
|
|
static void eap_pwd_exit(void)
|
|
{
|
|
l_debug("");
|
|
eap_unregister_method(&eap_pwd);
|
|
}
|
|
|
|
EAP_METHOD_BUILTIN(eap_pwd, eap_pwd_init, eap_pwd_exit)
|