[PATCH 05/18] crypto/krb5: Implement the Kerberos5 rfc3961 encrypt and decrypt functions

From: David Howells
Date: Thu Nov 12 2020 - 07:58:47 EST


Add functions that encrypt and decrypt a piece of an skbuff according to
rfc3961 sec 5.3, using Ki to checksum the data to be secured and Ke to
encrypt it during the encryption phase, then decrypting with Ke and
verifying the checksum with Ki in the decryption phase.

Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
---

crypto/krb5/internal.h | 18 +++
crypto/krb5/main.c | 102 +++++++++++++++++++
crypto/krb5/rfc3961_simplified.c | 204 ++++++++++++++++++++++++++++++++++++++
include/crypto/krb5.h | 12 ++
4 files changed, 336 insertions(+)

diff --git a/crypto/krb5/internal.h b/crypto/krb5/internal.h
index 874dddada713..ce07decf19f0 100644
--- a/crypto/krb5/internal.h
+++ b/crypto/krb5/internal.h
@@ -7,6 +7,7 @@

#include <linux/scatterlist.h>
#include <crypto/krb5.h>
+#include <crypto/hash.h>

/*
* Profile used for key derivation and encryption.
@@ -87,7 +88,24 @@ struct krb5_crypto_profile {
crypto_roundup(crypto_sync_skcipher_ivsize(TFM))
#define round16(x) (((x) + 15) & ~15)

+/*
+ * main.c
+ */
+int crypto_shash_update_sg(struct shash_desc *desc, struct scatterlist *sg,
+ size_t offset, size_t len);
+
/*
* rfc3961_simplified.c
*/
extern const struct krb5_crypto_profile rfc3961_simplified_profile;
+
+ssize_t rfc3961_encrypt(const struct krb5_enctype *krb5,
+ struct krb5_enc_keys *keys,
+ struct scatterlist *sg, unsigned nr_sg, size_t sg_len,
+ size_t data_offset, size_t data_len,
+ bool preconfounded);
+int rfc3961_decrypt(const struct krb5_enctype *krb5,
+ struct krb5_enc_keys *keys,
+ struct scatterlist *sg, unsigned nr_sg,
+ size_t *_offset, size_t *_len,
+ int *_error_code);
diff --git a/crypto/krb5/main.c b/crypto/krb5/main.c
index 58d40252adc9..db3fc34be272 100644
--- a/crypto/krb5/main.c
+++ b/crypto/krb5/main.c
@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <linux/export.h>
#include <linux/kernel.h>
+#include <linux/highmem.h>
#include "internal.h"

