Commit 203e2bf5 authored by Antonio Quartulli's avatar Antonio Quartulli Committed by Paolo Abeni
Browse files

ovpn: implement key add/get/del/swap via netlink



This change introduces the netlink commands needed to add, get, delete
and swap keys for a specific peer.

Userspace is expected to use these commands to create, inspect (non
sensitive data only), destroy and rotate session keys for a specific
peer.

Signed-off-by: default avatarAntonio Quartulli <antonio@openvpn.net>
Link: https://patch.msgid.link/20250415-b4-ovpn-v26-19-577f6097b964@openvpn.net


Reviewed-by: default avatarSabrina Dubroca <sd@queasysnail.net>
Tested-by: default avatarOleksandr Natalenko <oleksandr@natalenko.name>
Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent 1d36a36f
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -146,3 +146,43 @@ void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs)

	spin_unlock_bh(&cs->lock);
}

/**
 * ovpn_crypto_config_get - populate keyconf object with non-sensible key data
 * @cs: the crypto state to extract the key data from
 * @slot: the specific slot to inspect
 * @keyconf: the output object to populate
 *
 * Return: 0 on success or a negative error code otherwise
 */
int ovpn_crypto_config_get(struct ovpn_crypto_state *cs,
			   enum ovpn_key_slot slot,
			   struct ovpn_key_config *keyconf)
{
	struct ovpn_crypto_key_slot *ks;
	int idx;

	switch (slot) {
	case OVPN_KEY_SLOT_PRIMARY:
		idx = cs->primary_idx;
		break;
	case OVPN_KEY_SLOT_SECONDARY:
		idx = !cs->primary_idx;
		break;
	default:
		return -EINVAL;
	}

	rcu_read_lock();
	ks = rcu_dereference(cs->slots[idx]);
	if (!ks) {
		rcu_read_unlock();
		return -ENOENT;
	}

	keyconf->cipher_alg = ovpn_aead_crypto_alg(ks);
	keyconf->key_id = ks->key_id;
	rcu_read_unlock();

	return 0;
}
+4 −0
Original line number Diff line number Diff line
@@ -136,4 +136,8 @@ void ovpn_crypto_state_release(struct ovpn_crypto_state *cs);

void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs);

int ovpn_crypto_config_get(struct ovpn_crypto_state *cs,
			   enum ovpn_key_slot slot,
			   struct ovpn_key_config *keyconf);

#endif /* _NET_OVPN_OVPNCRYPTO_H_ */
+17 −0
Original line number Diff line number Diff line
@@ -364,3 +364,20 @@ ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc)
	ovpn_aead_crypto_key_slot_destroy(ks);
	return ERR_PTR(ret);
}

enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks)
{
	const char *alg_name;

	if (!ks->encrypt)
		return OVPN_CIPHER_ALG_NONE;

	alg_name = crypto_tfm_alg_name(crypto_aead_tfm(ks->encrypt));

	if (!strcmp(alg_name, ALG_NAME_AES))
		return OVPN_CIPHER_ALG_AES_GCM;
	else if (!strcmp(alg_name, ALG_NAME_CHACHAPOLY))
		return OVPN_CIPHER_ALG_CHACHA20_POLY1305;
	else
		return OVPN_CIPHER_ALG_NONE;
}
+2 −0
Original line number Diff line number Diff line
@@ -24,4 +24,6 @@ struct ovpn_crypto_key_slot *
ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc);
void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks);

enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks);

#endif /* _NET_OVPN_OVPNAEAD_H_ */
+297 −4
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#include "netlink.h"
#include "netlink-gen.h"
#include "bind.h"
#include "crypto.h"
#include "peer.h"
#include "socket.h"

@@ -790,24 +791,316 @@ int ovpn_nl_peer_del_doit(struct sk_buff *skb, struct genl_info *info)
	return ret;
}

static int ovpn_nl_get_key_dir(struct genl_info *info, struct nlattr *key,
			       enum ovpn_cipher_alg cipher,
			       struct ovpn_key_direction *dir)
{
	struct nlattr *attrs[OVPN_A_KEYDIR_MAX + 1];
	int ret;

	ret = nla_parse_nested(attrs, OVPN_A_KEYDIR_MAX, key,
			       ovpn_keydir_nl_policy, info->extack);
	if (ret)
		return ret;

