[Openvpn-devel,RFC,net-next,13/14] ovpn: accept epoch keys from netlink

Message ID 6a100e535685abf00f2b9d3982eda44ea01a2512.1782919654.git.ralf@mandelbit.com
State Superseded
Headers
Series ovpn: add epoch data channel keys |

Commit Message

Ralf Lici July 1, 2026, 3:43 p.m. UTC
  Allow KEY_NEW to receive either direct key material or epoch PRK
material. Userspace must provide exactly one key format: the existing
encrypt/decrypt direction attributes select direct keys, while the new
epoch attributes select epoch-derived keys.

Parse epoch key attributes into the common key configuration used by
crypto slot setup. Direct key attributes keep the existing UAPI shape,
so current userspace continues to install direct keys without any new
format attribute.

Signed-off-by: Ralf Lici <ralf@mandelbit.com>
---
 Documentation/netlink/specs/ovpn.yaml |  31 ++++++++
 drivers/net/ovpn/crypto_epoch.h       |   1 -
 drivers/net/ovpn/netlink-gen.c        |   9 ++-
 drivers/net/ovpn/netlink-gen.h        |   3 +-
 drivers/net/ovpn/netlink.c            | 110 ++++++++++++++++++++++----
 include/uapi/linux/ovpn.h             |  11 +++
 6 files changed, 146 insertions(+), 19 deletions(-)
  

Patch

diff --git a/Documentation/netlink/specs/ovpn.yaml b/Documentation/netlink/specs/ovpn.yaml
index b0c782e59a32..43b9ab1eb3da 100644
--- a/Documentation/netlink/specs/ovpn.yaml
+++ b/Documentation/netlink/specs/ovpn.yaml
@@ -16,6 +16,10 @@  definitions:
     type: const
     name: nonce-tail-size
     value: 8
+  -
+    type: const
+    name: epoch-prk-size
+    value: 32
   -
     type: enum
     name: cipher-alg
@@ -274,6 +278,16 @@  attribute-sets:
         type: nest
         doc: Key material for decrypt direction
         nested-attributes: keydir
+      -
+        name: encrypt-epoch
+        type: nest
+        doc: Epoch PRK material used to derive encrypt data keys
+        nested-attributes: epoch
+      -
+        name: decrypt-epoch
+        type: nest
+        doc: Epoch PRK material used to derive decrypt data keys
+        nested-attributes: epoch
   -
     name: keydir
     attributes:
@@ -291,6 +305,23 @@  attribute-sets:
           obtain the actual cipher IV
         checks:
           exact-len: nonce-tail-size
+  -
+    name: epoch
+    attributes:
+      -
+        name: key
+        type: binary
+        doc: >-
+          The epoch PRK used to derive cipher keys and implicit IVs
+        checks:
+          exact-len: epoch-prk-size
+      -
+        name: cipher-key-len
+        type: u32
+        doc: Length in bytes of the AEAD cipher key derived from the epoch PRK
+        checks:
+          min: 1
+          max: epoch-prk-size
 
   -
     name: keyconf-get
diff --git a/drivers/net/ovpn/crypto_epoch.h b/drivers/net/ovpn/crypto_epoch.h
index 7da31c2acd44..76278c73eba4 100644
--- a/drivers/net/ovpn/crypto_epoch.h
+++ b/drivers/net/ovpn/crypto_epoch.h
@@ -16,7 +16,6 @@ 
 #include <linux/types.h>
 #include <uapi/linux/ovpn.h>
 
-#define OVPN_EPOCH_PRK_SIZE 32
 #define OVPN_MAX_EPOCH U16_MAX
 #define OVPN_EPOCH_FUTURE_KEYS_COUNT 16
 
diff --git a/drivers/net/ovpn/netlink-gen.c b/drivers/net/ovpn/netlink-gen.c
index 2147cec7c2c5..667a51f52158 100644
--- a/drivers/net/ovpn/netlink-gen.c
+++ b/drivers/net/ovpn/netlink-gen.c
@@ -25,13 +25,20 @@  static const struct netlink_range_validation ovpn_a_keyconf_peer_id_range = {
 };
 
 /* Common nested types */
