@@ -36,6 +36,7 @@ Contents:
scaling
tls
tls-offload
+ tls-handshake
nfc
6lowpan
6pack
new file mode 100644
@@ -0,0 +1,146 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=======================
+In-Kernel TLS Handshake
+=======================
+
+Overview
+========
+
+Transport Layer Security (TLS) is a Upper Layer Protocol (ULP) that runs
+over TCP. TLS provides end-to-end data integrity and confidentiality,
+in addition to peer authentication.
+
+The kernel's kTLS implementation handles the TLS record subprotocol, but
+does not handle the TLS handshake subprotocol which is used to establish
+a TLS session. Kernel consumers can use the API described here to
+request TLS session establishment.
+
+There are several possible ways to provide a handshake service in the
+kernel. The API described here is designed to hide the details of those
+implementations so that in-kernel TLS consumers do not need to be
+aware of how the handshake gets done.
+
+
+User handshake agent
+====================
+
+As of this writing, there is no TLS handshake implementation in the
+Linux kernel. Thus, with the current implementation, a user agent is
+started in each network namespace where a kernel consumer might require
+a TLS handshake. This agent listens for events sent from the kernel
+that request a handshake on an open and connected TCP socket.
+
+The open socket is passed to user space via a netlink operation, which
+creates a socket descriptor in the agent's file descriptor table. If the
+handshake completes successfully, the user agent promotes the socket to
+use the TLS ULP and sets the session information using the SOL_TLS socket
+options. The user agent returns the socket to the kernel via a second
+netlink operation.
+
+
+Kernel Handshake API
+====================
+
+A kernel TLS consumer initiates a client-side TLS handshake on an open
+socket by invoking one of the tls_client_hello() functions. For example:
+
+.. code-block:: c
+
+ ret = tls_client_hello_x509(sock, done_func, cookie, priorities,
+ cert, privkey);
+
+The function returns zero when the handshake request is under way. A
+zero return guarantees the callback function @done_func will be invoked
+for this socket.
+
+The function returns a negative errno if the handshake could not be
+started. A negative errno guarantees the callback function @done_func
+will not be invoked on this socket.
+
+The @sock argument is an open and connected socket. The caller must hold
+a reference on the socket to prevent it from being destroyed while the
+handshake is in progress.
+
+@done_func and @cookie are a callback function that is invoked when the
+handshake has completed. The success status of the handshake is returned
+via the @status parameter of the callback function. A good practice is
+to close and destroy the socket immediately if the handshake has failed.
+
+@priorities is a GnuTLS priorities string that controls the handshake.
+The special value TLS_DEFAULT_PRIORITIES causes the handshake to
+operate using default TLS priorities. However, the caller can use the
+string to (for example) adjust the handshake to use a restricted set
+of ciphers (say, if the kernel consumer wishes to mandate only a
+limited set of ciphers).
+
+@cert is the serial number of a key that contains a DER format x.509
+certificate that the handshake agent presents to the remote as the local
+peer's identity.
+
+@privkey is the serial number of a key that contains a DER-format
+private key associated with the x.509 certificate.
+
+
+To initiate a client-side TLS handshake with a pre-shared key, use:
+
+.. code-block:: c
+
+ ret = tls_client_hello_psk(sock, done_func, cookie, priorities,
+ peerid);
+
+@peerid is the serial number of a key that contains the pre-shared
+key to be used for the handshake.
+
+The other parameters are as above.
+
+
+To initiate an anonymous client-side TLS handshake use:
+
+.. code-block:: c
+
+ ret = tls_client_hello_anon(sock, done_func, cookie, priorities);
+
+The parameters are as above.
+
+The handshake agent presents no peer identity information to the
+remote during the handshake. Only server authentication is performed
+during the handshake. Thus the established session uses encryption
+only.
+
+
+Consumers that are in-kernel servers use:
+
+.. code-block:: c
+
+ ret = tls_server_hello(sock, done_func, cookie, priorities);
+
+The parameters for this operation are as above.
+
+
+Lastly, if the consumer needs to cancel the handshake request, say,
+due to a ^C or other exigent event, the handshake core provides
+this API:
+
+.. code-block:: c
+
+ handshake_cancel(sock);
+
+
+Other considerations
+--------------------
+
+While a handshake is under way, the kernel consumer must alter the
+socket's sk_data_ready callback function to ignore all incoming data.
+Once the handshake completion callback function has been invoked,
+normal receive operation can be resumed.
+
+Once a TLS session is established, the consumer must provide a buffer
+for and then examine the control message (CMSG) that is part of every
+subsequent sock_recvmsg(). Each control message indicates whether the
+received message data is TLS record data or session metadata.
+
+See tls.rst for details on how a kTLS consumer recognizes incoming
+(decrypted) application data, alerts, and handshake packets once the
+socket has been promoted to use the TLS ULP.
+
@@ -512,4 +512,27 @@ static inline bool tls_is_sk_rx_device_offloaded(struct sock *sk)
return tls_get_ctx(sk)->rx_conf == TLS_HW;
}
#endif
+
+#define TLS_DEFAULT_PRIORITIES (NULL)
+
+enum {
+ TLS_NO_PEERID = 0,
+ TLS_NO_CERT = 0,
+ TLS_NO_PRIVKEY = 0,
+};
+
+typedef void (*tls_done_func_t)(void *data, int status,
+ key_serial_t peerid);
+
+int tls_client_hello_anon(struct socket *sock, tls_done_func_t done,
+ void *data, const char *priorities);
+int tls_client_hello_x509(struct socket *sock, tls_done_func_t done,
+ void *data, const char *priorities,
+ key_serial_t cert, key_serial_t privkey);
+int tls_client_hello_psk(struct socket *sock, tls_done_func_t done,
+ void *data, const char *priorities,
+ key_serial_t peerid);
+int tls_server_hello(struct socket *sock, tls_done_func_t done,
+ void *data, const char *priorities);
+
#endif /* _TLS_OFFLOAD_H */
@@ -21,9 +21,11 @@
enum handshake_genl_mcgrps {
HANDSHAKE_GENL_MCGRP_NONE = 0,
+ HANDSHAKE_GENL_MCGRP_TLS,
};
#define HANDSHAKE_GENL_MCGRP_NONE_NAME "none"
+#define HANDSHAKE_GENL_MCGRP_TLS_NAME "tls"
enum handshake_genl_cmds {
HANDSHAKE_GENL_CMD_UNSPEC = 0,
@@ -49,8 +51,50 @@ enum handshake_genl_attrs {
};
#define HANDSHAKE_GENL_ATTR_MAX (__HANDSHAKE_GENL_ATTR_MAX - 1)
+
+/*
+ * Items specific to TLSv1.3 handshakes
+ */
+
+enum handshake_tls_accept_attrs {
+ HANDSHAKE_GENL_ATTR_TLS_ACCEPT_UNSPEC = 0,
+ HANDSHAKE_GENL_ATTR_TLS_TYPE,
+ HANDSHAKE_GENL_ATTR_TLS_AUTH,
+ HANDSHAKE_GENL_ATTR_TLS_PRIORITIES,
+ HANDSHAKE_GENL_ATTR_TLS_X509_CERT,
+ HANDSHAKE_GENL_ATTR_TLS_X509_PRIVKEY,
+ HANDSHAKE_GENL_ATTR_TLS_PSK,
+
+ __HANDSHAKE_GENL_ATTR_TLS_ACCEPT_MAX
+};
+#define HANDSHAKE_GENL_ATTR_TLS_ACCEPT_MAX \
+ (__HANDSHAKE_GENL_ATTR_TLS_ACCEPT_MAX - 1)
+
+enum handshake_tls_done_attrs {
+ HANDSHAKE_GENL_ATTR_TLS_DONE_UNSPEC = 0,
+ HANDSHAKE_GENL_ATTR_TLS_REMOTE_PEERID,
+
+ __HANDSHAKE_GENL_ATTR_TLS_DONE_MAX
+};
+#define HANDSHAKE_GENL_ATTR_TLS_DONE_MAX \
+ (__HANDSHAKE_GENL_ATTR_TLS_DONE_MAX - 1)
+
enum handshake_genl_protocol {
HANDSHAKE_GENL_PROTO_UNSPEC = 0,
+ HANDSHAKE_GENL_PROTO_TLS,
+};
+
+enum handshake_genl_tls_type {
+ HANDSHAKE_GENL_TLS_TYPE_UNSPEC = 0,
+ HANDSHAKE_GENL_TLS_TYPE_CLIENTHELLO,
+ HANDSHAKE_GENL_TLS_TYPE_SERVERHELLO,
+};
+
+enum handshake_genl_tls_auth {
+ HANDSHAKE_GENL_TLS_AUTH_UNSPEC = 0,
+ HANDSHAKE_GENL_TLS_AUTH_UNAUTH,
+ HANDSHAKE_GENL_TLS_AUTH_X509,
+ HANDSHAKE_GENL_TLS_AUTH_PSK,
};
#endif /* _UAPI_LINUX_HANDSHAKE_H */
@@ -288,6 +288,9 @@ static const struct genl_multicast_group handshake_genl_mcgrps[] = {
[HANDSHAKE_GENL_MCGRP_NONE] = {
.name = HANDSHAKE_GENL_MCGRP_NONE_NAME,
},
+ [HANDSHAKE_GENL_MCGRP_TLS] = {
+ .name = HANDSHAKE_GENL_MCGRP_TLS_NAME,
+ },
};
static struct genl_family __ro_after_init handshake_genl_family = {
@@ -7,7 +7,7 @@ CFLAGS_trace.o := -I$(src)
obj-$(CONFIG_TLS) += tls.o
-tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o
+tls-y := tls_handshake.o tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o
tls-$(CONFIG_TLS_TOE) += tls_toe.o
tls-$(CONFIG_TLS_DEVICE) += tls_device.o tls_device_fallback.o
new file mode 100644
@@ -0,0 +1,388 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Establish a TLS session for a kernel socket consumer
+ *
+ * Author: Chuck Lever <chuck.lever@oracle.com>
+ *
+ * Copyright (c) 2021-2023, Oracle and/or its affiliates.
+ */
+
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <net/sock.h>
+#include <net/tls.h>
+#include <net/genetlink.h>
+#include <net/handshake.h>
+
+#include <uapi/linux/handshake.h>
+
+/*
+ * TLS priorities string passed to the GnuTLS library.
+ *
+ * Specifically for kernel TLS consumers: enable only TLS v1.3 and the
+ * ciphers that are supported by kTLS.
+ *
+ * Currently this list is generated by hand from the supported ciphers
+ * found in include/uapi/linux/tls.h.
+ */
+#define KTLS_DEFAULT_PRIORITIES \
+ "SECURE256:+SECURE128:-COMP-ALL" \
+ ":-VERS-ALL:+VERS-TLS1.3:%NO_TICKETS" \
+ ":-CIPHER-ALL:+CHACHA20-POLY1305:+AES-256-GCM:+AES-128-GCM:+AES-128-CCM"
+
+struct tls_handshake_req {
+ void (*th_consumer_done)(void *data, int status,
+ key_serial_t peerid);
+ void *th_consumer_data;
+
+ const char *th_priorities;
+ int th_type;
+ int th_auth_type;
+ key_serial_t th_peerid;
+ key_serial_t th_certificate;
+ key_serial_t th_privkey;
+
+};
+
+static const char *tls_handshake_dup_priorities(const char *priorities,
+ gfp_t flags)
+{
+ const char *tp;
+
+ if (priorities != TLS_DEFAULT_PRIORITIES && strlen(priorities))
+ tp = priorities;
+ else
+ tp = KTLS_DEFAULT_PRIORITIES;
+ return kstrdup(tp, flags);
+}
+
+static struct tls_handshake_req *
+tls_handshake_req_init(struct handshake_req *req, tls_done_func_t done,
+ void *data, const char *priorities)
+{
+ struct tls_handshake_req *treq = handshake_req_private(req);
+
+ treq->th_consumer_done = done;
+ treq->th_consumer_data = data;
+ treq->th_priorities = priorities;
+ treq->th_peerid = TLS_NO_PEERID;
+ treq->th_certificate = TLS_NO_CERT;
+ treq->th_privkey = TLS_NO_PRIVKEY;
+ return treq;
+}
+
+/**
+ * tls_handshake_destroy - callback to release a handshake request
+ * @req: handshake parameters to release
+ *
+ */
+static void tls_handshake_destroy(struct handshake_req *req)
+{
+ struct tls_handshake_req *treq = handshake_req_private(req);
+
+ kfree(treq->th_priorities);
+}
+
+static const struct nla_policy
+handshake_tls_done_policy[HANDSHAKE_GENL_ATTR_TLS_DONE_MAX + 1] = {
+ [HANDSHAKE_GENL_ATTR_TLS_REMOTE_PEERID] = {
+ .type = NLA_U32
+ },
+};
+
+/**
+ * tls_handshake_done - callback to handle a CMD_DONE request
+ * @req: socket on which the handshake was performed
+ * @status: session status code
+ * @args: nested attribute for CMD_DONE
+ *
+ * Eventually this will return information about the established
+ * session: whether it is authenticated, and if so, who the remote
+ * is.
+ */
+static void tls_handshake_done(struct handshake_req *req, int status,
+ struct nlattr *args)
+{
+ struct tls_handshake_req *treq = handshake_req_private(req);
+ struct nlattr *tb[HANDSHAKE_GENL_ATTR_TLS_DONE_MAX + 1];
+ key_serial_t peerid;
+ int err;
+
+ peerid = TLS_NO_PEERID;
+ if (args) {
+ err = nla_parse_nested(tb, HANDSHAKE_GENL_ATTR_TLS_DONE_MAX,
+ args, handshake_tls_done_policy, NULL);
+ if (err || !tb[HANDSHAKE_GENL_ATTR_TLS_REMOTE_PEERID])
+ goto out;
+ peerid = nla_get_u32(tb[HANDSHAKE_GENL_ATTR_TLS_REMOTE_PEERID]);
+ }
+
+out:
+ treq->th_consumer_done(treq->th_consumer_data, status, peerid);
+}
+
+static int tls_handshake_put_accept_resp(struct sk_buff *msg,
+ struct tls_handshake_req *treq)
+{
+ struct nlattr *entry_attr;
+ int ret;
+
+ ret = -EMSGSIZE;
+ entry_attr = nla_nest_start(msg, HANDSHAKE_GENL_ATTR_ACCEPT);
+ if (!entry_attr)
+ goto out;
+
+ ret = nla_put_u32(msg, HANDSHAKE_GENL_ATTR_TLS_TYPE,
+ HANDSHAKE_GENL_TLS_TYPE_CLIENTHELLO);
+ if (ret < 0)
+ goto out;
+ ret = nla_put_u32(msg, HANDSHAKE_GENL_ATTR_TLS_AUTH,
+ treq->th_auth_type);
+ if (ret < 0)
+ goto out;
+ if (treq->th_certificate != TLS_NO_CERT) {
+ ret = nla_put_u32(msg, HANDSHAKE_GENL_ATTR_TLS_X509_CERT,
+ treq->th_certificate);
+ if (ret < 0)
+ goto out;
+ }
+ if (treq->th_privkey != TLS_NO_PRIVKEY) {
+ ret = nla_put_u32(msg, HANDSHAKE_GENL_ATTR_TLS_X509_PRIVKEY,
+ treq->th_privkey);
+ if (ret < 0)
+ goto out;
+ }
+ if (treq->th_peerid != TLS_NO_PEERID) {
+ ret = nla_put_u32(msg, HANDSHAKE_GENL_ATTR_TLS_PSK,
+ treq->th_peerid);
+ if (ret < 0)
+ goto out;
+ }
+
+ ret = nla_put_string(msg, HANDSHAKE_GENL_ATTR_TLS_PRIORITIES,
+ treq->th_priorities);
+ if (ret < 0)
+ goto out;
+
+ nla_nest_end(msg, entry_attr);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+/**
+ * tls_handshake_accept - callback to construct a CMD_ACCEPT response
+ * @req: handshake parameters to return
+ * @gi: generic netlink message context
+ * @fd: file descriptor to be returned
+ *
+ * Returns zero on success, or a negative errno on failure.
+ */
+static int tls_handshake_accept(struct handshake_req *req,
+ struct genl_info *gi, int fd)
+{
+ struct tls_handshake_req *treq = handshake_req_private(req);
+ struct nlmsghdr *hdr;
+ struct sk_buff *msg;
+ int ret;
+
+ ret = -ENOMEM;
+ msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ goto out;
+ hdr = handshake_genl_put(msg, gi);
+ if (!hdr)
+ goto out_cancel;
+
+ ret = -EMSGSIZE;
+ ret = nla_put_u32(msg, HANDSHAKE_GENL_ATTR_SOCKFD, fd);
+ if (ret < 0)
+ goto out_cancel;
+
+ ret = tls_handshake_put_accept_resp(msg, treq);
+ if (ret < 0)
+ goto out_cancel;
+
+ genlmsg_end(msg, hdr);
+ return genlmsg_reply(msg, gi);
+
+out_cancel:
+ genlmsg_cancel(msg, hdr);
+out:
+ return ret;
+}
+
+static const struct handshake_proto tls_handshake_proto = {
+ .hp_protocol = HANDSHAKE_GENL_PROTO_TLS,
+ .hp_mcgrp = HANDSHAKE_GENL_MCGRP_TLS,
+ .hp_privsize = sizeof(struct tls_handshake_req),
+
+ .hp_accept = tls_handshake_accept,
+ .hp_done = tls_handshake_done,
+ .hp_destroy = tls_handshake_destroy,
+};
+
+/**
+ * tls_client_hello_anon - request an anonymous TLS handshake on a socket
+ * @sock: connected socket on which to perform the handshake
+ * @done: function to call when the handshake has completed
+ * @data: token to pass back to @done
+ * @priorities: GnuTLS TLS priorities string, or NULL
+ *
+ * Return values:
+ * %0: Handshake request enqueue; ->done will be called when complete
+ * %-ENOENT: No user agent is available
+ * %-ENOMEM: Memory allocation failed
+ */
+int tls_client_hello_anon(struct socket *sock, tls_done_func_t done,
+ void *data, const char *priorities)
+{
+ struct tls_handshake_req *treq;
+ struct handshake_req *req;
+ gfp_t flags = GFP_NOWAIT;
+ const char *tp;
+
+ tp = tls_handshake_dup_priorities(priorities, flags);
+ if (!tp)
+ return -ENOMEM;
+
+ req = handshake_req_alloc(sock, &tls_handshake_proto, flags);
+ if (!req) {
+ kfree(tp);
+ return -ENOMEM;
+ }
+
+ treq = tls_handshake_req_init(req, done, data, tp);
+ treq->th_type = HANDSHAKE_GENL_TLS_TYPE_CLIENTHELLO;
+ treq->th_auth_type = HANDSHAKE_GENL_TLS_AUTH_UNAUTH;
+
+ return handshake_req_submit(req, flags);
+}
+EXPORT_SYMBOL(tls_client_hello_anon);
+
+/**
+ * tls_client_hello_x509 - request an x.509-based TLS handshake on a socket
+ * @sock: connected socket on which to perform the handshake
+ * @done: function to call when the handshake has completed
+ * @data: token to pass back to @done
+ * @priorities: GnuTLS TLS priorities string
+ * @cert: serial number of key containing client's x.509 certificate
+ * @privkey: serial number of key containing client's private key
+ *
+ * Return values:
+ * %0: Handshake request enqueue; ->done will be called when complete
+ * %-ENOENT: No user agent is available
+ * %-ENOMEM: Memory allocation failed
+ */
+int tls_client_hello_x509(struct socket *sock, tls_done_func_t done,
+ void *data, const char *priorities,
+ key_serial_t cert, key_serial_t privkey)
+{
+ struct tls_handshake_req *treq;
+ struct handshake_req *req;
+ gfp_t flags = GFP_NOWAIT;
+ const char *tp;
+
+ tp = tls_handshake_dup_priorities(priorities, flags);
+ if (!tp)
+ return -ENOMEM;
+
+ req = handshake_req_alloc(sock, &tls_handshake_proto, flags);
+ if (!req) {
+ kfree(tp);
+ return -ENOMEM;
+ }
+
+ treq = tls_handshake_req_init(req, done, data, tp);
+ treq->th_type = HANDSHAKE_GENL_TLS_TYPE_CLIENTHELLO;
+ treq->th_auth_type = HANDSHAKE_GENL_TLS_AUTH_X509;
+ treq->th_certificate = cert;
+ treq->th_privkey = privkey;
+
+ return handshake_req_submit(req, flags);
+}
+EXPORT_SYMBOL(tls_client_hello_x509);
+
+/**
+ * tls_client_hello_psk - request a PSK-based TLS handshake on a socket
+ * @sock: connected socket on which to perform the handshake
+ * @done: function to call when the handshake has completed
+ * @data: token to pass back to @done
+ * @priorities: GnuTLS TLS priorities string
+ * @peerid: serial number of key containing TLS identity
+ *
+ * Return values:
+ * %0: Handshake request enqueue; ->done will be called when complete
+ * %-ENOENT: No user agent is available
+ * %-ENOMEM: Memory allocation failed
+ */
+int tls_client_hello_psk(struct socket *sock, tls_done_func_t done,
+ void *data, const char *priorities,
+ key_serial_t peerid)
+{
+ struct tls_handshake_req *treq;
+ struct handshake_req *req;
+ gfp_t flags = GFP_NOWAIT;
+ const char *tp;
+
+ tp = tls_handshake_dup_priorities(priorities, flags);
+ if (!tp)
+ return -ENOMEM;
+
+ req = handshake_req_alloc(sock, &tls_handshake_proto, flags);
+ if (!req) {
+ kfree(tp);
+ return -ENOMEM;
+ }
+
+ treq = tls_handshake_req_init(req, done, data, tp);
+ treq->th_type = HANDSHAKE_GENL_TLS_TYPE_CLIENTHELLO;
+ treq->th_auth_type = HANDSHAKE_GENL_TLS_AUTH_PSK;
+ treq->th_peerid = peerid;
+
+ return handshake_req_submit(req, flags);
+}
+EXPORT_SYMBOL(tls_client_hello_psk);
+
+/**
+ * tls_server_hello - request a server TLS handshake on a socket
+ * @sock: connected socket on which to perform the handshake
+ * @done: function to call when the handshake has completed
+ * @data: token to pass back to @done
+ * @priorities: GnuTLS TLS priorities string
+ *
+ * Return values:
+ * %0: Handshake request enqueue; ->done will be called when complete
+ * %-ENOENT: No user agent is available
+ * %-ENOMEM: Memory allocation failed
+ */
+int tls_server_hello(struct socket *sock, tls_done_func_t done,
+ void *data, const char *priorities)
+{
+ struct tls_handshake_req *treq;
+ struct handshake_req *req;
+ gfp_t flags = GFP_KERNEL;
+ const char *tp;
+
+ tp = tls_handshake_dup_priorities(priorities, flags);
+ if (!tp)
+ return -ENOMEM;
+
+ req = handshake_req_alloc(sock, &tls_handshake_proto, flags);
+ if (!req) {
+ kfree(tp);
+ return -ENOMEM;
+ }
+
+ treq = tls_handshake_req_init(req, done, data, tp);
+ treq->th_type = HANDSHAKE_GENL_TLS_TYPE_SERVERHELLO;
+ treq->th_auth_type = HANDSHAKE_GENL_TLS_AUTH_UNSPEC;
+
+ return handshake_req_submit(req, flags);
+}
+EXPORT_SYMBOL(tls_server_hello);
To enable kernel consumers of TLS to request a TLS handshake, add support to net/tls/ to send a handshake upcall. This patch also acts as a template for adding handshake upcall support to other transport layer security mechanisms. Signed-off-by: Chuck Lever <chuck.lever@oracle.com> --- Documentation/networking/index.rst | 1 Documentation/networking/tls-handshake.rst | 146 +++++++++++ include/net/tls.h | 23 ++ include/uapi/linux/handshake.h | 44 +++ net/handshake/netlink.c | 3 net/tls/Makefile | 2 net/tls/tls_handshake.c | 388 ++++++++++++++++++++++++++++ 7 files changed, 606 insertions(+), 1 deletion(-) create mode 100644 Documentation/networking/tls-handshake.rst create mode 100644 net/tls/tls_handshake.c