Teach the AEAD path to build and parse the epoch packet layout. Epoch
packets carry the authentication tag at the tail and use the epoch-aware
packet ID encoding.

Direct packets keep the existing layout. Epoch keys are still not
accepted through netlink, and key promotion is added separately.

Signed-off-by: Ralf Lici <[email protected]>
---
 drivers/net/ovpn/crypto.h      |   2 +
 drivers/net/ovpn/crypto_aead.c | 243 +++++++++++++++++++++++----------
 drivers/net/ovpn/crypto_key.c  |   8 ++
 drivers/net/ovpn/io.c          |  28 +++-
 4 files changed, 206 insertions(+), 75 deletions(-)

diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h
index 2127fe4c454c..4ed44bab031f 100644
--- a/drivers/net/ovpn/crypto.h
+++ b/drivers/net/ovpn/crypto.h
@@ -81,6 +81,8 @@ struct ovpn_crypto_key_slot {
        bool epoch_format;
        unsigned int aad_size;
        unsigned int pktid_size;
+       unsigned int payload_offset;
+       unsigned int tail_tag_size;
 
        struct ovpn_epoch_key epoch_key_send;
        struct ovpn_epoch_key epoch_key_recv;
diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c
index 11b1e490c3fb..4daf0314b046 100644
--- a/drivers/net/ovpn/crypto_aead.c
+++ b/drivers/net/ovpn/crypto_aead.c
@@ -44,11 +44,6 @@ bool ovpn_aead_decrypt_failure_record(struct ovpn_key_ctx 
*key)
                                 &key->decrypt_failure_flags);
 }
 
-static int ovpn_aead_encap_overhead(const struct ovpn_key_ctx *key)
-{
-       return OVPN_AEAD_DIRECT_AAD_SIZE + crypto_aead_authsize(key->tfm);
-}
-
 /**
  * ovpn_aead_crypto_tmp_size - compute the size of a temporary object 
containing
  *                            an AEAD request structure with extra space for SG
@@ -150,8 +145,8 @@ static struct scatterlist *ovpn_aead_crypto_req_sg(struct 
crypto_aead *aead,
 int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
                      struct sk_buff *skb)
 {
+       unsigned int plaintext_len, payload_sg_len;
        struct ovpn_key_ctx *key = NULL;
-       unsigned int plaintext_len;
        struct aead_request *req;
        struct sk_buff *trailer;
        struct scatterlist *sg;
@@ -159,7 +154,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct 
ovpn_crypto_key_slot *ks,
        bool pktid_notify;
        u64 aead_blocks;
        int nfrags, ret;
-       u64 pktid;
+       u64 pktid, seq;
        void *tmp;
        u32 op;
        u8 *iv;
@@ -168,6 +163,18 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct 
ovpn_crypto_key_slot *ks,
        ovpn_skb_cb(skb)->ks = ks;
        plaintext_len = skb->len;
 
+       /* direct-key DATA_V2 wire format:
+        * 48000001 00000005 7e7046bd 444a7e28 cc6387b1 64a4d6c1 380275a...
+        * [ OP32 ] [seq # ] [             auth tag            ] [ payload ... ]
+        *          [4-byte
+        *          IV head]
+        *
+        * epoch-key DATA_V2 wire format:
+        * 48000001 0001 000000000005 380275a... 7e7046bd 444a7e28 cc6387b1 
64a4d6c1
+        * [ OP32 ] [ epoch ][  seq #   ] [ payload ... ] [             auth 
tag            ]
+        *          [        8-byte packet ID          ]
+        */
+
        ret = ovpn_key_ctx_get(&key, &ks->encrypt);
        if (unlikely(ret))
                return ret;
@@ -175,12 +182,19 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct 
ovpn_crypto_key_slot *ks,
 
        tag_size = crypto_aead_authsize(key->tfm);
 
