OSDN Git Service

net/handshake: Add a kernel API for requesting a TLSv1.3 handshake
authorChuck Lever <chuck.lever@oracle.com>
Mon, 17 Apr 2023 14:32:33 +0000 (10:32 -0400)
committerJakub Kicinski <kuba@kernel.org>
Thu, 20 Apr 2023 01:48:48 +0000 (18:48 -0700)
To enable kernel consumers of TLS to request a TLS handshake, add
support to net/handshake/ to request a handshake upcall.

This patch also acts as a template for adding handshake upcall
support for other kernel transport layer security providers.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Documentation/netlink/specs/handshake.yaml
Documentation/networking/index.rst
Documentation/networking/tls-handshake.rst [new file with mode: 0644]
MAINTAINERS
include/net/handshake.h [new file with mode: 0644]
include/uapi/linux/handshake.h
net/handshake/Makefile
net/handshake/genl.c
net/handshake/genl.h
net/handshake/tlshd.c [new file with mode: 0644]

index 0333d92..614f1a5 100644 (file)
@@ -16,7 +16,7 @@ definitions:
     type: enum
     name: handler-class
     value-start: 0
-    entries: [ none, max ]
+    entries: [ none, tlshd, max ]
   -
     type: enum
     name: msg-type
@@ -120,3 +120,5 @@ mcast-groups:
   list:
     -
       name: none
+    -
+      name: tlshd
index 24bb256..a164ff0 100644 (file)
@@ -36,6 +36,7 @@ Contents:
    scaling
    tls
    tls-offload
+   tls-handshake
    nfc
    6lowpan
    6pack
