2023-04-17 17:32:33 +03:00
// 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 ) ,
2023-04-17 17:32:39 +03:00
. hp_flags = BIT ( HANDSHAKE_F_PROTO_NOTIFY ) ,
2023-04-17 17:32:33 +03:00
. 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 ) ;