	switch (cipher) {
	case OVPN_CIPHER_ALG_AES_GCM:
	case OVPN_CIPHER_ALG_CHACHA20_POLY1305:
		if (NL_REQ_ATTR_CHECK(info->extack, key, attrs,
				      OVPN_A_KEYDIR_CIPHER_KEY) ||
		    NL_REQ_ATTR_CHECK(info->extack, key, attrs,
				      OVPN_A_KEYDIR_NONCE_TAIL))
			return -EINVAL;

		dir->cipher_key = nla_data(attrs[OVPN_A_KEYDIR_CIPHER_KEY]);
		dir->cipher_key_size = nla_len(attrs[OVPN_A_KEYDIR_CIPHER_KEY]);

		/* These algorithms require a 96bit nonce,
		 * Construct it by combining 4-bytes packet id and
		 * 8-bytes nonce-tail from userspace
		 */
		dir->nonce_tail = nla_data(attrs[OVPN_A_KEYDIR_NONCE_TAIL]);
		dir->nonce_tail_size = nla_len(attrs[OVPN_A_KEYDIR_NONCE_TAIL]);
		break;
	default:
		NL_SET_ERR_MSG_MOD(info->extack, "unsupported cipher");
		return -EINVAL;
	}

	return 0;
}

/**
 * ovpn_nl_key_new_doit - configure a new key for the specified peer
 * @skb: incoming netlink message
 * @info: genetlink metadata
 *
 * This function allows the user to install a new key in the peer crypto
 * state.
 * Each peer has two 'slots', namely 'primary' and 'secondary', where
 * keys can be installed. The key in the 'primary' slot is used for
 * encryption, while both keys can be used for decryption by matching the
 * key ID carried in the incoming packet.
 *
 * The user is responsible for rotating keys when necessary. The user
 * may fetch peer traffic statistics via netlink in order to better
 * identify the right time to rotate keys.
 * The renegotiation follows these steps:
 * 1. a new key is computed by the user and is installed in the 'secondary'
 *    slot
 * 2. at user discretion (usually after a predetermined time) 'primary' and
 *    'secondary' contents are swapped and the new key starts being used for
 *    encryption, while the old key is kept around for decryption of late
 *    packets.
 *
 * Return: 0 on success or a negative error code otherwise.
 */
int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info)
{
	return -EOPNOTSUPP;
	struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1];
	struct ovpn_priv *ovpn = info->user_ptr[0];
	struct ovpn_peer_key_reset pkr;
	struct ovpn_peer *peer;
	u32 peer_id;
	int ret;

	if (GENL_REQ_ATTR_CHECK(info, OVPN_A_KEYCONF))
		return -EINVAL;

	ret = nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX,
			       info->attrs[OVPN_A_KEYCONF],
			       ovpn_keyconf_nl_policy, info->extack);
	if (ret)
		return ret;

	if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs,
			      OVPN_A_KEYCONF_PEER_ID))
		return -EINVAL;

	if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs,
			      OVPN_A_KEYCONF_SLOT) ||
	    NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs,
			      OVPN_A_KEYCONF_KEY_ID) ||
	    NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs,
			      OVPN_A_KEYCONF_CIPHER_ALG) ||
	    NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs,
			      OVPN_A_KEYCONF_ENCRYPT_DIR) ||
	    NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs,
			      OVPN_A_KEYCONF_DECRYPT_DIR))
		return -EINVAL;

	pkr.slot = nla_get_u32(attrs[OVPN_A_KEYCONF_SLOT]);
	pkr.key.key_id = nla_get_u32(attrs[OVPN_A_KEYCONF_KEY_ID]);
	pkr.key.cipher_alg = nla_get_u32(attrs[OVPN_A_KEYCONF_CIPHER_ALG]);

	ret = ovpn_nl_get_key_dir(info, attrs[OVPN_A_KEYCONF_ENCRYPT_DIR],
				  pkr.key.cipher_alg, &pkr.key.encrypt);
	if (ret < 0)
		return ret;

	ret = ovpn_nl_get_key_dir(info, attrs[OVPN_A_KEYCONF_DECRYPT_DIR],
				  pkr.key.cipher_alg, &pkr.key.decrypt);
	if (ret < 0)
		return ret;

	peer_id = nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]);
	peer = ovpn_peer_get_by_id(ovpn, peer_id);
	if (!peer) {
		NL_SET_ERR_MSG_FMT_MOD(info->extack,
				       "no peer with id %u to set key for",
				       peer_id);
		return -ENOENT;
	}

	ret = ovpn_crypto_state_reset(&peer->crypto, &pkr);
	if (ret < 0) {
		NL_SET_ERR_MSG_FMT_MOD(info->extack,
				       "cannot install new key for peer %u",
				       peer_id);
		goto out;
	}

	netdev_dbg(ovpn->dev, "new key installed (id=%u) for peer %u\n",
		   pkr.key.key_id, peer_id);
out:
	ovpn_peer_put(peer);
	return ret;
}

static int ovpn_nl_send_key(struct sk_buff *skb, const struct genl_info *info,
			    u32 peer_id, enum ovpn_key_slot slot,
			    const struct ovpn_key_config *keyconf)
{
	struct nlattr *attr;
	void *hdr;

	hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, &ovpn_nl_family,
			  0, OVPN_CMD_KEY_GET);
	if (!hdr)
		return -ENOBUFS;

	attr = nla_nest_start(skb, OVPN_A_KEYCONF);
	if (!attr)
		goto err;

	if (nla_put_u32(skb, OVPN_A_KEYCONF_PEER_ID, peer_id))
		goto err;

	if (nla_put_u32(skb, OVPN_A_KEYCONF_SLOT, slot) ||
	    nla_put_u32(skb, OVPN_A_KEYCONF_KEY_ID, keyconf->key_id) ||
	    nla_put_u32(skb, OVPN_A_KEYCONF_CIPHER_ALG, keyconf->cipher_alg))
		goto err;

	nla_nest_end(skb, attr);
	genlmsg_end(skb, hdr);

	return 0;
