Prepare the data path for packet ID spaces larger than the direct-key 32-bit counter.
Widen packet ID accounting to u64 and store packet-ID soft and hard limits in the transmit packet-ID state. This keeps wrap and rekey-notification policy with the packet-ID allocator instead of passing raw thresholds through the AEAD path. Make AES-GCM usage accounting include the packet layout's AAD length. Add shared packet ID read/write helpers and direct-key slot layout fields that later epoch support can reuse. Signed-off-by: Ralf Lici <[email protected]> --- No functional changes since v1 https://lore.kernel.org/openvpn-devel/ca32807ed19fe093ee611254a551bba6a5552e2d.1782919654.git.r...@mandelbit.com/ drivers/net/ovpn/crypto.h | 3 + drivers/net/ovpn/crypto_aead.c | 31 ++++---- drivers/net/ovpn/crypto_key.c | 12 +++- drivers/net/ovpn/io.c | 10 +-- drivers/net/ovpn/pktid.c | 13 ++-- drivers/net/ovpn/pktid.h | 126 +++++++++++++++++++++++---------- drivers/net/ovpn/proto.h | 8 +++ 7 files changed, 141 insertions(+), 62 deletions(-) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 1d62f2be23c9..8e7235f41960 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -58,6 +58,9 @@ struct ovpn_crypto_key_slot { u8 key_id; enum ovpn_cipher_alg cipher_alg; struct ovpn_limit usage_limit; + bool epoch_format; + unsigned int aad_size; + unsigned int pktid_size; struct ovpn_key_ctx __rcu *encrypt; struct ovpn_key_ctx __rcu *decrypt; diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 39bd320169d3..cf0a3d861eb9 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -154,11 +154,12 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct ovpn_key_ctx *key = NULL; struct aead_request *req; struct sk_buff *trailer; + u64 aead_blocks, pktid; struct scatterlist *sg; + bool pktid_notify; int nfrags, ret; - u64 aead_blocks; - u32 pktid, op; void *tmp; + u32 op; u8 *iv; ovpn_skb_cb(skb)->peer = peer; @@ -227,11 +228,15 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, /* obtain packet ID, which is used both as a first * 4 bytes of nonce and last 4 bytes of associated data. */ - aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, - OVPN_AEAD_DIRECT_AAD_SIZE, + 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_pktid_xmit_next(&key->pid.xmit, &key->usage, - &ks->usage_limit, aead_blocks, &pktid); + 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)) @@ -240,11 +245,11 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, /* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes * nonce */ - ovpn_pktid_aead_write(pktid, key->implicit_iv, iv); + pktid = ovpn_pktid_aead_write(0, pktid, key->implicit_iv, iv); /* make space for packet id and push it to the front */ - __skb_push(skb, OVPN_NONCE_WIRE_SIZE); - memcpy(skb->data, iv, OVPN_NONCE_WIRE_SIZE); + __skb_push(skb, ks->pktid_size); + ovpn_pktid_wire_write(skb->data, false, pktid); /* add packet op as head of additional data */ op = ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer->tx_id); @@ -253,14 +258,14 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, *((__force __be32 *)skb->data) = htonl(op); /* AEAD Additional data */ - sg_set_buf(sg, skb->data, OVPN_AEAD_DIRECT_AAD_SIZE); + sg_set_buf(sg, skb->data, ks->aad_size); /* 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_ad(req, OVPN_AEAD_DIRECT_AAD_SIZE); + aead_request_set_ad(req, ks->aad_size); /* encrypt it */ return crypto_aead_encrypt(req); @@ -334,7 +339,7 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, sg_init_table(sg, nfrags + 2); /* packet op is head of additional data */ - sg_set_buf(sg, skb->data, OVPN_AEAD_DIRECT_AAD_SIZE); + 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); @@ -358,7 +363,7 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, aead_request_set_callback(req, 0, ovpn_decrypt_post, skb); aead_request_set_crypt(req, sg, sg, payload_len + tag_size, iv); - aead_request_set_ad(req, OVPN_AEAD_DIRECT_AAD_SIZE); + aead_request_set_ad(req, ks->aad_size); /* decrypt it */ return crypto_aead_decrypt(req); diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c index 44110a0b38eb..8ac13a3485fe 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -102,6 +102,7 @@ static struct ovpn_key_ctx * ovpn_key_ctx_new(const char *title, const char *alg_name, const struct ovpn_key_direction *dir, bool encrypt) { + struct ovpn_limit pktid_limit; struct ovpn_key_ctx *key; size_t tail_offset; int ret; @@ -132,10 +133,12 @@ ovpn_key_ctx_new(const char *title, const char *alg_name, kref_init(&key->refcount); /* initialize only the packet ID direction this context owns */ - if (encrypt) - ovpn_pktid_xmit_init(&key->pid.xmit); - else + if (encrypt) { + ovpn_pktid_xmit_limit_init(&pktid_limit, false); + ovpn_pktid_xmit_init(&key->pid.xmit, &pktid_limit); + } else { ovpn_pktid_recv_init(&key->pid.recv); + } return key; } @@ -184,6 +187,9 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) kref_init(&ks->refcount); ks->key_id = kc->key_id; ks->cipher_alg = kc->cipher_alg; + ks->epoch_format = false; + ks->aad_size = OVPN_AEAD_DIRECT_AAD_SIZE; + ks->pktid_size = OVPN_NONCE_WIRE_SIZE; ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg); key = ovpn_key_ctx_new("encrypt", alg_name, &kc->encrypt, true); diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 006d83fe3e2a..f5198d5bf104 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -112,10 +112,10 @@ void ovpn_decrypt_post(void *data, int ret) struct sk_buff *skb = data; struct ovpn_socket *sock; struct ovpn_key_ctx *key; + u64 aead_blocks, pktid; struct ovpn_peer *peer; - u64 aead_blocks; + u16 pkt_epoch; __be16 proto; - u32 pktid; /* crypto is happening asynchronously. this function will be called * again later by the crypto callback with a proper return code @@ -140,7 +140,8 @@ void ovpn_decrypt_post(void *data, int ret) if (unlikely(ret < 0)) goto drop; - pktid = ovpn_aead_direct_pktid(skb); + pktid = ovpn_pktid_read(skb->data + OVPN_OPCODE_SIZE, false, + &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", @@ -149,8 +150,7 @@ void ovpn_decrypt_post(void *data, int ret) goto drop; } - aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, - OVPN_AEAD_DIRECT_AAD_SIZE, + aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size, skb->len - payload_offset); if (unlikely(ovpn_pktid_recv_update_aead(&key->pid.recv, &key->usage, &ks->usage_limit, diff --git a/drivers/net/ovpn/pktid.c b/drivers/net/ovpn/pktid.c index f1c243b84463..16289035c287 100644 --- a/drivers/net/ovpn/pktid.c +++ b/drivers/net/ovpn/pktid.c @@ -17,9 +17,12 @@ #include "main.h" #include "pktid.h" -void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid) +void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid, + const struct ovpn_limit *limit) { - atomic_set(&pid->seq_num, 1); + pid->seq_num = 1; + pid->limit = *limit; + spin_lock_init(&pid->lock); } void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr) @@ -31,7 +34,7 @@ void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr) /* Packet replay detection. * Allows ID backtrack of up to REPLAY_WINDOW_SIZE - 1. */ -int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time) +int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u64 pkt_id, u32 pkt_time) { const unsigned long now = jiffies; int ret; @@ -71,7 +74,7 @@ int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time) pr->id = pkt_id; } else if (pkt_id > pr->id) { /* ID jumped forward by more than one */ - const unsigned int delta = pkt_id - pr->id; + const u64 delta = pkt_id - pr->id; if (delta < REPLAY_WINDOW_SIZE) { unsigned int i; @@ -95,7 +98,7 @@ int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time) pr->id = pkt_id; } else { /* ID backtrack */ - const unsigned int delta = pr->id - pkt_id; + const u64 delta = pr->id - pkt_id; if (delta > pr->max_backtrack) pr->max_backtrack = delta; diff --git a/drivers/net/ovpn/pktid.h b/drivers/net/ovpn/pktid.h index 235c9111d085..62666017f051 100644 --- a/drivers/net/ovpn/pktid.h +++ b/drivers/net/ovpn/pktid.h @@ -13,6 +13,9 @@ #include "crypto_limits.h" #include "proto.h" +#include <linux/bitfield.h> +#include <linux/unaligned.h> + /* If no packets received for this length of time, set a backtrack floor * at highest received packet ID thus far. */ @@ -20,10 +23,17 @@ /* notify userspace when the packet ID space is close to wrapping */ #define PKTID_XMIT_REKEY_NOTIFY 0xff000000U +#define PKTID_DIRECT_MAX U32_MAX +#define PKTID_EPOCH_SEQ_MAX GENMASK_ULL(47, 0) +#define PKTID_EPOCH_MASK GENMASK_ULL(63, 48) +#define PKTID_EPOCH_SEQ_MASK GENMASK_ULL(47, 0) /* Packet-ID state for transmitter */ struct ovpn_pktid_xmit { - atomic_t seq_num; + u64 seq_num; + struct ovpn_limit limit; + /* protects seq_num */ + spinlock_t lock; }; /* replay window sizing in bytes = 2^REPLAY_WINDOW_ORDER */ @@ -46,56 +56,61 @@ struct ovpn_pktid_recv { /* expiration of history in jiffies */ unsigned long expire; /* highest sequence number received */ - u32 id; + u64 id; /* highest time stamp received */ u32 time; /* we will only accept backtrack IDs > id_floor */ - u32 id_floor; - unsigned int max_backtrack; + u64 id_floor; + u64 max_backtrack; /* protects entire pktd ID state */ spinlock_t lock; }; +static inline void ovpn_pktid_xmit_limit_init(struct ovpn_limit *limit, + bool epoch_format) +{ + if (epoch_format) { + limit->soft = U64_MAX; + limit->hard = PKTID_EPOCH_SEQ_MAX; + } else { + limit->soft = PKTID_XMIT_REKEY_NOTIFY; + limit->hard = PKTID_DIRECT_MAX; + } +} + /** * ovpn_pktid_xmit_next - allocate a transmit packet ID * @pid: transmit packet ID state - * @usage: key usage state - * @limit: key usage limits - * @aead_blocks: AEAD usage blocks consumed by this packet * @pktid: location where the generated packet ID is stored * - * The returned packet ID becomes part of the AEAD nonce, so the helper rejects - * the packet before the 32-bit packet-ID space wraps. It also reserves this - * packet's AEAD usage against the key and rejects the packet before the hard - * AES-GCM usage limit would be exceeded. + * The returned packet ID becomes part of the AEAD nonce. Reusing it would + * reuse an IV, so the helper rejects the packet before the configured packet-ID + * space wraps. * - * Soft thresholds do not reject the packet. They return 1 once so the caller - * can notify userspace to rekey while packet-ID space remains and before the - * hard AES-GCM usage limit is reached. + * The hard limit stops TX. The soft limit asks userspace to rekey before that + * happens and is reported as return value 1. * * Return: 1 if userspace should be notified, 0 if no notification is needed, * or a negative error code otherwise. */ -static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, - struct ovpn_key_usage *usage, - const struct ovpn_limit *limit, - u64 aead_blocks, u32 *pktid) +static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, u64 *pktid) { - const u32 seq_num = atomic_fetch_add_unless(&pid->seq_num, 1, 0); - bool pktid_notify; + u64 seq_num; int ret; + spin_lock_bh(&pid->lock); + seq_num = pid->seq_num; + /* packet IDs are used to create cipher IVs and must not wrap */ - if (unlikely(!seq_num)) + if (unlikely(!seq_num || seq_num > pid->limit.hard)) { + spin_unlock_bh(&pid->lock); return -ERANGE; - - pktid_notify = seq_num >= PKTID_XMIT_REKEY_NOTIFY; - ret = ovpn_key_usage_xmit(usage, limit, seq_num, aead_blocks, - pktid_notify); - if (unlikely(ret < 0)) - return ret; + } + pid->seq_num++; + spin_unlock_bh(&pid->lock); *pktid = seq_num; + ret = unlikely(seq_num >= pid->limit.soft) ? 1 : 0; return ret; } @@ -128,21 +143,60 @@ ovpn_pktid_recv_update_aead(struct ovpn_pktid_recv *pr, return ret; } -/* write the direct-key AEAD IV to dest */ -static inline void ovpn_pktid_aead_write(const u32 pktid, - const u8 implicit_iv[], - unsigned char *dest) +/* write the AEAD IV to dest */ +static inline u64 ovpn_pktid_aead_write(u16 epoch, u64 seq, + const u8 implicit_iv[], + unsigned char *dest) { - *(__force __be32 *)(dest) = htonl(pktid); - BUILD_BUG_ON(OVPN_NONCE_WIRE_SIZE + OVPN_NONCE_TAIL_SIZE != - OVPN_NONCE_SIZE); + /* epoch packets carry a 16-bit epoch and 48-bit counter */ + u64 pktid = FIELD_PREP(PKTID_EPOCH_MASK, epoch) | + FIELD_PREP(PKTID_EPOCH_SEQ_MASK, seq); + + if (epoch) { + u64 iv_head = get_unaligned_be64(implicit_iv); + + put_unaligned_be64(pktid ^ iv_head, dest); + memcpy(dest + OVPN_EPOCH_NONCE_WIRE_SIZE, + implicit_iv + OVPN_EPOCH_NONCE_WIRE_SIZE, + OVPN_NONCE_SIZE - OVPN_EPOCH_NONCE_WIRE_SIZE); + return pktid; + } + + put_unaligned_be32(seq, dest); memcpy(dest + OVPN_NONCE_WIRE_SIZE, implicit_iv + OVPN_NONCE_WIRE_SIZE, OVPN_NONCE_TAIL_SIZE); + + return seq; +} + +static inline u64 ovpn_pktid_read(const u8 *buf, bool epoch_format, + u16 *epoch) +{ + u64 pktid; + + if (epoch_format) { + pktid = get_unaligned_be64(buf); + *epoch = FIELD_GET(PKTID_EPOCH_MASK, pktid); + return FIELD_GET(PKTID_EPOCH_SEQ_MASK, pktid); + } + + *epoch = 0; + return get_unaligned_be32(buf); +} + +static inline void ovpn_pktid_wire_write(u8 *buf, bool epoch_format, + u64 pktid) +{ + if (epoch_format) + put_unaligned_be64(pktid, buf); + else + put_unaligned_be32(pktid, buf); } -void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid); +void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid, + const struct ovpn_limit *limit); void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr); -int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time); +int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u64 pkt_id, u32 pkt_time); #endif /* _NET_OVPN_OVPNPKTID_H_ */ diff --git a/drivers/net/ovpn/proto.h b/drivers/net/ovpn/proto.h index 5fa053584b15..a4dce8f0a5fa 100644 --- a/drivers/net/ovpn/proto.h +++ b/drivers/net/ovpn/proto.h @@ -37,6 +37,7 @@ * and is normally the head of the IV */ #define OVPN_NONCE_WIRE_SIZE (OVPN_NONCE_SIZE - OVPN_NONCE_TAIL_SIZE) +#define OVPN_EPOCH_NONCE_WIRE_SIZE 8 #define OVPN_OPCODE_SIZE 4 /* DATA_V2 opcode size */ #define OVPN_OPCODE_KEYID_MASK 0x07000000 @@ -56,6 +57,13 @@ OVPN_NONCE_WIRE_SIZE) #define OVPN_AEAD_DIRECT_AAD_SIZE OVPN_AEAD_DIRECT_TAG_OFFSET +/* epoch-key AEAD packet layout */ +#define OVPN_AEAD_EPOCH_OP_OFFSET 0 +#define OVPN_AEAD_EPOCH_PKTID_OFFSET (OVPN_AEAD_EPOCH_OP_OFFSET + \ + OVPN_OPCODE_SIZE) +#define OVPN_AEAD_EPOCH_AAD_SIZE (OVPN_AEAD_EPOCH_PKTID_OFFSET + \ + OVPN_EPOCH_NONCE_WIRE_SIZE) + #define OVPN_PEER_ID_UNDEF 0x00FFFFFF static inline unsigned int -- 2.54.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
