Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions crypto/skcipher.c
Original file line number Diff line number Diff line change
Expand Up @@ -432,13 +432,119 @@ int crypto_skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key,
}
EXPORT_SYMBOL_GPL(crypto_skcipher_setkey);

int crypto_skcipher_set_data_unit_size(struct crypto_skcipher *tfm,
unsigned int data_unit_size)
{
unsigned int blocksize;

if (!data_unit_size) {
tfm->data_unit_size = 0;
return 0;
}

if (!crypto_skcipher_supports_multi_data_unit(tfm))
return -EOPNOTSUPP;

blocksize = crypto_skcipher_blocksize(tfm);
if (data_unit_size < blocksize || data_unit_size % blocksize)
return -EINVAL;

tfm->data_unit_size = data_unit_size;
return 0;
}
EXPORT_SYMBOL_GPL(crypto_skcipher_set_data_unit_size);

static int crypto_skcipher_check_data_unit_size(struct crypto_skcipher *tfm,
struct skcipher_request *req)
{
unsigned int du = tfm->data_unit_size;

if (likely(!du))
return 0;
if (req->cryptlen % du)
return -EINVAL;
return 0;
}

/*
* Increment a 16-byte little-endian counter held in @iv. See
* crypto_skcipher_set_data_unit_size() for the convention.
*/
static inline void skcipher_iv_inc_le128(u8 *iv)
{
__le64 lo_le, hi_le;
u64 lo;

memcpy(&lo_le, iv, 8);
memcpy(&hi_le, iv + 8, 8);
lo = le64_to_cpu(lo_le) + 1;
lo_le = cpu_to_le64(lo);
memcpy(iv, &lo_le, 8);
if (unlikely(lo == 0)) {
hi_le = cpu_to_le64(le64_to_cpu(hi_le) + 1);
memcpy(iv + 8, &hi_le, 8);
}
}

int skcipher_walk_data_units(struct skcipher_request *req,
int (*body)(struct skcipher_request *))
{
struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
const unsigned int du = tfm->data_unit_size;
const unsigned int total = req->cryptlen;
struct scatterlist *orig_src = req->src;
struct scatterlist *orig_dst = req->dst;
struct scatterlist src_sg[2], dst_sg[2];
u8 iv_save[16];
unsigned int off;
int err = 0;

if (likely(!du))
return body(req);

/*
* Registration of an algorithm advertising
* CRYPTO_ALG_SKCIPHER_MULTI_DATA_UNIT enforces ivsize == 16
* (see skcipher_prepare_alg_common()), so this is purely
* defensive against algorithm-registration bugs.
*/
if (WARN_ON_ONCE(crypto_skcipher_ivsize(tfm) != 16))
return -EINVAL;

memcpy(iv_save, req->iv, 16);

for (off = 0; off < total; off += du) {
req->cryptlen = du;
req->src = scatterwalk_ffwd(src_sg, orig_src, off);
req->dst = (orig_src == orig_dst) ? req->src :
scatterwalk_ffwd(dst_sg, orig_dst, off);

err = body(req);
if (err)
break;

skcipher_iv_inc_le128(iv_save);
memcpy(req->iv, iv_save, 16);
}

req->src = orig_src;
req->dst = orig_dst;
req->cryptlen = total;
return err;
}
EXPORT_SYMBOL_GPL(skcipher_walk_data_units);