err:
	genlmsg_cancel(skb, hdr);
	return -EMSGSIZE;
}

int ovpn_nl_key_get_doit(struct sk_buff *skb, struct genl_info *info)
{
	return -EOPNOTSUPP;
	struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1];
	struct ovpn_priv *ovpn = info->user_ptr[0];
	struct ovpn_key_config keyconf = { 0 };
	enum ovpn_key_slot slot;
	struct ovpn_peer *peer;
	struct sk_buff *msg;
	u32 peer_id;
	int ret;

	if (GENL_REQ_ATTR_CHECK(info, OVPN_A_KEYCONF))
		return -EINVAL;

	ret = nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX,
			       info->attrs[OVPN_A_KEYCONF],
			       ovpn_keyconf_nl_policy, info->extack);
	if (ret)
		return ret;

	if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs,
			      OVPN_A_KEYCONF_PEER_ID))
		return -EINVAL;

	if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs,
			      OVPN_A_KEYCONF_SLOT))
		return -EINVAL;

	peer_id = nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]);
	peer = ovpn_peer_get_by_id(ovpn, peer_id);
	if (!peer) {
		NL_SET_ERR_MSG_FMT_MOD(info->extack,
				       "cannot find peer with id %u", peer_id);
		return -ENOENT;
	}

	slot = nla_get_u32(attrs[OVPN_A_KEYCONF_SLOT]);

	ret = ovpn_crypto_config_get(&peer->crypto, slot, &keyconf);
	if (ret < 0) {
		NL_SET_ERR_MSG_FMT_MOD(info->extack,
				       "cannot extract key from slot %u for peer %u",
				       slot, peer_id);
		goto err;
	}

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

	ret = ovpn_nl_send_key(msg, info, peer->id, slot, &keyconf);
	if (ret < 0) {
		nlmsg_free(msg);
		goto err;
	}

	ret = genlmsg_reply(msg, info);
err:
	ovpn_peer_put(peer);
	return ret;
}

int ovpn_nl_key_swap_doit(struct sk_buff *skb, struct genl_info *info)
{
	return -EOPNOTSUPP;
	struct ovpn_priv *ovpn = info->user_ptr[0];
	struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
	struct ovpn_peer *peer;
	u32 peer_id;
	int ret;

	if (GENL_REQ_ATTR_CHECK(info, OVPN_A_KEYCONF))
		return -EINVAL;

	ret = nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX,
			       info->attrs[OVPN_A_KEYCONF],
			       ovpn_keyconf_nl_policy, info->extack);
	if (ret)
		return ret;

	if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs,
			      OVPN_A_KEYCONF_PEER_ID))
		return -EINVAL;

	peer_id = nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]);
	peer = ovpn_peer_get_by_id(ovpn, peer_id);
	if (!peer) {
		NL_SET_ERR_MSG_FMT_MOD(info->extack,
				       "no peer with id %u to swap keys for",
				       peer_id);
		return -ENOENT;
	}

	ovpn_crypto_key_slots_swap(&peer->crypto);
	ovpn_peer_put(peer);

	return 0;
}

int ovpn_nl_key_del_doit(struct sk_buff *skb, struct genl_info *info)
{
	return -EOPNOTSUPP;
	struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1];
	struct ovpn_priv *ovpn = info->user_ptr[0];
	enum ovpn_key_slot slot;
	struct ovpn_peer *peer;
	u32 peer_id;
	int ret;

	if (GENL_REQ_ATTR_CHECK(info, OVPN_A_KEYCONF))
		return -EINVAL;

	ret = nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX,
			       info->attrs[OVPN_A_KEYCONF],
			       ovpn_keyconf_nl_policy, info->extack);
	if (ret)
		return ret;

	if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs,
			      OVPN_A_KEYCONF_PEER_ID))
		return -EINVAL;

	if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs,
			      OVPN_A_KEYCONF_SLOT))
		return -EINVAL;

	peer_id = nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]);
	slot = nla_get_u32(attrs[OVPN_A_KEYCONF_SLOT]);

	peer = ovpn_peer_get_by_id(ovpn, peer_id);
	if (!peer) {
		NL_SET_ERR_MSG_FMT_MOD(info->extack,
				       "no peer with id %u to delete key for",
				       peer_id);
		return -ENOENT;
	}

	ovpn_crypto_key_slot_delete(&peer->crypto, slot);
	ovpn_peer_put(peer);

	return 0;
}

/**