2025-04-11 10:52:53 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
/* GSSAPI-based RxRPC security
|
|
|
|
*
|
|
|
|
* Copyright (C) 2025 Red Hat, Inc. All Rights Reserved.
|
|
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
|
|
|
#include <linux/net.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/key-type.h>
|
|
|
|
#include "ar-internal.h"
|
|
|
|
#include "rxgk_common.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parse the information from a server key
|
|
|
|
*/
|
|
|
|
static int rxgk_preparse_server_key(struct key_preparsed_payload *prep)
|
|
|
|
{
|
|
|
|
const struct krb5_enctype *krb5;
|
|
|
|
struct krb5_buffer *server_key = (void *)&prep->payload.data[2];
|
|
|
|
unsigned int service, sec_class, kvno, enctype;
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
_enter("%zu", prep->datalen);
|
|
|
|
|
|
|
|
if (sscanf(prep->orig_description, "%u:%u:%u:%u%n",
|
|
|
|
&service, &sec_class, &kvno, &enctype, &n) != 4)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (prep->orig_description[n])
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
krb5 = crypto_krb5_find_enctype(enctype);
|
|
|
|
if (!krb5)
|
|
|
|
return -ENOPKG;
|
|
|
|
|
|
|
|
prep->payload.data[0] = (struct krb5_enctype *)krb5;
|
|
|
|
|
|
|
|
if (prep->datalen != krb5->key_len)
|
|
|
|
return -EKEYREJECTED;
|
|
|
|
|
|
|
|
server_key->len = prep->datalen;
|
|
|
|
server_key->data = kmemdup(prep->data, prep->datalen, GFP_KERNEL);
|
|
|
|
if (!server_key->data)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
_leave(" = 0");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rxgk_free_server_key(union key_payload *payload)
|
|
|
|
{
|
|
|
|
struct krb5_buffer *server_key = (void *)&payload->data[2];
|
|
|
|
|
|
|
|
kfree_sensitive(server_key->data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rxgk_free_preparse_server_key(struct key_preparsed_payload *prep)
|
|
|
|
{
|
|
|
|
rxgk_free_server_key(&prep->payload);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rxgk_destroy_server_key(struct key *key)
|
|
|
|
{
|
|
|
|
rxgk_free_server_key(&key->payload);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rxgk_describe_server_key(const struct key *key, struct seq_file *m)
|
|
|
|
{
|
|
|
|
const struct krb5_enctype *krb5 = key->payload.data[0];
|
|
|
|
|
|
|
|
if (krb5)
|
|
|
|
seq_printf(m, ": %s", krb5->name);
|
|
|
|
}
|
|
|
|
|
2025-04-11 10:52:54 +01:00
|
|
|
/*
|
|
|
|
* Handle rekeying the connection when we see our limits overrun or when the
|
|
|
|
* far side decided to rekey.
|
|
|
|
*
|
|
|
|
* Returns a ref on the context if successful or -ESTALE if the key is out of
|
|
|
|
* date.
|
|
|
|
*/
|
|
|
|
static struct rxgk_context *rxgk_rekey(struct rxrpc_connection *conn,
|
|
|
|
const u16 *specific_key_number)
|
|
|
|
{
|
|
|
|
struct rxgk_context *gk, *dead = NULL;
|
|
|
|
unsigned int key_number, current_key, mask = ARRAY_SIZE(conn->rxgk.keys) - 1;
|
|
|
|
bool crank = false;
|
|
|
|
|
|
|
|
_enter("%d", specific_key_number ? *specific_key_number : -1);
|
|
|
|
|
|
|
|
mutex_lock(&conn->security_lock);
|
|
|
|
|
|
|
|
current_key = conn->rxgk.key_number;
|
|
|
|
if (!specific_key_number) {
|
|
|
|
key_number = current_key;
|
|
|
|
} else {
|
|
|
|
if (*specific_key_number == (u16)current_key)
|
|
|
|
key_number = current_key;
|
|
|
|
else if (*specific_key_number == (u16)(current_key - 1))
|
|
|
|
key_number = current_key - 1;
|
|
|
|
else if (*specific_key_number == (u16)(current_key + 1))
|
|
|
|
goto crank_window;
|
|
|
|
else
|
|
|
|
goto bad_key;
|
|
|
|
}
|
|
|
|
|
|
|
|
gk = conn->rxgk.keys[key_number & mask];
|
|
|
|
if (!gk)
|
|
|
|
goto generate_key;
|
|
|
|
if (!specific_key_number &&
|
|
|
|
test_bit(RXGK_TK_NEEDS_REKEY, &gk->flags))
|
|
|
|
goto crank_window;
|
|
|
|
|
|
|
|
grab:
|
|
|
|
refcount_inc(&gk->usage);
|
|
|
|
mutex_unlock(&conn->security_lock);
|
|
|
|
rxgk_put(dead);
|
|
|
|
return gk;
|
|
|
|
|
|
|
|
crank_window:
|
|
|
|
trace_rxrpc_rxgk_rekey(conn, current_key,
|
|
|
|
specific_key_number ? *specific_key_number : -1);
|
|
|
|
if (current_key == UINT_MAX)
|
|
|
|
goto bad_key;
|
|
|
|
if (current_key + 1 == UINT_MAX)
|
|
|
|
set_bit(RXRPC_CONN_DONT_REUSE, &conn->flags);
|
|
|
|
|
|
|
|
key_number = current_key + 1;
|
|
|
|
if (WARN_ON(conn->rxgk.keys[key_number & mask]))
|
|
|
|
goto bad_key;
|
|
|
|
crank = true;
|
|
|
|
|
|
|
|
generate_key:
|
|
|
|
gk = conn->rxgk.keys[current_key & mask];
|
|
|
|
gk = rxgk_generate_transport_key(conn, gk->key, key_number, GFP_NOFS);
|
|
|
|
if (IS_ERR(gk)) {
|
|
|
|
mutex_unlock(&conn->security_lock);
|
|
|
|
return gk;
|
|
|
|
}
|
|
|
|
|
|
|
|
write_lock(&conn->security_use_lock);
|
|
|
|
if (crank) {
|
|
|
|
current_key++;
|
|
|
|
conn->rxgk.key_number = current_key;
|
|
|
|
dead = conn->rxgk.keys[(current_key - 2) & mask];
|
|
|
|
conn->rxgk.keys[(current_key - 2) & mask] = NULL;
|
|
|
|
}
|
|
|
|
conn->rxgk.keys[current_key & mask] = gk;
|
|
|
|
write_unlock(&conn->security_use_lock);
|
|
|
|
goto grab;
|
|
|
|
|
|
|
|
bad_key:
|
|
|
|
mutex_unlock(&conn->security_lock);
|
|
|
|
return ERR_PTR(-ESTALE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the specified keying context.
|
|
|
|
*
|
|
|
|
* Returns a ref on the context if successful or -ESTALE if the key is out of
|
|
|
|
* date.
|
|
|
|
*/
|
2025-04-11 10:52:53 +01:00
|
|
|
static struct rxgk_context *rxgk_get_key(struct rxrpc_connection *conn,
|
2025-04-11 10:52:54 +01:00
|
|
|
const u16 *specific_key_number)
|
2025-04-11 10:52:53 +01:00
|
|
|
{
|
2025-04-11 10:52:54 +01:00
|
|
|
struct rxgk_context *gk;
|
|
|
|
unsigned int key_number, current_key, mask = ARRAY_SIZE(conn->rxgk.keys) - 1;
|
|
|
|
|
|
|
|
_enter("{%u},%d",
|
|
|
|
conn->rxgk.key_number, specific_key_number ? *specific_key_number : -1);
|
|
|
|
|
|
|
|
read_lock(&conn->security_use_lock);
|
|
|
|
|
|
|
|
current_key = conn->rxgk.key_number;
|
|
|
|
if (!specific_key_number) {
|
|
|
|
key_number = current_key;
|
|
|
|
} else {
|
|
|
|
/* Only the bottom 16 bits of the key number are exposed in the
|
|
|
|
* header, so we try and keep the upper 16 bits in step. The
|
|
|
|
* whole 32 bits are used to generate the TK.
|
|
|
|
*/
|
|
|
|
if (*specific_key_number == (u16)current_key)
|
|
|
|
key_number = current_key;
|
|
|
|
else if (*specific_key_number == (u16)(current_key - 1))
|
|
|
|
key_number = current_key - 1;
|
|
|
|
else if (*specific_key_number == (u16)(current_key + 1))
|
|
|
|
goto rekey;
|
|
|
|
else
|
|
|
|
goto bad_key;
|
|
|
|
}
|
|
|
|
|
|
|
|
gk = conn->rxgk.keys[key_number & mask];
|
|
|
|
if (!gk)
|
|
|
|
goto slow_path;
|
|
|
|
if (!specific_key_number &&
|
|
|
|
key_number < UINT_MAX) {
|
|
|
|
if (time_after(jiffies, gk->expiry) ||
|
|
|
|
gk->bytes_remaining < 0) {
|
|
|
|
set_bit(RXGK_TK_NEEDS_REKEY, &gk->flags);
|
|
|
|
goto slow_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (test_bit(RXGK_TK_NEEDS_REKEY, &gk->flags))
|
|
|
|
goto slow_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
refcount_inc(&gk->usage);
|
|
|
|
read_unlock(&conn->security_use_lock);
|
|
|
|
return gk;
|
|
|
|
|
|
|
|
rekey:
|
|
|
|
_debug("rekey");
|
|
|
|
if (current_key == UINT_MAX)
|
|
|
|
goto bad_key;
|
|
|
|
gk = conn->rxgk.keys[current_key & mask];
|
|
|
|
if (gk)
|
|
|
|
set_bit(RXGK_TK_NEEDS_REKEY, &gk->flags);
|
|
|
|
slow_path:
|
|
|
|
read_unlock(&conn->security_use_lock);
|
|
|
|
return rxgk_rekey(conn, specific_key_number);
|
|
|
|
bad_key:
|
|
|
|
read_unlock(&conn->security_use_lock);
|
|
|
|
return ERR_PTR(-ESTALE);
|
2025-04-11 10:52:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* initialise connection security
|
|
|
|
*/
|
|
|
|
static int rxgk_init_connection_security(struct rxrpc_connection *conn,
|
|
|
|
struct rxrpc_key_token *token)
|
|
|
|
{
|
|
|
|
struct rxgk_context *gk;
|
|
|
|
int ret;
|
|
|
|
|
2025-04-11 10:52:54 +01:00
|
|
|
_enter("{%d,%u},{%x}",
|
|
|
|
conn->debug_id, conn->rxgk.key_number, key_serial(conn->key));
|
2025-04-11 10:52:53 +01:00
|
|
|
|
|
|
|
conn->security_ix = token->security_index;
|
|
|
|
conn->security_level = token->rxgk->level;
|
|
|
|
|
|
|
|
if (rxrpc_conn_is_client(conn)) {
|
|
|
|
conn->rxgk.start_time = ktime_get();
|
|
|
|
do_div(conn->rxgk.start_time, 100);
|
|
|
|
}
|
|
|
|
|
2025-04-11 10:52:54 +01:00
|
|
|
gk = rxgk_generate_transport_key(conn, token->rxgk, conn->rxgk.key_number,
|
|
|
|
GFP_NOFS);
|
2025-04-11 10:52:53 +01:00
|
|
|
if (IS_ERR(gk))
|
|
|
|
return PTR_ERR(gk);
|
|
|
|
conn->rxgk.enctype = gk->krb5->etype;
|
2025-04-11 10:52:54 +01:00
|
|
|
conn->rxgk.keys[gk->key_number & 3] = gk;
|
2025-04-11 10:52:53 +01:00
|
|
|
|
|
|
|
switch (conn->security_level) {
|
|
|
|
case RXRPC_SECURITY_PLAIN:
|
|
|
|
case RXRPC_SECURITY_AUTH:
|
|
|
|
case RXRPC_SECURITY_ENCRYPT:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = -EKEYREJECTED;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
error:
|
|
|
|
_leave(" = %d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Clean up the crypto on a call.
|
|
|
|
*/
|
|
|
|
static void rxgk_free_call_crypto(struct rxrpc_call *call)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Work out how much data we can put in a packet.
|
|
|
|
*/
|
|
|
|
static struct rxrpc_txbuf *rxgk_alloc_txbuf(struct rxrpc_call *call, size_t remain, gfp_t gfp)
|
|
|
|
{
|
|
|
|
enum krb5_crypto_mode mode;
|
|
|
|
struct rxgk_context *gk;
|
|
|
|
struct rxrpc_txbuf *txb;
|
|
|
|
size_t shdr, alloc, limit, part, offset, gap;
|
|
|
|
|
|
|
|
switch (call->conn->security_level) {
|
|
|
|
default:
|
|
|
|
alloc = umin(remain, RXRPC_JUMBO_DATALEN);
|
|
|
|
return rxrpc_alloc_data_txbuf(call, alloc, 1, gfp);
|
|
|
|
case RXRPC_SECURITY_AUTH:
|
|
|
|
shdr = 0;
|
|
|
|
mode = KRB5_CHECKSUM_MODE;
|
|
|
|
break;
|
|
|
|
case RXRPC_SECURITY_ENCRYPT:
|
|
|
|
shdr = sizeof(struct rxgk_header);
|
|
|
|
mode = KRB5_ENCRYPT_MODE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
gk = rxgk_get_key(call->conn, NULL);
|
|
|
|
if (IS_ERR(gk))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Work out the maximum amount of data that will fit. */
|
|
|
|
alloc = RXRPC_JUMBO_DATALEN;
|
|
|
|
limit = crypto_krb5_how_much_data(gk->krb5, mode, &alloc, &offset);
|
|
|
|
|
|
|
|
if (remain < limit - shdr) {
|
|
|
|
part = remain;
|
|
|
|
alloc = crypto_krb5_how_much_buffer(gk->krb5, mode,
|
|
|
|
shdr + part, &offset);
|
|
|
|
gap = 0;
|
|
|
|
} else {
|
|
|
|
part = limit - shdr;
|
|
|
|
gap = RXRPC_JUMBO_DATALEN - alloc;
|
|
|
|
alloc = RXRPC_JUMBO_DATALEN;
|
|
|
|
}
|
|
|
|
|
|
|
|
rxgk_put(gk);
|
|
|
|
|
|
|
|
txb = rxrpc_alloc_data_txbuf(call, alloc, 16, gfp);
|
|
|
|
if (!txb)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
txb->crypto_header = offset;
|
|
|
|
txb->sec_header = shdr;
|
|
|
|
txb->offset += offset + shdr;
|
|
|
|
txb->space = part;
|
|
|
|
|
|
|
|
/* Clear excess space in the packet */
|
|
|
|
if (gap)
|
|
|
|
memset(txb->data + alloc - gap, 0, gap);
|
|
|
|
return txb;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Integrity mode (sign a packet - level 1 security)
|
|
|
|
*/
|
|
|
|
static int rxgk_secure_packet_integrity(const struct rxrpc_call *call,
|
|
|
|
struct rxgk_context *gk,
|
|
|
|
struct rxrpc_txbuf *txb)
|
|
|
|
{
|
|
|
|
struct rxgk_header *hdr;
|
|
|
|
struct scatterlist sg[1];
|
|
|
|
struct krb5_buffer metadata;
|
|
|
|
int ret = -ENOMEM;
|
|
|
|
|
|
|
|
_enter("");
|
|
|
|
|
|
|
|
hdr = kzalloc(sizeof(*hdr), GFP_NOFS);
|
|
|
|
if (!hdr)
|
|
|
|
goto error_gk;
|
|
|
|
|
|
|
|
hdr->epoch = htonl(call->conn->proto.epoch);
|
|
|
|
hdr->cid = htonl(call->cid);
|
|
|
|
hdr->call_number = htonl(call->call_id);
|
|
|
|
hdr->seq = htonl(txb->seq);
|
|
|
|
hdr->sec_index = htonl(call->security_ix);
|
|
|
|
hdr->data_len = htonl(txb->len);
|
|
|
|
metadata.len = sizeof(*hdr);
|
|
|
|
metadata.data = hdr;
|
|
|
|
|
|
|
|
sg_init_table(sg, 1);
|
|
|
|
sg_set_buf(&sg[0], txb->data, txb->alloc_size);
|
|
|
|
|
|
|
|
ret = crypto_krb5_get_mic(gk->krb5, gk->tx_Kc, &metadata,
|
|
|
|
sg, 1, txb->alloc_size,
|
|
|
|
txb->crypto_header, txb->sec_header + txb->len);
|
|
|
|
if (ret >= 0) {
|
|
|
|
txb->pkt_len = ret;
|
|
|
|
if (txb->alloc_size == RXRPC_JUMBO_DATALEN)
|
|
|
|
txb->jumboable = true;
|
|
|
|
gk->bytes_remaining -= ret;
|
|
|
|
}
|
|
|
|
kfree(hdr);
|
|
|
|
error_gk:
|
|
|
|
rxgk_put(gk);
|
|
|
|
_leave(" = %d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* wholly encrypt a packet (level 2 security)
|
|
|
|
*/
|
|
|
|
static int rxgk_secure_packet_encrypted(const struct rxrpc_call *call,
|
|
|
|
struct rxgk_context *gk,
|
|
|
|
struct rxrpc_txbuf *txb)
|
|
|
|
{
|
|
|
|
struct rxgk_header *hdr;
|
|
|
|
struct scatterlist sg[1];
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
_enter("%x", txb->len);
|
|
|
|
|
|
|
|
/* Insert the header into the buffer. */
|
|
|
|
hdr = txb->data + txb->crypto_header;
|
|
|
|
hdr->epoch = htonl(call->conn->proto.epoch);
|
|
|
|
hdr->cid = htonl(call->cid);
|
|
|
|
hdr->call_number = htonl(call->call_id);
|
|
|
|
hdr->seq = htonl(txb->seq);
|
|
|
|
hdr->sec_index = htonl(call->security_ix);
|
|
|
|
hdr->data_len = htonl(txb->len);
|
|
|
|
|
|
|
|
sg_init_table(sg, 1);
|
|
|
|
sg_set_buf(&sg[0], txb->data, txb->alloc_size);
|
|
|
|
|
|
|
|
ret = crypto_krb5_encrypt(gk->krb5, gk->tx_enc,
|
|
|
|
sg, 1, txb->alloc_size,
|
|
|
|
txb->crypto_header, txb->sec_header + txb->len,
|
|
|
|
false);
|
|
|
|
if (ret >= 0) {
|
|
|
|
txb->pkt_len = ret;
|
|
|
|
if (txb->alloc_size == RXRPC_JUMBO_DATALEN)
|
|
|
|
txb->jumboable = true;
|
|
|
|
gk->bytes_remaining -= ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
rxgk_put(gk);
|
|
|
|
_leave(" = %d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* checksum an RxRPC packet header
|
|
|
|
*/
|
|
|
|
static int rxgk_secure_packet(struct rxrpc_call *call, struct rxrpc_txbuf *txb)
|
|
|
|
{
|
|
|
|
struct rxgk_context *gk;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
_enter("{%d{%x}},{#%u},%u,",
|
|
|
|
call->debug_id, key_serial(call->conn->key), txb->seq, txb->len);
|
|
|
|
|
|
|
|
gk = rxgk_get_key(call->conn, NULL);
|
|
|
|
if (IS_ERR(gk))
|
|
|
|
return PTR_ERR(gk) == -ESTALE ? -EKEYREJECTED : PTR_ERR(gk);
|
|
|
|
|
|
|
|
ret = key_validate(call->conn->key);
|
2025-04-23 11:25:45 +03:00
|
|
|
if (ret < 0) {
|
|
|
|
rxgk_put(gk);
|
2025-04-11 10:52:53 +01:00
|
|
|
return ret;
|
2025-04-23 11:25:45 +03:00
|
|
|
}
|
2025-04-11 10:52:53 +01:00
|
|
|
|
2025-04-11 10:52:56 +01:00
|
|
|
call->security_enctype = gk->krb5->etype;
|
2025-04-11 10:52:53 +01:00
|
|
|
txb->cksum = htons(gk->key_number);
|
|
|
|
|
|
|
|
switch (call->conn->security_level) {
|
|
|
|
case RXRPC_SECURITY_PLAIN:
|
|
|
|
rxgk_put(gk);
|
|
|
|
txb->pkt_len = txb->len;
|
|
|
|
return 0;
|
|
|
|
case RXRPC_SECURITY_AUTH:
|
|
|
|
return rxgk_secure_packet_integrity(call, gk, txb);
|
|
|
|
case RXRPC_SECURITY_ENCRYPT:
|
|
|
|
return rxgk_secure_packet_encrypted(call, gk, txb);
|
|
|
|
default:
|
|
|
|
rxgk_put(gk);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Integrity mode (check the signature on a packet - level 1 security)
|
|
|
|
*/
|
|
|
|
static int rxgk_verify_packet_integrity(struct rxrpc_call *call,
|
|
|
|
struct rxgk_context *gk,
|
|
|
|
struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
|
|
|
|
struct rxgk_header *hdr;
|
|
|
|
struct krb5_buffer metadata;
|
|
|
|
unsigned int offset = sp->offset, len = sp->len;
|
|
|
|
size_t data_offset = 0, data_len = len;
|
|
|
|
u32 ac;
|
|
|
|
int ret = -ENOMEM;
|
|
|
|
|
|
|
|
_enter("");
|
|
|
|
|
|
|
|
crypto_krb5_where_is_the_data(gk->krb5, KRB5_CHECKSUM_MODE,
|
|
|
|
&data_offset, &data_len);
|
|
|
|
|
|
|
|
hdr = kzalloc(sizeof(*hdr), GFP_NOFS);
|
|
|
|
if (!hdr)
|
2025-04-23 11:25:45 +03:00
|
|
|
goto put_gk;
|
2025-04-11 10:52:53 +01:00
|
|
|
|
|
|
|
hdr->epoch = htonl(call->conn->proto.epoch);
|
|
|
|
hdr->cid = htonl(call->cid);
|
|
|
|
hdr->call_number = htonl(call->call_id);
|
|
|
|
hdr->seq = htonl(sp->hdr.seq);
|
|
|
|
hdr->sec_index = htonl(call->security_ix);
|
|
|
|
hdr->data_len = htonl(data_len);
|
|
|
|
|
|
|
|
metadata.len = sizeof(*hdr);
|
|
|
|
metadata.data = hdr;
|
|
|
|
ret = rxgk_verify_mic_skb(gk->krb5, gk->rx_Kc, &metadata,
|
|
|
|
skb, &offset, &len, &ac);
|
|
|
|
kfree(hdr);
|
|
|
|
if (ret == -EPROTO) {
|
|
|
|
rxrpc_abort_eproto(call, skb, ac,
|
|
|
|
rxgk_abort_1_verify_mic_eproto);
|
|
|
|
} else {
|
|
|
|
sp->offset = offset;
|
|
|
|
sp->len = len;
|
|
|
|
}
|
|
|
|
|
2025-04-23 11:25:45 +03:00
|
|
|
put_gk:
|
2025-04-11 10:52:53 +01:00
|
|
|
rxgk_put(gk);
|
|
|
|
_leave(" = %d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Decrypt an encrypted packet (level 2 security).
|
|
|
|
*/
|
|
|
|
static int rxgk_verify_packet_encrypted(struct rxrpc_call *call,
|
|
|
|
struct rxgk_context *gk,
|
|
|
|
struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
|
|
|
|
struct rxgk_header hdr;
|
|
|
|
unsigned int offset = sp->offset, len = sp->len;
|
|
|
|
int ret;
|
|
|
|
u32 ac;
|
|
|
|
|
|
|
|
_enter("");
|
|
|
|
|
|
|
|
ret = rxgk_decrypt_skb(gk->krb5, gk->rx_enc, skb, &offset, &len, &ac);
|
|
|
|
if (ret == -EPROTO)
|
|
|
|
rxrpc_abort_eproto(call, skb, ac, rxgk_abort_2_decrypt_eproto);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if (len < sizeof(hdr)) {
|
|
|
|
ret = rxrpc_abort_eproto(call, skb, RXGK_PACKETSHORT,
|
|
|
|
rxgk_abort_2_short_header);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Extract the header from the skb */
|
|
|
|
ret = skb_copy_bits(skb, offset, &hdr, sizeof(hdr));
|
|
|
|
if (ret < 0) {
|
|
|
|
ret = rxrpc_abort_eproto(call, skb, RXGK_PACKETSHORT,
|
|
|
|
rxgk_abort_2_short_encdata);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
offset += sizeof(hdr);
|
|
|
|
len -= sizeof(hdr);
|
|
|
|
|
|
|
|
if (ntohl(hdr.epoch) != call->conn->proto.epoch ||
|
|
|
|
ntohl(hdr.cid) != call->cid ||
|
|
|
|
ntohl(hdr.call_number) != call->call_id ||
|
|
|
|
ntohl(hdr.seq) != sp->hdr.seq ||
|
|
|
|
ntohl(hdr.sec_index) != call->security_ix ||
|
|
|
|
ntohl(hdr.data_len) > len) {
|
|
|
|
ret = rxrpc_abort_eproto(call, skb, RXGK_SEALEDINCON,
|
|
|
|
rxgk_abort_2_short_data);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
sp->offset = offset;
|
|
|
|
sp->len = ntohl(hdr.data_len);
|
|
|
|
ret = 0;
|
|
|
|
error:
|
|
|
|
rxgk_put(gk);
|
|
|
|
_leave(" = %d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Verify the security on a received packet or subpacket (if part of a
|
|
|
|
* jumbo packet).
|
|
|
|
*/
|
|
|
|
static int rxgk_verify_packet(struct rxrpc_call *call, struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
|
|
|
|
struct rxgk_context *gk;
|
|
|
|
u16 key_number = sp->hdr.cksum;
|
|
|
|
|
|
|
|
_enter("{%d{%x}},{#%u}",
|
|
|
|
call->debug_id, key_serial(call->conn->key), sp->hdr.seq);
|
|
|
|
|
|
|
|
gk = rxgk_get_key(call->conn, &key_number);
|
|
|
|
if (IS_ERR(gk)) {
|
|
|
|
switch (PTR_ERR(gk)) {
|
|
|
|
case -ESTALE:
|
|
|
|
return rxrpc_abort_eproto(call, skb, RXGK_BADKEYNO,
|
|
|
|
rxgk_abort_bad_key_number);
|
|
|
|
default:
|
|
|
|
return PTR_ERR(gk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-11 10:52:56 +01:00
|
|
|
call->security_enctype = gk->krb5->etype;
|
2025-04-11 10:52:53 +01:00
|
|
|
switch (call->conn->security_level) {
|
|
|
|
case RXRPC_SECURITY_PLAIN:
|
2025-04-23 11:25:45 +03:00
|
|
|
rxgk_put(gk);
|
2025-04-11 10:52:53 +01:00
|
|
|
return 0;
|
|
|
|
case RXRPC_SECURITY_AUTH:
|
|
|
|
return rxgk_verify_packet_integrity(call, gk, skb);
|
|
|
|
case RXRPC_SECURITY_ENCRYPT:
|
|
|
|
return rxgk_verify_packet_encrypted(call, gk, skb);
|
|
|
|
default:
|
|
|
|
rxgk_put(gk);
|
|
|
|
return -ENOANO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate memory to hold a challenge or a response packet. We're not running
|
|
|
|
* in the io_thread, so we can't use ->tx_alloc.
|
|
|
|
*/
|
|
|
|
static struct page *rxgk_alloc_packet(size_t total_len)
|
|
|
|
{
|
|
|
|
gfp_t gfp = GFP_NOFS;
|
|
|
|
int order;
|
|
|
|
|
|
|
|
order = get_order(total_len);
|
|
|
|
if (order > 0)
|
|
|
|
gfp |= __GFP_COMP;
|
|
|
|
return alloc_pages(gfp, order);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Issue a challenge.
|
|
|
|
*/
|
|
|
|
static int rxgk_issue_challenge(struct rxrpc_connection *conn)
|
|
|
|
{
|
|
|
|
struct rxrpc_wire_header *whdr;
|
|
|
|
struct bio_vec bvec[1];
|
|
|
|
struct msghdr msg;
|
|
|
|
struct page *page;
|
|
|
|
size_t len = sizeof(*whdr) + sizeof(conn->rxgk.nonce);
|
|
|
|
u32 serial;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
_enter("{%d}", conn->debug_id);
|
|
|
|
|
|
|
|
get_random_bytes(&conn->rxgk.nonce, sizeof(conn->rxgk.nonce));
|
|
|
|
|
|
|
|
/* We can't use conn->tx_alloc without a lock */
|
|
|
|
page = rxgk_alloc_packet(sizeof(*whdr) + sizeof(conn->rxgk.nonce));
|
|
|
|
if (!page)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
bvec_set_page(&bvec[0], page, len, 0);
|
|
|
|
iov_iter_bvec(&msg.msg_iter, WRITE, bvec, 1, len);
|
|
|
|
|
|
|
|
msg.msg_name = &conn->peer->srx.transport;
|
|
|
|
msg.msg_namelen = conn->peer->srx.transport_len;
|
|
|
|
msg.msg_control = NULL;
|
|
|
|
msg.msg_controllen = 0;
|
|
|
|
msg.msg_flags = MSG_SPLICE_PAGES;
|
|
|
|
|
|
|
|
whdr = page_address(page);
|
|
|
|
whdr->epoch = htonl(conn->proto.epoch);
|
|
|
|
whdr->cid = htonl(conn->proto.cid);
|
|
|
|
whdr->callNumber = 0;
|
|
|
|
whdr->seq = 0;
|
|
|
|
whdr->type = RXRPC_PACKET_TYPE_CHALLENGE;
|
|
|
|
whdr->flags = conn->out_clientflag;
|
|
|
|
whdr->userStatus = 0;
|
|
|
|
whdr->securityIndex = conn->security_ix;
|
|
|
|
whdr->_rsvd = 0;
|
|
|
|
whdr->serviceId = htons(conn->service_id);
|
|
|
|
|
|
|
|
memcpy(whdr + 1, conn->rxgk.nonce, sizeof(conn->rxgk.nonce));
|
|
|
|
|
|
|
|
serial = rxrpc_get_next_serials(conn, 1);
|
|
|
|
whdr->serial = htonl(serial);
|
|
|
|
|
2025-04-11 10:52:58 +01:00
|
|
|
trace_rxrpc_tx_challenge(conn, serial, 0, *(u32 *)&conn->rxgk.nonce);
|
|
|
|
|
2025-04-11 10:52:53 +01:00
|
|
|
ret = do_udp_sendmsg(conn->local->socket, &msg, len);
|
|
|
|
if (ret > 0)
|
|
|
|
conn->peer->last_tx_at = ktime_get_seconds();
|
|
|
|
__free_page(page);
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
trace_rxrpc_tx_fail(conn->debug_id, serial, ret,
|
|
|
|
rxrpc_tx_point_rxgk_challenge);
|
|
|
|
return -EAGAIN;
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_rxrpc_tx_packet(conn->debug_id, whdr,
|
|
|
|
rxrpc_tx_point_rxgk_challenge);
|
|
|
|
_leave(" = 0");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate a challenge packet.
|
|
|
|
*/
|
|
|
|
static bool rxgk_validate_challenge(struct rxrpc_connection *conn,
|
|
|
|
struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
|
|
|
|
u8 nonce[20];
|
|
|
|
|
|
|
|
if (!conn->key) {
|
|
|
|
rxrpc_abort_conn(conn, skb, RX_PROTOCOL_ERROR, -EPROTO,
|
|
|
|
rxgk_abort_chall_no_key);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (key_validate(conn->key) < 0) {
|
|
|
|
rxrpc_abort_conn(conn, skb, RXGK_EXPIRED, -EPROTO,
|
|
|
|
rxgk_abort_chall_key_expired);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (skb_copy_bits(skb, sizeof(struct rxrpc_wire_header),
|
|
|
|
nonce, sizeof(nonce)) < 0) {
|
|
|
|
rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO,
|
|
|
|
rxgk_abort_chall_short);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_rxrpc_rx_challenge(conn, sp->hdr.serial, 0, *(u32 *)nonce, 0);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* rxgk_kernel_query_challenge - Query RxGK-specific challenge parameters
|
|
|
|
* @challenge: The challenge packet to query
|
|
|
|
*
|
|
|
|
* Return: The Kerberos 5 encoding type for the challenged connection.
|
|
|
|
*/
|
|
|
|
u32 rxgk_kernel_query_challenge(struct sk_buff *challenge)
|
|
|
|
{
|
|
|
|
struct rxrpc_skb_priv *sp = rxrpc_skb(challenge);
|
|
|
|
|
|
|
|
return sp->chall.conn->rxgk.enctype;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(rxgk_kernel_query_challenge);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Fill out the control message to pass to userspace to inform about the
|
|
|
|
* challenge.
|
|
|
|
*/
|
|
|
|
static int rxgk_challenge_to_recvmsg(struct rxrpc_connection *conn,
|
|
|
|
struct sk_buff *challenge,
|
|
|
|
struct msghdr *msg)
|
|
|
|
{
|
|
|
|
struct rxgk_challenge chall;
|
|
|
|
|
|
|
|
chall.base.service_id = conn->service_id;
|
|
|
|
chall.base.security_index = conn->security_ix;
|
|
|
|
chall.enctype = conn->rxgk.enctype;
|
|
|
|
|
|
|
|
return put_cmsg(msg, SOL_RXRPC, RXRPC_CHALLENGED, sizeof(chall), &chall);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Insert the requisite amount of XDR padding for the length given.
|
|
|
|
*/
|
|
|
|
static int rxgk_pad_out(struct sk_buff *response, size_t len, size_t offset)
|
|
|
|
{
|
|
|
|
__be32 zero = 0;
|
|
|
|
size_t pad = xdr_round_up(len) - len;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!pad)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ret = skb_store_bits(response, offset, &zero, pad);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
return pad;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Insert the header into the response.
|
|
|
|
*/
|
|
|
|
static noinline ssize_t rxgk_insert_response_header(struct rxrpc_connection *conn,
|
|
|
|
struct rxgk_context *gk,
|
|
|
|
struct sk_buff *response,
|
|
|
|
size_t offset)
|
|
|
|
{
|
|
|
|
struct rxrpc_skb_priv *rsp = rxrpc_skb(response);
|
|
|
|
|
|
|
|
struct {
|
|
|
|
struct rxrpc_wire_header whdr;
|
|
|
|
__be32 start_time_msw;
|
|
|
|
__be32 start_time_lsw;
|
|
|
|
__be32 ticket_len;
|
|
|
|
} h;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
rsp->resp.kvno = gk->key_number;
|
|
|
|
rsp->resp.version = gk->krb5->etype;
|
|
|
|
|
|
|
|
h.whdr.epoch = htonl(conn->proto.epoch);
|
|
|
|
h.whdr.cid = htonl(conn->proto.cid);
|
|
|
|
h.whdr.callNumber = 0;
|
|
|
|
h.whdr.serial = 0;
|
|
|
|
h.whdr.seq = 0;
|
|
|
|
h.whdr.type = RXRPC_PACKET_TYPE_RESPONSE;
|
|
|
|
h.whdr.flags = conn->out_clientflag;
|
|
|
|
h.whdr.userStatus = 0;
|
|
|
|
h.whdr.securityIndex = conn->security_ix;
|
|
|
|
h.whdr.cksum = htons(gk->key_number);
|
|
|
|
h.whdr.serviceId = htons(conn->service_id);
|
|
|
|
h.start_time_msw = htonl(upper_32_bits(conn->rxgk.start_time));
|
|
|
|
h.start_time_lsw = htonl(lower_32_bits(conn->rxgk.start_time));
|
|
|
|
h.ticket_len = htonl(gk->key->ticket.len);
|
|
|
|
|
|
|
|
ret = skb_store_bits(response, offset, &h, sizeof(h));
|
|
|
|
return ret < 0 ? ret : sizeof(h);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Construct the authenticator to go in the response packet
|
|
|
|
*
|
|
|
|
* struct RXGK_Authenticator {
|
|
|
|
* opaque nonce[20];
|
|
|
|
* opaque appdata<>;
|
|
|
|
* RXGK_Level level;
|
|
|
|
* unsigned int epoch;
|
|
|
|
* unsigned int cid;
|
|
|
|
* unsigned int call_numbers<>;
|
|
|
|
* };
|
|
|
|
*/
|
|
|
|
static ssize_t rxgk_construct_authenticator(struct rxrpc_connection *conn,
|
|
|
|
struct sk_buff *challenge,
|
|
|
|
const struct krb5_buffer *appdata,
|
|
|
|
struct sk_buff *response,
|
|
|
|
size_t offset)
|
|
|
|
{
|
|
|
|
struct {
|
|
|
|
u8 nonce[20];
|
|
|
|
__be32 appdata_len;
|
|
|
|
} a;
|
|
|
|
struct {
|
|
|
|
__be32 level;
|
|
|
|
__be32 epoch;
|
|
|
|
__be32 cid;
|
|
|
|
__be32 call_numbers_count;
|
|
|
|
__be32 call_numbers[4];
|
|
|
|
} b;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = skb_copy_bits(challenge, sizeof(struct rxrpc_wire_header),
|
|
|
|
a.nonce, sizeof(a.nonce));
|
|
|
|
if (ret < 0)
|
|
|
|
return -EPROTO;
|
|
|
|
|
|
|
|
a.appdata_len = htonl(appdata->len);
|
|
|
|
|
|
|
|
ret = skb_store_bits(response, offset, &a, sizeof(a));
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
offset += sizeof(a);
|
|
|
|
|
|
|
|
if (appdata->len) {
|
|
|
|
ret = skb_store_bits(response, offset, appdata->data, appdata->len);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
offset += appdata->len;
|
|
|
|
|
|
|
|
ret = rxgk_pad_out(response, appdata->len, offset);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
offset += ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
b.level = htonl(conn->security_level);
|
|
|
|
b.epoch = htonl(conn->proto.epoch);
|
|
|
|
b.cid = htonl(conn->proto.cid);
|
|
|
|
b.call_numbers_count = htonl(4);
|
|
|
|
b.call_numbers[0] = htonl(conn->channels[0].call_counter);
|
|
|
|
b.call_numbers[1] = htonl(conn->channels[1].call_counter);
|
|
|
|
b.call_numbers[2] = htonl(conn->channels[2].call_counter);
|
|
|
|
b.call_numbers[3] = htonl(conn->channels[3].call_counter);
|
|
|
|
|
|
|
|
ret = skb_store_bits(response, offset, &b, sizeof(b));
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
return sizeof(a) + xdr_round_up(appdata->len) + sizeof(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t rxgk_encrypt_authenticator(struct rxrpc_connection *conn,
|
|
|
|
struct rxgk_context *gk,
|
|
|
|
struct sk_buff *response,
|
|
|
|
size_t offset,
|
|
|
|
size_t alloc_len,
|
|
|
|
size_t auth_offset,
|
|
|
|
size_t auth_len)
|
|
|
|
{
|
|
|
|
struct scatterlist sg[16];
|
|
|
|
int nr_sg;
|
|
|
|
|
|
|
|
sg_init_table(sg, ARRAY_SIZE(sg));
|
|
|
|
nr_sg = skb_to_sgvec(response, sg, offset, alloc_len);
|
|
|
|
if (unlikely(nr_sg < 0))
|
|
|
|
return nr_sg;
|
|
|
|
return crypto_krb5_encrypt(gk->krb5, gk->resp_enc, sg, nr_sg, alloc_len,
|
|
|
|
auth_offset, auth_len, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Construct the response.
|
|
|
|
*
|
|
|
|
* struct RXGK_Response {
|
|
|
|
* rxgkTime start_time;
|
|
|
|
* RXGK_Data token;
|
|
|
|
* opaque authenticator<RXGK_MAXAUTHENTICATOR>
|
|
|
|
* };
|
|
|
|
*/
|
|
|
|
static int rxgk_construct_response(struct rxrpc_connection *conn,
|
|
|
|
struct sk_buff *challenge,
|
|
|
|
struct krb5_buffer *appdata)
|
|
|
|
{
|
|
|
|
struct rxrpc_skb_priv *csp, *rsp;
|
|
|
|
struct rxgk_context *gk;
|
|
|
|
struct sk_buff *response;
|
|
|
|
size_t len, auth_len, authx_len, offset, auth_offset, authx_offset;
|
|
|
|
__be32 tmp;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
gk = rxgk_get_key(conn, NULL);
|
|
|
|
if (IS_ERR(gk))
|
|
|
|
return PTR_ERR(gk);
|
|
|
|
|
|
|
|
auth_len = 20 + (4 + appdata->len) + 12 + (1 + 4) * 4;
|
|
|
|
authx_len = crypto_krb5_how_much_buffer(gk->krb5, KRB5_ENCRYPT_MODE,
|
|
|
|
auth_len, &auth_offset);
|
|
|
|
len = sizeof(struct rxrpc_wire_header) +
|
|
|
|
8 + (4 + xdr_round_up(gk->key->ticket.len)) + (4 + authx_len);
|
|
|
|
|
|
|
|
response = alloc_skb_with_frags(0, len, 0, &ret, GFP_NOFS);
|
|
|
|
if (!response)
|
|
|
|
goto error;
|
|
|
|
rxrpc_new_skb(response, rxrpc_skb_new_response_rxgk);
|
|
|
|
response->len = len;
|
|
|
|
response->data_len = len;
|
|
|
|
|
|
|
|
ret = rxgk_insert_response_header(conn, gk, response, 0);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
offset = ret;
|
|
|
|
|
|
|
|
ret = skb_store_bits(response, offset, gk->key->ticket.data, gk->key->ticket.len);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
offset += gk->key->ticket.len;
|
|
|
|
ret = rxgk_pad_out(response, gk->key->ticket.len, offset);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
authx_offset = offset + ret + 4; /* Leave a gap for the length. */
|
|
|
|
|
|
|
|
ret = rxgk_construct_authenticator(conn, challenge, appdata, response,
|
|
|
|
authx_offset + auth_offset);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
auth_len = ret;
|
|
|
|
|
|
|
|
ret = rxgk_encrypt_authenticator(conn, gk, response,
|
|
|
|
authx_offset, authx_len,
|
|
|
|
auth_offset, auth_len);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
authx_len = ret;
|
|
|
|
|
|
|
|
tmp = htonl(authx_len);
|
|
|
|
ret = skb_store_bits(response, authx_offset - 4, &tmp, 4);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
ret = rxgk_pad_out(response, authx_len, authx_offset + authx_len);
|
|
|
|
if (ret < 0)
|
2025-04-23 11:25:45 +03:00
|
|
|
goto error;
|
2025-04-11 10:52:53 +01:00
|
|
|
len = authx_offset + authx_len + ret;
|
|
|
|
|
|
|
|
if (len != response->len) {
|
|
|
|
response->len = len;
|
|
|
|
response->data_len = len;
|
|
|
|
}
|
|
|
|
|
|
|
|
csp = rxrpc_skb(challenge);
|
|
|
|
rsp = rxrpc_skb(response);
|
|
|
|
rsp->resp.len = len;
|
|
|
|
rsp->resp.challenge_serial = csp->hdr.serial;
|
|
|
|
rxrpc_post_response(conn, response);
|
|
|
|
response = NULL;
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
rxrpc_free_skb(response, rxrpc_skb_put_response);
|
|
|
|
rxgk_put(gk);
|
|
|
|
_leave(" = %d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Respond to a challenge packet.
|
|
|
|
*/
|
|
|
|
static int rxgk_respond_to_challenge(struct rxrpc_connection *conn,
|
|
|
|
struct sk_buff *challenge,
|
|
|
|
struct krb5_buffer *appdata)
|
|
|
|
{
|
|
|
|
_enter("{%d,%x}", conn->debug_id, key_serial(conn->key));
|
|
|
|
|
|
|
|
if (key_validate(conn->key) < 0)
|
|
|
|
return rxrpc_abort_conn(conn, NULL, RXGK_EXPIRED, -EPROTO,
|
|
|
|
rxgk_abort_chall_key_expired);
|
|
|
|
|
|
|
|
return rxgk_construct_response(conn, challenge, appdata);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rxgk_respond_to_challenge_no_appdata(struct rxrpc_connection *conn,
|
|
|
|
struct sk_buff *challenge)
|
|
|
|
{
|
|
|
|
struct krb5_buffer appdata = {};
|
|
|
|
|
|
|
|
return rxgk_respond_to_challenge(conn, challenge, &appdata);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* rxgk_kernel_respond_to_challenge - Respond to a challenge with appdata
|
|
|
|
* @challenge: The challenge to respond to
|
|
|
|
* @appdata: The application data to include in the RESPONSE authenticator
|
|
|
|
*
|
|
|
|
* Allow a kernel application to respond to a CHALLENGE with application data
|
|
|
|
* to be included in the RxGK RESPONSE Authenticator.
|
|
|
|
*
|
|
|
|
* Return: %0 if successful and a negative error code otherwise.
|
|
|
|
*/
|
|
|
|
int rxgk_kernel_respond_to_challenge(struct sk_buff *challenge,
|
|
|
|
struct krb5_buffer *appdata)
|
|
|
|
{
|
|
|
|
struct rxrpc_skb_priv *csp = rxrpc_skb(challenge);
|
|
|
|
|
|
|
|
return rxgk_respond_to_challenge(csp->chall.conn, challenge, appdata);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(rxgk_kernel_respond_to_challenge);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parse sendmsg() control message and respond to challenge. We need to see if
|
|
|
|
* there's an appdata to fish out.
|
|
|
|
*/
|
|
|
|
static int rxgk_sendmsg_respond_to_challenge(struct sk_buff *challenge,
|
|
|
|
struct msghdr *msg)
|
|
|
|
{
|
|
|
|
struct krb5_buffer appdata = {};
|
|
|
|
struct cmsghdr *cmsg;
|
|
|
|
|
|
|
|
for_each_cmsghdr(cmsg, msg) {
|
|
|
|
if (cmsg->cmsg_level != SOL_RXRPC ||
|
|
|
|
cmsg->cmsg_type != RXRPC_RESP_RXGK_APPDATA)
|
|
|
|
continue;
|
|
|
|
if (appdata.data)
|
|
|
|
return -EINVAL;
|
|
|
|
appdata.data = CMSG_DATA(cmsg);
|
|
|
|
appdata.len = cmsg->cmsg_len - sizeof(struct cmsghdr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rxgk_kernel_respond_to_challenge(challenge, &appdata);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Verify the authenticator.
|
|
|
|
*
|
|
|
|
* struct RXGK_Authenticator {
|
|
|
|
* opaque nonce[20];
|
|
|
|
* opaque appdata<>;
|
|
|
|
* RXGK_Level level;
|
|
|
|
* unsigned int epoch;
|
|
|
|
* unsigned int cid;
|
|
|
|
* unsigned int call_numbers<>;
|
|
|
|
* };
|
|
|
|
*/
|
|
|
|
static int rxgk_do_verify_authenticator(struct rxrpc_connection *conn,
|
|
|
|
const struct krb5_enctype *krb5,
|
|
|
|
struct sk_buff *skb,
|
|
|
|
__be32 *p, __be32 *end)
|
|
|
|
{
|
|
|
|
u32 app_len, call_count, level, epoch, cid, i;
|
|
|
|
|
|
|
|
_enter("");
|
|
|
|
|
|
|
|
if (memcmp(p, conn->rxgk.nonce, 20) != 0)
|
|
|
|
return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO,
|
|
|
|
rxgk_abort_resp_bad_nonce);
|
|
|
|
p += 20 / sizeof(__be32);
|
|
|
|
|
|
|
|
app_len = ntohl(*p++);
|
|
|
|
if (app_len > (end - p) * sizeof(__be32))
|
|
|
|
return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO,
|
|
|
|
rxgk_abort_resp_short_applen);
|
|
|
|
|
|
|
|
p += xdr_round_up(app_len) / sizeof(__be32);
|
|
|
|
if (end - p < 4)
|
|
|
|
return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO,
|
|
|
|
rxgk_abort_resp_short_applen);
|
|
|
|
|
|
|
|
level = ntohl(*p++);
|
|
|
|
epoch = ntohl(*p++);
|
|
|
|
cid = ntohl(*p++);
|
|
|
|
call_count = ntohl(*p++);
|
|
|
|
|
|
|
|
if (level != conn->security_level ||
|
|
|
|
epoch != conn->proto.epoch ||
|
|
|
|
cid != conn->proto.cid ||
|
|
|
|
call_count > 4)
|
|
|
|
return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO,
|
|
|
|
rxgk_abort_resp_bad_param);
|
|
|
|
|
|
|
|
if (end - p < call_count)
|
|
|
|
return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO,
|
|
|
|
rxgk_abort_resp_short_call_list);
|
|
|
|
|
|
|
|
for (i = 0; i < call_count; i++) {
|
|
|
|
u32 call_id = ntohl(*p++);
|
|
|
|
|
|
|
|
if (call_id > INT_MAX)
|
|
|
|
return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO,
|
|
|
|
rxgk_abort_resp_bad_callid);
|
|
|
|
|
|
|
|
if (call_id < conn->channels[i].call_counter)
|
|
|
|
return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO,
|
|
|
|
rxgk_abort_resp_call_ctr);
|
|
|
|
|
|
|
|
if (call_id > conn->channels[i].call_counter) {
|
|
|
|
if (conn->channels[i].call)
|
|
|
|
return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO,
|
|
|
|
rxgk_abort_resp_call_state);
|
|
|
|
|
|
|
|
conn->channels[i].call_counter = call_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_leave(" = 0");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Extract the authenticator and verify it.
|
|
|
|
*/
|
|
|
|
static int rxgk_verify_authenticator(struct rxrpc_connection *conn,
|
|
|
|
const struct krb5_enctype *krb5,
|
|
|
|
struct sk_buff *skb,
|
|
|
|
unsigned int auth_offset, unsigned int auth_len)
|
|
|
|
{
|
|
|
|
void *auth;
|
|
|
|
__be32 *p;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
auth = kmalloc(auth_len, GFP_NOFS);
|
|
|
|
if (!auth)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
ret = skb_copy_bits(skb, auth_offset, auth, auth_len);
|
|
|
|
if (ret < 0) {
|
|
|
|
ret = rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EPROTO,
|
|
|
|
rxgk_abort_resp_short_auth);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = auth;
|
|
|
|
ret = rxgk_do_verify_authenticator(conn, krb5, skb, p, p + auth_len);
|
|
|
|
error:
|
|
|
|
kfree(auth);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Verify a response.
|
|
|
|
*
|
|
|
|
* struct RXGK_Response {
|
|
|
|
* rxgkTime start_time;
|
|
|
|
* RXGK_Data token;
|
|
|
|
* opaque authenticator<RXGK_MAXAUTHENTICATOR>
|
|
|
|
* };
|
|
|
|
*/
|
|
|
|
static int rxgk_verify_response(struct rxrpc_connection *conn,
|
|
|
|
struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
const struct krb5_enctype *krb5;
|
|
|
|
struct rxrpc_key_token *token;
|
|
|
|
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
|
|
|
|
struct rxgk_response rhdr;
|
|
|
|
struct rxgk_context *gk;
|
|
|
|
struct key *key = NULL;
|
|
|
|
unsigned int offset = sizeof(struct rxrpc_wire_header);
|
|
|
|
unsigned int len = skb->len - sizeof(struct rxrpc_wire_header);
|
|
|
|
unsigned int token_offset, token_len;
|
|
|
|
unsigned int auth_offset, auth_len;
|
|
|
|
__be32 xauth_len;
|
|
|
|
int ret, ec;
|
|
|
|
|
|
|
|
_enter("{%d}", conn->debug_id);
|
|
|
|
|
|
|
|
/* Parse the RXGK_Response object */
|
|
|
|
if (sizeof(rhdr) + sizeof(__be32) > len)
|
|
|
|
goto short_packet;
|
|
|
|
|
|
|
|
if (skb_copy_bits(skb, offset, &rhdr, sizeof(rhdr)) < 0)
|
|
|
|
goto short_packet;
|
|
|
|
offset += sizeof(rhdr);
|
|
|
|
len -= sizeof(rhdr);
|
|
|
|
|
|
|
|
token_offset = offset;
|
|
|
|
token_len = ntohl(rhdr.token_len);
|
|
|
|
if (xdr_round_up(token_len) + sizeof(__be32) > len)
|
|
|
|
goto short_packet;
|
|
|
|
|
2025-04-11 10:52:58 +01:00
|
|
|
trace_rxrpc_rx_response(conn, sp->hdr.serial, 0, sp->hdr.cksum, token_len);
|
|
|
|
|
2025-04-11 10:52:53 +01:00
|
|
|
offset += xdr_round_up(token_len);
|
|
|
|
len -= xdr_round_up(token_len);
|
|
|
|
|
|
|
|
if (skb_copy_bits(skb, offset, &xauth_len, sizeof(xauth_len)) < 0)
|
|
|
|
goto short_packet;
|
|
|
|
offset += sizeof(xauth_len);
|
|
|
|
len -= sizeof(xauth_len);
|
|
|
|
|
|
|
|
auth_offset = offset;
|
|
|
|
auth_len = ntohl(xauth_len);
|
|
|
|
if (auth_len < len)
|
|
|
|
goto short_packet;
|
|
|
|
if (auth_len & 3)
|
|
|
|
goto inconsistent;
|
|
|
|
if (auth_len < 20 + 9 * 4)
|
|
|
|
goto auth_too_short;
|
|
|
|
|
|
|
|
/* We need to extract and decrypt the token and instantiate a session
|
|
|
|
* key for it. This bit, however, is application-specific. If
|
|
|
|
* possible, we use a default parser, but we might end up bumping this
|
|
|
|
* to the app to deal with - which might mean a round trip to
|
|
|
|
* userspace.
|
|
|
|
*/
|
|
|
|
ret = rxgk_extract_token(conn, skb, token_offset, token_len, &key);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/* We now have a key instantiated from the decrypted ticket. We can
|
|
|
|
* pass this to the application so that they can parse the ticket
|
|
|
|
* content and we can use the session key it contains to derive the
|
|
|
|
* keys we need.
|
|
|
|
*
|
|
|
|
* Note that we have to switch enctype at this point as the enctype of
|
|
|
|
* the ticket doesn't necessarily match that of the transport.
|
|
|
|
*/
|
|
|
|
token = key->payload.data[0];
|
|
|
|
conn->security_level = token->rxgk->level;
|
|
|
|
conn->rxgk.start_time = __be64_to_cpu(rhdr.start_time);
|
|
|
|
|
|
|
|
gk = rxgk_generate_transport_key(conn, token->rxgk, sp->hdr.cksum, GFP_NOFS);
|
|
|
|
if (IS_ERR(gk)) {
|
|
|
|
ret = PTR_ERR(gk);
|
|
|
|
goto cant_get_token;
|
|
|
|
}
|
|
|
|
|
|
|
|
krb5 = gk->krb5;
|
|
|
|
|
|
|
|
trace_rxrpc_rx_response(conn, sp->hdr.serial, krb5->etype, sp->hdr.cksum, token_len);
|
|
|
|
|
|
|
|
/* Decrypt, parse and verify the authenticator. */
|
|
|
|
ret = rxgk_decrypt_skb(krb5, gk->resp_enc, skb,
|
|
|
|
&auth_offset, &auth_len, &ec);
|
|
|
|
if (ret < 0) {
|
|
|
|
rxrpc_abort_conn(conn, skb, RXGK_SEALEDINCON, ret,
|
|
|
|
rxgk_abort_resp_auth_dec);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = rxgk_verify_authenticator(conn, krb5, skb, auth_offset, auth_len);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
conn->key = key;
|
|
|
|
key = NULL;
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
|
|
key_put(key);
|
|
|
|
_leave(" = %d", ret);
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
inconsistent:
|
|
|
|
ret = rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO,
|
|
|
|
rxgk_abort_resp_xdr_align);
|
|
|
|
goto out;
|
|
|
|
auth_too_short:
|
|
|
|
ret = rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO,
|
|
|
|
rxgk_abort_resp_short_auth);
|
|
|
|
goto out;
|
|
|
|
short_packet:
|
|
|
|
ret = rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO,
|
|
|
|
rxgk_abort_resp_short_packet);
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
cant_get_token:
|
|
|
|
switch (ret) {
|
|
|
|
case -ENOMEM:
|
|
|
|
goto temporary_error;
|
|
|
|
case -EINVAL:
|
|
|
|
ret = rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EKEYREJECTED,
|
|
|
|
rxgk_abort_resp_internal_error);
|
|
|
|
goto out;
|
|
|
|
case -ENOPKG:
|
|
|
|
ret = rxrpc_abort_conn(conn, skb, KRB5_PROG_KEYTYPE_NOSUPP,
|
|
|
|
-EKEYREJECTED, rxgk_abort_resp_nopkg);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
temporary_error:
|
|
|
|
/* Ignore the response packet if we got a temporary error such as
|
|
|
|
* ENOMEM. We just want to send the challenge again. Note that we
|
|
|
|
* also come out this way if the ticket decryption fails.
|
|
|
|
*/
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* clear the connection security
|
|
|
|
*/
|
|
|
|
static void rxgk_clear(struct rxrpc_connection *conn)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(conn->rxgk.keys); i++)
|
|
|
|
rxgk_put(conn->rxgk.keys[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialise the RxGK security service.
|
|
|
|
*/
|
|
|
|
static int rxgk_init(void)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Clean up the RxGK security service.
|
|
|
|
*/
|
|
|
|
static void rxgk_exit(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* RxRPC YFS GSSAPI-based security
|
|
|
|
*/
|
|
|
|
const struct rxrpc_security rxgk_yfs = {
|
|
|
|
.name = "yfs-rxgk",
|
|
|
|
.security_index = RXRPC_SECURITY_YFS_RXGK,
|
|
|
|
.no_key_abort = RXGK_NOTAUTH,
|
|
|
|
.init = rxgk_init,
|
|
|
|
.exit = rxgk_exit,
|
|
|
|
.preparse_server_key = rxgk_preparse_server_key,
|
|
|
|
.free_preparse_server_key = rxgk_free_preparse_server_key,
|
|
|
|
.destroy_server_key = rxgk_destroy_server_key,
|
|
|
|
.describe_server_key = rxgk_describe_server_key,
|
|
|
|
.init_connection_security = rxgk_init_connection_security,
|
|
|
|
.alloc_txbuf = rxgk_alloc_txbuf,
|
|
|
|
.secure_packet = rxgk_secure_packet,
|
|
|
|
.verify_packet = rxgk_verify_packet,
|
|
|
|
.free_call_crypto = rxgk_free_call_crypto,
|
|
|
|
.issue_challenge = rxgk_issue_challenge,
|
|
|
|
.validate_challenge = rxgk_validate_challenge,
|
|
|
|
.challenge_to_recvmsg = rxgk_challenge_to_recvmsg,
|
|
|
|
.sendmsg_respond_to_challenge = rxgk_sendmsg_respond_to_challenge,
|
|
|
|
.respond_to_challenge = rxgk_respond_to_challenge_no_appdata,
|
|
|
|
.verify_response = rxgk_verify_response,
|
|
|
|
.clear = rxgk_clear,
|
|
|
|
.default_decode_ticket = rxgk_yfs_decode_ticket,
|
|
|
|
};
|