Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 41ade00f authored by Johannes Berg's avatar Johannes Berg Committed by David S. Miller
Browse files

cfg80211/nl80211: introduce key handling



This introduces key handling to cfg80211/nl80211. Default
and group keys can be added, changed and removed; sequence
counters for each key can be retrieved.

Signed-off-by: default avatarJohannes Berg <johannes@sipsolutions.net>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent a1464ab6
Loading
Loading
Loading
Loading
+34 −0
Original line number Diff line number Diff line
@@ -37,6 +37,16 @@
 *	userspace to request deletion of a virtual interface, then requires
 *	attribute %NL80211_ATTR_IFINDEX.
 *
 * @NL80211_CMD_GET_KEY: Get sequence counter information for a key specified
 *	by %NL80211_ATTR_KEY_IDX and/or %NL80211_ATTR_MAC.
 * @NL80211_CMD_SET_KEY: Set key attributes %NL80211_ATTR_KEY_DEFAULT or
 *	%NL80211_ATTR_KEY_THRESHOLD.
 * @NL80211_CMD_NEW_KEY: add a key with given %NL80211_ATTR_KEY_DATA,
 *	%NL80211_ATTR_KEY_IDX, %NL80211_ATTR_MAC and %NL80211_ATTR_KEY_CIPHER
 *	attributes.
 * @NL80211_CMD_DEL_KEY: delete a key identified by %NL80211_ATTR_KEY_IDX
 *	or %NL80211_ATTR_MAC.
 *
 * @NL80211_CMD_MAX: highest used command number
 * @__NL80211_CMD_AFTER_LAST: internal use
 */
