root/crypto/krb5/rfc3961_simplified.c
// SPDX-License-Identifier: BSD-3-Clause
/* rfc3961 Kerberos 5 simplified crypto profile.
 *
 * Parts borrowed from net/sunrpc/auth_gss/.
 */
/*
 * COPYRIGHT (c) 2008
 * The Regents of the University of Michigan
 * ALL RIGHTS RESERVED
 *
 * Permission is granted to use, copy, create derivative works
 * and redistribute this software and such derivative works
 * for any purpose, so long as the name of The University of
 * Michigan is not used in any advertising or publicity
 * pertaining to the use of distribution of this software
 * without specific, written prior authorization.  If the
 * above copyright notice or any other identification of the
 * University of Michigan is included in any copy of any
 * portion of this software, then the disclaimer below must
 * also be included.
 *
 * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
 * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
 * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
 * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
 * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
 * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
 * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
 * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
 * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGES.
 */

/*
 * Copyright (C) 1998 by the FundsXpress, INC.
 *
 * All rights reserved.
 *
 * Export of this software from the United States of America may require
 * a specific license from the United States Government.  It is the
 * responsibility of any person or organization contemplating export to
 * obtain such a license before exporting.
 *
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of FundsXpress. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  FundsXpress makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

/*
 * 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/random.h>
#include <linux/scatterlist.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/lcm.h>
#include <linux/rtnetlink.h>
#include <crypto/authenc.h>
#include <crypto/skcipher.h>
#include <crypto/hash.h>
#include "internal.h"

/* Maximum blocksize for the supported crypto algorithms */
#define KRB5_MAX_BLOCKSIZE  (16)

int crypto_shash_update_sg(struct shash_desc *desc, struct scatterlist *sg,
                           size_t offset, size_t len)
{
        struct sg_mapping_iter miter;
        size_t i, n;
        int ret = 0;

        sg_miter_start(&miter, sg, sg_nents(sg),
                       SG_MITER_FROM_SG | SG_MITER_LOCAL);
        sg_miter_skip(&miter, offset);
        for (i = 0; i < len; i += n) {
                sg_miter_next(&miter);
                n = min(miter.length, len - i);
                ret = crypto_shash_update(desc, miter.addr, n);
                if (ret < 0)
                        break;
        }
        sg_miter_stop(&miter);
        return ret;
}

static int rfc3961_do_encrypt(struct crypto_sync_skcipher *tfm, void *iv,
                              const struct krb5_buffer *in, struct krb5_buffer *out)
{
        struct scatterlist sg[1];
        u8 local_iv[KRB5_MAX_BLOCKSIZE] __aligned(KRB5_MAX_BLOCKSIZE) = {0};
        SYNC_SKCIPHER_REQUEST_ON_STACK(req, tfm);
        int ret;

        if (WARN_ON(in->len != out->len))
                return -EINVAL;
        if (out->len % crypto_sync_skcipher_blocksize(tfm) != 0)
                return -EINVAL;

        if (crypto_sync_skcipher_ivsize(tfm) > KRB5_MAX_BLOCKSIZE)
                return -EINVAL;

        if (iv)
                memcpy(local_iv, iv, crypto_sync_skcipher_ivsize(tfm));

        memcpy(out->data, in->data, out->len);
        sg_init_one(sg, out->data, out->len);

        skcipher_request_set_sync_tfm(req, tfm);
        skcipher_request_set_callback(req, 0, NULL, NULL);
        skcipher_request_set_crypt(req, sg, sg, out->len, local_iv);

        ret = crypto_skcipher_encrypt(req);
        skcipher_request_zero(req);
        return ret;
}

/*
 * Calculate an unkeyed basic hash.
 */
