[09/15] mka: Lowest acceptable Packet Number (LPN) calculated and used incorrectly

Message ID 20180302201103.16264-10-msiedzik@extremenetworks.com
State New
Headers show
Series
  • MKA bugfixes and enhancements
Related show

Commit Message

Michael Siedzik March 2, 2018, 8:10 p.m.
From: Mike Siedzik <msiedzik@extremenetworks.com>

The purpose of the Lowest Acceptable PN (lpn) parameters in the MACsec
SAK Use parameter set is to enforce delay protection.  Per
IEEE802.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 MKPDU's 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>
---
 src/drivers/driver.h              |  8 +++++
 src/drivers/driver_macsec_linux.c | 43 ++++++++++++++++++++++
 src/pae/ieee802_1x_kay.c          | 76 ++++++++++++++++++++++++---------------
 src/pae/ieee802_1x_kay.h          |  1 +
 src/pae/ieee802_1x_secy_ops.c     | 21 +++++++++++
 src/pae/ieee802_1x_secy_ops.h     |  2 ++
 wpa_supplicant/driver_i.h         |  8 +++++
 wpa_supplicant/wpas_kay.c         |  7 ++++
 8 files changed, 138 insertions(+), 28 deletions(-)

--
2.11.1

Patch

diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 92a58b2f2..7c15e9bc8 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -3744,6 +3744,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
         * @sc: secure channel
diff --git a/src/drivers/driver_macsec_linux.c b/src/drivers/driver_macsec_linux.c
index e89b3ba14..ee0f64de3 100644
--- a/src/drivers/driver_macsec_linux.c
+++ b/src/drivers/driver_macsec_linux.c
@@ -670,6 +670,48 @@  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, "%s -> %d: %d", __func__, 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
  * @sa: secure association
@@ -1293,6 +1335,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,
diff --git a/src/pae/ieee802_1x_kay.c b/src/pae/ieee802_1x_kay.c
index ba2636ad6..4ac4fdc15 100644
--- a/src/pae/ieee802_1x_kay.c
+++ b/src/pae/ieee802_1x_kay.c
@@ -1144,27 +1144,37 @@  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);
-
-                               lpn = lpn > rxsa->lowest_pn ?
-                                       lpn : rxsa->lowest_pn;
-                               break;
-                       }
+       dl_list_for_each(txsa, &principal->txsc->sa_list,
+                        struct transmit_sa, list) {
+               if (is_ki_equal(&txsa->pkey->key_identifier, ki)) {
+                       /* Per IEEE802.1X-2010 Clase 9, "Eacy SecY uses MKA to
+                        * communicate the lowest PN used for transmission with
+                        * the SAK within the last two seconds".  Achive 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;
+
+                       /* Now read the current transmit next PN for use next
+                        * time through. */
+                       secy_get_transmit_next_pn(principal->kay,
+                                                 txsa);
+                       break;
                }
        }

@@ -1265,7 +1275,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;
@@ -1385,25 +1396,34 @@  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 != NULL && rxsa->pkey == sa_key) {
+                               found = TRUE;
+                               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);
+               }
+               /* FIXME: 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;
diff --git a/src/pae/ieee802_1x_kay.h b/src/pae/ieee802_1x_kay.h
index 7031c1a83..01af578fc 100644
--- a/src/pae/ieee802_1x_kay.h
+++ b/src/pae/ieee802_1x_kay.h
@@ -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);
diff --git a/src/pae/ieee802_1x_secy_ops.c b/src/pae/ieee802_1x_secy_ops.c
index ab5339bb2..4e5379ff7 100644
--- a/src/pae/ieee802_1x_secy_ops.c
+++ b/src/pae/ieee802_1x_secy_ops.c
@@ -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;
diff --git a/src/pae/ieee802_1x_secy_ops.h b/src/pae/ieee802_1x_secy_ops.h
index 9fb29c3dd..2d112ba7c 100644
--- a/src/pae/ieee802_1x_secy_ops.h
+++ b/src/pae/ieee802_1x_secy_ops.h
@@ -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);
diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h
index 078de23f7..92a0a8fda 100644
--- a/wpa_supplicant/driver_i.h
+++ b/wpa_supplicant/driver_i.h
@@ -796,6 +796,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)
diff --git a/wpa_supplicant/wpas_kay.c b/wpa_supplicant/wpas_kay.c
index 11708b8a6..5235af77a 100644
--- a/wpa_supplicant/wpas_kay.c
+++ b/wpa_supplicant/wpas_kay.c
@@ -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;