-const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR + 1] = {
+const struct nla_policy ovpn_epoch_nl_policy[OVPN_A_EPOCH_CIPHER_KEY_LEN + 1] = {
+	[OVPN_A_EPOCH_KEY] = NLA_POLICY_EXACT_LEN(OVPN_EPOCH_PRK_SIZE),
+	[OVPN_A_EPOCH_CIPHER_KEY_LEN] = NLA_POLICY_RANGE(NLA_U32, 1, OVPN_EPOCH_PRK_SIZE),
+};
+
+const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_EPOCH + 1] = {
 	[OVPN_A_KEYCONF_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_keyconf_peer_id_range),
 	[OVPN_A_KEYCONF_SLOT] = NLA_POLICY_MAX(NLA_U32, 1),
 	[OVPN_A_KEYCONF_KEY_ID] = NLA_POLICY_MAX(NLA_U32, 7),
 	[OVPN_A_KEYCONF_CIPHER_ALG] = NLA_POLICY_MAX(NLA_U32, 2),
 	[OVPN_A_KEYCONF_ENCRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy),
 	[OVPN_A_KEYCONF_DECRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy),
+	[OVPN_A_KEYCONF_ENCRYPT_EPOCH] = NLA_POLICY_NESTED(ovpn_epoch_nl_policy),
+	[OVPN_A_KEYCONF_DECRYPT_EPOCH] = NLA_POLICY_NESTED(ovpn_epoch_nl_policy),
 };
 
 const struct nla_policy ovpn_keyconf_del_input_nl_policy[OVPN_A_KEYCONF_SLOT + 1] = {
diff --git a/drivers/net/ovpn/netlink-gen.h b/drivers/net/ovpn/netlink-gen.h
index 67cd85f86173..72f765f574a5 100644
--- a/drivers/net/ovpn/netlink-gen.h
+++ b/drivers/net/ovpn/netlink-gen.h
@@ -13,7 +13,8 @@ 
 #include <uapi/linux/ovpn.h>
 
 /* Common nested types */
-extern const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR + 1];
+extern const struct nla_policy ovpn_epoch_nl_policy[OVPN_A_EPOCH_CIPHER_KEY_LEN + 1];
+extern const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_EPOCH + 1];
 extern const struct nla_policy ovpn_keyconf_del_input_nl_policy[OVPN_A_KEYCONF_SLOT + 1];
 extern const struct nla_policy ovpn_keyconf_get_nl_policy[OVPN_A_KEYCONF_CIPHER_ALG + 1];
 extern const struct nla_policy ovpn_keyconf_swap_input_nl_policy[OVPN_A_KEYCONF_PEER_ID + 1];
diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
index 910e4e67a68a..14c91729b392 100644
--- a/drivers/net/ovpn/netlink.c
+++ b/drivers/net/ovpn/netlink.c
@@ -859,6 +859,40 @@  static int ovpn_nl_get_key_dir(struct genl_info *info, struct nlattr *key,
 	return 0;
 }
 
+static int ovpn_nl_get_epoch_key(struct genl_info *info, struct nlattr *key,
+				 enum ovpn_cipher_alg cipher,
+				 struct ovpn_epoch_prk *prk)
+{
+	struct nlattr *attrs[OVPN_A_EPOCH_MAX + 1];
+	int ret;
+
+	ret = nla_parse_nested(attrs, OVPN_A_EPOCH_MAX, key,
+			       ovpn_epoch_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_EPOCH_KEY) ||
+		    NL_REQ_ATTR_CHECK(info->extack, key, attrs,
+				      OVPN_A_EPOCH_CIPHER_KEY_LEN))
+			return -EINVAL;
+
+		prk->key = nla_data(attrs[OVPN_A_EPOCH_KEY]);
+		prk->key_size = nla_len(attrs[OVPN_A_EPOCH_KEY]);
+		prk->cipher_key_len =
+			nla_get_u32(attrs[OVPN_A_EPOCH_CIPHER_KEY_LEN]);
+		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
@@ -887,9 +921,11 @@  static int ovpn_nl_get_key_dir(struct genl_info *info, struct nlattr *key,
 int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info)
 {
 	struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1];
-	struct ovpn_priv *ovpn = info->user_ptr[0];
 	struct ovpn_peer_key_reset pkr = {};
+	struct ovpn_priv *ovpn = info->user_ptr[0];
 	struct ovpn_peer *peer;
