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,
