| .. 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. |