static int rfc3961_calc_H(const struct krb5_enctype *krb5,
                          const struct krb5_buffer *data,
                          struct krb5_buffer *digest,
                          gfp_t gfp)
{
        struct crypto_shash *tfm;
        struct shash_desc *desc;
        size_t desc_size;
        int ret = -ENOMEM;

        tfm = crypto_alloc_shash(krb5->hash_name, 0, 0);
        if (IS_ERR(tfm))
                return (PTR_ERR(tfm) == -ENOENT) ? -ENOPKG : PTR_ERR(tfm);

        desc_size = crypto_shash_descsize(tfm) + sizeof(*desc);

        desc = kzalloc(desc_size, gfp);
        if (!desc)
                goto error_tfm;

        digest->len = crypto_shash_digestsize(tfm);
        digest->data = kzalloc(digest->len, gfp);
        if (!digest->data)
                goto error_desc;

        desc->tfm = tfm;
        ret = crypto_shash_init(desc);
        if (ret < 0)
                goto error_digest;

        ret = crypto_shash_finup(desc, data->data, data->len, digest->data);
        if (ret < 0)
                goto error_digest;

        goto error_desc;

error_digest:
        kfree_sensitive(digest->data);
error_desc:
        kfree_sensitive(desc);
error_tfm:
        crypto_free_shash(tfm);
        return ret;
}

/*
 * This is the n-fold function as described in rfc3961, sec 5.1
 * Taken from MIT Kerberos and modified.
 */
static void rfc3961_nfold(const struct krb5_buffer *source, struct krb5_buffer *result)
{
        const u8 *in = source->data;
        u8 *out = result->data;
        unsigned long ulcm;
        unsigned int inbits, outbits;
        int byte, i, msbit;

        /* the code below is more readable if I make these bytes instead of bits */
        inbits = source->len;
        outbits = result->len;

        /* first compute lcm(n,k) */
        ulcm = lcm(inbits, outbits);

        /* now do the real work */
        memset(out, 0, outbits);
        byte = 0;

        /* this will end up cycling through k lcm(k,n)/k times, which
         * is correct.
         */
        for (i = ulcm-1; i >= 0; i--) {
                /* compute the msbit in k which gets added into this byte */
                msbit = (
                        /* first, start with the msbit in the first,
                         * unrotated byte
                         */
                        ((inbits << 3) - 1) +
                        /* then, for each byte, shift to the right
                         * for each repetition
                         */
                        (((inbits << 3) + 13) * (i/inbits)) +
                        /* last, pick out the correct byte within
                         * that shifted repetition
                         */
                        ((inbits - (i % inbits)) << 3)
                         ) % (inbits << 3);

                /* pull out the byte value itself */
                byte += (((in[((inbits - 1) - (msbit >> 3)) % inbits] << 8) |
                          (in[((inbits)     - (msbit >> 3)) % inbits]))
                         >> ((msbit & 7) + 1)) & 0xff;

                /* do the addition */
                byte += out[i % outbits];
                out[i % outbits] = byte & 0xff;

                /* keep around the carry bit, if any */
                byte >>= 8;
        }

        /* if there's a carry bit left over, add it back in */
        if (byte) {
                for (i = outbits - 1; i >= 0; i--) {
                        /* do the addition */
                        byte += out[i];
                        out[i] = byte & 0xff;

                        /* keep around the carry bit, if any */
                        byte >>= 8;
                }
        }
}

/*
 * Calculate a derived key, DK(Base Key, Well-Known Constant)
 *
 * DK(Key, Constant) = random-to-key(DR(Key, Constant))
 * DR(Key, Constant) = k-truncate(E(Key, Constant, initial-cipher-state))
 * K1 = E(Key, n-fold(Constant), initial-cipher-state)
 * K2 = E(Key, K1, initial-cipher-state)
 * K3 = E(Key, K2, initial-cipher-state)
 * K4 = ...
 * DR(Key, Constant) = k-truncate(K1 | K2 | K3 | K4 ...)
 * [rfc3961 sec 5.1]
 */
