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
@ -16,7 +16,7 @@ definitions:
|
|||||||
type: enum
|
type: enum
|
||||||
name: handler-class
|
name: handler-class
|
||||||
value-start: 0
|
value-start: 0
|
||||||
entries: [ none, max ]
|
entries: [ none, tlshd, max ]
|
||||||
-
|
-
|
||||||
type: enum
|
type: enum
|
||||||
name: msg-type
|
name: msg-type
|
||||||
@ -120,3 +120,5 @@ mcast-groups:
|
|||||||
list:
|
list:
|
||||||
-
|
-
|
||||||
name: none
|
name: none
|
||||||
|
-
|
||||||
|
name: tlshd
|
||||||
|
@ -36,6 +36,7 @@ Contents:
|
|||||||
scaling
|
scaling
|
||||||
tls
|
tls
|
||||||
tls-offload
|
tls-offload
|
||||||
|
tls-handshake
|
||||||
nfc
|
nfc
|
||||||
6lowpan
|
6lowpan
|
||||||
6pack
|
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
|
L: netdev@vger.kernel.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: Documentation/netlink/specs/handshake.yaml
|
F: Documentation/netlink/specs/handshake.yaml
|
||||||
|
F: Documentation/networking/tls-handshake.rst
|
||||||
|
F: include/net/handshake.h
|
||||||
F: include/trace/events/handshake.h
|
F: include/trace/events/handshake.h
|
||||||
F: net/handshake/
|
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 {
|
enum handshake_handler_class {
|
||||||
HANDSHAKE_HANDLER_CLASS_NONE,
|
HANDSHAKE_HANDLER_CLASS_NONE,
|
||||||
|
HANDSHAKE_HANDLER_CLASS_TLSHD,
|
||||||
HANDSHAKE_HANDLER_CLASS_MAX,
|
HANDSHAKE_HANDLER_CLASS_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,5 +68,6 @@ enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#define HANDSHAKE_MCGRP_NONE "none"
|
#define HANDSHAKE_MCGRP_NONE "none"
|
||||||
|
#define HANDSHAKE_MCGRP_TLSHD "tlshd"
|
||||||
|
|
||||||
#endif /* _UAPI_LINUX_HANDSHAKE_H */
|
#endif /* _UAPI_LINUX_HANDSHAKE_H */
|
||||||
|
@ -8,4 +8,4 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
obj-y += handshake.o
|
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 */
|
/* HANDSHAKE_CMD_ACCEPT - do */
|
||||||
static const struct nla_policy handshake_accept_nl_policy[HANDSHAKE_A_ACCEPT_HANDLER_CLASS + 1] = {
|
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 */
|
/* 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[] = {
|
static const struct genl_multicast_group handshake_nl_mcgrps[] = {
|
||||||
[HANDSHAKE_NLGRP_NONE] = { "none", },
|
[HANDSHAKE_NLGRP_NONE] = { "none", },
|
||||||
|
[HANDSHAKE_NLGRP_TLSHD] = { "tlshd", },
|
||||||
};
|
};
|
||||||
|
|
||||||
struct genl_family handshake_nl_family __ro_after_init = {
|
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 {
|
enum {
|
||||||
HANDSHAKE_NLGRP_NONE,
|
HANDSHAKE_NLGRP_NONE,
|
||||||
|
HANDSHAKE_NLGRP_TLSHD,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct genl_family handshake_nl_family;
|
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 New Issue
Block a user