|  | .. 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; | 
|  | const char      *ta_peername; | 
|  | 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 provide a NUL-terminated hostname in the @ta_peername | 
|  | field that is sent as part of ClientHello. If no peername is provided, | 
|  | the DNS hostname associated with the server's IP address is used instead. | 
|  |  | 
|  | 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. |