static int rfc3961_calc_DK(const struct krb5_enctype *krb5,
                           const struct krb5_buffer *inkey,
                           const struct krb5_buffer *in_constant,
                           struct krb5_buffer *result,
                           gfp_t gfp)
{
        unsigned int blocksize, keybytes, keylength, n;
        struct krb5_buffer inblock, outblock, rawkey;
        struct crypto_sync_skcipher *cipher;
        int ret = -EINVAL;

        blocksize = krb5->block_len;
        keybytes = krb5->key_bytes;
        keylength = krb5->key_len;

        if (inkey->len != keylength || result->len != keylength)
                return -EINVAL;
        if (!krb5->random_to_key && result->len != keybytes)
                return -EINVAL;

        cipher = crypto_alloc_sync_skcipher(krb5->derivation_enc, 0, 0);
        if (IS_ERR(cipher)) {
                ret = (PTR_ERR(cipher) == -ENOENT) ? -ENOPKG : PTR_ERR(cipher);
                goto err_return;
        }
        ret = crypto_sync_skcipher_setkey(cipher, inkey->data, inkey->len);
        if (ret < 0)
                goto err_free_cipher;

        ret = -ENOMEM;
        inblock.data = kzalloc(blocksize * 2 + keybytes, gfp);
        if (!inblock.data)
                goto err_free_cipher;

        inblock.len     = blocksize;
        outblock.data   = inblock.data + blocksize;
        outblock.len    = blocksize;
        rawkey.data     = outblock.data + blocksize;
        rawkey.len      = keybytes;

        /* initialize the input block */

        if (in_constant->len == inblock.len)
                memcpy(inblock.data, in_constant->data, inblock.len);
        else
                rfc3961_nfold(in_constant, &inblock);

        /* loop encrypting the blocks until enough key bytes are generated */
        n = 0;
        while (n < rawkey.len) {
                rfc3961_do_encrypt(cipher, NULL, &inblock, &outblock);

                if (keybytes - n <= outblock.len) {
                        memcpy(rawkey.data + n, outblock.data, keybytes - n);
                        break;
                }

                memcpy(rawkey.data + n, outblock.data, outblock.len);
                memcpy(inblock.data, outblock.data, outblock.len);
                n += outblock.len;
        }

        /* postprocess the key */
        if (!krb5->random_to_key) {
                /* Identity random-to-key function. */
                memcpy(result->data, rawkey.data, rawkey.len);
                ret = 0;
        } else {
                ret = krb5->random_to_key(krb5, &rawkey, result);
        }

        kfree_sensitive(inblock.data);
err_free_cipher:
        crypto_free_sync_skcipher(cipher);
err_return:
        return ret;
}

/*
 * Calculate single encryption, E()
 *
 *      E(Key, octets)
 */
static int rfc3961_calc_E(const struct krb5_enctype *krb5,
                          const struct krb5_buffer *key,
                          const struct krb5_buffer *in_data,
                          struct krb5_buffer *result,
                          gfp_t gfp)
{
        struct crypto_sync_skcipher *cipher;
        int ret;

        cipher = crypto_alloc_sync_skcipher(krb5->derivation_enc, 0, 0);
        if (IS_ERR(cipher)) {
                ret = (PTR_ERR(cipher) == -ENOENT) ? -ENOPKG : PTR_ERR(cipher);
                goto err;
        }

        ret = crypto_sync_skcipher_setkey(cipher, key->data, key->len);
        if (ret < 0)
                goto err_free;

        ret = rfc3961_do_encrypt(cipher, NULL, in_data, result);

err_free:
        crypto_free_sync_skcipher(cipher);
err:
        return ret;
}

/*
 * Calculate the pseudo-random function, PRF().
 *
 *      tmp1 = H(octet-string)
 *      tmp2 = truncate tmp1 to multiple of m
 *      PRF = E(DK(protocol-key, prfconstant), tmp2, initial-cipher-state)
 *
 *      The "prfconstant" used in the PRF operation is the three-octet string
 *      "prf".
 *      [rfc3961 sec 5.3]
 */
