mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Implement the aes128-cts-hmac-sha256-128 and aes256-cts-hmac-sha384-192 enctypes from rfc8009, overriding the rfc3961 kerberos 5 simplified crypto scheme. Signed-off-by: David Howells <dhowells@redhat.com> cc: Herbert Xu <herbert@gondor.apana.org.au> cc: "David S. Miller" <davem@davemloft.net> cc: Chuck Lever <chuck.lever@oracle.com> cc: Marc Dionne <marc.dionne@auristor.com> cc: Eric Dumazet <edumazet@google.com> cc: Jakub Kicinski <kuba@kernel.org> cc: Paolo Abeni <pabeni@redhat.com> cc: Simon Horman <horms@kernel.org> cc: linux-afs@lists.infradead.org cc: linux-nfs@vger.kernel.org cc: linux-crypto@vger.kernel.org cc: netdev@vger.kernel.org
439 lines
13 KiB
C
439 lines
13 KiB
C
// 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,
|
|
};
|
|
|
|
/**
|
|
* 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);
|