| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- // 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_obj(*gk);
- 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;
- }
|