+	bool direct_key;
+	bool epoch_key;
 	u32 peer_id;
 	int ret;
 
@@ -911,28 +947,70 @@  int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info)
 	    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))
+			      OVPN_A_KEYCONF_CIPHER_ALG))
 		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.direct.encrypt);
-	if (ret < 0)
-		return ret;
+	direct_key = attrs[OVPN_A_KEYCONF_ENCRYPT_DIR] ||
+		     attrs[OVPN_A_KEYCONF_DECRYPT_DIR];
+	epoch_key = attrs[OVPN_A_KEYCONF_ENCRYPT_EPOCH] ||
+		    attrs[OVPN_A_KEYCONF_DECRYPT_EPOCH];
+	if (direct_key == epoch_key) {
+		NL_SET_ERR_MSG_MOD(info->extack,
+				   "specify exactly one key format");
+		return -EINVAL;
+	}
 
-	ret = ovpn_nl_get_key_dir(info, attrs[OVPN_A_KEYCONF_DECRYPT_DIR],
-				  pkr.key.cipher_alg,
-				  &pkr.key.direct.decrypt);
-	if (ret < 0)
-		return ret;
+	if (direct_key) {
+		if (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.key.use_epoch_keys = false;
+		ret = ovpn_nl_get_key_dir(info,
+					  attrs[OVPN_A_KEYCONF_ENCRYPT_DIR],
+					  pkr.key.cipher_alg,
+					  &pkr.key.direct.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.direct.decrypt);
+		if (ret < 0)
+			return ret;
+	} else {
+		if (NL_REQ_ATTR_CHECK(info->extack,
+				      info->attrs[OVPN_A_KEYCONF], attrs,
+				      OVPN_A_KEYCONF_ENCRYPT_EPOCH) ||
+		    NL_REQ_ATTR_CHECK(info->extack,
+				      info->attrs[OVPN_A_KEYCONF], attrs,
+				      OVPN_A_KEYCONF_DECRYPT_EPOCH))
+			return -EINVAL;
+
+		pkr.key.use_epoch_keys = true;
+		ret = ovpn_nl_get_epoch_key(info,
+					    attrs[OVPN_A_KEYCONF_ENCRYPT_EPOCH],
+					    pkr.key.cipher_alg,
+					    &pkr.key.epoch.encrypt);
+		if (ret < 0)
+			return ret;
+
+		ret = ovpn_nl_get_epoch_key(info,
+					    attrs[OVPN_A_KEYCONF_DECRYPT_EPOCH],
+					    pkr.key.cipher_alg,
+					    &pkr.key.epoch.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);
diff --git a/include/uapi/linux/ovpn.h b/include/uapi/linux/ovpn.h
index 06690090a1a9..fa6436557abd 100644
--- a/include/uapi/linux/ovpn.h
+++ b/include/uapi/linux/ovpn.h
@@ -11,6 +11,7 @@ 
 #define OVPN_FAMILY_VERSION	1
 
 #define OVPN_NONCE_TAIL_SIZE	8
+#define OVPN_EPOCH_PRK_SIZE	32
 
 enum ovpn_cipher_alg {
 	OVPN_CIPHER_ALG_NONE,
@@ -68,6 +69,8 @@  enum {
 	OVPN_A_KEYCONF_CIPHER_ALG,
 	OVPN_A_KEYCONF_ENCRYPT_DIR,
 	OVPN_A_KEYCONF_DECRYPT_DIR,
+	OVPN_A_KEYCONF_ENCRYPT_EPOCH,
+	OVPN_A_KEYCONF_DECRYPT_EPOCH,
 
 	__OVPN_A_KEYCONF_MAX,
 	OVPN_A_KEYCONF_MAX = (__OVPN_A_KEYCONF_MAX - 1)
@@ -81,6 +84,14 @@  enum {
 	OVPN_A_KEYDIR_MAX = (__OVPN_A_KEYDIR_MAX - 1)
 };
 
+enum {
+	OVPN_A_EPOCH_KEY = 1,
+	OVPN_A_EPOCH_CIPHER_KEY_LEN,
+
+	__OVPN_A_EPOCH_MAX,
+	OVPN_A_EPOCH_MAX = (__OVPN_A_EPOCH_MAX - 1)
+};
+
 enum {
 	OVPN_A_IFINDEX = 1,
 	OVPN_A_PEER,