mirror of https://github.com/torvalds/linux.git
289 lines
6.9 KiB
C
289 lines
6.9 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* RxGK transport key derivation.
|
|
*
|
|
* 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/key-type.h>
|
|
#include <linux/slab.h>
|
|
#include <keys/rxrpc-type.h>
|
|
#include "ar-internal.h"
|
|
#include "rxgk_common.h"
|
|
|
|
#define round16(x) (((x) + 15) & ~15)
|
|
|
|
/*
|
|
* Constants used to derive the keys and hmacs actually used for doing stuff.
|
|
*/
|
|
#define RXGK_CLIENT_ENC_PACKET 1026U // 0x402
|
|
#define RXGK_CLIENT_MIC_PACKET 1027U // 0x403
|
|
#define RXGK_SERVER_ENC_PACKET 1028U // 0x404
|
|
#define RXGK_SERVER_MIC_PACKET 1029U // 0x405
|
|
#define RXGK_CLIENT_ENC_RESPONSE 1030U // 0x406
|
|
#define RXGK_SERVER_ENC_TOKEN 1036U // 0x40c
|
|
|
|
static void rxgk_free(struct rxgk_context *gk)
|
|
{
|
|
if (gk->tx_Kc)
|
|
crypto_free_shash(gk->tx_Kc);
|
|
if (gk->rx_Kc)
|
|
crypto_free_shash(gk->rx_Kc);
|
|
if (gk->tx_enc)
|
|
crypto_free_aead(gk->tx_enc);
|
|
if (gk->rx_enc)
|
|
crypto_free_aead(gk->rx_enc);
|
|
if (gk->resp_enc)
|
|
crypto_free_aead(gk->resp_enc);
|
|
kfree(gk);
|
|
}
|
|
|
|
void rxgk_put(struct rxgk_context *gk)
|
|
{
|
|
if (gk && refcount_dec_and_test(&gk->usage))
|
|
rxgk_free(gk);
|
|
}
|
|
|
|
/*
|
|
* Transport key derivation function.
|
|
*
|
|
* TK = random-to-key(PRF+(K0, L,
|
|
* epoch || cid || start_time || key_number))
|
|
* [tools.ietf.org/html/draft-wilkinson-afs3-rxgk-11 sec 8.3]
|
|
*/
|
|
static int rxgk_derive_transport_key(struct rxrpc_connection *conn,
|
|
struct rxgk_context *gk,
|
|
const struct rxgk_key *rxgk,
|
|
struct krb5_buffer *TK,
|
|
gfp_t gfp)
|
|
{
|
|
const struct krb5_enctype *krb5 = gk->krb5;
|
|
struct krb5_buffer conn_info;
|
|
unsigned int L = krb5->key_bytes;
|
|
__be32 *info;
|
|
u8 *buffer;
|
|
int ret;
|
|
|
|
_enter("");
|
|
|
|
conn_info.len = sizeof(__be32) * 5;
|
|
|
|
buffer = kzalloc(round16(conn_info.len), gfp);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
|
|
conn_info.data = buffer;
|
|
|
|
info = (__be32 *)conn_info.data;
|
|
info[0] = htonl(conn->proto.epoch);
|
|
info[1] = htonl(conn->proto.cid);
|
|
info[2] = htonl(conn->rxgk.start_time >> 32);
|
|
info[3] = htonl(conn->rxgk.start_time >> 0);
|
|
info[4] = htonl(gk->key_number);
|
|
|
|
ret = crypto_krb5_calc_PRFplus(krb5, &rxgk->key, L, &conn_info, TK, gfp);
|
|
kfree_sensitive(buffer);
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Set up the ciphers for the usage keys.
|
|
*/
|
|
static int rxgk_set_up_ciphers(struct rxrpc_connection *conn,
|
|
struct rxgk_context *gk,
|
|
const struct rxgk_key *rxgk,
|
|
gfp_t gfp)
|
|
{
|
|
const struct krb5_enctype *krb5 = gk->krb5;
|
|
struct crypto_shash *shash;
|
|
struct crypto_aead *aead;
|
|
struct krb5_buffer TK;
|
|
bool service = rxrpc_conn_is_service(conn);
|
|
int ret;
|
|
u8 *buffer;
|
|
|
|
buffer = kzalloc(krb5->key_bytes, gfp);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
|
|
TK.len = krb5->key_bytes;
|
|
TK.data = buffer;
|
|
|
|
ret = rxgk_derive_transport_key(conn, gk, rxgk, &TK, gfp);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
aead = crypto_krb5_prepare_encryption(krb5, &TK, RXGK_CLIENT_ENC_RESPONSE, gfp);
|
|
if (IS_ERR(aead))
|
|
goto aead_error;
|
|
gk->resp_enc = aead;
|
|
|
|
if (crypto_aead_blocksize(gk->resp_enc) != krb5->block_len ||
|
|
crypto_aead_authsize(gk->resp_enc) != krb5->cksum_len) {
|
|
pr_notice("algo inconsistent with krb5 table %u!=%u or %u!=%u\n",
|
|
crypto_aead_blocksize(gk->resp_enc), krb5->block_len,
|
|
crypto_aead_authsize(gk->resp_enc), krb5->cksum_len);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (service) {
|
|
switch (conn->security_level) {
|
|
case RXRPC_SECURITY_AUTH:
|
|
shash = crypto_krb5_prepare_checksum(
|
|
krb5, &TK, RXGK_SERVER_MIC_PACKET, gfp);
|
|
if (IS_ERR(shash))
|
|
goto hash_error;
|
|
gk->tx_Kc = shash;
|
|
shash = crypto_krb5_prepare_checksum(
|
|
krb5, &TK, RXGK_CLIENT_MIC_PACKET, gfp);
|
|
if (IS_ERR(shash))
|
|
goto hash_error;
|
|
gk->rx_Kc = shash;
|
|
break;
|
|
case RXRPC_SECURITY_ENCRYPT:
|
|
aead = crypto_krb5_prepare_encryption(
|
|
krb5, &TK, RXGK_SERVER_ENC_PACKET, gfp);
|
|
if (IS_ERR(aead))
|
|
goto aead_error;
|
|
gk->tx_enc = aead;
|
|
aead = crypto_krb5_prepare_encryption(
|
|
krb5, &TK, RXGK_CLIENT_ENC_PACKET, gfp);
|
|
if (IS_ERR(aead))
|
|
goto aead_error;
|
|
gk->rx_enc = aead;
|
|
break;
|
|
}
|
|
} else {
|
|
switch (conn->security_level) {
|
|
case RXRPC_SECURITY_AUTH:
|
|
shash = crypto_krb5_prepare_checksum(
|
|
krb5, &TK, RXGK_CLIENT_MIC_PACKET, gfp);
|
|
if (IS_ERR(shash))
|
|
goto hash_error;
|
|
gk->tx_Kc = shash;
|
|
shash = crypto_krb5_prepare_checksum(
|
|
krb5, &TK, RXGK_SERVER_MIC_PACKET, gfp);
|
|
if (IS_ERR(shash))
|
|
goto hash_error;
|
|
gk->rx_Kc = shash;
|
|
break;
|
|
case RXRPC_SECURITY_ENCRYPT:
|
|
aead = crypto_krb5_prepare_encryption(
|
|
krb5, &TK, RXGK_CLIENT_ENC_PACKET, gfp);
|
|
if (IS_ERR(aead))
|
|
goto aead_error;
|
|
gk->tx_enc = aead;
|
|
aead = crypto_krb5_prepare_encryption(
|
|
krb5, &TK, RXGK_SERVER_ENC_PACKET, gfp);
|
|
if (IS_ERR(aead))
|
|
goto aead_error;
|
|
gk->rx_enc = aead;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
out:
|
|
kfree_sensitive(buffer);
|
|
return ret;
|
|
aead_error:
|
|
ret = PTR_ERR(aead);
|
|
goto out;
|
|
hash_error:
|
|
ret = PTR_ERR(shash);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Derive a transport key for a connection and then derive a bunch of usage
|
|
* keys from it and set up ciphers using them.
|
|
*/
|
|
struct rxgk_context *rxgk_generate_transport_key(struct rxrpc_connection *conn,
|
|
const struct rxgk_key *key,
|
|
unsigned int key_number,
|
|
gfp_t gfp)
|
|
{
|
|
struct rxgk_context *gk;
|
|
unsigned long lifetime;
|
|
int ret = -ENOPKG;
|
|
|
|
_enter("");
|
|
|
|
gk = kzalloc(sizeof(*gk), GFP_KERNEL);
|
|
if (!gk)
|
|
return ERR_PTR(-ENOMEM);
|
|
refcount_set(&gk->usage, 1);
|
|
gk->key = key;
|
|
gk->key_number = key_number;
|
|
|
|
gk->krb5 = crypto_krb5_find_enctype(key->enctype);
|
|
if (!gk->krb5)
|
|
goto err_tk;
|
|
|
|
ret = rxgk_set_up_ciphers(conn, gk, key, gfp);
|
|
if (ret)
|
|
goto err_tk;
|
|
|
|
/* Set the remaining number of bytes encrypted with this key that may
|
|
* be transmitted before rekeying. Note that the spec has been
|
|
* interpreted differently on this point...
|
|
*/
|
|
switch (key->bytelife) {
|
|
case 0:
|
|
case 63:
|
|
gk->bytes_remaining = LLONG_MAX;
|
|
break;
|
|
case 1 ... 62:
|
|
gk->bytes_remaining = 1LL << key->bytelife;
|
|
break;
|
|
default:
|
|
gk->bytes_remaining = key->bytelife;
|
|
break;
|
|
}
|
|
|
|
/* Set the time after which rekeying must occur */
|
|
if (key->lifetime) {
|
|
lifetime = min_t(u64, key->lifetime, INT_MAX / HZ);
|
|
lifetime *= HZ;
|
|
} else {
|
|
lifetime = MAX_JIFFY_OFFSET;
|
|
}
|
|
gk->expiry = jiffies + lifetime;
|
|
return gk;
|
|
|
|
err_tk:
|
|
rxgk_put(gk);
|
|
_leave(" = %d", ret);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/*
|
|
* Use the server secret key to set up the ciphers that will be used to extract
|
|
* the token from a response packet.
|
|
*/
|
|
int rxgk_set_up_token_cipher(const struct krb5_buffer *server_key,
|
|
struct crypto_aead **token_aead,
|
|
unsigned int enctype,
|
|
const struct krb5_enctype **_krb5,
|
|
gfp_t gfp)
|
|
{
|
|
const struct krb5_enctype *krb5;
|
|
struct crypto_aead *aead;
|
|
|
|
krb5 = crypto_krb5_find_enctype(enctype);
|
|
if (!krb5)
|
|
return -ENOPKG;
|
|
|
|
aead = crypto_krb5_prepare_encryption(krb5, server_key, RXGK_SERVER_ENC_TOKEN, gfp);
|
|
if (IS_ERR(aead))
|
|
return PTR_ERR(aead);
|
|
|
|
*_krb5 = krb5;
|
|
*token_aead = aead;
|
|
return 0;
|
|
}
|