static int rfc3961_calc_PRF(const struct krb5_enctype *krb5,
                            const struct krb5_buffer *protocol_key,
                            const struct krb5_buffer *octet_string,
                            struct krb5_buffer *result,
                            gfp_t gfp)
{
        static const struct krb5_buffer prfconstant = { 3, "prf" };
        struct krb5_buffer derived_key;
        struct krb5_buffer tmp1, tmp2;
        unsigned int m = krb5->block_len;
        void *buffer;
        int ret;

        if (result->len != krb5->prf_len)
                return -EINVAL;

        tmp1.len = krb5->hash_len;
        derived_key.len = krb5->key_bytes;
        buffer = kzalloc(round16(tmp1.len) + round16(derived_key.len), gfp);
        if (!buffer)
                return -ENOMEM;

        tmp1.data = buffer;
        derived_key.data = buffer + round16(tmp1.len);

        ret = rfc3961_calc_H(krb5, octet_string, &tmp1, gfp);
        if (ret < 0)
                goto err;

        tmp2.len = tmp1.len & ~(m - 1);
        tmp2.data = tmp1.data;

        ret = rfc3961_calc_DK(krb5, protocol_key, &prfconstant, &derived_key, gfp);
        if (ret < 0)
                goto err;

        ret = rfc3961_calc_E(krb5, &derived_key, &tmp2, result, gfp);

err:
        kfree_sensitive(buffer);
        return ret;
}

/*
 * Derive the Ke and Ki keys and package them into a key parameter that can be
 * given to the setkey of a authenc AEAD crypto object.
 */
int authenc_derive_encrypt_keys(const struct krb5_enctype *krb5,
                                const struct krb5_buffer *TK,
                                unsigned int usage,
                                struct krb5_buffer *setkey,
                                gfp_t gfp)
{
        struct crypto_authenc_key_param *param;
        struct krb5_buffer Ke, Ki;
        struct rtattr *rta;
        int ret;

        Ke.len  = krb5->Ke_len;
        Ki.len  = krb5->Ki_len;
        setkey->len = RTA_LENGTH(sizeof(*param)) + Ke.len + Ki.len;
        setkey->data = kzalloc(setkey->len, GFP_KERNEL);
        if (!setkey->data)
                return -ENOMEM;

        rta = setkey->data;
        rta->rta_type = CRYPTO_AUTHENC_KEYA_PARAM;
        rta->rta_len = RTA_LENGTH(sizeof(*param));
        param = RTA_DATA(rta);
        param->enckeylen = htonl(Ke.len);

        Ki.data = (void *)(param + 1);
        Ke.data = Ki.data + Ki.len;

        ret = krb5_derive_Ke(krb5, TK, usage, &Ke, gfp);
        if (ret < 0) {
                pr_err("get_Ke failed %d\n", ret);
                return ret;
        }
        ret = krb5_derive_Ki(krb5, TK, usage, &Ki, gfp);
        if (ret < 0)
                pr_err("get_Ki failed %d\n", ret);
        return ret;
}

/*
 * Package predefined Ke and Ki keys and into a key parameter that can be given
 * to the setkey of an authenc AEAD crypto object.
 */
int authenc_load_encrypt_keys(const struct krb5_enctype *krb5,
                              const struct krb5_buffer *Ke,
                              const struct krb5_buffer *Ki,
                              struct krb5_buffer *setkey,
                              gfp_t gfp)
{
        struct crypto_authenc_key_param *param;
        struct rtattr *rta;

        setkey->len = RTA_LENGTH(sizeof(*param)) + Ke->len + Ki->len;
        setkey->data = kzalloc(setkey->len, GFP_KERNEL);
        if (!setkey->data)
                return -ENOMEM;

        rta = setkey->data;
        rta->rta_type = CRYPTO_AUTHENC_KEYA_PARAM;
        rta->rta_len = RTA_LENGTH(sizeof(*param));
        param = RTA_DATA(rta);
        param->enckeylen = htonl(Ke->len);
        memcpy((void *)(param + 1), Ki->data, Ki->len);
        memcpy((void *)(param + 1) + Ki->len, Ke->data, Ke->len);
        return 0;
}

/*
 * Derive the Kc key for checksum-only mode and package it into a key parameter
 * that can be given to the setkey of a hash crypto object.
 */
int rfc3961_derive_checksum_key(const struct krb5_enctype *krb5,
                                const struct krb5_buffer *TK,
                                unsigned int usage,
                                struct krb5_buffer *setkey,
                                gfp_t gfp)
{
        int ret;

        setkey->len = krb5->Kc_len;
        setkey->data = kzalloc(setkey->len, GFP_KERNEL);
        if (!setkey->data)
                return -ENOMEM;

        ret = krb5_derive_Kc(krb5, TK, usage, setkey, gfp);
        if (ret < 0)
                pr_err("get_Kc failed %d\n", ret);
        return ret;
}

