mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	net/handshake: Add a kernel API for requesting a TLSv1.3 handshake
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>
This commit is contained in:
		
							parent
							
								
									3b3009ea8a
								
							
						
					
					
						commit
						2fd5532044
					
				
					 10 changed files with 689 additions and 3 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,7 @@ Contents:
 | 
			
		|||
   scaling
 | 
			
		||||
   tls
 | 
			
		||||
   tls-offload
 | 
			
		||||
   tls-handshake
 | 
			
		||||
   nfc
 | 
			
		||||
   6lowpan
 | 
			
		||||
   6pack
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										217
									
								
								Documentation/networking/tls-handshake.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								Documentation/networking/tls-handshake.rst
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -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/
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										43
									
								
								include/net/handshake.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								include/net/handshake.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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 */
 | 
			
		||||
| 
						 | 
				
			
			@ -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 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										417
									
								
								net/handshake/tlshd.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								net/handshake/tlshd.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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);
 | 
			
		||||
		Loading…
	
		Reference in a new issue