mka: Fix lowest acceptable Packet Number (LPN) calculation and use

The purpose of the Lowest Acceptable PN (lpn) parameters in the MACsec
SAK Use parameter set is to enforce delay protection. Per IEEE Std
802.1X-2010, Clause 9, "Each SecY uses MKA to communicate the lowest PN
used for transmission with the SAK within the last two seconds, allowing
receivers to bound transmission delays."

When encoding the SAK Use parameter set the KaY should set llpn and olpn
to the lowest PN transmitted by the latest SAK and oldest SAK (if
active) within the last two seconds. Because MKPDUs are transmitted
every 2 seconds (MKA_HELLO_TIME), the solution implemented here
calculates lpn based on the txsc->next_pn read during the previous MKPDU
transmit.

Upon receiving and decoding a SAK Use parameter set with delay
protection enabled, the KaY will update the SecY's lpn if the delay
protect lpn is greater than the SecY's current lpn (which is a product
of last PN received and replay protection and window size).

Signed-off-by: Michael Siedzik <msiedzik@extremenetworks.com>
This commit is contained in:
Mike Siedzik 2018-02-20 14:28:39 -05:00 committed by Jouni Malinen
parent d9a0a72229
commit 2fc0675683
8 changed files with 144 additions and 27 deletions

View file

