427 lines
11 KiB
C
427 lines
11 KiB
C
// 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;
|
|
const char *th_peername;
|
|
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_peername = args->ta_peername;
|
|
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_peername) {
|
|
ret = nla_put_string(msg, HANDSHAKE_A_ACCEPT_PEERNAME,
|
|
treq->th_peername);
|
|
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_flags = BIT(HANDSHAKE_F_PROTO_NOTIFY),
|
|
|
|
.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);
|