int crypto_skcipher_encrypt(struct skcipher_request *req)
{
struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
struct skcipher_alg *alg = crypto_skcipher_alg(tfm);
int err;

if (crypto_skcipher_get_flags(tfm) & CRYPTO_TFM_NEED_KEY)
return -ENOKEY;
err = crypto_skcipher_check_data_unit_size(tfm, req);
if (err)
return err;
if (alg->co.base.cra_type != &crypto_skcipher_type)
return crypto_lskcipher_encrypt_sg(req);
return alg->encrypt(req);
Expand All @@ -449,9 +555,13 @@ int crypto_skcipher_decrypt(struct skcipher_request *req)
{
struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
struct skcipher_alg *alg = crypto_skcipher_alg(tfm);
int err;

if (crypto_skcipher_get_flags(tfm) & CRYPTO_TFM_NEED_KEY)
return -ENOKEY;
err = crypto_skcipher_check_data_unit_size(tfm, req);
if (err)
return err;
if (alg->co.base.cra_type != &crypto_skcipher_type)
return crypto_lskcipher_decrypt_sg(req);
return alg->decrypt(req);
Expand Down Expand Up @@ -680,6 +790,16 @@ int skcipher_prepare_alg_common(struct skcipher_alg_common *alg)
(alg->ivsize + alg->statesize) > PAGE_SIZE / 2)
return -EINVAL;

/*
* Algorithms advertising multi-data-unit support must use the
* 16-byte little-endian counter convention documented in
* crypto_skcipher_set_data_unit_size(); see also
* skcipher_walk_data_units().
*/
if ((base->cra_flags & CRYPTO_ALG_SKCIPHER_MULTI_DATA_UNIT) &&
alg->ivsize != 16)
return -EINVAL;

if (!alg->chunksize)
alg->chunksize = base->cra_blocksize;

Expand Down
129 changes: 129 additions & 0 deletions crypto/testmgr.c
Original file line number Diff line number Diff line change
Expand Up @@ -3211,6 +3211,123 @@ static int test_skcipher(int enc, const struct cipher_test_suite *suite,
return 0;
}

/*
* For algorithms that advertise CRYPTO_ALG_SKCIPHER_MULTI_DATA_UNIT,
* verify that one request batching N data units produces the same
* ciphertext as N back-to-back single-data-unit requests with IVs
* derived from the original IV by adding the data-unit index (treated
* as a 128-bit little-endian counter).
*
* This is a self-comparison: it does not depend on test-vector
* authoritativeness, only on the algorithm being internally consistent
* between its single-DU and multi-DU paths.
*/
#define TEST_MDU_NR_UNITS 4
static int test_skcipher_multi_du(struct crypto_skcipher *tfm,
unsigned int du_size)
{
const char *driver = crypto_skcipher_driver_name(tfm);
const unsigned int ivsize = crypto_skcipher_ivsize(tfm);
const unsigned int total = du_size * TEST_MDU_NR_UNITS;
struct skcipher_request *req = NULL;
struct scatterlist sg_in, sg_out;
DECLARE_CRYPTO_WAIT(wait);
u8 iv_orig[16] = {0};
u8 iv_work[16];
u8 *plain = NULL, *batched = NULL, *unit = NULL;
unsigned int i;
int err;

if (ivsize != 16)
return 0;

plain = kmalloc(total, GFP_KERNEL);
batched = kmalloc(total, GFP_KERNEL);
unit = kmalloc(total, GFP_KERNEL);
req = skcipher_request_alloc(tfm, GFP_KERNEL);
if (!plain || !batched || !unit || !req) {
err = -ENOMEM;
goto out;
}

get_random_bytes(plain, total);
get_random_bytes(iv_orig, ivsize);

/* Pass 1: one batched encrypt with data_unit_size set. */
err = crypto_skcipher_set_data_unit_size(tfm, du_size);
if (err) {
pr_err("alg: skcipher: %s set_data_unit_size(%u) failed: %d\n",
driver, du_size, err);
goto out;
}
memcpy(batched, plain, total);
memcpy(iv_work, iv_orig, ivsize);
sg_init_one(&sg_in, batched, total);
sg_out = sg_in;
skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
skcipher_request_set_crypt(req, &sg_in, &sg_out, total, iv_work);
err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
if (err) {
pr_err("alg: skcipher: %s multi-DU batched encrypt failed: %d\n",
driver, err);
goto out_clear_du;
}

/* Pass 2: TEST_MDU_NR_UNITS single-DU encrypts with derived IVs. */
err = crypto_skcipher_set_data_unit_size(tfm, 0);
if (err)
goto out;
memcpy(unit, plain, total);
memcpy(iv_work, iv_orig, ivsize);
for (i = 0; i < TEST_MDU_NR_UNITS; i++) {
sg_init_one(&sg_in, unit + i * du_size, du_size);
sg_out = sg_in;
skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
skcipher_request_set_crypt(req, &sg_in, &sg_out, du_size,
iv_work);
err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
if (err) {
pr_err("alg: skcipher: %s single-DU[%u] encrypt failed: %d\n",
driver, i, err);
goto out;
}
/* Increment iv_work as a 128-bit little-endian counter. */
{
__le64 lo_le, hi_le;
u64 lo;

memcpy(&lo_le, iv_work, 8);
memcpy(&hi_le, iv_work + 8, 8);
lo = le64_to_cpu(lo_le) + 1;
lo_le = cpu_to_le64(lo);
memcpy(iv_work, &lo_le, 8);
if (lo == 0) {
hi_le = cpu_to_le64(le64_to_cpu(hi_le) + 1);
memcpy(iv_work + 8, &hi_le, 8);
}
}
}

if (memcmp(batched, unit, total) != 0) {
pr_err("alg: skcipher: %s multi-DU mismatch (du=%u, n=%u)\n",
driver, du_size, TEST_MDU_NR_UNITS);
err = -EINVAL;
}

out_clear_du:
(void)crypto_skcipher_set_data_unit_size(tfm, 0);
out:
skcipher_request_free(req);
kfree(unit);
kfree(batched);
kfree(plain);
return err;
}

static int alg_test_skcipher(const struct alg_test_desc *desc,
const char *driver, u32 type, u32 mask)
{
Expand Down Expand Up @@ -3259,6 +3376,18 @@ static int alg_test_skcipher(const struct alg_test_desc *desc,
if (err)
goto out;

if (crypto_skcipher_supports_multi_data_unit(tfm)) {
static const unsigned int du_sizes[] = { 512, 1024, 2048, 4096 };
unsigned int j;

for (j = 0; j < ARRAY_SIZE(du_sizes); j++) {
err = test_skcipher_multi_du(tfm, du_sizes[j]);
if (err)
goto out;
cond_resched();
}
}

err = test_skcipher_vs_generic_impl(desc->generic_driver, req, tsgls);
out:
free_cipher_test_sglists(tsgls);
Expand Down
25 changes: 23 additions & 2 deletions crypto/xts.c
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ static int xts_init_crypt(struct skcipher_request *req,
return 0;
}

static int xts_encrypt(struct skcipher_request *req)
static int xts_encrypt_one(struct skcipher_request *req)
{
struct xts_request_ctx *rctx = skcipher_request_ctx(req);
struct skcipher_request *subreq = &rctx->subreq;
Expand All @@ -275,7 +275,7 @@ static int xts_encrypt(struct skcipher_request *req)
return xts_cts_final(req, crypto_skcipher_encrypt);
}

static int xts_decrypt(struct skcipher_request *req)
static int xts_decrypt_one(struct skcipher_request *req)
{
struct xts_request_ctx *rctx = skcipher_request_ctx(req);
struct skcipher_request *subreq = &rctx->subreq;
Expand All @@ -292,6 +292,16 @@ static int xts_decrypt(struct skcipher_request *req)
return xts_cts_final(req, crypto_skcipher_decrypt);
}

static int xts_encrypt(struct skcipher_request *req)
{
return skcipher_walk_data_units(req, xts_encrypt_one);
}

static int xts_decrypt(struct skcipher_request *req)
{
return skcipher_walk_data_units(req, xts_decrypt_one);
}

static int xts_init_tfm(struct crypto_skcipher *tfm)
{
struct skcipher_instance *inst = skcipher_alg_instance(tfm);
Expand Down Expand Up @@ -427,6 +437,17 @@ static int xts_create(struct crypto_template *tmpl, struct rtattr **tb)
inst->alg.base.cra_alignmask = alg->base.cra_alignmask |
(__alignof__(u64) - 1);

/*
* Advertise multi-data-unit support only when the inner cipher is
* synchronous. The dispatcher in skcipher_walk_data_units() calls
* the single-DU body in a loop and assumes synchronous completion;
* supporting async would require a per-DU callback chain, which
* the slow software template does not need.
*/
if (!(alg->base.cra_flags & CRYPTO_ALG_ASYNC))
inst->alg.base.cra_flags |=
CRYPTO_ALG_SKCIPHER_MULTI_DATA_UNIT;

inst->alg.ivsize = XTS_BLOCK_SIZE;
inst->alg.min_keysize = alg->min_keysize * 2;
inst->alg.max_keysize = alg->max_keysize * 2;
Expand Down
Loading