// 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 #include #include #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; }