-       /* Sample AEAD header format:
-        * 48000001 00000005 7e7046bd 444a7e28 cc6387b1 64a4d6c1 380275a...
-        * [ OP32 ] [seq # ] [             auth tag            ] [ payload ... ]
-        *          [4-byte
-        *          IV head]
-        */
+       ret = ovpn_pktid_xmit_next(&key->pid.xmit, &seq);
+       if (unlikely(ret < 0))
+               return ret;
+       pktid_notify = ret > 0;
+
+       aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size,
+                                            plaintext_len);
+       ret = ovpn_key_usage_xmit(&key->usage, &ks->usage_limit, seq,
+                                 aead_blocks, pktid_notify);
+       if (unlikely(ret < 0))
+               return ret;
+       if (unlikely(ret > 0 && !ks->epoch_format))
+               ovpn_nl_key_swap_notify(peer, ks->key_id);
 
        /* check that there's enough headroom in the skb for packet
         * encapsulation
@@ -188,14 +202,19 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct 
ovpn_crypto_key_slot *ks,
        if (unlikely(skb_cow_head(skb, OVPN_HEAD_ROOM)))
                return -ENOBUFS;
 
-       /* get number of skb frags and ensure that packet data is writable */
-       nfrags = skb_cow_data(skb, 0, &trailer);
+       /* ensure packet data is writable and epoch tag tailroom exists */
+       nfrags = skb_cow_data(skb, ks->tail_tag_size, &trailer);
        if (unlikely(nfrags < 0))
                return nfrags;
 
        if (unlikely(nfrags + 2 > (MAX_SKB_FRAGS + 2)))
                return -ENOSPC;
 
+       /* epoch packets place the authentication tag after payload */
+       if (unlikely(ks->tail_tag_size))
+               pskb_put(skb, trailer, ks->tail_tag_size);
+       payload_sg_len = plaintext_len + ks->tail_tag_size;
+
        /* allocate temporary memory for iv, sg and req */
        tmp = kmalloc(ovpn_aead_crypto_tmp_size(key->tfm, nfrags),
                      GFP_ATOMIC);
@@ -209,49 +228,33 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct 
ovpn_crypto_key_slot *ks,
        sg = ovpn_aead_crypto_req_sg(key->tfm, req);
 
        /* sg table:
-        * 0: op, wire nonce (AD, len=OVPN_AEAD_DIRECT_AAD_SIZE),
+        * 0: op, packet ID (AD, len=ks->aad_size),
         * 1, 2, 3, ..., n: payload,
         * n+1: auth_tag (len=tag_size)
         */
        sg_init_table(sg, nfrags + 2);
 
        /* build scatterlist to encrypt packet payload */
-       ret = skb_to_sgvec_nomark(skb, sg + 1, 0, skb->len);
+       ret = skb_to_sgvec_nomark(skb, sg + 1, 0, payload_sg_len);
        if (unlikely(ret < 0)) {
                netdev_err(peer->ovpn->dev,
                           "encrypt: cannot map skb to sg: %d\n", ret);
                return ret;
        }
 
-       /* append auth_tag onto scatterlist */
-       __skb_push(skb, tag_size);
-       sg_set_buf(sg + ret + 1, skb->data, tag_size);
-
-       /* obtain packet ID, which is used both as a first
-        * 4 bytes of nonce and last 4 bytes of associated data.
-        */
-       ret = ovpn_pktid_xmit_next(&key->pid.xmit, &pktid);
-       if (unlikely(ret < 0))
-               return ret;
-       pktid_notify = ret > 0;
-
-       aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size,
-                                            plaintext_len);
-       ret = ovpn_key_usage_xmit(&key->usage, &ks->usage_limit, pktid,
-                                 aead_blocks, pktid_notify);
-       if (unlikely(ret < 0))
-               return ret;
-       if (unlikely(ret > 0))
-               ovpn_nl_key_swap_notify(peer, ks->key_id);
+       if (likely(!ks->tail_tag_size)) {
+               /* direct packets prepend the tag before payload */
+               __skb_push(skb, tag_size);
+               sg_set_buf(sg + ret + 1, skb->data, tag_size);
+       }
 
-       /* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes
-        * nonce
-        */
-       pktid = ovpn_pktid_aead_write(0, pktid, key->implicit_iv, iv);
+       /* create the AEAD IV from packet ID and implicit IV */
+       pktid = ovpn_pktid_aead_write(key->epoch, seq, key->implicit_iv, iv);
 
        /* make space for packet id and push it to the front */
        __skb_push(skb, ks->pktid_size);
-       ovpn_pktid_wire_write(skb->data, false, pktid);
+       /* packet ID is 64 bits for epoch packets and 32 bits otherwise */
+       ovpn_pktid_wire_write(skb->data, ks->epoch_format, pktid);
 
        /* add packet op as head of additional data */
        op = ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer->tx_id);
@@ -265,54 +268,152 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct 
ovpn_crypto_key_slot *ks,
        /* setup async crypto operation */
        aead_request_set_tfm(req, key->tfm);
        aead_request_set_callback(req, 0, ovpn_encrypt_post, skb);
