From dc16c14b3de3c7f25cd1475563d47b98a196bd0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Fri, 2 Oct 2020 14:47:38 +0200 Subject: [PATCH] sys: Add IEEE 802.15.4 security --- makefiles/pseudomodules.inc.mk | 1 + sys/Makefile.dep | 6 + sys/include/net/ieee802154_security.h | 489 ++++++++++++++++ .../netif/ieee802154/gnrc_netif_ieee802154.c | 142 ++++- sys/net/link_layer/ieee802154/Makefile | 4 + sys/net/link_layer/ieee802154/security.c | 522 ++++++++++++++++++ 6 files changed, 1139 insertions(+), 25 deletions(-) create mode 100644 sys/include/net/ieee802154_security.h create mode 100644 sys/net/link_layer/ieee802154/security.c diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index a40e41d80c..12880d8252 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -64,6 +64,7 @@ PSEUDOMODULES += gnrc_txtsnd PSEUDOMODULES += heap_cmd PSEUDOMODULES += i2c_scan PSEUDOMODULES += ieee802154_radio_hal +PSEUDOMODULES += ieee802154_security PSEUDOMODULES += ieee802154_submac PSEUDOMODULES += ina3221_alerts PSEUDOMODULES += l2filter_blacklist diff --git a/sys/Makefile.dep b/sys/Makefile.dep index c2f557cb6b..a4f3c6ac08 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -50,6 +50,12 @@ ifneq (,$(filter sys_bus_%,$(USEMODULE))) USEMODULE += core_msg_bus endif +ifneq (,$(filter ieee802154_security,$(USEMODULE))) + USEMODULE += crypto + USEMODULE += crypto_aes + USEMODULE += cipher_modes +endif + ifneq (,$(filter rtt_cmd,$(USEMODULE))) FEATURES_REQUIRED += periph_rtt endif diff --git a/sys/include/net/ieee802154_security.h b/sys/include/net/ieee802154_security.h new file mode 100644 index 0000000000..777ce2dcc8 --- /dev/null +++ b/sys/include/net/ieee802154_security.h @@ -0,0 +1,489 @@ +/* + * Copyright (C) 2020 Otto-von-Gericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup net_ieee802154_security IEEE 802.15.4 security + * @ingroup net + * @brief IEEE 802.15.4 security header + * @{ + * + * @file + * @brief IEEE 802.15.4 security interface + * + * Specification: IEEE 802.15.4 - 2015 + * https://www.silabs.com/content/usergenerated/asi/cloud/attachments/siliconlabs/en/community/wireless/proprietary/forum/jcr:content/content/primary/qna/802_15_4_promiscuous-tbzR/hivukadin_vukadi-iTXQ/802.15.4-2015.pdf + * + * @author Fabian Hüßler + */ + +#ifndef NET_IEEE802154_SECURITY_H +#define NET_IEEE802154_SECURITY_H + +#include +#include "kernel_defines.h" +#include "ieee802154.h" +#include "crypto/ciphers.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if IS_USED(MODULE_IEEE802154_RADIO_HAL) +#include "net/ieee802154/radio.h" +#else +/** + * @brief Forward declaration of an IEEE802.15.4 abstract security device + */ +typedef struct ieee802154_sec_dev ieee802154_sec_dev_t; + +/** + * @brief Struct of security operations + */ +typedef struct ieee802154_radio_cipher_ops { + /** + * @brief Function to set the encryption key for the + * next cipher operation + * + * @param[in] dev Will be @ref ieee802154_sec_context_t::ieee802154_sec_dev_t + * @param[in] key Key to be used for the next cipher operation + * @param[in] key_size key size in bytes + */ + void (*set_key)(ieee802154_sec_dev_t *dev, + const uint8_t *key, + uint8_t key_size); + /** + * @brief Function type to compute CBC-MAC + * + * @param[in] dev Will be @ref ieee802154_sec_context_t::ieee802154_sec_dev_t + * @param[in] cipher Output cipher blocks + * @param[in, out] iv in: IV; out: computed MIC + * @param[in] plain Input plain blocks + * @param[in] nblocks Number of blocks + */ + void (*cbc)(const ieee802154_sec_dev_t *dev, + uint8_t *cipher, + uint8_t *iv, + const uint8_t *plain, + uint8_t nblocks); + /** + * @brief Function type to perform ECB encryption + * + * @param[in] dev Will be @ref ieee802154_sec_context_t::ieee802154_sec_dev_t + * @param[out] cipher Output cipher blocks + * @param[in] plain Input plain blocks + * @param[in] nblocks Number of blocks + */ + void (*ecb)(const ieee802154_sec_dev_t *dev, + uint8_t *cipher, + const uint8_t *plain, + uint8_t nblocks); +} ieee802154_radio_cipher_ops_t; + +/** + * @brief IEEE802.15.4 security device descriptor + */ +struct ieee802154_sec_dev { + /** + * @brief Pointer to the operations of the device + */ + const struct ieee802154_radio_cipher_ops *cipher_ops; + /** + * @brief Pointer to the context of the device + */ + void *ctx; +}; +#endif + +#if !defined(IEEE802154_DEFAULT_KEY) || defined(DOXYGEN) +/** + * @brief AES key that is used in the test vectors from the specification + * + * @note Predefine it yourself, + * if you want another key to be set up on initialization + */ +#define IEEE802154_DEFAULT_KEY { 0xc0, 0xc1, 0xc2, 0xc3, \ + 0xc4, 0xc5, 0xc6, 0xc7, \ + 0xc8, 0xc9, 0xca, 0xcb, \ + 0xcc, 0xcd, 0xce, 0xcf } +#endif + +/** + * @brief Length of an AES key in bytes + */ +#define IEEE802154_SEC_KEY_LENGTH (16U) + +/** + * @brief Block size of an encryption block + */ +#define IEEE802154_SEC_BLOCK_SIZE (16U) + +/** + * @brief Maximum length of the security auxiliary header in bytes + */ +#define IEEE802154_MAX_AUX_HDR_LEN (14U) + +/** + * @brief Maximum Size of IEEE 802.15.4 MAC + */ +#define IEEE802154_MAC_SIZE (16U) + +/** + * @brief Mask to get security level bits + */ +#define IEEE802154_SCF_SECLEVEL_MASK (0x07) + +/** + * @brief Number of shifts to set/get security level bits + */ +#define IEEE802154_SCF_SECLEVEL_SHIFT (0) + +/** + * @brief Mask to get key mode bits + */ +#define IEEE802154_SCF_KEYMODE_MASK (0x18) + +/** + * @brief Number of shifts to set/get key mode bits + */ +#define IEEE802154_SCF_KEYMODE_SHIFT (3) + +/** + * @brief Security levels + * + * IEEE802154_SCF_SECLEVEL_MIC*: + * A message integrity code (MIC), also known as MAC, + * is used to prove authentication. The MIC covers the whole frame + * i.e. header, auxiliary header, and frame payload. + * The MIC is always encrypted, thus it must be decrypted by the receiver, + * to be checked. + * + * IEEE802154_SCF_SECLEVEL_ENC*: + * AES-128 in ECB mode is used to encrypt the payload of a frame to provide + * confidentiality. + * + * IEEE802154_SCF_SECLEVEL_ENC_MIC*: + * A combination of the two modes above is used to ensure + * authentication and confidentiality. + */ +typedef enum { + IEEE802154_SCF_SECLEVEL_NONE = 0x00, /**< no security */ + IEEE802154_SCF_SECLEVEL_MIC32 = 0x01, /**< 32 bit MIC */ + IEEE802154_SCF_SECLEVEL_MIC64 = 0x02, /**< 64 bit MIC */ + IEEE802154_SCF_SECLEVEL_MIC128 = 0x03, /**< 128 bit MIC */ + IEEE802154_SCF_SECLEVEL_ENC = 0x04, /**< encryption */ + IEEE802154_SCF_SECLEVEL_ENC_MIC32 = 0x05, /**< enc. + 32 bit MIC */ + IEEE802154_SCF_SECLEVEL_ENC_MIC64 = 0x06, /**< enc. + 64 bit MIC (mandatory) */ + IEEE802154_SCF_SECLEVEL_ENC_MIC128 = 0x07 /**< enc. + 128 bit MIC */ +} ieee802154_scf_seclevel_t; + +/** + * @brief Key identifier modes + * + * The key identifier field in the auxiliary header + * consists of the key source and the key index fields and is only present + * if the key identifier mode is not IEEE802154_SCF_KEYMODE_IMPLICIT. + * (see 9.4.3 in the spec.) + * + * +----------------+-------------+------------------+------------------------------------+ + * | mode | key source | key index | description | + * +----------------+-------------+------------------+------------------------------------+ + * | IMPLICIT | 0 bytes | 0 bytes | The key is implicitly | + * | | | | known to the receiver. | + * +----------------+-------------+------------------+------------------------------------+ + * | INDEX | 0 bytes | 1 byte | The key can be determined | + * | | | | from the key index. | + * +----------------+-------------+------------------+------------------------------------+ + * | SHORT_INDEX | 4 bytes | 1 byte | The key is a group key and can be | + * | | | | determined from the key index and | + * | | | | the source PAN ID and the | + * | | | | short source address | + * | | | | of the originator of the frame. | + * +----------------+-------------+------------------+------------------------------------+ + * | HX_INDEX | 8 bytes | 1 byte | The key can be determined | + * | | | | from the key index and | + * | | | | the long address of the originator | + * | | | | of the frame. | + * +----------------+-------------+------------------+------------------------------------+ + */ +typedef enum { + IEEE802154_SCF_KEYMODE_IMPLICIT = 0x00, /**< Key is determined implicitly */ + IEEE802154_SCF_KEYMODE_INDEX = 0x01, /**< Key is determined from key index */ + IEEE802154_SCF_KEYMODE_SHORT_INDEX = 0x02, /**< Key is determined from 4 byte key source and key index */ + IEEE802154_SCF_KEYMODE_HW_INDEX = 0x03 /**< Key is determined from 8 byte key source and key index */ +} ieee802154_scr_keymode_t; + +/** + * @brief IEEE 802.15.4 security error codes + */ +typedef enum { + IEEE802154_SEC_OK, /**< Everything went fine */ + IEEE802154_SEC_FRAME_COUNTER_OVERFLOW, /**< The requested operation would let the frame counter overflow */ + IEEE802154_SEC_NO_KEY, /**< Could not find the key to perform a requested cipher operation */ + IEEE802154_SEC_MAC_CHECK_FAILURE, /**< The computet MAC did not match */ + IEEE802154_SEC_UNSUPORTED, /**< Unsupported operation */ +} ieee802154_sec_error_t; + +/** + * @brief Struct to hold IEEE 802.15.4 security information + */ +typedef struct ieee802154_sec_context { + /** + * @brief Cipher context with AES128 interface and key storage + */ + cipher_t cipher; + /** + * @brief Security level IEEE802154_SCF_SECLEVEL_* + */ + uint8_t security_level; + /** + * @brief Key mode IEEE802154_SCF_KEYMODE_* + */ + uint8_t key_id_mode; + /** + * @brief Key index + */ + uint8_t key_index; + /** + * @brief Key source + * + * Content depends on key_id_mode + */ + uint8_t key_source[IEEE802154_LONG_ADDRESS_LEN]; + /** + * @brief Own frame counter + */ + uint32_t frame_counter; + /** + * @brief 802.15.4 security dev + */ + ieee802154_sec_dev_t dev; +} ieee802154_sec_context_t; + +/** + * @brief IEEE 802.15.4 auxiliary security header + */ +typedef struct __attribute__((packed)) { + /** + * @brief Security Control field (SCF) + * + * Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0 + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | security level | key id. mode | fc sup.| ASN | r | r | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * + * security level: + * one of IEEE802154_SCF_SECLEVEL_* + * key identifier mode: + * one of IEEE802154_SCF_KEY_* + * frame counter suppression: + * basically always zero because we do not support TSCH right now + * ASN: + * basically always zero because we do not support TSCG right now + */ + uint8_t scf; + /** + * @brief frame counter + */ + uint32_t fc; + /** + * @brief key identifier (0 - 9 bytes) according to key id. mode + */ + uint8_t key_id[]; +} ieee802154_aux_sec_t; + +/** + * @brief Content of key_source if key mode is IEEE802154_SCF_KEYMODE_INDEX + */ +typedef struct __attribute__((packed)) { + /** + * @brief Key index of key from originator, defined by key source + */ + uint8_t key_index; +} ieee802154_aux_sec_key_identifier_1_t; + +/** + * @brief Content of key_source if key mode is IEEE802154_SCF_KEYMODE_SHORT_INDEX + */ +typedef struct __attribute__((packed)) { + /** + * @brief macPANId concatenated with macShortAddress + */ + uint8_t key_source[4]; + /** + * @brief Key index of key from originator, defined by key source + */ + uint8_t key_index; +} ieee802154_aux_sec_key_identifier_5_t; + +/** + * @brief Content of key_source if key mode is IEEE802154_SCF_KEYMODE_HW_INDEX + */ +typedef struct __attribute__((packed)) { + /** + * @brief macExtendedAddress + */ + uint8_t key_source[IEEE802154_LONG_ADDRESS_LEN]; + /** + * @brief Key index of key from originator, defined by key source + */ + uint8_t key_index; +} ieee802154_aux_sec_key_identifier_9_t; + +/** + * @brief Format of 13 byte nonce + */ +typedef struct __attribute__((packed)) { + /** + * @brief Source long address + */ + uint8_t src_addr[IEEE802154_LONG_ADDRESS_LEN]; + /** + * @brief Frame counter + */ + uint32_t frame_counter; + /** + * @brief One of IEEE802154_SCF_SECLEVEL_* + */ + uint8_t security_level; +} ieee802154_ccm_nonce_t; + +/** + * @brief Format of 16 byte input block of CCM + */ +typedef struct __attribute__((packed)) { + /** + * @brief Flags field + */ + uint8_t flags; + /** + * @brief Nonce (Number that is only used once) + */ + ieee802154_ccm_nonce_t nonce; + /** + * @brief Either the length of the actual message (for CBC-MAC) or + * a block counter (for CTR) + */ + uint16_t counter; +} ieee802154_ccm_block_t; + +/** + * @brief Initialize IEEE 802.15.4 security context with default values + * + * @param[out] ctx security context + */ +void ieee802154_sec_init(ieee802154_sec_context_t *ctx); + +/** + * @brief Encrypt IEEE 802.15.4 frame according to @p ctx + * + * @param[in] ctx IEEE 802.15.4 security context + * @param[in] header Pointer to frame header + * @param[in, out] header_size in: Header size; out: Size of header and auxiliary header + * @param[in,out] payload in: Plain payload; out: Encrypted payload + * @param[in] payload_size Size of payload + * @param[out] mic Buffer to store computed MIC + * @param[out] mic_size Size of MIC + * @param[in] src_address Source address + * + * @pre @p header should be large enough to also store the auxiliary header + * + * @return 0 Success + * @return negative integer on error + */ +int ieee802154_sec_encrypt_frame(ieee802154_sec_context_t *ctx, + const uint8_t *header, uint8_t *header_size, + uint8_t *payload, uint16_t payload_size, + uint8_t *mic, uint8_t *mic_size, + const uint8_t *src_address); + +/** + * @brief Decrypt IEEE 802.15.4 frame according to @p ctx + * + * @param[in] ctx IEEE 802.15.4 security context + * @param[in] frame_size Size of received frame + * @param[in] header Poinzter to header, which is also the frame + * @param[in, out] header_size in: Header size; out: Size of header and auxiliary header + * @param[out] payload Will point to the beginning of the payload + * @param[out] payload_size Pointer to store the payload size + * @param[out] mic Will point to the beginning of the MIC + * @param[out] mic_size Pointer to store the size of the MIC + * @param[in] src_address Pointer to remote long source address + * + * @pre After @p header follows the auxiliary header + * + * @return 0 Success + * @return negative integer on error + */ +int ieee802154_sec_decrypt_frame(ieee802154_sec_context_t *ctx, + uint16_t frame_size, + uint8_t *header, uint8_t *header_size, + uint8_t **payload, uint16_t *payload_size, + uint8_t **mic, uint8_t *mic_size, + const uint8_t *src_address); + +/** + * @brief Set the encryption key to be used for the next cipher operation + * + * This function should be the default callback operation to set the encryption key, + * if a radio does not provide special hardware security features. + * + * @param[in] dev Security device + * @param[in] key Key to be use for the next cipher operation + * @param[in] key_size Key size + */ +void ieee802154_sec_set_key(ieee802154_sec_dev_t *dev, + const uint8_t *key, uint8_t key_size); + +/** + * @brief Perform ECB block cipher for IEEE802154 security layer + * + * This function should be the default callback operation to perform ECB, + * if a radio does not provide special hardware security features. + * + * @param[in] dev Security device + * @param[out] cipher Output cipher blocks + * @param[in] plain Input plain blocks + * @param[in] nblocks Number of blocks + */ +void ieee802154_sec_ecb(const ieee802154_sec_dev_t *dev, + uint8_t *cipher, + const uint8_t *plain, + uint8_t nblocks); + +/** + * @brief Perform CBC block cipher for IEEE802154 security layer + * MIC computation + * + * This function should be the default callback operation to perform CBC, + * if a radio does not provide special hardware security features. + * + * @param[in] dev Security device + * @param[out] cipher Output cipher blocks + * @param[in] iv Initial vector + * @param[in] plain Input plain blocks + * @param[in] nblocks Number of blocks + */ +void ieee802154_sec_cbc(const ieee802154_sec_dev_t *dev, + uint8_t *cipher, + uint8_t *iv, + const uint8_t *plain, + uint8_t nblocks); + +/** + * @brief Implements @ref ieee802154_sec_set_key, + * @ref ieee802154_sec_ecb, + * @ref ieee802154_sec_cbc + */ +extern const ieee802154_radio_cipher_ops_t ieee802154_radio_cipher_ops; + +#ifdef __cplusplus +} +#endif + +#endif /* NET_IEEE802154_SECURITY_H */ +/** @} */ diff --git a/sys/net/gnrc/netif/ieee802154/gnrc_netif_ieee802154.c b/sys/net/gnrc/netif/ieee802154/gnrc_netif_ieee802154.c index 8a53aeba13..642f3539d6 100644 --- a/sys/net/gnrc/netif/ieee802154/gnrc_netif_ieee802154.c +++ b/sys/net/gnrc/netif/ieee802154/gnrc_netif_ieee802154.c @@ -137,7 +137,7 @@ static gnrc_pktsnip_t *_recv(gnrc_netif_t *netif) gnrc_pktsnip_t *ieee802154_hdr, *netif_hdr; gnrc_netif_hdr_t *hdr; size_t mhr_len = ieee802154_get_frame_hdr_len(pkt->data); - + uint8_t *mhr = pkt->data; /* nread was checked for <= 0 before so we can safely cast it to * unsigned */ if ((mhr_len == 0) || ((size_t)nread < mhr_len)) { @@ -145,21 +145,12 @@ static gnrc_pktsnip_t *_recv(gnrc_netif_t *netif) gnrc_pktbuf_release(pkt); return NULL; } - nread -= mhr_len; - /* mark IEEE 802.15.4 header */ - ieee802154_hdr = gnrc_pktbuf_mark(pkt, mhr_len, GNRC_NETTYPE_UNDEF); - if (ieee802154_hdr == NULL) { - DEBUG("_recv_ieee802154: no space left in packet buffer\n"); - gnrc_pktbuf_release(pkt); - return NULL; - } - netif_hdr = _make_netif_hdr(ieee802154_hdr->data); + netif_hdr = _make_netif_hdr(mhr); if (netif_hdr == NULL) { DEBUG("_recv_ieee802154: no space left in packet buffer\n"); gnrc_pktbuf_release(pkt); return NULL; } - hdr = netif_hdr->data; #ifdef MODULE_L2FILTER @@ -172,7 +163,7 @@ static gnrc_pktsnip_t *_recv(gnrc_netif_t *netif) } #endif #ifdef MODULE_GNRC_NETIF_DEDUP - if (_already_received(netif, hdr, ieee802154_hdr->data)) { + if (_already_received(netif, hdr, mhr)) { gnrc_pktbuf_release(pkt); gnrc_pktbuf_release(netif_hdr); DEBUG("_recv_ieee802154: packet dropped by deduplication\n"); @@ -181,9 +172,30 @@ static gnrc_pktsnip_t *_recv(gnrc_netif_t *netif) memcpy(netif->last_pkt.src, gnrc_netif_hdr_get_src_addr(hdr), hdr->src_l2addr_len); netif->last_pkt.src_len = hdr->src_l2addr_len; - netif->last_pkt.seq = ieee802154_get_seq(ieee802154_hdr->data); + netif->last_pkt.seq = ieee802154_get_seq(mhr); #endif /* MODULE_GNRC_NETIF_DEDUP */ - +#if IS_USED(MODULE_IEEE802154_SECURITY) + { + uint8_t *payload = NULL; + uint16_t payload_size = 0; + uint8_t *mic = NULL; + uint8_t mic_size = 0; + if (mhr[0] & NETDEV_IEEE802154_SECURITY_EN) { + if (ieee802154_sec_decrypt_frame(&((netdev_ieee802154_t *)dev)->sec_ctx, + nread, + mhr, (uint8_t *)&mhr_len, + &payload, &payload_size, + &mic, &mic_size, + gnrc_netif_hdr_get_src_addr(hdr)) != 0) { + DEBUG("_recv_ieee802154: packet dropped by security check\n"); + gnrc_pktbuf_release(pkt); + gnrc_pktbuf_release(netif_hdr); + return NULL; + } + } + nread -= mic_size; + } +#endif hdr->lqi = rx_info.lqi; hdr->rssi = rx_info.rssi; gnrc_netif_hdr_set_netif(hdr, netif); @@ -200,11 +212,20 @@ static gnrc_pktsnip_t *_recv(gnrc_netif_t *netif) od_hex_dump(pkt->data, nread, OD_WIDTH_DEFAULT); } } + /* mark IEEE 802.15.4 header */ + ieee802154_hdr = gnrc_pktbuf_mark(pkt, mhr_len, GNRC_NETTYPE_UNDEF); + if (ieee802154_hdr == NULL) { + DEBUG("_recv_ieee802154: no space left in packet buffer\n"); + gnrc_pktbuf_release(pkt); + gnrc_pktbuf_release(netif_hdr); + return NULL; + } + nread -= ieee802154_hdr->size; gnrc_pktbuf_remove_snip(pkt, ieee802154_hdr); pkt = gnrc_pkt_append(pkt, netif_hdr); } - DEBUG("_recv_ieee802154: reallocating.\n"); + DEBUG("_recv_ieee802154: reallocating MAC payload for upper layer.\n"); gnrc_pktbuf_realloc_data(pkt, nread); } else if (bytes_expected > 0) { DEBUG("_recv_ieee802154: received frame is too short\n"); @@ -222,7 +243,12 @@ static int _send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt) const uint8_t *src, *dst = NULL; int res = 0; size_t src_len, dst_len; + uint8_t mhr_len; +#if IS_USED(MODULE_IEEE802154_SECURITY) + uint8_t mhr[IEEE802154_MAX_HDR_LEN + IEEE802154_MAX_AUX_HDR_LEN]; +#else uint8_t mhr[IEEE802154_MAX_HDR_LEN]; +#endif uint8_t flags = (uint8_t)(state->flags & NETDEV_IEEE802154_SEND_MASK); le_uint16_t dev_pan = byteorder_btols(byteorder_htons(state->pan)); @@ -250,29 +276,95 @@ static int _send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt) dst = gnrc_netif_hdr_get_dst_addr(netif_hdr); dst_len = netif_hdr->dst_l2addr_len; } - src_len = netif_hdr->src_l2addr_len; - if (src_len > 0) { - src = gnrc_netif_hdr_get_src_addr(netif_hdr); + if (flags & NETDEV_IEEE802154_SECURITY_EN) { + /* need to include long source address because the recipient + will need it to decrypt the frame */ + src_len = IEEE802154_LONG_ADDRESS_LEN; + src = state->long_addr; } else { - src_len = netif->l2addr_len; - src = netif->l2addr; + src_len = netif_hdr->src_l2addr_len; + if (src_len > 0) { + src = gnrc_netif_hdr_get_src_addr(netif_hdr); + } + else { + src_len = netif->l2addr_len; + src = netif->l2addr; + } } /* fill MAC header, seq should be set by device */ if ((res = ieee802154_set_frame_hdr(mhr, src, src_len, dst, dst_len, dev_pan, dev_pan, flags, state->seq++)) == 0) { DEBUG("_send_ieee802154: Error preperaring frame\n"); + gnrc_pktbuf_release(pkt); return -EINVAL; } + mhr_len = res; /* prepare iolist for netdev / mac layer */ - iolist_t iolist = { + iolist_t iolist_header = { .iol_next = (iolist_t *)pkt->next, .iol_base = mhr, - .iol_len = (size_t)res + .iol_len = mhr_len }; +#if IS_USED(MODULE_IEEE802154_SECURITY) + { + /* write protect `pkt` to set `pkt->next` */ + gnrc_pktsnip_t *tmp = gnrc_pktbuf_start_write(pkt); + if (!tmp) { + DEBUG("_send_ieee802154: no write access to pkt"); + gnrc_pktbuf_release(pkt); + return -ENOMEM; + } + pkt = tmp; + tmp = gnrc_pktbuf_start_write(pkt->next); + if (!tmp) { + DEBUG("_send_ieee802154: no write access to pkt->next"); + gnrc_pktbuf_release(pkt); + return -ENOMEM; + } + pkt->next = tmp; + /* merge snippets to store the L2 payload uniformly in one buffer */ + res = gnrc_pktbuf_merge(pkt->next); + if (res < 0) { + DEBUG("_send_ieee802154: failed to merge pktbuf\n"); + gnrc_pktbuf_release(pkt); + return res; + } + + iolist_header.iol_next = (iolist_t *)pkt->next; + + uint8_t mic[IEEE802154_MAC_SIZE]; + uint8_t mic_size = 0; + + if (flags & NETDEV_IEEE802154_SECURITY_EN) { + res = ieee802154_sec_encrypt_frame(&state->sec_ctx, + mhr, &mhr_len, + pkt->next->data, pkt->next->size, + mic, &mic_size, + state->long_addr); + if (res != 0) { + DEBUG("_send_ieee802154: encryption failedf\n"); + gnrc_pktbuf_release(pkt); + return res; + } + } + if (mic_size) { + gnrc_pktsnip_t *pktmic = gnrc_pktbuf_add(pkt->next->next, + mic, mic_size, + GNRC_NETTYPE_UNDEF); + if (!pktmic) { + DEBUG("_send_ieee802154: no space left in pktbuf to allocate MIC\n"); + gnrc_pktbuf_release(pkt); + return -ENOMEM; + } + pkt->next->next = pktmic; + } + iolist_header.iol_len = mhr_len; + } +#endif #ifdef MODULE_NETSTATS_L2 if (netif_hdr->flags & (GNRC_NETIF_HDR_FLAGS_BROADCAST | GNRC_NETIF_HDR_FLAGS_MULTICAST)) { @@ -284,13 +376,13 @@ static int _send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt) #endif #ifdef MODULE_GNRC_MAC if (netif->mac.mac_info & GNRC_NETIF_MAC_INFO_CSMA_ENABLED) { - res = csma_sender_csma_ca_send(dev, &iolist, &netif->mac.csma_conf); + res = csma_sender_csma_ca_send(dev, &iolist_header, &netif->mac.csma_conf); } else { - res = dev->driver->send(dev, &iolist); + res = dev->driver->send(dev, &iolist_header); } #else - res = dev->driver->send(dev, &iolist); + res = dev->driver->send(dev, &iolist_header); #endif /* release old data */ diff --git a/sys/net/link_layer/ieee802154/Makefile b/sys/net/link_layer/ieee802154/Makefile index 75f19ccb7f..d60468641c 100644 --- a/sys/net/link_layer/ieee802154/Makefile +++ b/sys/net/link_layer/ieee802154/Makefile @@ -4,6 +4,10 @@ SRC = \ ieee802154.c \ # +ifneq (,$(filter ieee802154_security,$(USEMODULE))) + SRC += security.c +endif + ifneq (,$(filter ieee802154_submac,$(USEMODULE))) SRC += submac.c endif diff --git a/sys/net/link_layer/ieee802154/security.c b/sys/net/link_layer/ieee802154/security.c new file mode 100644 index 0000000000..3b5d53c43e --- /dev/null +++ b/sys/net/link_layer/ieee802154/security.c @@ -0,0 +1,522 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @author Fabian Hüßler + * @} + */ + +#include +#include +#include + +#include "crypto/ciphers.h" +#include "crypto/modes/ecb.h" +#include "crypto/modes/cbc.h" +#include "net/ieee802154_security.h" + +const ieee802154_radio_cipher_ops_t ieee802154_radio_cipher_ops = { + .set_key = ieee802154_sec_set_key, + .ecb = ieee802154_sec_ecb, + .cbc = ieee802154_sec_cbc +}; + +static inline uint16_t _min(uint16_t a, uint16_t b) +{ + return a < b ? a : b; +} + +/** + * @brief Flag field of CCM input block + * + * Bit 7 Bit6 Bit 5 - Bit 3 Bit2 - Bit 0 + * +--------+-------+-----------------------+-----------------------+ + * | 0 (r) | Adata | M | L | + * +--------+-------+-----------------------+-----------------------+ + * + * r: value reserved + * Adata: 0 if no MIC is present, 1 else + * M: Number of octets in authentication field (M-2)/2 + * L: Number of octets in length field L-1 + * + * L will actually always be 2 because the maximul message length is 127 + * which is expressed as two bytes. + * Valid values for M are 0 (No MIC), 4 (32 bit MIC), 8 (64 bit MIC) + * and 16 (128 bit MIC) + */ +static inline uint8_t _ccm_flag(uint8_t M, uint8_t L) +{ + assert(M == 0 || M == 4 || M == 8 || M == 16); + assert(L == 2); + return (M >= 4 ? ((1 << 6) | (((M) - 2) / 2)) : 0) | ((L) - 1); +} + +static inline uint8_t _get_sec_level(uint8_t scf) +{ + return (scf & IEEE802154_SCF_SECLEVEL_MASK) + >> IEEE802154_SCF_SECLEVEL_SHIFT; +} + +static inline uint8_t _get_key_id_mode(uint8_t scf) +{ + return (scf & IEEE802154_SCF_KEYMODE_MASK) + >> IEEE802154_SCF_KEYMODE_SHIFT; +} + +static inline uint8_t _mac_size(uint8_t sec_level) +{ + switch (sec_level) { + case IEEE802154_SCF_SECLEVEL_MIC32: + case IEEE802154_SCF_SECLEVEL_ENC_MIC32: + return 4; + case IEEE802154_SCF_SECLEVEL_MIC64: + case IEEE802154_SCF_SECLEVEL_ENC_MIC64: + return 8; + case IEEE802154_SCF_SECLEVEL_MIC128: + case IEEE802154_SCF_SECLEVEL_ENC_MIC128: + return 16; + default: + return 0; + } +} + +/* frame is secured with signature */ +static inline bool _req_mac(uint8_t sec_level) +{ + switch (sec_level) { + case IEEE802154_SCF_SECLEVEL_MIC32: + case IEEE802154_SCF_SECLEVEL_MIC64: + case IEEE802154_SCF_SECLEVEL_MIC128: + case IEEE802154_SCF_SECLEVEL_ENC_MIC32: + case IEEE802154_SCF_SECLEVEL_ENC_MIC64: + case IEEE802154_SCF_SECLEVEL_ENC_MIC128: + return true; + default: + return false; + } +} + +/* frame is encrypted */ +static inline bool _req_encryption(uint8_t sec_level) +{ + switch (sec_level) { + case IEEE802154_SCF_SECLEVEL_ENC: + case IEEE802154_SCF_SECLEVEL_ENC_MIC32: + case IEEE802154_SCF_SECLEVEL_ENC_MIC64: + case IEEE802154_SCF_SECLEVEL_ENC_MIC128: + return true; + default: + return false; + } +} + +static inline void _memxor(void *dst, const void* src, size_t size) +{ + while (size--) { + ((uint8_t *)dst)[size] ^= ((uint8_t *)src)[size]; + } +} + +static inline uint8_t _scf(uint8_t sec_level, uint8_t key_mode) +{ + return (sec_level << IEEE802154_SCF_SECLEVEL_SHIFT) | + (key_mode << IEEE802154_SCF_KEYMODE_SHIFT); +} + +static inline uint8_t _get_aux_hdr_size(uint8_t security_level, + uint8_t key_mode) +{ + if (security_level == IEEE802154_SCF_SECLEVEL_NONE) { + return 0; + } + switch (key_mode) { + case IEEE802154_SCF_KEYMODE_IMPLICIT: + return 5; + case IEEE802154_SCF_KEYMODE_INDEX: + return 6; + case IEEE802154_SCF_KEYMODE_SHORT_INDEX: + return 10; + case IEEE802154_SCF_KEYMODE_HW_INDEX: + return 14; + default: + return 0; + } +} + +static uint8_t _set_aux_hdr(const ieee802154_sec_context_t *ctx, + ieee802154_aux_sec_t *ahr) +{ + ahr->scf = _scf(ctx->security_level, ctx->key_id_mode); + /* If you look in the specification: Annex C, + integers values are in little endian */ + ahr->fc = byteorder_btoll(byteorder_htonl(ctx->frame_counter)).u32; + size_t len = 5; + switch (ctx->key_id_mode) { + case IEEE802154_SCF_KEYMODE_IMPLICIT: + break; + case IEEE802154_SCF_KEYMODE_INDEX: + memcpy(ahr->key_id, &ctx->key_index, 1); + len++; + break; + case IEEE802154_SCF_KEYMODE_SHORT_INDEX: + memcpy(ahr->key_id, ctx->key_source, 4); + memcpy(ahr->key_id + 4, &ctx->key_index, 1); + len += 5; + break; + case IEEE802154_SCF_KEYMODE_HW_INDEX: + memcpy(ahr->key_id, ctx->key_source, 8); + memcpy(ahr->key_id + 4, &ctx->key_index, 1); + len += 9; + break; + default: + break; + } + return len; +} + +/** + * @brief Construct the first block A0 for CTR + */ +static inline void _init_ctr_A0(ieee802154_ccm_block_t *A0, + uint32_t frame_counter, + uint8_t security_level, + const uint8_t *src_address) +{ + A0->flags = _ccm_flag(0, 2); + A0->nonce.frame_counter = htonl(frame_counter); + A0->nonce.security_level = security_level; + A0->counter = 0; + memcpy(A0->nonce.src_addr, src_address, IEEE802154_LONG_ADDRESS_LEN); +} + +/** + * @brief In CTR, the blocks Ai differ in a successive counter + */ +static inline void _advance_ctr_Ai(ieee802154_ccm_block_t *Ai) +{ + Ai->counter = htons(ntohs(Ai->counter) + 1); +} + +/** + * @brief Construct the first block B0 for CBC-MAC + */ +static inline void _init_cbc_B0(ieee802154_ccm_block_t *B0, + uint32_t frame_counter, + uint8_t security_level, + uint16_t m_len, + uint8_t mic_size, + const uint8_t *src_address) +{ + B0->flags = _ccm_flag(mic_size, 2); + B0->nonce.frame_counter = htonl(frame_counter), + B0->nonce.security_level = security_level, + B0->counter = htons(m_len); + memcpy(B0->nonce.src_addr, src_address, IEEE802154_LONG_ADDRESS_LEN); +} + +static const uint8_t *_get_encryption_key(const ieee802154_sec_context_t *ctx, + const uint8_t *mhr, uint8_t mhr_len, + const ieee802154_aux_sec_t *ahr) +{ + (void)mhr; + (void)mhr_len; + (void)ahr; + /* For simplicity, assume that everyone has the same key */ + /* Else you´d have to look up the key based on the destination address */ + return ctx->cipher.context.context; +} + +static const uint8_t *_get_decryption_key(const ieee802154_sec_context_t *ctx, + const uint8_t *mhr, uint8_t mhr_len, + const ieee802154_aux_sec_t *ahr) +{ + (void)mhr; + (void)mhr_len; + (void)ahr; + /* For simplicity, assume that everyone has the same key */ + /* Else you´d have to look up the key based on the source address */ + return ctx->cipher.context.context; +} + +/** + * @brief Perform ECB on one block of data and and add padding if necessary + */ +static uint8_t _ecb(ieee802154_sec_context_t *ctx, + uint8_t *tmp1, uint8_t *tmp2, uint8_t *data, + const uint8_t *Ai, uint16_t size) +{ + uint16_t s = _min(IEEE802154_SEC_BLOCK_SIZE, size); + ctx->dev.cipher_ops->ecb(&ctx->dev, tmp2, Ai, 1); + memcpy(tmp1, data, s); + memset(tmp1 + s, 0, IEEE802154_SEC_BLOCK_SIZE - s); + _memxor(tmp1, tmp2, IEEE802154_SEC_BLOCK_SIZE); + memcpy(data, tmp1, s); + return s; +} + +/** + * @brief Perform CBC on one block of data and add padding if necessary + */ +static uint8_t _cbc_next(ieee802154_sec_context_t *ctx, + uint8_t *last, uint8_t *tmp, + const uint8_t *next, uint16_t size) +{ + uint16_t s = _min(IEEE802154_SEC_BLOCK_SIZE, size); + memcpy(tmp, next, s); + memset(tmp + s, 0, IEEE802154_SEC_BLOCK_SIZE - s); + ctx->dev.cipher_ops->cbc(&ctx->dev, last, last, tmp, 1); + return s; +} + +static void _set_key(ieee802154_sec_context_t *ctx, const uint8_t *key) +{ + ctx->dev.cipher_ops->set_key(&ctx->dev, key, IEEE802154_SEC_BLOCK_SIZE); + memcpy(ctx->cipher.context.context, key, IEEE802154_SEC_KEY_LENGTH); +} + +static void _comp_mic(ieee802154_sec_context_t *ctx, + uint8_t mic[IEEE802154_MAC_SIZE], + ieee802154_ccm_block_t *B0, + const void *a, uint16_t a_len, + const void *m, uint16_t m_len) +{ + uint8_t tmp[IEEE802154_SEC_BLOCK_SIZE] = { 0 }; + uint16_t off; + memset(mic, 0, IEEE802154_MAC_SIZE); + _cbc_next(ctx, mic, tmp, (uint8_t *)B0, sizeof(*B0)); + byteorder_htobebufs(tmp, a_len); + off = _min(sizeof(tmp) - sizeof(uint16_t), a_len); + memcpy(tmp + sizeof(uint16_t), a, off); + _cbc_next(ctx, mic, tmp, tmp, sizeof(uint16_t) + off); + for (;off < a_len;) { + off += _cbc_next(ctx, mic, tmp, &(((uint8_t *)a)[off]), a_len - off); + } + for (off = 0; off < m_len;) { + off += _cbc_next(ctx, mic, tmp, &(((uint8_t *)m)[off]), m_len - off); + } +} + +static void _ctr(ieee802154_sec_context_t *ctx, + ieee802154_ccm_block_t *A0, + const void *m, uint16_t m_len) +{ + uint8_t tmp1[IEEE802154_SEC_BLOCK_SIZE] = { 0 }; + uint8_t tmp2[IEEE802154_SEC_BLOCK_SIZE] = { 0 }; + + for (uint16_t off = 0; off < m_len;) { + _advance_ctr_Ai(A0); + off += _ecb(ctx, tmp1, tmp2, + &(((uint8_t *)m)[off]), (uint8_t *)A0, m_len - off); + } +} + +static void _ctr_mic(ieee802154_sec_context_t *ctx, + ieee802154_ccm_block_t *A0, + void *mic, uint8_t mic_size) +{ + uint8_t tmp1[IEEE802154_SEC_BLOCK_SIZE] = { 0 }; + uint8_t tmp2[IEEE802154_SEC_BLOCK_SIZE] = { 0 }; + + _ecb(ctx, tmp1, tmp2, mic, (uint8_t *)A0, mic_size); +} + +void ieee802154_sec_init(ieee802154_sec_context_t *ctx) +{ + /* device driver can override this */ + ctx->dev.cipher_ops = &ieee802154_radio_cipher_ops; + /* device driver can override this */ + ctx->dev.ctx = ctx; + /* MIC64 is the only mandatory security mode */ + ctx->security_level = IEEE802154_SCF_SECLEVEL_ENC_MIC64; + ctx->key_id_mode = IEEE802154_SCF_KEYMODE_IMPLICIT; + memset(ctx->key_source, 0, sizeof(ctx->key_source)); + ctx->key_index = 0; + ctx->frame_counter = 0; + uint8_t key[] = IEEE802154_DEFAULT_KEY; + + assert(CIPHER_MAX_CONTEXT_SIZE >= IEEE802154_SEC_KEY_LENGTH); + cipher_init(&ctx->cipher, CIPHER_AES_128, key, IEEE802154_SEC_KEY_LENGTH); +} + +int ieee802154_sec_encrypt_frame(ieee802154_sec_context_t *ctx, + const uint8_t *header, uint8_t *header_size, + uint8_t *payload, uint16_t payload_size, + uint8_t *mic, uint8_t *mic_size, + const uint8_t *src_address) +{ + /* For non data frames (MAC commands, beacons) a and a_len would be larger. + ACKs are not encrypted. */ + assert((*((uint8_t *)header)) & IEEE802154_FCF_TYPE_DATA); + + if (ctx->security_level == IEEE802154_SCF_SECLEVEL_NONE) { + *mic_size = 0; + return IEEE802154_SEC_OK; + } + if (ctx->frame_counter == 0xFFFFFFFF) { + /* Letting the frame counter overflow is explicitly prohibited by the specification. + (see 9.4.2) */ + return -IEEE802154_SEC_FRAME_COUNTER_OVERFLOW; + } + + /* write the auxiliary header */ + ieee802154_aux_sec_t *aux = (ieee802154_aux_sec_t *)(header + *header_size); + uint8_t aux_size = _get_aux_hdr_size(ctx->security_level, ctx->key_id_mode); + _set_aux_hdr(ctx, aux); + + /* attempt to find the encrypton key */ + const uint8_t *key; + if (!(key = _get_encryption_key(ctx, header, *header_size, aux))) { + return -IEEE802154_SEC_NO_KEY; + } + _set_key(ctx, key); + + *mic_size = _mac_size(ctx->security_level); + const uint8_t *a = header; + uint8_t *m = payload; + uint16_t a_len = *header_size + aux_size; + uint16_t m_len = payload_size; + ieee802154_ccm_block_t ccm; /* Ai or Bi */ + + /* compute MIC */ + if (_req_mac(ctx->security_level)) { + _init_cbc_B0(&ccm, ctx->frame_counter, ctx->security_level, m_len, *mic_size, src_address); + _comp_mic(ctx, mic, &ccm, a, a_len, m, m_len); + + /* encrypt MIC */ + _init_ctr_A0(&ccm, ctx->frame_counter, ctx->security_level, src_address); + _ctr_mic(ctx, &ccm, mic, *mic_size); + } + /* encrypt payload */ + if (_req_encryption(ctx->security_level)) { + _init_ctr_A0(&ccm, ctx->frame_counter, ctx->security_level, src_address); + _ctr(ctx, &ccm, m, m_len); + } + *header_size += aux_size; + ctx->frame_counter++; + return IEEE802154_SEC_OK; +} + +int ieee802154_sec_decrypt_frame(ieee802154_sec_context_t *ctx, + uint16_t frame_size, + uint8_t *header, uint8_t *header_size, + uint8_t **payload, uint16_t *payload_size, + uint8_t **mic, uint8_t *mic_size, + const uint8_t *src_address) +{ + /* For non data frames (MAC commands, beacons) a and a_len would be larger. + ACKs are not encrypted. */ + assert(*header & IEEE802154_FCF_TYPE_DATA); + + /* read the fields of the auxiliary header */ + ieee802154_aux_sec_t *aux = (ieee802154_aux_sec_t *)(header + *header_size); + uint8_t security_level = _get_sec_level(aux->scf); + uint8_t key_mode = _get_key_id_mode(aux->scf); + uint8_t aux_size = _get_aux_hdr_size(security_level, key_mode); + uint8_t mac_size = _mac_size(security_level); + /* remember that the frame counter was stored in little endian */ + uint32_t frame_counter = byteorder_ntohl( + byteorder_ltobl((le_uint32_t){aux->fc})); + + if (security_level == IEEE802154_SCF_SECLEVEL_NONE) { + *payload = header + *header_size; + *payload_size = frame_size - *header_size; + *mic = NULL; + *mic_size = 0; + return IEEE802154_SEC_OK; + } + + *payload_size = frame_size - *header_size - aux_size - mac_size; + *payload = header + *header_size + aux_size; + *mic_size = mac_size; + *mic = header + frame_size - mac_size; + + /* attempt to find the decryption key */ + const uint8_t *key; + if (!(key = _get_decryption_key(ctx, header, *header_size, aux))) { + return -IEEE802154_SEC_NO_KEY; + } + _set_key(ctx, key); + + const uint8_t *a = header; + uint8_t *c = *payload; + uint16_t a_len = *header_size + aux_size; + uint16_t c_len = *payload_size; + uint8_t *mac = *mic; + ieee802154_ccm_block_t ccm; /* Ai or Bi */ + + /* TODO: + A better implementation would check if the received frame counter is + greater then the frame counter that has previously been received from + the other endpoint. This is done to protect against replay attacks. + But we do not store this information because we also do not have + a proper key store, to avoid complexity on embedded devices. */ + + /* decrypt MIC */ + if (mac_size) { + _init_ctr_A0(&ccm, frame_counter, security_level, src_address); + _ctr_mic(ctx, &ccm, mac, mac_size); + } + /* decrypt cipher */ + if (_req_encryption(security_level)) { + _init_ctr_A0(&ccm, frame_counter, security_level, src_address); + _ctr(ctx, &ccm, c, c_len); + } + /* check MIC */ + if (_req_mac(security_level)) { + uint8_t tmp_mic[IEEE802154_MAC_SIZE]; + _init_cbc_B0(&ccm, frame_counter, security_level, c_len, mac_size, src_address); + _comp_mic(ctx, tmp_mic, &ccm, a, a_len, c, c_len); + if (memcmp(tmp_mic, *mic, mac_size)) { + return -IEEE802154_SEC_MAC_CHECK_FAILURE; + } + } + *header_size += aux_size; + return IEEE802154_SEC_OK; +} + +void ieee802154_sec_set_key(ieee802154_sec_dev_t *ctx, + const uint8_t *key, uint8_t key_size) +{ + /* This is a dummy implementation of the set_key callback + in ieee802154_radio_cipher_ops_t. + The copying of the key is done in the static _set_key() function, + which wraps around the set_key callback and then copies. + For the software encryption / decryption, there is + nothing else to do, hence the NOP. For hardware support, + the key must be transferred to the transceiver. */ + (void)ctx; + (void)key; + (void)key_size; +} + +void ieee802154_sec_ecb(const ieee802154_sec_dev_t *ctx, + uint8_t *cipher, + const uint8_t *plain, + uint8_t nblocks) +{ + cipher_encrypt_ecb(&((ieee802154_sec_context_t *)ctx->ctx)->cipher, + plain, + nblocks * IEEE802154_SEC_BLOCK_SIZE, + cipher); +} + +void ieee802154_sec_cbc(const ieee802154_sec_dev_t *ctx, + uint8_t *cipher, + uint8_t *iv, + const uint8_t *plain, + uint8_t nblocks) +{ + cipher_encrypt_cbc(&((ieee802154_sec_context_t *)ctx->ctx)->cipher, + iv, + plain, + nblocks * IEEE802154_SEC_BLOCK_SIZE, + cipher); +}