diff --git a/Documentation/networking/tls-handshake.rst b/Documentation/networking/tls-handshake.rst
new file mode 100644 (file)
index 0000000..a2817a8
--- /dev/null
@@ -0,0 +1,217 @@
+.. 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. To provide a handshake service, a handshake agent
+(typically in user space) is started in each network namespace where a
+kernel consumer might require a TLS handshake. Handshake agents listen
+for events sent from the kernel that indicate a handshake request is
+waiting.
+
+An open socket is passed to a handshake agent via a netlink operation,
+which creates a socket descriptor in the agent's file descriptor table.
+If the handshake completes successfully, the handshake agent promotes
+the socket to use the TLS ULP and sets the session information using the
+SOL_TLS socket options. The handshake 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. First, it
+fills in a structure that contains the parameters of the request:
+
+.. code-block:: c
+
+  struct tls_handshake_args {
+        struct socket   *ta_sock;
+        tls_done_func_t ta_done;
+        void            *ta_data;
+        unsigned int    ta_timeout_ms;
+        key_serial_t    ta_keyring;
+        key_serial_t    ta_my_cert;
+        key_serial_t    ta_my_privkey;
+        unsigned int    ta_num_peerids;
+        key_serial_t    ta_my_peerids[5];
+  };
+
+The @ta_sock field references an open and connected socket. The consumer
+must hold a reference on the socket to prevent it from being destroyed
+while the handshake is in progress. The consumer must also have
+instantiated a struct file in sock->file.
+
+
+@ta_done contains a callback function that is invoked when the handshake
+has completed. Further explanation of this function is in the "Handshake
+Completion" sesction below.
+
+The consumer can fill in the @ta_timeout_ms field to force the servicing
+handshake agent to exit after a number of milliseconds. This enables the
+socket to be fully closed once both the kernel and the handshake agent
+have closed their endpoints.
+
+Authentication material such as x.509 certificates, private certificate
+keys, and pre-shared keys are provided to the handshake agent in keys
+that are instantiated by the consumer before making the handshake
+request. The consumer can provide a private keyring that is linked into
+the handshake agent's process keyring in the @ta_keyring field to prevent
+access of those keys by other subsystems.
+
+To request an x.509-authenticated TLS session, the consumer fills in
+the @ta_my_cert and @ta_my_privkey fields with the serial numbers of
+keys containing an x.509 certificate and the private key for that
+certificate. Then, it invokes this function:
+
+.. code-block:: c
+
+  ret = tls_client_hello_x509(args, gfp_flags);
+
+The function returns zero when the handshake request is under way. A
+zero return guarantees the callback function @ta_done 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
+@ta_done will not be invoked on this socket.
+
+
+To initiate a client-side TLS handshake with a pre-shared key, use:
+
+.. code-block:: c
+
+  ret = tls_client_hello_psk(args, gfp_flags);
+
+However, in this case, the consumer fills in the @ta_my_peerids array
+with serial numbers of keys containing the peer identities it wishes
+to offer, and the @ta_num_peerids field with the number of array
+entries it has filled in. The other fields are filled in as above.
+
+
+To initiate an anonymous client-side TLS handshake use:
+
+.. code-block:: c
+
+  ret = tls_client_hello_anon(args, gfp_flags);
+
+The handshake agent presents no peer identity information to the remote
+during this type of handshake. Only server authentication (ie the client
+verifies the server's identity) 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_x509(args, gfp_flags);
+
+or
+
+.. code-block:: c
+
+  ret = tls_server_hello_psk(args, gfp_flags);
+
+The argument structure is filled in as above.
+
+
+If the consumer needs to cancel the handshake request, say, due to a ^C
+or other exigent event, the consumer can invoke:
+
+.. code-block:: c
+
+  bool tls_handshake_cancel(sock);
+
+This function returns true if the handshake request associated with
+@sock has been canceled. The consumer's handshake completion callback
+will not be invoked. If this function returns false, then the consumer's
+completion callback has already been invoked.
+
+
+Handshake Completion
+====================
+
+When the handshake agent has completed processing, it notifies the
+kernel that the socket may be used by the consumer again. At this point,
+the consumer's handshake completion callback, provided in the @ta_done
+field in the tls_handshake_args structure, is invoked.
+
+The synopsis of this function is:
+
+.. code-block:: c
+
+  typedef void (*tls_done_func_t)(void *data, int status,
+                                   key_serial_t peerid);
+
+The consumer provides a cookie in the @ta_data field of the
+tls_handshake_args structure that is returned in the @data parameter of
+this callback. The consumer uses the cookie to match the callback to the
+thread waiting for the handshake to complete.
+
+The success status of the handshake is returned via the @status
+parameter:
+
++------------+----------------------------------------------+
+|  status    |  meaning                                     |
++============+==============================================+
+|  0         |  TLS session established successfully        |
++------------+----------------------------------------------+
+|  -EACCESS  |  Remote peer rejected the handshake or       |
+|            |  authentication failed                       |
++------------+----------------------------------------------+
+|  -ENOMEM   |  Temporary resource allocation failure       |
++------------+----------------------------------------------+
+|  -EINVAL   |  Consumer provided an invalid argument       |
++------------+----------------------------------------------+
+|  -ENOKEY   |  Missing authentication material             |
++------------+----------------------------------------------+
+|  -EIO      |  An unexpected fault occurred                |
++------------+----------------------------------------------+
+
+The @peerid parameter contains the serial number of a key containing the
+remote peer's identity or the value TLS_NO_PEERID if the session is not
+authenticated.
+
+A best practice is to close and destroy the socket immediately if the
+handshake failed.
+
+
+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.
index cdc7748..04ebde8 100644 (file)
@@ -8953,6 +8953,8 @@ L:        kernel-tls-handshake@lists.linux.dev
 L:     netdev@vger.kernel.org
 S:     Maintained
 F:     Documentation/netlink/specs/handshake.yaml
+F:     Documentation/networking/tls-handshake.rst
+F:     include/net/handshake.h
 F:     include/trace/events/handshake.h
 F:     net/handshake/
 
diff --git a/include/net/handshake.h b/include/net/handshake.h
new file mode 100644 (file)
index 0000000..3352b1a
--- /dev/null
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Generic netlink HANDSHAKE service.
+ *
+ * Author: Chuck Lever <chuck.lever@oracle.com>
+ *
+ * Copyright (c) 2023, Oracle and/or its affiliates.
+ */
+
+#ifndef _NET_HANDSHAKE_H
+#define _NET_HANDSHAKE_H
+
+enum {
+       TLS_NO_KEYRING = 0,
+       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);
+
+struct tls_handshake_args {
+       struct socket           *ta_sock;
+       tls_done_func_t         ta_done;
+       void                    *ta_data;
+       unsigned int            ta_timeout_ms;
+       key_serial_t            ta_keyring;
+       key_serial_t            ta_my_cert;
+       key_serial_t            ta_my_privkey;
+       unsigned int            ta_num_peerids;
+       key_serial_t            ta_my_peerids[5];
+};
+
+int tls_client_hello_anon(const struct tls_handshake_args *args, gfp_t flags);
+int tls_client_hello_x509(const struct tls_handshake_args *args, gfp_t flags);
+int tls_client_hello_psk(const struct tls_handshake_args *args, gfp_t flags);
+int tls_server_hello_x509(const struct tls_handshake_args *args, gfp_t flags);
+int tls_server_hello_psk(const struct tls_handshake_args *args, gfp_t flags);
+
+bool tls_handshake_cancel(struct sock *sk);
+
+#endif /* _NET_HANDSHAKE_H */
index 7f66ff4..1de4d0b 100644 (file)
@@ -11,6 +11,7 @@
 
 enum handshake_handler_class {
        HANDSHAKE_HANDLER_CLASS_NONE,
+       HANDSHAKE_HANDLER_CLASS_TLSHD,
        HANDSHAKE_HANDLER_CLASS_MAX,
 };
 
@@ -67,5 +68,6 @@ enum {
 };
 
 #define HANDSHAKE_MCGRP_NONE   "none"
+#define HANDSHAKE_MCGRP_TLSHD  "tlshd"
 
 #endif /* _UAPI_LINUX_HANDSHAKE_H */
index d38736d..a089f7e 100644 (file)
@@ -8,4 +8,4 @@
 #
 
 obj-y += handshake.o
-handshake-y := genl.o netlink.o request.o trace.o
+handshake-y := genl.o netlink.o request.o tlshd.o trace.o
index 652f37d..9f29efb 100644 (file)
@@ -12,7 +12,7 @@
 
 /* HANDSHAKE_CMD_ACCEPT - do */
 static const struct nla_policy handshake_accept_nl_policy[HANDSHAKE_A_ACCEPT_HANDLER_CLASS + 1] = {
-       [HANDSHAKE_A_ACCEPT_HANDLER_CLASS] = NLA_POLICY_MAX(NLA_U32, 1),
+       [HANDSHAKE_A_ACCEPT_HANDLER_CLASS] = NLA_POLICY_MAX(NLA_U32, 2),
 };
 
 /* HANDSHAKE_CMD_DONE - do */
@@ -42,6 +42,7 @@ static const struct genl_split_ops handshake_nl_ops[] = {
 
 static const struct genl_multicast_group handshake_nl_mcgrps[] = {
        [HANDSHAKE_NLGRP_NONE] = { "none", },
+       [HANDSHAKE_NLGRP_TLSHD] = { "tlshd", },
 };
 
 struct genl_family handshake_nl_family __ro_after_init = {
index a1eb7cc..2c1f1aa 100644 (file)
@@ -16,6 +16,7 @@ int handshake_nl_done_doit(struct sk_buff *skb, struct genl_info *info);
 
 enum {
        HANDSHAKE_NLGRP_NONE,
+       HANDSHAKE_NLGRP_TLSHD,
 };
 
 extern struct genl_family handshake_nl_family;
diff --git a/net/handshake/tlshd.c b/net/handshake/tlshd.c
new file mode 100644 (file)
index 0000000..1b83532
--- /dev/null
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Establish a TLS session for a kernel socket consumer
+ * using the tlshd user space handler.
+ *
+ * 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 <linux/key.h>
+
+#include <net/sock.h>
+#include <net/handshake.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/keyctl.h>
+#include <uapi/linux/handshake.h>
+#include "handshake.h"
+
+struct tls_handshake_req {
+       void                    (*th_consumer_done)(void *data, int status,
+                                                   key_serial_t peerid);
+       void                    *th_consumer_data;
+
+       int                     th_type;
+       unsigned int            th_timeout_ms;
+       int                     th_auth_mode;
+       key_serial_t            th_keyring;
+       key_serial_t            th_certificate;
+       key_serial_t            th_privkey;
+
+       unsigned int            th_num_peerids;
+       key_serial_t            th_peerid[5];
+};
+
+static struct tls_handshake_req *
+tls_handshake_req_init(struct handshake_req *req,
+                      const struct tls_handshake_args *args)
+{
+       struct tls_handshake_req *treq = handshake_req_private(req);
+
+       treq->th_timeout_ms = args->ta_timeout_ms;
+       treq->th_consumer_done = args->ta_done;
+       treq->th_consumer_data = args->ta_data;
+       treq->th_keyring = args->ta_keyring;
+       treq->th_num_peerids = 0;
+       treq->th_certificate = TLS_NO_CERT;
+       treq->th_privkey = TLS_NO_PRIVKEY;
+       return treq;
+}
+
+static void tls_handshake_remote_peerids(struct tls_handshake_req *treq,
+                                        struct genl_info *info)
+{
+       struct nlattr *head = nlmsg_attrdata(info->nlhdr, GENL_HDRLEN);
+       int rem, len = nlmsg_attrlen(info->nlhdr, GENL_HDRLEN);
+       struct nlattr *nla;
+       unsigned int i;
+
+       i = 0;
+       nla_for_each_attr(nla, head, len, rem) {
+               if (nla_type(nla) == HANDSHAKE_A_DONE_REMOTE_AUTH)
+                       i++;
+       }
+       if (!i)
+               return;
+       treq->th_num_peerids = min_t(unsigned int, i,
+                                    ARRAY_SIZE(treq->th_peerid));
+
+       i = 0;
+       nla_for_each_attr(nla, head, len, rem) {
+               if (nla_type(nla) == HANDSHAKE_A_DONE_REMOTE_AUTH)
+                       treq->th_peerid[i++] = nla_get_u32(nla);
+               if (i >= treq->th_num_peerids)
+                       break;
+       }
+}
+
+/**
+ * tls_handshake_done - callback to handle a CMD_DONE request
+ * @req: socket on which the handshake was performed
+ * @status: session status code
+ * @info: full results of session establishment
+ *
+ */
+static void tls_handshake_done(struct handshake_req *req,
+                              unsigned int status, struct genl_info *info)
+{
+       struct tls_handshake_req *treq = handshake_req_private(req);
+
+       treq->th_peerid[0] = TLS_NO_PEERID;
+       if (info)
+               tls_handshake_remote_peerids(treq, info);
+
+       treq->th_consumer_done(treq->th_consumer_data, -status,
+                              treq->th_peerid[0]);
+}
+
+#if IS_ENABLED(CONFIG_KEYS)
+static int tls_handshake_private_keyring(struct tls_handshake_req *treq)
+{
+       key_ref_t process_keyring_ref, keyring_ref;
+       int ret;
+
+       if (treq->th_keyring == TLS_NO_KEYRING)
+               return 0;
+
+       process_keyring_ref = lookup_user_key(KEY_SPEC_PROCESS_KEYRING,
+                                             KEY_LOOKUP_CREATE,
+                                             KEY_NEED_WRITE);
+       if (IS_ERR(process_keyring_ref)) {
+               ret = PTR_ERR(process_keyring_ref);
+               goto out;
+       }
+
+       keyring_ref = lookup_user_key(treq->th_keyring, KEY_LOOKUP_CREATE,
+                                     KEY_NEED_LINK);
+       if (IS_ERR(keyring_ref)) {
+               ret = PTR_ERR(keyring_ref);
+               goto out_put_key;
+       }
+
+       ret = key_link(key_ref_to_ptr(process_keyring_ref),
+                      key_ref_to_ptr(keyring_ref));
+
+       key_ref_put(keyring_ref);
+out_put_key:
+       key_ref_put(process_keyring_ref);
+out:
+       return ret;
+}
+#else
+static int tls_handshake_private_keyring(struct tls_handshake_req *treq)
+{
+       return 0;
+}
+#endif
+
+static int tls_handshake_put_peer_identity(struct sk_buff *msg,
+                                          struct tls_handshake_req *treq)
+{
+       unsigned int i;
+
+       for (i = 0; i < treq->th_num_peerids; i++)
+               if (nla_put_u32(msg, HANDSHAKE_A_ACCEPT_PEER_IDENTITY,
+                               treq->th_peerid[i]) < 0)
+                       return -EMSGSIZE;
+       return 0;
+}
+
+static int tls_handshake_put_certificate(struct sk_buff *msg,
+                                        struct tls_handshake_req *treq)
+{
+       struct nlattr *entry_attr;
+
+       if (treq->th_certificate == TLS_NO_CERT &&
+           treq->th_privkey == TLS_NO_PRIVKEY)
+               return 0;
+
+       entry_attr = nla_nest_start(msg, HANDSHAKE_A_ACCEPT_CERTIFICATE);
+       if (!entry_attr)
+               return -EMSGSIZE;
+
+       if (nla_put_u32(msg, HANDSHAKE_A_X509_CERT,
+                       treq->th_certificate) ||
+           nla_put_u32(msg, HANDSHAKE_A_X509_PRIVKEY,
+                       treq->th_privkey)) {
+               nla_nest_cancel(msg, entry_attr);
+               return -EMSGSIZE;
+       }
+
+       nla_nest_end(msg, entry_attr);
+       return 0;
+}
+
+/**
+ * tls_handshake_accept - callback to construct a CMD_ACCEPT response
+ * @req: handshake parameters to return
+ * @info: 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 *info, int fd)
+{
+       struct tls_handshake_req *treq = handshake_req_private(req);
+       struct nlmsghdr *hdr;
+       struct sk_buff *msg;
+       int ret;
+
+       ret = tls_handshake_private_keyring(treq);
+       if (ret < 0)
+               goto out;
+
+       ret = -ENOMEM;
+       msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg)
+               goto out;
+       hdr = handshake_genl_put(msg, info);
+       if (!hdr)
+               goto out_cancel;
+
+       ret = -EMSGSIZE;
+       ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_SOCKFD, fd);
+       if (ret < 0)
+               goto out_cancel;
+       ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_MESSAGE_TYPE, treq->th_type);
+       if (ret < 0)
+               goto out_cancel;
+       if (treq->th_timeout_ms) {
+               ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_TIMEOUT, treq->th_timeout_ms);
+               if (ret < 0)
+                       goto out_cancel;
+       }
+
+       ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_AUTH_MODE,
+                         treq->th_auth_mode);
+       if (ret < 0)
+               goto out_cancel;
+       switch (treq->th_auth_mode) {
+       case HANDSHAKE_AUTH_PSK:
+               ret = tls_handshake_put_peer_identity(msg, treq);
+               if (ret < 0)
+                       goto out_cancel;
+               break;
+       case HANDSHAKE_AUTH_X509:
+               ret = tls_handshake_put_certificate(msg, treq);
+               if (ret < 0)
+                       goto out_cancel;
+               break;
+       }
+
+       genlmsg_end(msg, hdr);
+       return genlmsg_reply(msg, info);
+
+out_cancel:
+       genlmsg_cancel(msg, hdr);
+out:
+       return ret;
+}
+
+static const struct handshake_proto tls_handshake_proto = {
+       .hp_handler_class       = HANDSHAKE_HANDLER_CLASS_TLSHD,
+       .hp_privsize            = sizeof(struct tls_handshake_req),
+
+       .hp_accept              = tls_handshake_accept,
+       .hp_done                = tls_handshake_done,
+};
+
+/**
+ * tls_client_hello_anon - request an anonymous TLS handshake on a socket
+ * @args: socket and handshake parameters for this request
+ * @flags: memory allocation control flags
+ *
+ * Return values:
+ *   %0: Handshake request enqueue; ->done will be called when complete
+ *   %-ESRCH: No user agent is available
+ *   %-ENOMEM: Memory allocation failed
+ */
+int tls_client_hello_anon(const struct tls_handshake_args *args, gfp_t flags)
+{
+       struct tls_handshake_req *treq;
+       struct handshake_req *req;
+
+       req = handshake_req_alloc(&tls_handshake_proto, flags);
+       if (!req)
+               return -ENOMEM;
+       treq = tls_handshake_req_init(req, args);
+       treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTHELLO;
+       treq->th_auth_mode = HANDSHAKE_AUTH_UNAUTH;
+
+       return handshake_req_submit(args->ta_sock, req, flags);
+}
+EXPORT_SYMBOL(tls_client_hello_anon);
+
+/**
+ * tls_client_hello_x509 - request an x.509-based TLS handshake on a socket
+ * @args: socket and handshake parameters for this request
+ * @flags: memory allocation control flags
+ *
+ * Return values:
+ *   %0: Handshake request enqueue; ->done will be called when complete
+ *   %-ESRCH: No user agent is available
+ *   %-ENOMEM: Memory allocation failed
+ */
+int tls_client_hello_x509(const struct tls_handshake_args *args, gfp_t flags)
+{
+       struct tls_handshake_req *treq;
+       struct handshake_req *req;
+
+       req = handshake_req_alloc(&tls_handshake_proto, flags);
+       if (!req)
+               return -ENOMEM;
+       treq = tls_handshake_req_init(req, args);
+       treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTHELLO;
+       treq->th_auth_mode = HANDSHAKE_AUTH_X509;
+       treq->th_certificate = args->ta_my_cert;
+       treq->th_privkey = args->ta_my_privkey;
+
+       return handshake_req_submit(args->ta_sock, req, flags);
+}
+EXPORT_SYMBOL(tls_client_hello_x509);
+
+/**
+ * tls_client_hello_psk - request a PSK-based TLS handshake on a socket
+ * @args: socket and handshake parameters for this request
+ * @flags: memory allocation control flags
+ *
+ * Return values:
+ *   %0: Handshake request enqueue; ->done will be called when complete
+ *   %-EINVAL: Wrong number of local peer IDs
+ *   %-ESRCH: No user agent is available
+ *   %-ENOMEM: Memory allocation failed
+ */
+int tls_client_hello_psk(const struct tls_handshake_args *args, gfp_t flags)
+{
+       struct tls_handshake_req *treq;
+       struct handshake_req *req;
+       unsigned int i;
+
+       if (!args->ta_num_peerids ||
+           args->ta_num_peerids > ARRAY_SIZE(treq->th_peerid))
+               return -EINVAL;
+
+       req = handshake_req_alloc(&tls_handshake_proto, flags);
+       if (!req)
+               return -ENOMEM;
+       treq = tls_handshake_req_init(req, args);
+       treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTHELLO;
+       treq->th_auth_mode = HANDSHAKE_AUTH_PSK;
+       treq->th_num_peerids = args->ta_num_peerids;
+       for (i = 0; i < args->ta_num_peerids; i++)
+               treq->th_peerid[i] = args->ta_my_peerids[i];
+
+       return handshake_req_submit(args->ta_sock, req, flags);
+}
+EXPORT_SYMBOL(tls_client_hello_psk);
+
+/**
+ * tls_server_hello_x509 - request a server TLS handshake on a socket
+ * @args: socket and handshake parameters for this request
+ * @flags: memory allocation control flags
+ *
+ * Return values:
+ *   %0: Handshake request enqueue; ->done will be called when complete
+ *   %-ESRCH: No user agent is available
+ *   %-ENOMEM: Memory allocation failed
+ */
+int tls_server_hello_x509(const struct tls_handshake_args *args, gfp_t flags)
+{
+       struct tls_handshake_req *treq;
+       struct handshake_req *req;
+
+       req = handshake_req_alloc(&tls_handshake_proto, flags);
+       if (!req)
+               return -ENOMEM;
+       treq = tls_handshake_req_init(req, args);
+       treq->th_type = HANDSHAKE_MSG_TYPE_SERVERHELLO;
+       treq->th_auth_mode = HANDSHAKE_AUTH_X509;
+       treq->th_certificate = args->ta_my_cert;
+       treq->th_privkey = args->ta_my_privkey;
+
+       return handshake_req_submit(args->ta_sock, req, flags);
+}
+EXPORT_SYMBOL(tls_server_hello_x509);
+
+/**
+ * tls_server_hello_psk - request a server TLS handshake on a socket
+ * @args: socket and handshake parameters for this request
+ * @flags: memory allocation control flags
+ *
+ * Return values:
+ *   %0: Handshake request enqueue; ->done will be called when complete
+ *   %-ESRCH: No user agent is available
+ *   %-ENOMEM: Memory allocation failed
+ */
+int tls_server_hello_psk(const struct tls_handshake_args *args, gfp_t flags)
+{
+       struct tls_handshake_req *treq;
+       struct handshake_req *req;
+
+       req = handshake_req_alloc(&tls_handshake_proto, flags);
+       if (!req)
+               return -ENOMEM;
+       treq = tls_handshake_req_init(req, args);
+       treq->th_type = HANDSHAKE_MSG_TYPE_SERVERHELLO;
+       treq->th_auth_mode = HANDSHAKE_AUTH_PSK;
+       treq->th_num_peerids = 1;
+       treq->th_peerid[0] = args->ta_my_peerids[0];
+
+       return handshake_req_submit(args->ta_sock, req, flags);
+}
+EXPORT_SYMBOL(tls_server_hello_psk);
+
+/**
+ * tls_handshake_cancel - cancel a pending handshake
+ * @sk: socket on which there is an ongoing handshake
+ *
+ * Request cancellation races with request completion. To determine
+ * who won, callers examine the return value from this function.
+ *
+ * Return values:
+ *   %true - Uncompleted handshake request was canceled
+ *   %false - Handshake request already completed or not found
+ */
+bool tls_handshake_cancel(struct sock *sk)
+{
+       return handshake_req_cancel(sk);
+}
+EXPORT_SYMBOL(tls_handshake_cancel);