-       aead_request_set_crypt(req, sg, sg,
-                              skb->len - ovpn_aead_encap_overhead(key), iv);
+       aead_request_set_crypt(req, sg, sg, plaintext_len, iv);
        aead_request_set_ad(req, ks->aad_size);
 
        /* encrypt it */
        return crypto_aead_encrypt(req);
 }
 
+/**
+ * ovpn_aead_decrypt_key - get the decrypt key matching a packet epoch
+ * @ks: key slot containing RX epoch state
+ * @pkt_epoch: epoch decoded from the packet ID
+ *
+ * Direct-key packets must carry epoch 0. Epoch RX first tries the current key,
+ * then the retiring key for reordered packets, and finally the future ring. A
+ * future key is returned only for authentication; promotion happens after the
+ * packet has authenticated.
+ *
+ * Return: referenced key context or an error pointer.
+ */
+static struct ovpn_key_ctx *
+ovpn_aead_decrypt_key(struct ovpn_crypto_key_slot *ks, u16 pkt_epoch)
+{
+       u16 current_epoch, epoch_diff, index;
+       struct ovpn_key_ctx *key;
+
+       if (likely(!ks->epoch_format)) {
+               if (unlikely(pkt_epoch))
+                       return ERR_PTR(-EINVAL);
+               if (unlikely(ovpn_key_ctx_get(&key, &ks->decrypt)))
+                       return ERR_PTR(-ENOKEY);
+               return key;
+       }
+
+       spin_lock_bh(&ks->rx_lock);
+
+       key = rcu_dereference_protected(ks->decrypt,
+                                       lockdep_is_held(&ks->rx_lock));
+       if (unlikely(!key)) {
+               key = ERR_PTR(-ENOKEY);
+               goto out;
+       }
+       /* current key is the expected rx path */
+       if (likely(key->epoch == pkt_epoch)) {
+               if (kref_get_unless_zero(&key->refcount))
+                       goto out;
+               key = ERR_PTR(-ENOKEY);
+               goto out;
+       }
+       current_epoch = key->epoch;
+
+       key = rcu_dereference_protected(ks->retiring_key,
+                                       lockdep_is_held(&ks->rx_lock));
+       /* retiring key accepts late packets from the previous epoch */
+       if (unlikely(key && key->epoch == pkt_epoch)) {
+               if (kref_get_unless_zero(&key->refcount))
+                       goto out;
+               key = ERR_PTR(-ENOKEY);
+               goto out;
+       }
+
+       if (unlikely(pkt_epoch < current_epoch)) {
+               key = ERR_PTR(-ERANGE);
+               goto out;
+       }
+       /* stay away from the epoch range where future refill would wrap */
+       if (unlikely(pkt_epoch >
+                    OVPN_MAX_EPOCH - OVPN_EPOCH_FUTURE_KEYS_COUNT - 1)) {
+               key = ERR_PTR(-ERANGE);
+               goto out;
+       }
+
+       epoch_diff = pkt_epoch - current_epoch;
+       if (unlikely(!epoch_diff ||
+                    epoch_diff > OVPN_EPOCH_FUTURE_KEYS_COUNT)) {
+               key = ERR_PTR(-ERANGE);
+               goto out;
+       }
+
+       /* future keys are consecutive starting at tail/current_epoch + 1 */
+       index = (ks->future_rx_keys.tail + epoch_diff - 1) %
+               OVPN_EPOCH_FUTURE_KEYS_COUNT;
+       key = rcu_dereference_protected(ks->future_rx_keys.keys[index],
+                                       lockdep_is_held(&ks->rx_lock));
+       if (unlikely(!key || key->epoch != pkt_epoch ||
+                    !kref_get_unless_zero(&key->refcount))) {
+               key = ERR_PTR(-ENOKEY);
+               goto out;
+       }
+
+out:
+       spin_unlock_bh(&ks->rx_lock);
+
+       return key;
+}
+
 int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
                      struct sk_buff *skb)
 {
-       struct ovpn_key_ctx *key = NULL;
-       unsigned int payload_offset;
+       unsigned int payload_offset, payload_sg_len;
        int ret, payload_len, nfrags;
+       struct ovpn_key_ctx *key;
        struct aead_request *req;
        struct sk_buff *trailer;
        struct scatterlist *sg;
        unsigned int tag_size;
+       u16 pkt_epoch;
+       u64 pkt_seq;
        void *tmp;
        u8 *iv;
 
+       payload_offset = ks->payload_offset;
+       ovpn_skb_cb(skb)->payload_offset = payload_offset;
        ovpn_skb_cb(skb)->peer = peer;
        ovpn_skb_cb(skb)->ks = ks;
 
-       ret = ovpn_key_ctx_get(&key, &ks->decrypt);
-       if (unlikely(ret))
-               return ret;
-       ovpn_skb_cb(skb)->key = key;
-
-       tag_size = crypto_aead_authsize(key->tfm);
-       payload_offset = ovpn_aead_direct_payload_offset(tag_size);
+       if (unlikely(skb->len < payload_offset))
+               return -EINVAL;
        payload_len = skb->len - payload_offset;
-
-       ovpn_skb_cb(skb)->payload_offset = payload_offset;
+       if (unlikely(ks->tail_tag_size)) {
+               if (unlikely(payload_len < ks->tail_tag_size))
+                       return -EINVAL;
+               payload_len -= ks->tail_tag_size;
+       }
 
        /* sanity check on packet size, payload size must be >= 0 */
        if (unlikely(payload_len < 0))
                return -EINVAL;
 
+       /* make additional data contiguous for sg[0] */
+       if (unlikely(!pskb_may_pull(skb, payload_offset)))
+               return -ENODATA;
+
+       /* epoch packet ID includes both epoch and per-epoch counter */
+       pkt_seq = ovpn_pktid_read(skb->data + OVPN_OPCODE_SIZE,
+                                 ks->epoch_format, &pkt_epoch);
+       key = ovpn_aead_decrypt_key(ks, pkt_epoch);
+       if (IS_ERR(key))
+               return PTR_ERR(key);
+       ovpn_skb_cb(skb)->key = key;
+
        if (unlikely(ovpn_aead_decrypt_failure_exceeded(key)))
                return -EKEYREJECTED;
 
-       /* Prepare the skb data buffer to be accessed up until the auth tag.
-        * This is required because this area is directly mapped into the sg
-        * list.
-        */
-       if (unlikely(!pskb_may_pull(skb, payload_offset)))
-               return -ENODATA;
+       tag_size = crypto_aead_authsize(key->tfm);
+       /* epoch tag is at the tail and remains in the crypto input */
+       payload_sg_len = payload_len + ks->tail_tag_size;
 
        /* get number of skb frags and ensure that packet data is writable */
        nfrags = skb_cow_data(skb, 0, &trailer);
@@ -335,7 +436,7 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct 
ovpn_crypto_key_slot *ks,
        sg = ovpn_aead_crypto_req_sg(key->tfm, req);
 
        /* sg table:
-        * 0: op, wire nonce (AD, len=OVPN_AEAD_DIRECT_AAD_SIZE),
+        * 0: op, packet ID (AD, len=ks->aad_size),
         * 1, 2, 3, ..., n: payload,
         * n+1: auth_tag (len=tag_size)
         */
@@ -345,21 +446,23 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct 
ovpn_crypto_key_slot *ks,
        sg_set_buf(sg, skb->data, ks->aad_size);
 
        /* build scatterlist to decrypt packet payload */
-       ret = skb_to_sgvec_nomark(skb, sg + 1, payload_offset, payload_len);
+       ret = skb_to_sgvec_nomark(skb, sg + 1, payload_offset,
+                                 payload_sg_len);
        if (unlikely(ret < 0)) {
                netdev_err(peer->ovpn->dev,
                           "decrypt: cannot map skb to sg: %d\n", ret);
                return ret;
        }
 
-       /* append auth_tag onto scatterlist */
-       sg_set_buf(sg + ret + 1, skb->data + OVPN_AEAD_DIRECT_TAG_OFFSET,
-                  tag_size);
+       if (likely(!ks->tail_tag_size)) {
+               /* direct tag is prepended; epoch tag is in payload sg */
+               sg_set_buf(sg + ret + 1,
+                          skb->data + OVPN_AEAD_DIRECT_TAG_OFFSET,
+                          tag_size);
+       }
 
-       /* copy nonce into IV buffer */
-       memcpy(iv, ovpn_aead_direct_wire_nonce(skb), OVPN_NONCE_WIRE_SIZE);
-       memcpy(iv + OVPN_NONCE_WIRE_SIZE,
-              key->implicit_iv + OVPN_NONCE_WIRE_SIZE, OVPN_NONCE_TAIL_SIZE);
+       /* rebuild the AEAD IV from packet ID and implicit IV */
+       ovpn_pktid_aead_write(pkt_epoch, pkt_seq, key->implicit_iv, iv);
 
        /* setup async crypto operation */
        aead_request_set_tfm(req, key->tfm);
diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c
index 6b193c5fd7e4..b4f40db4b30b 100644
--- a/drivers/net/ovpn/crypto_key.c
+++ b/drivers/net/ovpn/crypto_key.c
@@ -394,6 +394,7 @@ struct ovpn_crypto_key_slot *
 ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc)
 {
        struct ovpn_crypto_key_slot *ks = NULL;
+       unsigned int direct_payload_offset;
        struct ovpn_key_ctx *key;
        const char *alg_name;
        int ret;
@@ -427,6 +428,9 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc)
        if (!ks)
                return ERR_PTR(-ENOMEM);
 
