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