1023 lines · 24.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * Asynchronous Cryptographic Hash operations. * * This is the implementation of the ahash (asynchronous hash) API.  It differs * from shash (synchronous hash) in that ahash supports asynchronous operations, * and it hashes data from scatterlists instead of virtually addressed buffers. * * The ahash API provides access to both ahash and shash algorithms.  The shash * API only provides access to shash algorithms. * * Copyright (c) 2008 Loc Ho <lho@amcc.com> */ #include <crypto/scatterwalk.h>#include <linux/cryptouser.h>#include <linux/err.h>#include <linux/kernel.h>#include <linux/mm.h>#include <linux/module.h>#include <linux/scatterlist.h>#include <linux/slab.h>#include <linux/seq_file.h>#include <linux/string.h>#include <linux/string_choices.h>#include <net/netlink.h> #include "hash.h" #define CRYPTO_ALG_TYPE_AHASH_MASK	0x0000000e static int ahash_def_finup(struct ahash_request *req); static inline bool crypto_ahash_block_only(struct crypto_ahash *tfm){	return crypto_ahash_alg(tfm)->halg.base.cra_flags &	       CRYPTO_AHASH_ALG_BLOCK_ONLY;} static inline bool crypto_ahash_final_nonzero(struct crypto_ahash *tfm){	return crypto_ahash_alg(tfm)->halg.base.cra_flags &	       CRYPTO_AHASH_ALG_FINAL_NONZERO;} static inline bool crypto_ahash_need_fallback(struct crypto_ahash *tfm){	return crypto_ahash_alg(tfm)->halg.base.cra_flags &	       CRYPTO_ALG_NEED_FALLBACK;} static inline void ahash_op_done(void *data, int err,				 int (*finish)(struct ahash_request *, int)){	struct ahash_request *areq = data;	crypto_completion_t compl; 	compl = areq->saved_complete;	data = areq->saved_data;	if (err == -EINPROGRESS)		goto out; 	areq->base.flags &= ~CRYPTO_TFM_REQ_MAY_SLEEP; 	err = finish(areq, err);	if (err == -EINPROGRESS || err == -EBUSY)		return; out:	compl(data, err);} static int hash_walk_next(struct crypto_hash_walk *walk){	unsigned int offset = walk->offset;	unsigned int nbytes = min(walk->entrylen,				  ((unsigned int)(PAGE_SIZE)) - offset); 	walk->data = kmap_local_page(walk->pg);	walk->data += offset;	walk->entrylen -= nbytes;	return nbytes;} static int hash_walk_new_entry(struct crypto_hash_walk *walk){	struct scatterlist *sg; 	sg = walk->sg;	walk->offset = sg->offset;	walk->pg = sg_page(walk->sg) + (walk->offset >> PAGE_SHIFT);	walk->offset = offset_in_page(walk->offset);	walk->entrylen = sg->length; 	if (walk->entrylen > walk->total)		walk->entrylen = walk->total;	walk->total -= walk->entrylen; 	return hash_walk_next(walk);} int crypto_hash_walk_first(struct ahash_request *req,			   struct crypto_hash_walk *walk){	walk->total = req->nbytes;	walk->entrylen = 0; 	if (!walk->total)		return 0; 	walk->flags = req->base.flags; 	if (ahash_request_isvirt(req)) {		walk->data = req->svirt;		walk->total = 0;		return req->nbytes;	} 	walk->sg = req->src; 	return hash_walk_new_entry(walk);}EXPORT_SYMBOL_GPL(crypto_hash_walk_first); int crypto_hash_walk_done(struct crypto_hash_walk *walk, int err){	if ((walk->flags & CRYPTO_AHASH_REQ_VIRT))		return err; 	walk->data -= walk->offset; 	kunmap_local(walk->data);	crypto_yield(walk->flags); 	if (err)		return err; 	if (walk->entrylen) {		walk->offset = 0;		walk->pg++;		return hash_walk_next(walk);	} 	if (!walk->total)		return 0; 	walk->sg = sg_next(walk->sg); 	return hash_walk_new_entry(walk);}EXPORT_SYMBOL_GPL(crypto_hash_walk_done); /* * For an ahash tfm that is using an shash algorithm (instead of an ahash * algorithm), this returns the underlying shash tfm. */static inline struct crypto_shash *ahash_to_shash(struct crypto_ahash *tfm){	return *(struct crypto_shash **)crypto_ahash_ctx(tfm);} static inline struct shash_desc *prepare_shash_desc(struct ahash_request *req,						    struct crypto_ahash *tfm){	struct shash_desc *desc = ahash_request_ctx(req); 	desc->tfm = ahash_to_shash(tfm);	return desc;} int shash_ahash_update(struct ahash_request *req, struct shash_desc *desc){	struct crypto_hash_walk walk;	int nbytes; 	for (nbytes = crypto_hash_walk_first(req, &walk); nbytes > 0;	     nbytes = crypto_hash_walk_done(&walk, nbytes))		nbytes = crypto_shash_update(desc, walk.data, nbytes); 	return nbytes;}EXPORT_SYMBOL_GPL(shash_ahash_update); int shash_ahash_finup(struct ahash_request *req, struct shash_desc *desc){	struct crypto_hash_walk walk;	int nbytes; 	nbytes = crypto_hash_walk_first(req, &walk);	if (!nbytes)		return crypto_shash_final(desc, req->result); 	do {		nbytes = crypto_hash_walk_last(&walk) ?			 crypto_shash_finup(desc, walk.data, nbytes,					    req->result) :			 crypto_shash_update(desc, walk.data, nbytes);		nbytes = crypto_hash_walk_done(&walk, nbytes);	} while (nbytes > 0); 	return nbytes;}EXPORT_SYMBOL_GPL(shash_ahash_finup); int shash_ahash_digest(struct ahash_request *req, struct shash_desc *desc){	unsigned int nbytes = req->nbytes;	struct scatterlist *sg;	unsigned int offset;	struct page *page;	const u8 *data;	int err; 	data = req->svirt;	if (!nbytes || ahash_request_isvirt(req))		return crypto_shash_digest(desc, data, nbytes, req->result); 	sg = req->src;	if (nbytes > sg->length)		return crypto_shash_init(desc) ?:		       shash_ahash_finup(req, desc); 	page = sg_page(sg);	offset = sg->offset;	data = lowmem_page_address(page) + offset;	if (!IS_ENABLED(CONFIG_HIGHMEM))		return crypto_shash_digest(desc, data, nbytes, req->result); 	page += offset >> PAGE_SHIFT;	offset = offset_in_page(offset); 	if (nbytes > (unsigned int)PAGE_SIZE - offset)		return crypto_shash_init(desc) ?:		       shash_ahash_finup(req, desc); 	data = kmap_local_page(page);	err = crypto_shash_digest(desc, data + offset, nbytes,				  req->result);	kunmap_local(data);	return err;}EXPORT_SYMBOL_GPL(shash_ahash_digest); static void crypto_exit_ahash_using_shash(struct crypto_tfm *tfm){	struct crypto_shash **ctx = crypto_tfm_ctx(tfm); 	crypto_free_shash(*ctx);} static int crypto_init_ahash_using_shash(struct crypto_tfm *tfm){	struct crypto_alg *calg = tfm->__crt_alg;	struct crypto_ahash *crt = __crypto_ahash_cast(tfm);	struct crypto_shash **ctx = crypto_tfm_ctx(tfm);	struct crypto_shash *shash; 	if (!crypto_mod_get(calg))		return -EAGAIN; 	shash = crypto_create_tfm(calg, &crypto_shash_type);	if (IS_ERR(shash)) {		crypto_mod_put(calg);		return PTR_ERR(shash);	} 	crt->using_shash = true;	*ctx = shash;	tfm->exit = crypto_exit_ahash_using_shash; 	crypto_ahash_set_flags(crt, crypto_shash_get_flags(shash) &				    CRYPTO_TFM_NEED_KEY); 	return 0;} static int ahash_nosetkey(struct crypto_ahash *tfm, const u8 *key,			  unsigned int keylen){	return -ENOSYS;} static void ahash_set_needkey(struct crypto_ahash *tfm, struct ahash_alg *alg){	if (alg->setkey != ahash_nosetkey &&	    !(alg->halg.base.cra_flags & CRYPTO_ALG_OPTIONAL_KEY))		crypto_ahash_set_flags(tfm, CRYPTO_TFM_NEED_KEY);} int crypto_ahash_setkey(struct crypto_ahash *tfm, const u8 *key,			unsigned int keylen){	if (likely(tfm->using_shash)) {		struct crypto_shash *shash = ahash_to_shash(tfm);		int err; 		err = crypto_shash_setkey(shash, key, keylen);		if (unlikely(err)) {			crypto_ahash_set_flags(tfm,					       crypto_shash_get_flags(shash) &					       CRYPTO_TFM_NEED_KEY);			return err;		}	} else {		struct ahash_alg *alg = crypto_ahash_alg(tfm);		int err; 		err = alg->setkey(tfm, key, keylen);		if (!err && crypto_ahash_need_fallback(tfm))			err = crypto_ahash_setkey(crypto_ahash_fb(tfm),						  key, keylen);		if (unlikely(err)) {			ahash_set_needkey(tfm, alg);			return err;		}	}	crypto_ahash_clear_flags(tfm, CRYPTO_TFM_NEED_KEY);	return 0;}EXPORT_SYMBOL_GPL(crypto_ahash_setkey); static int ahash_do_req_chain(struct ahash_request *req,			      int (*const *op)(struct ahash_request *req)){	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);	int err; 	if (crypto_ahash_req_virt(tfm) || !ahash_request_isvirt(req))		return (*op)(req); 	if (crypto_ahash_statesize(tfm) > HASH_MAX_STATESIZE)		return -ENOSYS; 	if (!crypto_ahash_need_fallback(tfm))		return -ENOSYS; 	if (crypto_hash_no_export_core(tfm))		return -ENOSYS; 	{		u8 state[HASH_MAX_STATESIZE]; 		if (op == &crypto_ahash_alg(tfm)->digest) {			ahash_request_set_tfm(req, crypto_ahash_fb(tfm));			err = crypto_ahash_digest(req);			goto out_no_state;		} 		err = crypto_ahash_export(req, state);		ahash_request_set_tfm(req, crypto_ahash_fb(tfm));		err = err ?: crypto_ahash_import(req, state); 		if (op == &crypto_ahash_alg(tfm)->finup) {			err = err ?: crypto_ahash_finup(req);			goto out_no_state;		} 		err = err ?:		      crypto_ahash_update(req) ?:		      crypto_ahash_export(req, state); 		ahash_request_set_tfm(req, tfm);		return err ?: crypto_ahash_import(req, state); out_no_state:		ahash_request_set_tfm(req, tfm);		return err;	}} int crypto_ahash_init(struct ahash_request *req){	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); 	if (likely(tfm->using_shash))		return crypto_shash_init(prepare_shash_desc(req, tfm));	if (crypto_ahash_get_flags(tfm) & CRYPTO_TFM_NEED_KEY)		return -ENOKEY;	if (ahash_req_on_stack(req) && ahash_is_async(tfm))		return -EAGAIN;	if (crypto_ahash_block_only(tfm)) {		u8 *buf = ahash_request_ctx(req); 		buf += crypto_ahash_reqsize(tfm) - 1;		*buf = 0;	}	return crypto_ahash_alg(tfm)->init(req);}EXPORT_SYMBOL_GPL(crypto_ahash_init); static void ahash_save_req(struct ahash_request *req, crypto_completion_t cplt){	req->saved_complete = req->base.complete;	req->saved_data = req->base.data;	req->base.complete = cplt;	req->base.data = req;} static void ahash_restore_req(struct ahash_request *req){	req->base.complete = req->saved_complete;	req->base.data = req->saved_data;} static int ahash_update_finish(struct ahash_request *req, int err){	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);	bool nonzero = crypto_ahash_final_nonzero(tfm);	int bs = crypto_ahash_blocksize(tfm);	u8 *blenp = ahash_request_ctx(req);	int blen;	u8 *buf; 	blenp += crypto_ahash_reqsize(tfm) - 1;	blen = *blenp;	buf = blenp - bs; 	if (blen) {		req->src = req->sg_head + 1;		if (sg_is_chain(req->src))			req->src = sg_chain_ptr(req->src);	} 	req->nbytes += nonzero - blen; 	blen = 0;	if (err >= 0) {		blen = err + nonzero;		err = 0;	}	if (ahash_request_isvirt(req))		memcpy(buf, req->svirt + req->nbytes - blen, blen);	else		memcpy_from_sglist(buf, req->src, req->nbytes - blen, blen);	*blenp = blen; 	ahash_restore_req(req); 	return err;} static void ahash_update_done(void *data, int err){	ahash_op_done(data, err, ahash_update_finish);} int crypto_ahash_update(struct ahash_request *req){	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);	bool nonzero = crypto_ahash_final_nonzero(tfm);	int bs = crypto_ahash_blocksize(tfm);	u8 *blenp = ahash_request_ctx(req);	int blen, err;	u8 *buf; 	if (likely(tfm->using_shash))		return shash_ahash_update(req, ahash_request_ctx(req));	if (ahash_req_on_stack(req) && ahash_is_async(tfm))		return -EAGAIN;	if (!crypto_ahash_block_only(tfm))		return ahash_do_req_chain(req, &crypto_ahash_alg(tfm)->update); 	blenp += crypto_ahash_reqsize(tfm) - 1;	blen = *blenp;	buf = blenp - bs; 	if (blen + req->nbytes < bs + nonzero) {		if (ahash_request_isvirt(req))			memcpy(buf + blen, req->svirt, req->nbytes);		else			memcpy_from_sglist(buf + blen, req->src, 0,					   req->nbytes); 		*blenp += req->nbytes;		return 0;	} 	if (blen) {		memset(req->sg_head, 0, sizeof(req->sg_head[0]));		sg_set_buf(req->sg_head, buf, blen);		if (req->src != req->sg_head + 1)			sg_chain(req->sg_head, 2, req->src);		req->src = req->sg_head;		req->nbytes += blen;	}	req->nbytes -= nonzero; 	ahash_save_req(req, ahash_update_done); 	err = ahash_do_req_chain(req, &crypto_ahash_alg(tfm)->update);	if (err == -EINPROGRESS || err == -EBUSY)		return err; 	return ahash_update_finish(req, err);}EXPORT_SYMBOL_GPL(crypto_ahash_update); static int ahash_finup_finish(struct ahash_request *req, int err){	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);

Lines 1–500 of 1023.