+       direct_payload_offset =
+               ovpn_aead_direct_payload_offset(OVPN_AEAD_TAG_SIZE);
+
        ks->encrypt = NULL;
        ks->decrypt = NULL;
        kref_init(&ks->refcount);
@@ -438,6 +442,10 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc)
                                             OVPN_AEAD_DIRECT_AAD_SIZE;
        ks->pktid_size = kc->use_epoch_keys ? OVPN_EPOCH_NONCE_WIRE_SIZE :
                                               OVPN_NONCE_WIRE_SIZE;
+       ks->payload_offset = kc->use_epoch_keys ?
+                            OVPN_AEAD_EPOCH_AAD_SIZE :
+                            direct_payload_offset;
+       ks->tail_tag_size = kc->use_epoch_keys ? OVPN_AEAD_TAG_SIZE : 0;
        ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg);
 
        if (kc->use_epoch_keys) {
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index 27c61947ebaa..de145fb0b758 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -113,6 +113,7 @@ void ovpn_decrypt_post(void *data, int ret)
        struct ovpn_key_ctx *key;
        struct ovpn_socket *sock;
        struct ovpn_peer *peer;
+       int payload_len;
        u64 aead_blocks;
        u16 pkt_epoch;
        __be16 proto;
@@ -133,7 +134,8 @@ void ovpn_decrypt_post(void *data, int ret)
        kfree(ovpn_skb_cb(skb)->crypto_tmp);
 
        if (unlikely(ret == -EBADMSG)) {
-               if (key && unlikely(ovpn_aead_decrypt_failure_record(key)))
+               if (key && unlikely(ovpn_aead_decrypt_failure_record(key)) &&
+                   likely(!ks->epoch_format))
                        ovpn_nl_key_swap_notify(peer, ks->key_id);
                goto drop;
        }
@@ -141,8 +143,8 @@ void ovpn_decrypt_post(void *data, int ret)
        if (unlikely(ret < 0))
                goto drop;
 
-       pktid = ovpn_pktid_read(skb->data + OVPN_OPCODE_SIZE, false,
-                               &pkt_epoch);
+       pktid = ovpn_pktid_read(skb->data + OVPN_OPCODE_SIZE,
+                               ks->epoch_format, &pkt_epoch);
        ret = ovpn_pktid_recv(&key->pid.recv, pktid, 0);
        if (unlikely(ret < 0)) {
                net_err_ratelimited("%s: PKT ID RX error for peer %u: %d\n",
@@ -151,13 +153,29 @@ void ovpn_decrypt_post(void *data, int ret)
                goto drop;
        }
 
+       if (unlikely(skb->len < payload_offset))
+               goto drop;
+       payload_len = skb->len - payload_offset;
+       if (unlikely(ks->tail_tag_size)) {
+               if (unlikely(payload_len < ks->tail_tag_size))
+                       goto drop;
+               payload_len -= ks->tail_tag_size;
+       }
+       if (unlikely(payload_len < 0))
+               goto drop;
+
        aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size,
-                                            skb->len - payload_offset);
+                                            payload_len);
        if (unlikely(ovpn_pktid_recv_update_aead(&key->pid.recv, &key->usage,
                                                 &ks->usage_limit,
-                                                aead_blocks)))
+                                                aead_blocks) &&
+                    !ks->epoch_format))
                ovpn_nl_key_swap_notify(peer, ks->key_id);
 
+       if (unlikely(ks->tail_tag_size &&
+                    pskb_trim(skb, skb->len - ks->tail_tag_size)))
+               goto drop;
+
        /* keep track of last received authenticated packet for keepalive */
        WRITE_ONCE(peer->last_recv, ktime_get_real_seconds());
 
-- 
2.54.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to