/* SPDX-License-Identifier: GPL-2.0-only */ /* OpenVPN data channel offload * * Copyright (C) 2020-2025 OpenVPN, Inc. * * Author: James Yonan * Antonio Quartulli */ #ifndef _NET_OVPN_OVPNCRYPTO_H_ #define _NET_OVPN_OVPNCRYPTO_H_ #include "pktid.h" #include "proto.h" /* info needed for both encrypt and decrypt directions */ struct ovpn_key_direction { const u8 *cipher_key; size_t cipher_key_size; const u8 *nonce_tail; /* only needed for GCM modes */ size_t nonce_tail_size; /* only needed for GCM modes */ }; /* all info for a particular symmetric key (primary or secondary) */ struct ovpn_key_config { enum ovpn_cipher_alg cipher_alg; u8 key_id; struct ovpn_key_direction encrypt; struct ovpn_key_direction decrypt; }; /* used to pass settings from netlink to the crypto engine */ struct ovpn_peer_key_reset { enum ovpn_key_slot slot; struct ovpn_key_config key; }; struct ovpn_crypto_key_slot { u8 key_id; struct crypto_aead *encrypt; struct crypto_aead *decrypt; u8 nonce_tail_xmit[OVPN_NONCE_TAIL_SIZE]; u8 nonce_tail_recv[OVPN_NONCE_TAIL_SIZE]; struct ovpn_pktid_recv pid_recv ____cacheline_aligned_in_smp; struct ovpn_pktid_xmit pid_xmit ____cacheline_aligned_in_smp; struct kref refcount; struct rcu_head rcu; }; struct ovpn_crypto_state { struct ovpn_crypto_key_slot __rcu *slots[2]; u8 primary_idx; /* protects primary and secondary slots */ spinlock_t lock; }; static inline bool ovpn_crypto_key_slot_hold(struct ovpn_crypto_key_slot *ks) { return kref_get_unless_zero(&ks->refcount); } static inline void ovpn_crypto_state_init(struct ovpn_crypto_state *cs) { RCU_INIT_POINTER(cs->slots[0], NULL); RCU_INIT_POINTER(cs->slots[1], NULL); cs->primary_idx = 0; spin_lock_init(&cs->lock); } static inline struct ovpn_crypto_key_slot * ovpn_crypto_key_id_to_slot(const struct ovpn_crypto_state *cs, u8 key_id) { struct ovpn_crypto_key_slot *ks; u8 idx; if (unlikely(!cs)) return NULL; rcu_read_lock(); idx = READ_ONCE(cs->primary_idx); ks = rcu_dereference(cs->slots[idx]); if (ks && ks->key_id == key_id) { if (unlikely(!ovpn_crypto_key_slot_hold(ks))) ks = NULL; goto out; } ks = rcu_dereference(cs->slots[!idx]); if (ks && ks->key_id == key_id) { if (unlikely(!ovpn_crypto_key_slot_hold(ks))) ks = NULL; goto out; } /* when both key slots are occupied but no matching key ID is found, ks * has to be reset to NULL to avoid carrying a stale pointer */ ks = NULL; out: rcu_read_unlock(); return ks; } static inline struct ovpn_crypto_key_slot * ovpn_crypto_key_slot_primary(const struct ovpn_crypto_state *cs) { struct ovpn_crypto_key_slot *ks; rcu_read_lock(); ks = rcu_dereference(cs->slots[cs->primary_idx]); if (unlikely(ks && !ovpn_crypto_key_slot_hold(ks))) ks = NULL; rcu_read_unlock(); return ks; } void ovpn_crypto_key_slot_release(struct kref *kref); static inline void ovpn_crypto_key_slot_put(struct ovpn_crypto_key_slot *ks) { kref_put(&ks->refcount, ovpn_crypto_key_slot_release); } int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs, const struct ovpn_peer_key_reset *pkr); void ovpn_crypto_key_slot_delete(struct ovpn_crypto_state *cs, enum ovpn_key_slot slot); 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); bool ovpn_crypto_kill_key(struct ovpn_crypto_state *cs, u8 key_id); #endif /* _NET_OVPN_OVPNCRYPTO_H_ */