mbox series

[net-next,v3,0/6] tls: implement key updates for TLS1.3

Message ID cover.1691584074.git.sd@queasysnail.net
Headers show
Series tls: implement key updates for TLS1.3 | expand

Message

Sabrina Dubroca Aug. 9, 2023, 12:58 p.m. UTC
This adds support for receiving KeyUpdate messages (RFC 8446, 4.6.3
[1]). A sender transmits a KeyUpdate message and then changes its TX
key. The receiver should react by updating its RX key before
processing the next message.

This patchset implements key updates by:
 1. pausing decryption when a KeyUpdate message is received, to avoid
    attempting to use the old key to decrypt a record encrypted with
    the new key
 2. returning -EKEYEXPIRED to syscalls that cannot receive the
    KeyUpdate message, until the rekey has been performed by userspace
 3. passing the KeyUpdate message to userspace as a control message
 4. allowing updates of the crypto_info via the TLS_TX/TLS_RX
    setsockopts

This API has been tested with gnutls to make sure that it allows
userspace libraries to implement key updates [2]. Thanks to Frantisek
Krenzelok <fkrenzel@redhat.com> for providing the implementation in
gnutls and testing the kernel patches.


=======================================================================
Discussions around v2 of this patchset focused on how HW offload would
interact with rekey.

RX
 - The existing SW path will handle all records between the KeyUpdate
   message signaling the change of key and the new key becoming known
   to the kernel -- those will be queued encrypted, and decrypted in
   SW as they are read by userspace (once the key is provided, ie same
   as this patchset)
 - Call ->tls_dev_del + ->tls_dev_add immediately during
   setsockopt(TLS_RX)

TX
 - After setsockopt(TLS_TX), switch to the existing SW path (not the
   current device_fallback) until we're able to re-enable HW offload
   - tls_device_sendmsg will call into tls_sw_sendmsg under lock_sock
     to avoid changing socket ops during the rekey while another
     thread might be waiting on the lock
 - We only re-enable HW offload (call ->tls_dev_add to install the new
   key in HW) once all records sent with the old key have been
   ACKed. At this point, all unacked records are SW-encrypted with the
   new key, and the old key is unused by both HW and retransmissions.
   - If there are no unacked records when userspace does
     setsockopt(TLS_TX), we can (try to) install the new key in HW
     immediately.
   - If yet another key has been provided via setsockopt(TLS_TX), we
     don't install intermediate keys, only the latest.
   - TCP notifies ktls of ACKs via the icsk_clean_acked callback. In
     case of a rekey, tls_icsk_clean_acked will record when all data
     sent with the most recent past key has been sent. The next call
     to sendmsg will install the new key in HW.
   - We close and push the current SW record before reenabling
     offload.

If ->tls_dev_add fails to install the new key in HW, we stay in SW
mode. We can add a counter to keep track of this.


In addition:

Because we can't change socket ops during a rekey, we'll also have to
modify do_tls_setsockopt_conf to check ctx->tx_conf and only call
either tls_set_device_offload or tls_set_sw_offload. RX already uses
the same ops for both TLS_HW and TLS_SW, so we could switch between HW
and SW mode on rekey.

An alternative would be to have a common sendmsg which locks
the socket and then calls the correct implementation. We'll need that
anyway for the offload under rekey case, so that would only add a test
to the SW path's ops (compared to the current code). That should allow
us to simplify build_protos a bit, but might have a performance
impact - we'll need to check it if we want to go that route.
=======================================================================

Note: in a future series, I'll clean up tls_set_sw_offload and
eliminate the per-cipher copy-paste using tls_cipher_size_desc.

[1] https://www.rfc-editor.org/rfc/rfc8446#section-4.6.3
[2] https://gitlab.com/gnutls/gnutls/-/merge_requests/1625

Sabrina Dubroca (6):
  tls: remove tls_context argument from tls_set_sw_offload
  tls: block decryption when a rekey is pending
  tls: implement rekey for TLS1.3
  docs: tls: document TLS1.3 key updates
  selftests: tls: add key_generation argument to tls_crypto_info_init
  selftests: tls: add rekey tests

 Documentation/networking/tls.rst  |  21 ++
 include/net/tls.h                 |   3 +
 net/tls/tls.h                     |   3 +-
 net/tls/tls_device.c              |   2 +-
 net/tls/tls_main.c                |  47 ++-
 net/tls/tls_sw.c                  | 184 +++++++++---
 tools/testing/selftests/net/tls.c | 466 +++++++++++++++++++++++++++++-
 7 files changed, 661 insertions(+), 65 deletions(-)

Comments

Jakub Kicinski Aug. 12, 2023, 1:37 a.m. UTC | #1
On Wed,  9 Aug 2023 14:58:51 +0200 Sabrina Dubroca wrote:
> +static int tls_check_pending_rekey(struct sock *sk, struct sk_buff *skb)
> +{
> +	const struct tls_msg *tlm = tls_msg(skb);
> +	const struct strp_msg *rxm = strp_msg(skb);
> +
> +	if (tlm->control == TLS_RECORD_TYPE_HANDSHAKE) {

unlikely()

does the nachine code look worse if we flip the condition and return
early instead of indenting the entire function?

> +		char hs_type;
> +		int err;

I'd probably err on the side of declaring those on the outside, but if
we don't we should move rxm in here, it's not needed outside. Either,
or.

> +		if (rxm->full_len < 1)
> +			return -EINVAL;
> +
> +		err = skb_copy_bits(skb, rxm->offset, &hs_type, 1);
> +		if (err < 0)
> +			return err;
> +
> +		if (hs_type == TLS_HANDSHAKE_KEYUPDATE) {
> +			struct tls_context *ctx = tls_get_ctx(sk);

feels a bit like we should just pass ctx rather than sk?

> +			struct tls_sw_context_rx *rx_ctx = ctx->priv_ctx_rx;
> +
> +			rx_ctx->key_update_pending = true;
> +		}
> +	}
> +
> +	return 0;
> +}