MODULE_DESCRIPTION("Kerberos 5 crypto");
@@ -40,3 +41,104 @@ const struct krb5_enctype *crypto_krb5_find_enctype(u32 enctype)
return NULL;
}
EXPORT_SYMBOL(crypto_krb5_find_enctype);
+
+int crypto_shash_update_sg(struct shash_desc *desc, struct scatterlist *sg,
+ size_t offset, size_t len)
+{
+ for (;; sg++) {
+ int ret;
+
+ if (offset < sg->length) {
+ struct page *page = sg_page(sg);
+ void *p = kmap_atomic(page);
+ size_t seg = min_t(size_t, len, sg->length - offset);
+
+ ret = crypto_shash_update(desc, p + sg->offset + offset, seg);
+ kunmap_atomic(p);
+ if (ret < 0)
+ return ret;
+ len -= seg;
+ offset = 0;
+ } else {
+ offset -= sg->length;
+ }
+ if (sg_is_last(sg) || len > 0)
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * crypto_krb5_encrypt - Apply Kerberos encryption and integrity.
+ * @krb5: The encoding to use.
+ * @keys: The encryption and integrity keys 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 krb5_enc_keys *keys,
+ struct scatterlist *sg, unsigned 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, keys, 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.
+ * @keys: The encryption and integrity keys 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.
+ * @_error_code: Set to a Kerberos error code for parsing/validation errors.
+ *
+ * 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 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 krb5_enc_keys *keys,
+ struct scatterlist *sg, unsigned nr_sg,
+ size_t *_offset, size_t *_len,
+ int *_error_code)
+{
+ return krb5->profile->decrypt(krb5, keys, sg, nr_sg,
+ _offset, _len, _error_code);
+}
+EXPORT_SYMBOL(crypto_krb5_decrypt);
diff --git a/crypto/krb5/rfc3961_simplified.c b/crypto/krb5/rfc3961_simplified.c
index 0a5c689f6354..0a5c19b83f51 100644
--- a/crypto/krb5/rfc3961_simplified.c
+++ b/crypto/krb5/rfc3961_simplified.c
@@ -65,6 +65,8 @@

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

+#include <linux/random.h>
+#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/lcm.h>
#include <crypto/skcipher.h>
@@ -386,9 +388,211 @@ static int rfc3961_calc_PRF(const struct krb5_enctype *krb5,
return ret;
}

+/*
+ * Apply encryption and checksumming functions to part of a scatterlist.
+ */
+ssize_t rfc3961_encrypt(const struct krb5_enctype *krb5,
+ struct krb5_enc_keys *keys,
+ struct scatterlist *sg, unsigned nr_sg, size_t sg_len,
+ size_t data_offset, size_t data_len,
+ bool preconfounded)
+{
+ struct skcipher_request *req;
+ struct shash_desc *desc;
+ ssize_t ret, done;
+ size_t bsize, base_len, secure_offset, secure_len, pad_len, cksum_offset;
+ void *buffer;
+ u8 *cksum, *iv;
+
+ if (WARN_ON(data_offset != krb5->conf_len))
+ return -EINVAL; /* Can't set offset on skcipher */
+
+ secure_offset = 0;
+ base_len = krb5->conf_len + data_len;
+ if (krb5->pad) {
+ secure_len = round_up(base_len, krb5->block_len);
+ pad_len = secure_len - base_len;
+ } else {
+ secure_len = base_len;
+ pad_len = 0;
+ }
+ cksum_offset = secure_len;
+ if (WARN_ON(cksum_offset + krb5->cksum_len > sg_len))
+ return -EFAULT;
+
+ bsize = krb5_shash_size(keys->Ki) +
+ krb5_digest_size(keys->Ki) +
+ krb5_sync_skcipher_size(keys->Ke) +
+ krb5_sync_skcipher_ivsize(keys->Ke);
+ bsize = max_t(size_t, bsize, krb5->conf_len);
+ bsize = max_t(size_t, bsize, krb5->block_len);
+ buffer = kzalloc(bsize, GFP_NOFS);
+ if (!buffer)
+ return -ENOMEM;
+
+ /* Insert the confounder into the skb */
+ 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 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;
+ }
+
+ /* Calculate the checksum using key Ki */
+ cksum = buffer + krb5_shash_size(keys->Ki);
+
+ desc = buffer;
+ desc->tfm = keys->Ki;
+ ret = crypto_shash_init(desc);
+ if (ret < 0)
+ goto error;
+
+ ret = crypto_shash_update_sg(desc, sg, secure_offset, secure_len);
+ if (ret < 0)
+ goto error;
+
+ ret = crypto_shash_final(desc, cksum);
+ if (ret < 0)
+ goto error;
+
+ /* Append the checksum into the buffer. */
+ ret = -EFAULT;
+ done = sg_pcopy_from_buffer(sg, nr_sg, cksum, krb5->cksum_len, cksum_offset);
+ if (done != krb5->cksum_len)
+ goto error;
+
+ /* Encrypt the secure region with key Ke. */
+ req = buffer +
+ krb5_shash_size(keys->Ki) +
+ krb5_digest_size(keys->Ki);
+ iv = buffer +
+ krb5_shash_size(keys->Ki) +
+ krb5_digest_size(keys->Ki) +
+ krb5_sync_skcipher_size(keys->Ke);
+
+ skcipher_request_set_sync_tfm(req, keys->Ke);
+ skcipher_request_set_callback(req, 0, NULL, NULL);
+ skcipher_request_set_crypt(req, sg, sg, secure_len, iv);
+ ret = crypto_skcipher_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 part of an skbuff. The
+ * offset and length are updated to reflect the actual content of the encrypted
+ * region.
+ */
+int rfc3961_decrypt(const struct krb5_enctype *krb5,
+ struct krb5_enc_keys *keys,
+ struct scatterlist *sg, unsigned nr_sg,
+ size_t *_offset, size_t *_len,
+ int *_error_code)
+{
+ struct skcipher_request *req;
+ struct shash_desc *desc;
+ ssize_t done;
+ size_t bsize, secure_len, offset = *_offset, len = *_len;
+ void *buffer = NULL;
+ int ret;
+ u8 *cksum, *cksum2, *iv;
+
+ if (WARN_ON(*_offset != 0))
+ return -EINVAL; /* Can't set offset on skcipher */
+
+ if (len < krb5->conf_len + krb5->cksum_len) {
+ *_error_code = 1; //RXGK_SEALED_INCON;
+ return -EPROTO;
+ }
+ secure_len = len - krb5->cksum_len;
+
+ bsize = krb5_shash_size(keys->Ki) +
+ krb5_digest_size(keys->Ki) * 2 +
+ krb5_sync_skcipher_size(keys->Ke) +
+ krb5_sync_skcipher_ivsize(keys->Ke);
+ buffer = kzalloc(bsize, GFP_NOFS);
+ if (!buffer)
+ return -ENOMEM;
+
+ /* Decrypt the secure region with key Ke. */
+ req = buffer +
+ krb5_shash_size(keys->Ki) +
+ krb5_digest_size(keys->Ki) * 2;
+ iv = buffer +
+ krb5_shash_size(keys->Ki) +
+ krb5_digest_size(keys->Ki) * 2 +
+ krb5_sync_skcipher_size(keys->Ke);
+
+ skcipher_request_set_sync_tfm(req, keys->Ke);
+ skcipher_request_set_callback(req, 0, NULL, NULL);
+ skcipher_request_set_crypt(req, sg, sg, secure_len, iv);
+ ret = crypto_skcipher_decrypt(req);
+ if (ret < 0)
+ goto error;
+
+ /* Calculate the checksum using key Ki */
+ cksum = buffer +
+ krb5_shash_size(keys->Ki);
+ cksum2 = buffer +
+ krb5_shash_size(keys->Ki) +
+ krb5_digest_size(keys->Ki);
+
+ desc = buffer;
+ desc->tfm = keys->Ki;
+ ret = crypto_shash_init(desc);
+ if (ret < 0)
+ goto error;
+
+ ret = crypto_shash_update_sg(desc, sg, 0, secure_len);
+ if (ret < 0)
+ goto error;
+
+ ret = crypto_shash_final(desc, cksum);
+ if (ret < 0)
+ goto error;
+
+ /* Get the checksum from the buffer. */
+ ret = -EFAULT;
+ done = sg_pcopy_to_buffer(sg, nr_sg, cksum2, krb5->cksum_len,
+ offset + len - krb5->cksum_len);
+ if (done != krb5->cksum_len)
+ goto error;
+
+ if (memcmp(cksum, cksum2, krb5->cksum_len) != 0) {
+ *_error_code = 1; //RXGK_SEALED_INCON;
+ ret = -EPROTO;
+ goto error;
+ }
+
+ *_offset += krb5->conf_len;
+ *_len -= krb5->conf_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,
+ .encrypt = rfc3961_encrypt,
+ .decrypt = rfc3961_decrypt,
};
diff --git a/include/crypto/krb5.h b/include/crypto/krb5.h
index 04286bacaf06..fb77f70117c1 100644
--- a/include/crypto/krb5.h
+++ b/include/crypto/krb5.h
@@ -12,6 +12,8 @@
#ifndef _CRYPTO_KRB5_H
#define _CRYPTO_KRB5_H

+#include <linux/crypto.h>
+
struct crypto_shash;
struct scatterlist;

@@ -103,6 +105,16 @@ struct krb5_enctype {
*/
extern const struct krb5_enctype *crypto_krb5_find_enctype(u32 enctype);

+extern ssize_t crypto_krb5_encrypt(const struct krb5_enctype *krb5,
+ struct krb5_enc_keys *keys,
+ struct scatterlist *sg, unsigned nr_sg, size_t sg_len,
+ size_t data_offset, size_t data_len,
+ bool preconfounded);
+extern int crypto_krb5_decrypt(const struct krb5_enctype *krb5,
+ struct krb5_enc_keys *keys,
+ struct scatterlist *sg, unsigned nr_sg,
+ size_t *_offset, size_t *_len,
+ int *_error_code);
/*
* kdf.c
*/