@ -3829,6 +3829,14 @@ struct wpa_driver_ops {
*/
int (*set_transmit_next_pn)(void *priv, struct transmit_sa *sa);
/**
* set_receive_lowest_pn - Set receive lowest PN
* @priv: Private driver interface data
* @sa: secure association
* Returns: 0 on success, -1 on failure (or if not supported)
*/
int (*set_receive_lowest_pn)(void *priv, struct receive_sa *sa);
/**
* create_receive_sc - create secure channel for receiving
* @priv: Private driver interface data

View file

@ -689,6 +689,50 @@ static int macsec_drv_get_receive_lowest_pn(void *priv, struct receive_sa *sa)
}
/**
* macsec_drv_set_receive_lowest_pn - Set receive lowest PN
* @priv: Private driver interface data
* @sa: secure association
* Returns: 0 on success, -1 on failure (or if not supported)
*/
static int macsec_drv_set_receive_lowest_pn(void *priv, struct receive_sa *sa)
{
struct macsec_drv_data *drv = priv;
struct macsec_genl_ctx *ctx = &drv->ctx;
struct nl_msg *msg;
struct nlattr *nest;
int ret = -1;
wpa_printf(MSG_DEBUG,
DRV_PREFIX "%s: set_receive_lowest_pn -> %d: %d",
drv->ifname, sa->an, sa->next_pn);
msg = msg_prepare(MACSEC_CMD_UPD_RXSA, ctx, drv->ifi);
if (!msg)
return ret;
nest = nla_nest_start(msg, MACSEC_ATTR_SA_CONFIG);
if (!nest)
goto nla_put_failure;
NLA_PUT_U8(msg, MACSEC_SA_ATTR_AN, sa->an);
NLA_PUT_U32(msg, MACSEC_SA_ATTR_PN, sa->next_pn);
nla_nest_end(msg, nest);
ret = nl_send_recv(ctx->sk, msg);
if (ret < 0) {
wpa_printf(MSG_ERROR,
DRV_PREFIX "failed to communicate: %d (%s)",
ret, nl_geterror(-ret));
}
nla_put_failure:
nlmsg_free(msg);
return ret;
}
/**
* macsec_drv_get_transmit_next_pn - Get transmit next PN
* @priv: Private driver interface data
@ -1373,6 +1417,7 @@ const struct wpa_driver_ops wpa_driver_macsec_linux_ops = {
.set_current_cipher_suite = macsec_drv_set_current_cipher_suite,
.enable_controlled_port = macsec_drv_enable_controlled_port,
.get_receive_lowest_pn = macsec_drv_get_receive_lowest_pn,
.set_receive_lowest_pn = macsec_drv_set_receive_lowest_pn,
.get_transmit_next_pn = macsec_drv_get_transmit_next_pn,
.set_transmit_next_pn = macsec_drv_set_transmit_next_pn,
.create_receive_sc = macsec_drv_create_receive_sc,

View file

@ -1155,27 +1155,38 @@ ieee802_1x_mka_get_sak_use_length(
/**
*
* ieee802_1x_mka_get_lpn
*/
static u32
ieee802_1x_mka_get_lpn(struct ieee802_1x_mka_participant *principal,
struct ieee802_1x_mka_ki *ki)
{
struct receive_sa *rxsa;
struct receive_sc *rxsc;
struct transmit_sa *txsa;
u32 lpn = 0;
dl_list_for_each(rxsc, &principal->rxsc_list, struct receive_sc, list) {
dl_list_for_each(rxsa, &rxsc->sa_list, struct receive_sa, list)
{
if (is_ki_equal(&rxsa->pkey->key_identifier, ki)) {
secy_get_receive_lowest_pn(principal->kay,
rxsa);
dl_list_for_each(txsa, &principal->txsc->sa_list,
struct transmit_sa, list) {
if (is_ki_equal(&txsa->pkey->key_identifier, ki)) {
/* Per IEEE Std 802.1X-2010, Clause 9, "Each SecY uses
* MKA to communicate the lowest PN used for
* transmission with the SAK within the last two
* seconds". Achieve this 2 second delay by setting the
* lpn using the transmit next PN (i.e., txsa->next_pn)
* that was read last time here (i.e., mka_hello_time
* 2 seconds ago).
*
* The lowest acceptable PN is the same as the last
* transmitted PN, which is one less than the next
* transmit PN.
*
* NOTE: This method only works if mka_hello_time is 2s.
*/
lpn = (txsa->next_pn > 0) ? (txsa->next_pn - 1) : 0;
lpn = lpn > rxsa->lowest_pn ?
lpn : rxsa->lowest_pn;
break;
}
/* Now read the current transmit next PN for use next
* time through. */
secy_get_transmit_next_pn(principal->kay, txsa);
break;
}
}
@ -1277,7 +1288,8 @@ ieee802_1x_mka_decode_sak_use_body(
struct ieee802_1x_mka_hdr *hdr;
struct ieee802_1x_mka_sak_use_body *body;
struct ieee802_1x_kay_peer *peer;
struct transmit_sa *txsa;
struct receive_sc *rxsc;
struct receive_sa *rxsa;
struct data_key *sa_key = NULL;
size_t body_len;
struct ieee802_1x_mka_ki ki;
@ -1396,25 +1408,38 @@ ieee802_1x_mka_decode_sak_use_body(
}
found = FALSE;
dl_list_for_each(txsa, &participant->txsc->sa_list,
struct transmit_sa, list) {
if (sa_key != NULL && txsa->pkey == sa_key) {
found = TRUE;
break;
dl_list_for_each(rxsc, &participant->rxsc_list, struct receive_sc,
list) {
dl_list_for_each(rxsa, &rxsc->sa_list, struct receive_sa,
list) {
if (sa_key && rxsa->pkey == sa_key) {
found = TRUE;
break;
}
}
if (found)
break;
}
if (!found) {
wpa_printf(MSG_WARNING, "KaY: Can't find txsa");
wpa_printf(MSG_WARNING, "KaY: Can't find rxsa");
return -1;
}
/* FIXME: Secy creates txsa with default npn. If MKA detected Latest Key
* npn is larger than txsa's npn, set it to txsa.
*/
secy_get_transmit_next_pn(kay, txsa);
if (lpn > txsa->next_pn) {
secy_set_transmit_next_pn(kay, txsa);
wpa_printf(MSG_INFO, "KaY: update lpn =0x%x", lpn);
if (body->delay_protect) {
secy_get_receive_lowest_pn(participant->kay, rxsa);
if (lpn > rxsa->lowest_pn) {
/* Delay protect window (communicated via MKA) is
* tighter than SecY's current replay protect window,
* so tell SecY the new (and higher) lpn. */
rxsa->lowest_pn = lpn;
secy_set_receive_lowest_pn(participant->kay, rxsa);
wpa_printf(MSG_DEBUG, "KaY: update lpn =0x%x", lpn);
}
/* FIX: Delay protection for olpn not implemented.
* Note that Old Key is only active for MKA_SAK_RETIRE_TIME
* (3 seconds) and delay protection does allow PN's within
* a 2 seconds window, so olpn would be a lot of work for
* just 1 second's worth of protection. */
}
return 0;

View file

@ -150,6 +150,7 @@ struct ieee802_1x_kay_ctx {
int (*get_receive_lowest_pn)(void *ctx, struct receive_sa *sa);
int (*get_transmit_next_pn)(void *ctx, struct transmit_sa *sa);
int (*set_transmit_next_pn)(void *ctx, struct transmit_sa *sa);
int (*set_receive_lowest_pn)(void *ctx, struct receive_sa *sa);
int (*create_receive_sc)(void *ctx, struct receive_sc *sc,
enum validate_frames vf,
enum confidentiality_offset co);

View file

@ -216,6 +216,27 @@ int secy_set_transmit_next_pn(struct ieee802_1x_kay *kay,
}
int secy_set_receive_lowest_pn(struct ieee802_1x_kay *kay,
struct receive_sa *rxsa)
{
struct ieee802_1x_kay_ctx *ops;
if (!kay || !rxsa) {
wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__);
return -1;
}
ops = kay->ctx;
if (!ops || !ops->set_receive_lowest_pn) {
wpa_printf(MSG_ERROR,
"KaY: secy set_receive_lowest_pn operation not supported");
return -1;
}
return ops->set_receive_lowest_pn(ops->ctx, rxsa);
}
int secy_create_receive_sc(struct ieee802_1x_kay *kay, struct receive_sc *rxsc)
{
struct ieee802_1x_kay_ctx *ops;

View file

@ -36,6 +36,8 @@ int secy_get_transmit_next_pn(struct ieee802_1x_kay *kay,
struct transmit_sa *txsa);
int secy_set_transmit_next_pn(struct ieee802_1x_kay *kay,
struct transmit_sa *txsa);
int secy_set_receive_lowest_pn(struct ieee802_1x_kay *kay,
struct receive_sa *txsa);
int secy_create_receive_sc(struct ieee802_1x_kay *kay, struct receive_sc *rxsc);
int secy_delete_receive_sc(struct ieee802_1x_kay *kay, struct receive_sc *rxsc);
int secy_create_receive_sa(struct ieee802_1x_kay *kay, struct receive_sa *rxsa);

View file

@ -804,6 +804,14 @@ static inline int wpa_drv_set_transmit_next_pn(struct wpa_supplicant *wpa_s,
return wpa_s->driver->set_transmit_next_pn(wpa_s->drv_priv, sa);
}
static inline int wpa_drv_set_receive_lowest_pn(struct wpa_supplicant *wpa_s,
struct receive_sa *sa)
{
if (!wpa_s->driver->set_receive_lowest_pn)
return -1;
return wpa_s->driver->set_receive_lowest_pn(wpa_s->drv_priv, sa);
}
static inline int
wpa_drv_create_receive_sc(struct wpa_supplicant *wpa_s, struct receive_sc *sc,
unsigned int conf_offset, int validation)

View file

@ -92,6 +92,12 @@ static int wpas_set_transmit_next_pn(void *wpa_s, struct transmit_sa *sa)
}
static int wpas_set_receive_lowest_pn(void *wpa_s, struct receive_sa *sa)
{
return wpa_drv_set_receive_lowest_pn(wpa_s, sa);
}
static unsigned int conf_offset_val(enum confidentiality_offset co)
{
switch (co) {
@ -219,6 +225,7 @@ int ieee802_1x_alloc_kay_sm(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid)
kay_ctx->get_receive_lowest_pn = wpas_get_receive_lowest_pn;
kay_ctx->get_transmit_next_pn = wpas_get_transmit_next_pn;
kay_ctx->set_transmit_next_pn = wpas_set_transmit_next_pn;
kay_ctx->set_receive_lowest_pn = wpas_set_receive_lowest_pn;
kay_ctx->create_receive_sc = wpas_create_receive_sc;
kay_ctx->delete_receive_sc = wpas_delete_receive_sc;
kay_ctx->create_receive_sa = wpas_create_receive_sa;