[RFC PATCH 13/24] crypto/krb5: Implement the Kerberos5 rfc3961 get_mic and verify_mic

David Howells
2025-01-17T18:37:10Z

Add functions that sign and verify a message according to rfc3961 sec 5.4,
using Kc to generate a checksum and insert it into the MIC field in the
skbuff in the sign phase then checksum the data and compare it to the MIC
in the verify phase.

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
---
crypto/krb5/internal.h | 10 +++
crypto/krb5/rfc3961_simplified.c | 130 +++++++++++++++++++++++++++++++
2 files changed, 140 insertions(+)

diff --git a/crypto/krb5/internal.h b/crypto/krb5/internal.h
index 4533fb342953..eaaa9c13c8b3 100644
--- a/crypto/krb5/internal.h
+++ b/crypto/krb5/internal.h
@@ -169,3 +169,13 @@ 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);
+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);
+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);
diff --git a/crypto/krb5/rfc3961_simplified.c b/crypto/krb5/rfc3961_simplified.c
index 55f295f3ac5f..59f0e7c823f7 100644
--- a/crypto/krb5/rfc3961_simplified.c
+++ b/crypto/krb5/rfc3961_simplified.c
@@ -647,6 +647,134 @@ int krb5_aead_decrypt(const struct krb5_enctype *krb5,
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,
@@ -658,4 +786,6 @@ const struct krb5_crypto_profile rfc3961_simplified_profile = {
.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,
};