| // SPDX-License-Identifier: GPL-2.0-or-later | 
 | /* Kerberos 5 crypto library. | 
 |  * | 
 |  * 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/module.h> | 
 | #include <linux/export.h> | 
 | #include <linux/kernel.h> | 
 | #include "internal.h" | 
 |  | 
 | MODULE_DESCRIPTION("Kerberos 5 crypto"); | 
 | MODULE_AUTHOR("Red Hat, Inc."); | 
 | MODULE_LICENSE("GPL"); | 
 |  | 
 | static const struct krb5_enctype *const krb5_supported_enctypes[] = { | 
 | 	&krb5_aes128_cts_hmac_sha1_96, | 
 | 	&krb5_aes256_cts_hmac_sha1_96, | 
 | 	&krb5_aes128_cts_hmac_sha256_128, | 
 | 	&krb5_aes256_cts_hmac_sha384_192, | 
 | 	&krb5_camellia128_cts_cmac, | 
 | 	&krb5_camellia256_cts_cmac, | 
 | }; | 
 |  | 
 | /** | 
 |  * crypto_krb5_find_enctype - Find the handler for a Kerberos5 encryption type | 
 |  * @enctype: The standard Kerberos encryption type number | 
 |  * | 
 |  * Look up a Kerberos encryption type by number.  If successful, returns a | 
 |  * pointer to the type tables; returns NULL otherwise. | 
 |  */ | 
 | const struct krb5_enctype *crypto_krb5_find_enctype(u32 enctype) | 
 | { | 
 | 	const struct krb5_enctype *krb5; | 
 | 	size_t i; | 
 |  | 
 | 	for (i = 0; i < ARRAY_SIZE(krb5_supported_enctypes); i++) { | 
 | 		krb5 = krb5_supported_enctypes[i]; | 
 | 		if (krb5->etype == enctype) | 
 | 			return krb5; | 
 | 	} | 
 |  | 
 | 	return NULL; | 
 | } | 
 | EXPORT_SYMBOL(crypto_krb5_find_enctype); | 
 |  | 
 | /** | 
 |  * crypto_krb5_how_much_buffer - Work out how much buffer is required for an amount of data | 
 |  * @krb5: The encoding to use. | 
 |  * @mode: The mode in which to operated (checksum/encrypt) | 
 |  * @data_size: How much data we want to allow for | 
 |  * @_offset: Where to place the offset into the buffer | 
 |  * | 
 |  * Calculate how much buffer space is required to wrap a given amount of data. | 
 |  * This allows for a confounder, padding and checksum as appropriate.  The | 
 |  * amount of buffer required is returned and the offset into the buffer at | 
 |  * which the data will start is placed in *_offset. | 
 |  */ | 
 | size_t crypto_krb5_how_much_buffer(const struct krb5_enctype *krb5, | 
 | 				   enum krb5_crypto_mode mode, | 
 | 				   size_t data_size, size_t *_offset) | 
 | { | 
 | 	switch (mode) { | 
 | 	case KRB5_CHECKSUM_MODE: | 
 | 		*_offset = krb5->cksum_len; | 
 | 		return krb5->cksum_len + data_size; | 
 |  | 
 | 	case KRB5_ENCRYPT_MODE: | 
 | 		*_offset = krb5->conf_len; | 
 | 		return krb5->conf_len + data_size + krb5->cksum_len; | 
 |  | 
 | 	default: | 
 | 		WARN_ON(1); | 
 | 		*_offset = 0; | 
 | 		return 0; | 
 | 	} | 
 | } | 
 | EXPORT_SYMBOL(crypto_krb5_how_much_buffer); | 
 |  | 
 | /** | 
 |  * crypto_krb5_how_much_data - Work out how much data can fit in an amount of buffer | 
 |  * @krb5: The encoding to use. | 
 |  * @mode: The mode in which to operated (checksum/encrypt) | 
 |  * @_buffer_size: How much buffer we want to allow for (may be reduced) | 
 |  * @_offset: Where to place the offset into the buffer | 
 |  * | 
 |  * Calculate how much data can be fitted into given amount of buffer.  This | 
 |  * allows for a confounder, padding and checksum as appropriate.  The amount of | 
 |  * data that will fit is returned, the amount of buffer required is shrunk to | 
 |  * allow for alignment and the offset into the buffer at which the data will | 
 |  * start is placed in *_offset. | 
 |  */ | 
 | size_t crypto_krb5_how_much_data(const struct krb5_enctype *krb5, | 
 | 				 enum krb5_crypto_mode mode, | 
 | 				 size_t *_buffer_size, size_t *_offset) | 
 | { | 
 | 	size_t buffer_size = *_buffer_size, data_size; | 
 |  | 
 | 	switch (mode) { | 
 | 	case KRB5_CHECKSUM_MODE: | 
 | 		if (WARN_ON(buffer_size < krb5->cksum_len + 1)) | 
 | 			goto bad; | 
 | 		*_offset = krb5->cksum_len; | 
 | 		return buffer_size - krb5->cksum_len; | 
 |  | 
 | 	case KRB5_ENCRYPT_MODE: | 
 | 		if (WARN_ON(buffer_size < krb5->conf_len + 1 + krb5->cksum_len)) | 
 | 			goto bad; | 
 | 		data_size = buffer_size - krb5->cksum_len; | 
 | 		*_offset = krb5->conf_len; | 
 | 		return data_size - krb5->conf_len; | 
 |  | 
 | 	default: | 
 | 		WARN_ON(1); | 
 | 		goto bad; | 
 | 	} | 
 |  | 
 | bad: | 
 | 	*_offset = 0; | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL(crypto_krb5_how_much_data); | 
 |  | 
 | /** | 
 |  * crypto_krb5_where_is_the_data - Find the data in a decrypted message | 
 |  * @krb5: The encoding to use. | 
 |  * @mode: Mode of operation | 
 |  * @_offset: Offset of the secure blob in the buffer; updated to data offset. | 
 |  * @_len: The length of the secure blob; updated to data length. | 
 |  * | 
 |  * Find the offset and size of the data in a secure message so that this | 
 |  * information can be used in the metadata buffer which will get added to the | 
 |  * digest by crypto_krb5_verify_mic(). | 
 |  */ | 
 | void crypto_krb5_where_is_the_data(const struct krb5_enctype *krb5, | 
 | 				   enum krb5_crypto_mode mode, | 
 | 				   size_t *_offset, size_t *_len) | 
 | { | 
 | 	switch (mode) { | 
 | 	case KRB5_CHECKSUM_MODE: | 
 | 		*_offset += krb5->cksum_len; | 
 | 		*_len -= krb5->cksum_len; | 
 | 		return; | 
 | 	case KRB5_ENCRYPT_MODE: | 
 | 		*_offset += krb5->conf_len; | 
 | 		*_len -= krb5->conf_len + krb5->cksum_len; | 
 | 		return; | 
 | 	default: | 
 | 		WARN_ON_ONCE(1); | 
 | 		return; | 
 | 	} | 
 | } | 
 | EXPORT_SYMBOL(crypto_krb5_where_is_the_data); | 
 |  | 
 | /* | 
 |  * Prepare the encryption with derived key data. | 
 |  */ | 
 | struct crypto_aead *krb5_prepare_encryption(const struct krb5_enctype *krb5, | 
 | 					    const struct krb5_buffer *keys, | 
 | 					    gfp_t gfp) | 
 | { | 
 | 	struct crypto_aead *ci = NULL; | 
 | 	int ret = -ENOMEM; | 
 |  | 
 | 	ci = crypto_alloc_aead(krb5->encrypt_name, 0, 0); | 
 | 	if (IS_ERR(ci)) { | 
 | 		ret = PTR_ERR(ci); | 
 | 		if (ret == -ENOENT) | 
 | 			ret = -ENOPKG; | 
 | 		goto err; | 
 | 	} | 
 |  | 
 | 	ret = crypto_aead_setkey(ci, keys->data, keys->len); | 
 | 	if (ret < 0) { | 
 | 		pr_err("Couldn't set AEAD key %s: %d\n", krb5->encrypt_name, ret); | 
 | 		goto err_ci; | 
 | 	} | 
 |  | 
 | 	ret = crypto_aead_setauthsize(ci, krb5->cksum_len); | 
 | 	if (ret < 0) { | 
 | 		pr_err("Couldn't set AEAD authsize %s: %d\n", krb5->encrypt_name, ret); | 
 | 		goto err_ci; | 
 | 	} | 
 |  | 
 | 	return ci; | 
 | err_ci: | 
 | 	crypto_free_aead(ci); | 
 | err: | 
 | 	return ERR_PTR(ret); | 
 | } | 
 |  | 
 | /** | 
 |  * crypto_krb5_prepare_encryption - Prepare AEAD crypto object for encryption-mode | 
 |  * @krb5: The encoding to use. | 
 |  * @TK: The transport key to use. | 
 |  * @usage: The usage constant for key derivation. | 
 |  * @gfp: Allocation flags. | 
 |  * | 
 |  * Allocate a crypto object that does all the necessary crypto, key it and set | 
 |  * its parameters and return the crypto handle to it.  This can then be used to | 
 |  * dispatch encrypt and decrypt operations. | 
 |  */ | 
 | struct crypto_aead *crypto_krb5_prepare_encryption(const struct krb5_enctype *krb5, | 
 | 						   const struct krb5_buffer *TK, | 
 | 						   u32 usage, gfp_t gfp) | 
 | { | 
 | 	struct crypto_aead *ci = NULL; | 
 | 	struct krb5_buffer keys = {}; | 
 | 	int ret; | 
 |  | 
 | 	ret = krb5->profile->derive_encrypt_keys(krb5, TK, usage, &keys, gfp); | 
 | 	if (ret < 0) | 
 | 		goto err; | 
 |  | 
 | 	ci = krb5_prepare_encryption(krb5, &keys, gfp); | 
 | 	if (IS_ERR(ci)) { | 
 | 		ret = PTR_ERR(ci); | 
 | 		goto err; | 
 | 	} | 
 |  | 
 | 	kfree(keys.data); | 
 | 	return ci; | 
 | err: | 
 | 	kfree(keys.data); | 
 | 	return ERR_PTR(ret); | 
 | } | 
 | EXPORT_SYMBOL(crypto_krb5_prepare_encryption); | 
 |  | 
 | /* | 
 |  * Prepare the checksum with derived key data. | 
 |  */ | 
 | struct crypto_shash *krb5_prepare_checksum(const struct krb5_enctype *krb5, | 
 | 					   const struct krb5_buffer *Kc, | 
 | 					   gfp_t gfp) | 
 | { | 
 | 	struct crypto_shash *ci = NULL; | 
 | 	int ret = -ENOMEM; | 
 |  | 
 | 	ci = crypto_alloc_shash(krb5->cksum_name, 0, 0); | 
 | 	if (IS_ERR(ci)) { | 
 | 		ret = PTR_ERR(ci); | 
 | 		if (ret == -ENOENT) | 
 | 			ret = -ENOPKG; | 
 | 		goto err; | 
 | 	} | 
 |  | 
 | 	ret = crypto_shash_setkey(ci, Kc->data, Kc->len); | 
 | 	if (ret < 0) { | 
 | 		pr_err("Couldn't set shash key %s: %d\n", krb5->cksum_name, ret); | 
 | 		goto err_ci; | 
 | 	} | 
 |  | 
 | 	return ci; | 
 | err_ci: | 
 | 	crypto_free_shash(ci); | 
 | err: | 
 | 	return ERR_PTR(ret); | 
 | } | 
 |  | 
 | /** | 
 |  * crypto_krb5_prepare_checksum - Prepare AEAD crypto object for checksum-mode | 
 |  * @krb5: The encoding to use. | 
 |  * @TK: The transport key to use. | 
 |  * @usage: The usage constant for key derivation. | 
 |  * @gfp: Allocation flags. | 
 |  * | 
 |  * Allocate a crypto object that does all the necessary crypto, key it and set | 
 |  * its parameters and return the crypto handle to it.  This can then be used to | 
 |  * dispatch get_mic and verify_mic operations. | 
 |  */ | 
 | struct crypto_shash *crypto_krb5_prepare_checksum(const struct krb5_enctype *krb5, | 
 | 						  const struct krb5_buffer *TK, | 
 | 						  u32 usage, gfp_t gfp) | 
 | { | 
 | 	struct crypto_shash *ci = NULL; | 
 | 	struct krb5_buffer keys = {}; | 
 | 	int ret; | 
 |  | 
 | 	ret = krb5->profile->derive_checksum_key(krb5, TK, usage, &keys, gfp); | 
 | 	if (ret < 0) { | 
 | 		pr_err("get_Kc failed %d\n", ret); | 
 | 		goto err; | 
 | 	} | 
 |  | 
 | 	ci = krb5_prepare_checksum(krb5, &keys, gfp); | 
 | 	if (IS_ERR(ci)) { | 
 | 		ret = PTR_ERR(ci); | 
 | 		goto err; | 
 | 	} | 
 |  | 
 | 	kfree(keys.data); | 
 | 	return ci; | 
 | err: | 
 | 	kfree(keys.data); | 
 | 	return ERR_PTR(ret); | 
 | } | 
 | EXPORT_SYMBOL(crypto_krb5_prepare_checksum); | 
 |  | 
 | /** | 
 |  * crypto_krb5_encrypt - Apply Kerberos encryption and integrity. | 
 |  * @krb5: The encoding to use. | 
 |  * @aead: The keyed crypto object to use. | 
 |  * @sg: Scatterlist defining the crypto buffer. | 
 |  * @nr_sg: The number of elements in @sg. | 
 |  * @sg_len: The size of the buffer. | 
 |  * @data_offset: The offset of the data in the @sg buffer. | 
 |  * @data_len: The length of the data. | 
 |  * @preconfounded: True if the confounder is already inserted. | 
 |  * | 
 |  * Using the specified Kerberos encoding, insert a confounder and padding as | 
 |  * needed, encrypt this and the data in place and insert an integrity checksum | 
 |  * into the buffer. | 
 |  * | 
 |  * The buffer must include space for the confounder, the checksum and any | 
 |  * padding required.  The caller can preinsert the confounder into the buffer | 
 |  * (for testing, for example). | 
 |  * | 
 |  * The resulting secured blob may be less than the size of the buffer. | 
 |  * | 
 |  * Returns the size of the secure blob if successful, -ENOMEM on an allocation | 
 |  * failure, -EFAULT if there is insufficient space, -EMSGSIZE if the confounder | 
 |  * is too short or the data is misaligned.  Other errors may also be returned | 
 |  * from the crypto layer. | 
 |  */ | 
 | ssize_t crypto_krb5_encrypt(const struct krb5_enctype *krb5, | 
 | 			    struct crypto_aead *aead, | 
 | 			    struct scatterlist *sg, unsigned int nr_sg, | 
 | 			    size_t sg_len, | 
 | 			    size_t data_offset, size_t data_len, | 
 | 			    bool preconfounded) | 
 | { | 
 | 	if (WARN_ON(data_offset > sg_len || | 
 | 		    data_len > sg_len || | 
 | 		    data_offset > sg_len - data_len)) | 
 | 		return -EMSGSIZE; | 
 | 	return krb5->profile->encrypt(krb5, aead, sg, nr_sg, sg_len, | 
 | 				      data_offset, data_len, preconfounded); | 
 | } | 
 | EXPORT_SYMBOL(crypto_krb5_encrypt); | 
 |  | 
 | /** | 
 |  * crypto_krb5_decrypt - Validate and remove Kerberos encryption and integrity. | 
 |  * @krb5: The encoding to use. | 
 |  * @aead: The keyed crypto object to use. | 
 |  * @sg: Scatterlist defining the crypto buffer. | 
 |  * @nr_sg: The number of elements in @sg. | 
 |  * @_offset: Offset of the secure blob in the buffer; updated to data offset. | 
 |  * @_len: The length of the secure blob; updated to data length. | 
 |  * | 
 |  * Using the specified Kerberos encoding, check and remove the integrity | 
 |  * checksum and decrypt the secure region, stripping off the confounder. | 
 |  * | 
 |  * If successful, @_offset and @_len are updated to outline the region in which | 
 |  * the data plus the trailing padding are stored.  The caller is responsible | 
 |  * for working out how much padding there is and removing it. | 
 |  * | 
 |  * Returns the 0 if successful, -ENOMEM on an allocation failure; sets | 
 |  * *_error_code and returns -EPROTO if the data cannot be parsed, or -EBADMSG | 
 |  * if the integrity checksum doesn't match).  Other errors may also be returned | 
 |  * from the crypto layer. | 
 |  */ | 
 | int crypto_krb5_decrypt(const struct krb5_enctype *krb5, | 
 | 			struct crypto_aead *aead, | 
 | 			struct scatterlist *sg, unsigned int nr_sg, | 
 | 			size_t *_offset, size_t *_len) | 
 | { | 
 | 	return krb5->profile->decrypt(krb5, aead, sg, nr_sg, _offset, _len); | 
 | } | 
 | EXPORT_SYMBOL(crypto_krb5_decrypt); | 
 |  | 
 | /** | 
 |  * crypto_krb5_get_mic - Apply Kerberos integrity checksum. | 
 |  * @krb5: The encoding to use. | 
 |  * @shash: The keyed hash to use. | 
 |  * @metadata: Metadata to add into the hash before adding the data. | 
 |  * @sg: Scatterlist defining the crypto buffer. | 
 |  * @nr_sg: The number of elements in @sg. | 
 |  * @sg_len: The size of the buffer. | 
 |  * @data_offset: The offset of the data in the @sg buffer. | 
 |  * @data_len: The length of the data. | 
 |  * | 
 |  * Using the specified Kerberos encoding, calculate and insert an integrity | 
 |  * checksum into the buffer. | 
 |  * | 
 |  * The buffer must include space for the checksum at the front. | 
 |  * | 
 |  * Returns the size of the secure blob if successful, -ENOMEM on an allocation | 
 |  * failure, -EFAULT if there is insufficient space, -EMSGSIZE if the gap for | 
 |  * the checksum is too short.  Other errors may also be returned from the | 
 |  * crypto layer. | 
 |  */ | 
 | ssize_t crypto_krb5_get_mic(const struct krb5_enctype *krb5, | 
 | 			    struct crypto_shash *shash, | 
 | 			    const struct krb5_buffer *metadata, | 
 | 			    struct scatterlist *sg, unsigned int nr_sg, | 
 | 			    size_t sg_len, | 
 | 			    size_t data_offset, size_t data_len) | 
 | { | 
 | 	if (WARN_ON(data_offset > sg_len || | 
 | 		    data_len > sg_len || | 
 | 		    data_offset > sg_len - data_len)) | 
 | 		return -EMSGSIZE; | 
 | 	return krb5->profile->get_mic(krb5, shash, metadata, sg, nr_sg, sg_len, | 
 | 				      data_offset, data_len); | 
 | } | 
 | EXPORT_SYMBOL(crypto_krb5_get_mic); | 
 |  | 
 | /** | 
 |  * crypto_krb5_verify_mic - Validate and remove Kerberos integrity checksum. | 
 |  * @krb5: The encoding to use. | 
 |  * @shash: The keyed hash to use. | 
 |  * @metadata: Metadata to add into the hash before adding the data. | 
 |  * @sg: Scatterlist defining the crypto buffer. | 
 |  * @nr_sg: The number of elements in @sg. | 
 |  * @_offset: Offset of the secure blob in the buffer; updated to data offset. | 
 |  * @_len: The length of the secure blob; updated to data length. | 
 |  * | 
 |  * Using the specified Kerberos encoding, check and remove the integrity | 
 |  * checksum. | 
 |  * | 
 |  * If successful, @_offset and @_len are updated to outline the region in which | 
 |  * the data is stored. | 
 |  * | 
 |  * Returns the 0 if successful, -ENOMEM on an allocation failure; sets | 
 |  * *_error_code and returns -EPROTO if the data cannot be parsed, or -EBADMSG | 
 |  * if the checksum doesn't match).  Other errors may also be returned from the | 
 |  * crypto layer. | 
 |  */ | 
 | int crypto_krb5_verify_mic(const struct krb5_enctype *krb5, | 
 | 			   struct crypto_shash *shash, | 
 | 			   const struct krb5_buffer *metadata, | 
 | 			   struct scatterlist *sg, unsigned int nr_sg, | 
 | 			   size_t *_offset, size_t *_len) | 
 | { | 
 | 	return krb5->profile->verify_mic(krb5, shash, metadata, sg, nr_sg, | 
 | 					 _offset, _len); | 
 | } | 
 | EXPORT_SYMBOL(crypto_krb5_verify_mic); | 
 |  | 
 | static int __init crypto_krb5_init(void) | 
 | { | 
 | 	return krb5_selftest(); | 
 | } | 
 | module_init(crypto_krb5_init); | 
 |  | 
 | static void __exit crypto_krb5_exit(void) | 
 | { | 
 | } | 
 | module_exit(crypto_krb5_exit); |