/*
 * Package a predefined Kc key for checksum-only mode into a key parameter that
 * can be given to the setkey of a hash crypto object.
 */
int rfc3961_load_checksum_key(const struct krb5_enctype *krb5,
                              const struct krb5_buffer *Kc,
                              struct krb5_buffer *setkey,
                              gfp_t gfp)
{
        setkey->len = krb5->Kc_len;
        setkey->data = kmemdup(Kc->data, Kc->len, GFP_KERNEL);
        if (!setkey->data)
                return -ENOMEM;
        return 0;
}

/*
 * Apply encryption and checksumming functions to part of a scatterlist.
 */
ssize_t krb5_aead_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)
{
        struct aead_request *req;
        ssize_t ret, done;
        size_t bsize, base_len, secure_offset, secure_len, pad_len, cksum_offset;
        void *buffer;
        u8 *iv;

        if (WARN_ON(data_offset != krb5->conf_len))
                return -EINVAL; /* Data is in wrong place */

        secure_offset   = 0;
        base_len        = krb5->conf_len + data_len;
        pad_len         = 0;
        secure_len      = base_len + pad_len;
        cksum_offset    = secure_len;
        if (WARN_ON(cksum_offset + krb5->cksum_len > sg_len))
                return -EFAULT;

        bsize = krb5_aead_size(aead) +
                krb5_aead_ivsize(aead);
        buffer = kzalloc(bsize, GFP_NOFS);
        if (!buffer)
                return -ENOMEM;

        /* Insert the confounder into the buffer */
        ret = -EFAULT;
        if (!preconfounded) {
                get_random_bytes(buffer, krb5->conf_len);
                done = sg_pcopy_from_buffer(sg, nr_sg, buffer, krb5->conf_len,
                                            secure_offset);
                if (done != krb5->conf_len)
                        goto error;
        }

        /* We may need to pad out to the crypto blocksize. */
        if (pad_len) {
                done = sg_zero_buffer(sg, nr_sg, pad_len, data_offset + data_len);
                if (done != pad_len)
                        goto error;
        }

        /* Hash and encrypt the message. */
        req = buffer;
        iv = buffer + krb5_aead_size(aead);

        aead_request_set_tfm(req, aead);
        aead_request_set_callback(req, 0, NULL, NULL);
        aead_request_set_crypt(req, sg, sg, secure_len, iv);
        ret = crypto_aead_encrypt(req);
        if (ret < 0)
                goto error;

        ret = secure_len + krb5->cksum_len;

error:
        kfree_sensitive(buffer);
        return ret;
}

/*
 * Apply decryption and checksumming functions to a message.  The offset and
 * length are updated to reflect the actual content of the encrypted region.
 */
int krb5_aead_decrypt(const struct krb5_enctype *krb5,
                      struct crypto_aead *aead,
                      struct scatterlist *sg, unsigned int nr_sg,
                      size_t *_offset, size_t *_len)
{
        struct aead_request *req;
        size_t bsize;
        void *buffer;
        int ret;
        u8 *iv;

        if (WARN_ON(*_offset != 0))
                return -EINVAL; /* Can't set offset on aead */

        if (*_len < krb5->conf_len + krb5->cksum_len)
                return -EPROTO;

        bsize = krb5_aead_size(aead) +
                krb5_aead_ivsize(aead);
        buffer = kzalloc(bsize, GFP_NOFS);
        if (!buffer)
                return -ENOMEM;

        /* Decrypt the message and verify its checksum. */
        req = buffer;
        iv = buffer + krb5_aead_size(aead);

        aead_request_set_tfm(req, aead);
        aead_request_set_callback(req, 0, NULL, NULL);
        aead_request_set_crypt(req, sg, sg, *_len, iv);
        ret = crypto_aead_decrypt(req);
        if (ret < 0)
                goto error;

        /* Adjust the boundaries of the data. */
        *_offset += krb5->conf_len;
        *_len -= krb5->conf_len + krb5->cksum_len;
        ret = 0;

error:
        kfree_sensitive(buffer);
        return ret;
}