@@ -54,6 +64,11 @@ enum nl80211_commands {
	NL80211_CMD_NEW_INTERFACE,
	NL80211_CMD_DEL_INTERFACE,

	NL80211_CMD_GET_KEY,
	NL80211_CMD_SET_KEY,
	NL80211_CMD_NEW_KEY,
	NL80211_CMD_DEL_KEY,

	/* add commands here */

	/* used to define NL80211_CMD_MAX below */
@@ -75,6 +90,17 @@ enum nl80211_commands {
 * @NL80211_ATTR_IFNAME: network interface name
 * @NL80211_ATTR_IFTYPE: type of virtual interface, see &enum nl80211_iftype
 *
 * @NL80211_ATTR_MAC: MAC address (various uses)
 *
 * @NL80211_ATTR_KEY_DATA: (temporal) key data; for TKIP this consists of
 *	16 bytes encryption key followed by 8 bytes each for TX and RX MIC
 *	keys
 * @NL80211_ATTR_KEY_IDX: key ID (u8, 0-3)
 * @NL80211_ATTR_KEY_CIPHER: key cipher suite (u32, as defined by IEEE 802.11
 *	section 7.3.2.25.1, e.g. 0x000FAC04)
 * @NL80211_ATTR_KEY_SEQ: transmit key sequence number (IV/PN) for TKIP and
 *	CCMP keys, each six bytes in little endian
 *
 * @NL80211_ATTR_MAX: highest attribute number currently defined
 * @__NL80211_ATTR_AFTER_LAST: internal use
 */
@@ -89,6 +115,14 @@ enum nl80211_attrs {
	NL80211_ATTR_IFNAME,
	NL80211_ATTR_IFTYPE,

	NL80211_ATTR_MAC,

	NL80211_ATTR_KEY_DATA,
	NL80211_ATTR_KEY_IDX,
	NL80211_ATTR_KEY_CIPHER,
	NL80211_ATTR_KEY_SEQ,
	NL80211_ATTR_KEY_DEFAULT,

	/* add attributes here, update the policy in nl80211.c */

	__NL80211_ATTR_AFTER_LAST,
+44 −0
Original line number Diff line number Diff line
@@ -49,6 +49,26 @@ extern int ieee80211_radiotap_iterator_next(
   struct ieee80211_radiotap_iterator *iterator);


 /**
 * struct key_params - key information
 *
 * Information about a key
 *
 * @key: key material
 * @key_len: length of key material
 * @cipher: cipher suite selector
 * @seq: sequence counter (IV/PN) for TKIP and CCMP keys, only used
 *	with the get_key() callback, must be in little endian,
 *	length given by @seq_len.
 */
struct key_params {
	u8 *key;
	u8 *seq;
	int key_len;
	int seq_len;
	u32 cipher;
};

/* from net/wireless.h */
struct wiphy;

@@ -71,6 +91,18 @@ struct wiphy;
 *
 * @change_virtual_intf: change type of virtual interface
 *
 * @add_key: add a key with the given parameters. @mac_addr will be %NULL
 *	when adding a group key.
 *
 * @get_key: get information about the key with the given parameters.
 *	@mac_addr will be %NULL when requesting information for a group
 *	key. All pointers given to the @callback function need not be valid
 *	after it returns.
 *
 * @del_key: remove a key given the @mac_addr (%NULL for a group key)
 *	and @key_index
 *
 * @set_default_key: set the default key on an interface
 */
struct cfg80211_ops {
	int	(*add_virtual_intf)(struct wiphy *wiphy, char *name,
@@ -78,6 +110,18 @@ struct cfg80211_ops {
	int	(*del_virtual_intf)(struct wiphy *wiphy, int ifindex);
	int	(*change_virtual_intf)(struct wiphy *wiphy, int ifindex,
				       enum nl80211_iftype type);

	int	(*add_key)(struct wiphy *wiphy, struct net_device *netdev,
			   u8 key_index, u8 *mac_addr,
			   struct key_params *params);
	int	(*get_key)(struct wiphy *wiphy, struct net_device *netdev,
			   u8 key_index, u8 *mac_addr, void *cookie,
			   void (*callback)(void *cookie, struct key_params*));
	int	(*del_key)(struct wiphy *wiphy, struct net_device *netdev,
			   u8 key_index, u8 *mac_addr);
	int	(*set_default_key)(struct wiphy *wiphy,
				   struct net_device *netdev,
				   u8 key_index);
};

#endif /* __NET_CFG80211_H */
+3 −0
Original line number Diff line number Diff line
@@ -184,6 +184,9 @@ struct wiphy *wiphy_new(struct cfg80211_ops *ops, int sizeof_priv)
	struct cfg80211_registered_device *drv;
	int alloc_size;

	WARN_ON(!ops->add_key && ops->del_key);
	WARN_ON(ops->add_key && !ops->del_key);

	alloc_size = sizeof(*drv) + sizeof_priv;

	drv = kzalloc(alloc_size, GFP_KERNEL);
+289 −0
Original line number Diff line number Diff line
@@ -61,6 +61,14 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
	[NL80211_ATTR_IFTYPE] = { .type = NLA_U32 },
	[NL80211_ATTR_IFINDEX] = { .type = NLA_U32 },
	[NL80211_ATTR_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ-1 },

	[NL80211_ATTR_MAC] = { .type = NLA_BINARY, .len = ETH_ALEN },

	[NL80211_ATTR_KEY_DATA] = { .type = NLA_BINARY,
				    .len = WLAN_MAX_KEY_LEN },
	[NL80211_ATTR_KEY_IDX] = { .type = NLA_U8 },
	[NL80211_ATTR_KEY_CIPHER] = { .type = NLA_U32 },
	[NL80211_ATTR_KEY_DEFAULT] = { .type = NLA_FLAG },
};

/* message building helper */
@@ -335,6 +343,263 @@ static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)
	return err;
}

struct get_key_cookie {
	struct sk_buff *msg;
	int error;
};

static void get_key_callback(void *c, struct key_params *params)
{
	struct get_key_cookie *cookie = c;

	if (params->key)
		NLA_PUT(cookie->msg, NL80211_ATTR_KEY_DATA,
			params->key_len, params->key);

	if (params->seq)
		NLA_PUT(cookie->msg, NL80211_ATTR_KEY_SEQ,
			params->seq_len, params->seq);

	if (params->cipher)
		NLA_PUT_U32(cookie->msg, NL80211_ATTR_KEY_CIPHER,
			    params->cipher);

	return;
 nla_put_failure:
	cookie->error = 1;
}

static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *drv;
	int err;
	struct net_device *dev;
	u8 key_idx = 0;
	u8 *mac_addr = NULL;
	struct get_key_cookie cookie = {
		.error = 0,
	};
	void *hdr;
	struct sk_buff *msg;

	if (info->attrs[NL80211_ATTR_KEY_IDX])
		key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);

	if (key_idx > 3)
		return -EINVAL;

	if (info->attrs[NL80211_ATTR_MAC])
		mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);

	err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
	if (err)
		return err;

	if (!drv->ops->get_key) {
		err = -EOPNOTSUPP;
		goto out;
	}

	msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
	if (!msg) {
		err = -ENOMEM;
		goto out;
	}

	hdr = nl80211hdr_put(msg, info->snd_pid, info->snd_seq, 0,
			     NL80211_CMD_NEW_KEY);

	if (IS_ERR(hdr)) {
		err = PTR_ERR(hdr);
		goto out;
	}

	cookie.msg = msg;

	NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
	NLA_PUT_U8(msg, NL80211_ATTR_KEY_IDX, key_idx);
	if (mac_addr)
		NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr);

	rtnl_lock();
	err = drv->ops->get_key(&drv->wiphy, dev, key_idx, mac_addr,
				&cookie, get_key_callback);
	rtnl_unlock();

	if (err)
		goto out;

	if (cookie.error)
		goto nla_put_failure;

	genlmsg_end(msg, hdr);
	err = genlmsg_unicast(msg, info->snd_pid);
	goto out;

 nla_put_failure:
	err = -ENOBUFS;
	nlmsg_free(msg);
 out:
	cfg80211_put_dev(drv);
	dev_put(dev);
	return err;
}