/*
 * Generate a checksum over some metadata and part of an skbuff and insert the
 * MIC into the skbuff immediately prior to the data.
 */
ssize_t rfc3961_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)
{
        struct shash_desc *desc;
        ssize_t ret, done;
        size_t bsize;
        void *buffer, *digest;

        if (WARN_ON(data_offset != krb5->cksum_len))
                return -EMSGSIZE;

        bsize = krb5_shash_size(shash) +
                krb5_digest_size(shash);
        buffer = kzalloc(bsize, GFP_NOFS);
        if (!buffer)
                return -ENOMEM;

        /* Calculate the MIC with key Kc and store it into the skb */
        desc = buffer;
        desc->tfm = shash;
        ret = crypto_shash_init(desc);
        if (ret < 0)
                goto error;

        if (metadata) {
                ret = crypto_shash_update(desc, metadata->data, metadata->len);
                if (ret < 0)
                        goto error;
        }

        ret = crypto_shash_update_sg(desc, sg, data_offset, data_len);
        if (ret < 0)
                goto error;

        digest = buffer + krb5_shash_size(shash);
        ret = crypto_shash_final(desc, digest);
        if (ret < 0)
                goto error;

        ret = -EFAULT;
        done = sg_pcopy_from_buffer(sg, nr_sg, digest, krb5->cksum_len,
                                    data_offset - krb5->cksum_len);
        if (done != krb5->cksum_len)
                goto error;

        ret = krb5->cksum_len + data_len;

error:
        kfree_sensitive(buffer);
        return ret;
}

/*
 * Check the MIC on a region of an skbuff.  The offset and length are updated
 * to reflect the actual content of the secure region.
 */
int rfc3961_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)
{
        struct shash_desc *desc;
        ssize_t done;
        size_t bsize, data_offset, data_len, offset = *_offset, len = *_len;
        void *buffer = NULL;
        int ret;
        u8 *cksum, *cksum2;

        if (len < krb5->cksum_len)
                return -EPROTO;
        data_offset = offset + krb5->cksum_len;
        data_len = len - krb5->cksum_len;

        bsize = krb5_shash_size(shash) +
                krb5_digest_size(shash) * 2;
        buffer = kzalloc(bsize, GFP_NOFS);
        if (!buffer)
                return -ENOMEM;

        cksum = buffer +
                krb5_shash_size(shash);
        cksum2 = buffer +
                krb5_shash_size(shash) +
                krb5_digest_size(shash);

        /* Calculate the MIC */
        desc = buffer;
        desc->tfm = shash;
        ret = crypto_shash_init(desc);
        if (ret < 0)
                goto error;

        if (metadata) {
                ret = crypto_shash_update(desc, metadata->data, metadata->len);
                if (ret < 0)
                        goto error;
        }

        crypto_shash_update_sg(desc, sg, data_offset, data_len);
        crypto_shash_final(desc, cksum);

        ret = -EFAULT;
        done = sg_pcopy_to_buffer(sg, nr_sg, cksum2, krb5->cksum_len, offset);
        if (done != krb5->cksum_len)
                goto error;

        if (memcmp(cksum, cksum2, krb5->cksum_len) != 0) {
                ret = -EBADMSG;
                goto error;
        }

        *_offset += krb5->cksum_len;
        *_len -= krb5->cksum_len;
        ret = 0;

error:
        kfree_sensitive(buffer);
        return ret;
}

const struct krb5_crypto_profile rfc3961_simplified_profile = {
        .calc_PRF               = rfc3961_calc_PRF,
        .calc_Kc                = rfc3961_calc_DK,
        .calc_Ke                = rfc3961_calc_DK,
        .calc_Ki                = rfc3961_calc_DK,
        .derive_encrypt_keys    = authenc_derive_encrypt_keys,
        .load_encrypt_keys      = authenc_load_encrypt_keys,
        .derive_checksum_key    = rfc3961_derive_checksum_key,
        .load_checksum_key      = rfc3961_load_checksum_key,
        .encrypt                = krb5_aead_encrypt,
        .decrypt                = krb5_aead_decrypt,
        .get_mic                = rfc3961_get_mic,
        .verify_mic             = rfc3961_verify_mic,
};