static int nl80211_set_key(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *drv;
	int err;
	struct net_device *dev;
	u8 key_idx;

	if (!info->attrs[NL80211_ATTR_KEY_IDX])
		return -EINVAL;

	key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);

	if (key_idx > 3)
		return -EINVAL;

	/* currently only support setting default key */
	if (!info->attrs[NL80211_ATTR_KEY_DEFAULT])
		return -EINVAL;

	err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
	if (err)
		return err;

	if (!drv->ops->set_default_key) {
		err = -EOPNOTSUPP;
		goto out;
	}

	rtnl_lock();
	err = drv->ops->set_default_key(&drv->wiphy, dev, key_idx);
	rtnl_unlock();

 out:
	cfg80211_put_dev(drv);
	dev_put(dev);
	return err;
}

static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *drv;
	int err;
	struct net_device *dev;
	struct key_params params;
	u8 key_idx = 0;
	u8 *mac_addr = NULL;

	memset(&params, 0, sizeof(params));

	if (!info->attrs[NL80211_ATTR_KEY_CIPHER])
		return -EINVAL;

	if (info->attrs[NL80211_ATTR_KEY_DATA]) {
		params.key = nla_data(info->attrs[NL80211_ATTR_KEY_DATA]);
		params.key_len = nla_len(info->attrs[NL80211_ATTR_KEY_DATA]);
	}

	if (info->attrs[NL80211_ATTR_KEY_IDX])
		key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);

	params.cipher = nla_get_u32(info->attrs[NL80211_ATTR_KEY_CIPHER]);

	if (info->attrs[NL80211_ATTR_MAC])
		mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);

	if (key_idx > 3)
		return -EINVAL;

	/*
	 * Disallow pairwise keys with non-zero index unless it's WEP
	 * (because current deployments use pairwise WEP keys with
	 * non-zero indizes but 802.11i clearly specifies to use zero)
	 */
	if (mac_addr && key_idx &&
	    params.cipher != WLAN_CIPHER_SUITE_WEP40 &&
	    params.cipher != WLAN_CIPHER_SUITE_WEP104)
		return -EINVAL;

	/* TODO: add definitions for the lengths to linux/ieee80211.h */
	switch (params.cipher) {
	case WLAN_CIPHER_SUITE_WEP40:
		if (params.key_len != 5)
			return -EINVAL;
		break;
	case WLAN_CIPHER_SUITE_TKIP:
		if (params.key_len != 32)
			return -EINVAL;
		break;
	case WLAN_CIPHER_SUITE_CCMP:
		if (params.key_len != 16)
			return -EINVAL;
		break;
	case WLAN_CIPHER_SUITE_WEP104:
		if (params.key_len != 13)
			return -EINVAL;
		break;
	default:
		return -EINVAL;
	}

	err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
	if (err)
		return err;

	if (!drv->ops->add_key) {
		err = -EOPNOTSUPP;
		goto out;
	}

	rtnl_lock();
	err = drv->ops->add_key(&drv->wiphy, dev, key_idx, mac_addr, &params);
	rtnl_unlock();

 out:
	cfg80211_put_dev(drv);
	dev_put(dev);
	return err;
}

static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info)
{
	struct cfg80211_registered_device *drv;
	int err;
	struct net_device *dev;
	u8 key_idx = 0;
	u8 *mac_addr = NULL;

	if (info->attrs[NL80211_ATTR_KEY_IDX])
		key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);

	if (key_idx > 3)
		return -EINVAL;

	if (info->attrs[NL80211_ATTR_MAC])
		mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);

	err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
	if (err)
		return err;

	if (!drv->ops->del_key) {
		err = -EOPNOTSUPP;
		goto out;
	}

	rtnl_lock();
	err = drv->ops->del_key(&drv->wiphy, dev, key_idx, mac_addr);
	rtnl_unlock();

 out:
	cfg80211_put_dev(drv);
	dev_put(dev);
	return err;
}

static struct genl_ops nl80211_ops[] = {
	{
		.cmd = NL80211_CMD_GET_WIPHY,
@@ -374,6 +639,30 @@ static struct genl_ops nl80211_ops[] = {
		.policy = nl80211_policy,
		.flags = GENL_ADMIN_PERM,
	},
	{
		.cmd = NL80211_CMD_GET_KEY,
		.doit = nl80211_get_key,
		.policy = nl80211_policy,
		.flags = GENL_ADMIN_PERM,
	},
	{
		.cmd = NL80211_CMD_SET_KEY,
		.doit = nl80211_set_key,
		.policy = nl80211_policy,
		.flags = GENL_ADMIN_PERM,
	},
	{
		.cmd = NL80211_CMD_NEW_KEY,
		.doit = nl80211_new_key,
		.policy = nl80211_policy,
		.flags = GENL_ADMIN_PERM,
	},
	{
		.cmd = NL80211_CMD_DEL_KEY,
		.doit = nl80211_del_key,
		.policy = nl80211_policy,
		.flags = GENL_ADMIN_PERM,
	},
};

/* multicast groups */