| // 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); | |
| u8 *blenp = ahash_request_ctx(req); | |
| int blen; | |
| blenp += crypto_ahash_reqsize(tfm) - 1; | |
| blen = *blenp; | |
| if (blen) { | |
| if (sg_is_last(req->src)) | |
| req->src = NULL; | |
| else { | |
| req->src = req->sg_head + 1; | |
| if (sg_is_chain(req->src)) | |
| req->src = sg_chain_ptr(req->src); | |
| } | |
| req->nbytes -= blen; | |
| } | |
| ahash_restore_req(req); | |
| return err; | |
| } | |
| static void ahash_finup_done(void *data, int err) | |
| { | |
| ahash_op_done(data, err, ahash_finup_finish); | |
| } | |
| int crypto_ahash_finup(struct ahash_request *req) | |
| { | |
| struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); | |
| 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_finup(req, ahash_request_ctx(req)); | |
| if (ahash_req_on_stack(req) && ahash_is_async(tfm)) | |
| return -EAGAIN; | |
| if (!crypto_ahash_alg(tfm)->finup) | |
| return ahash_def_finup(req); | |
| if (!crypto_ahash_block_only(tfm)) | |
| return ahash_do_req_chain(req, &crypto_ahash_alg(tfm)->finup); | |
| blenp += crypto_ahash_reqsize(tfm) - 1; | |
| blen = *blenp; | |
| buf = blenp - bs; | |
| if (blen) { | |
| memset(req->sg_head, 0, sizeof(req->sg_head[0])); | |
| sg_set_buf(req->sg_head, buf, blen); | |
| if (!req->src) | |
| sg_mark_end(req->sg_head); | |
| else if (req->src != req->sg_head + 1) | |
| sg_chain(req->sg_head, 2, req->src); | |
| req->src = req->sg_head; | |
| req->nbytes += blen; | |
| } | |
| ahash_save_req(req, ahash_finup_done); | |
| err = ahash_do_req_chain(req, &crypto_ahash_alg(tfm)->finup); | |
| if (err == -EINPROGRESS || err == -EBUSY) | |
| return err; | |
| return ahash_finup_finish(req, err); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_ahash_finup); | |
| int crypto_ahash_digest(struct ahash_request *req) | |
| { | |
| struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); | |
| if (likely(tfm->using_shash)) | |
| return shash_ahash_digest(req, prepare_shash_desc(req, tfm)); | |
| if (ahash_req_on_stack(req) && ahash_is_async(tfm)) | |
| return -EAGAIN; | |
| if (crypto_ahash_get_flags(tfm) & CRYPTO_TFM_NEED_KEY) | |
| return -ENOKEY; | |
| return ahash_do_req_chain(req, &crypto_ahash_alg(tfm)->digest); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_ahash_digest); | |
| static void ahash_def_finup_done2(void *data, int err) | |
| { | |
| struct ahash_request *areq = data; | |
| if (err == -EINPROGRESS) | |
| return; | |
| ahash_restore_req(areq); | |
| ahash_request_complete(areq, err); | |
| } | |
| static int ahash_def_finup_finish1(struct ahash_request *req, int err) | |
| { | |
| struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); | |
| if (err) | |
| goto out; | |
| req->base.complete = ahash_def_finup_done2; | |
| err = crypto_ahash_alg(tfm)->final(req); | |
| if (err == -EINPROGRESS || err == -EBUSY) | |
| return err; | |
| out: | |
| ahash_restore_req(req); | |
| return err; | |
| } | |
| static void ahash_def_finup_done1(void *data, int err) | |
| { | |
| ahash_op_done(data, err, ahash_def_finup_finish1); | |
| } | |
| static int ahash_def_finup(struct ahash_request *req) | |
| { | |
| int err; | |
| ahash_save_req(req, ahash_def_finup_done1); | |
| err = crypto_ahash_update(req); | |
| if (err == -EINPROGRESS || err == -EBUSY) | |
| return err; | |
| return ahash_def_finup_finish1(req, err); | |
| } | |
| int crypto_ahash_export_core(struct ahash_request *req, void *out) | |
| { | |
| struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); | |
| if (likely(tfm->using_shash)) | |
| return crypto_shash_export_core(ahash_request_ctx(req), out); | |
| return crypto_ahash_alg(tfm)->export_core(req, out); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_ahash_export_core); | |
| int crypto_ahash_export(struct ahash_request *req, void *out) | |
| { | |
| struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); | |
| if (likely(tfm->using_shash)) | |
| return crypto_shash_export(ahash_request_ctx(req), out); | |
| if (crypto_ahash_block_only(tfm)) { | |
| unsigned int plen = crypto_ahash_blocksize(tfm) + 1; | |
| unsigned int reqsize = crypto_ahash_reqsize(tfm); | |
| unsigned int ss = crypto_ahash_statesize(tfm); | |
| u8 *buf = ahash_request_ctx(req); | |
| memcpy(out + ss - plen, buf + reqsize - plen, plen); | |
| } | |
| return crypto_ahash_alg(tfm)->export(req, out); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_ahash_export); | |
| int crypto_ahash_import_core(struct ahash_request *req, const void *in) | |
| { | |
| struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); | |
| if (likely(tfm->using_shash)) | |
| return crypto_shash_import_core(prepare_shash_desc(req, tfm), | |
| in); | |
| if (crypto_ahash_get_flags(tfm) & CRYPTO_TFM_NEED_KEY) | |
| return -ENOKEY; | |
| if (crypto_ahash_block_only(tfm)) { | |
| unsigned int reqsize = crypto_ahash_reqsize(tfm); | |
| u8 *buf = ahash_request_ctx(req); | |
| buf[reqsize - 1] = 0; | |
| } | |
| return crypto_ahash_alg(tfm)->import_core(req, in); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_ahash_import_core); | |
| int crypto_ahash_import(struct ahash_request *req, const void *in) | |
| { | |
| struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); | |
| if (likely(tfm->using_shash)) | |
| return crypto_shash_import(prepare_shash_desc(req, tfm), in); | |
| if (crypto_ahash_get_flags(tfm) & CRYPTO_TFM_NEED_KEY) | |
| return -ENOKEY; | |
| if (crypto_ahash_block_only(tfm)) { | |
| unsigned int plen = crypto_ahash_blocksize(tfm) + 1; | |
| unsigned int reqsize = crypto_ahash_reqsize(tfm); | |
| unsigned int ss = crypto_ahash_statesize(tfm); | |
| u8 *buf = ahash_request_ctx(req); | |
| memcpy(buf + reqsize - plen, in + ss - plen, plen); | |
| if (buf[reqsize - 1] >= plen) | |
| return -EOVERFLOW; | |
| } | |
| return crypto_ahash_alg(tfm)->import(req, in); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_ahash_import); | |
| static void crypto_ahash_exit_tfm(struct crypto_tfm *tfm) | |
| { | |
| struct crypto_ahash *hash = __crypto_ahash_cast(tfm); | |
| struct ahash_alg *alg = crypto_ahash_alg(hash); | |
| if (alg->exit_tfm) | |
| alg->exit_tfm(hash); | |
| else if (tfm->__crt_alg->cra_exit) | |
| tfm->__crt_alg->cra_exit(tfm); | |
| if (crypto_ahash_need_fallback(hash)) | |
| crypto_free_ahash(crypto_ahash_fb(hash)); | |
| } | |
| static int crypto_ahash_init_tfm(struct crypto_tfm *tfm) | |
| { | |
| struct crypto_ahash *hash = __crypto_ahash_cast(tfm); | |
| struct ahash_alg *alg = crypto_ahash_alg(hash); | |
| struct crypto_ahash *fb = NULL; | |
| int err; | |
| crypto_ahash_set_statesize(hash, alg->halg.statesize); | |
| crypto_ahash_set_reqsize(hash, crypto_tfm_alg_reqsize(tfm)); | |
| if (tfm->__crt_alg->cra_type == &crypto_shash_type) | |
| return crypto_init_ahash_using_shash(tfm); | |
| if (crypto_ahash_need_fallback(hash)) { | |
| fb = crypto_alloc_ahash(crypto_ahash_alg_name(hash), | |
| CRYPTO_ALG_REQ_VIRT, | |
| CRYPTO_ALG_ASYNC | | |
| CRYPTO_ALG_REQ_VIRT | | |
| CRYPTO_AHASH_ALG_NO_EXPORT_CORE); | |
| if (IS_ERR(fb)) | |
| return PTR_ERR(fb); | |
| tfm->fb = crypto_ahash_tfm(fb); | |
| } | |
| ahash_set_needkey(hash, alg); | |
| tfm->exit = crypto_ahash_exit_tfm; | |
| if (alg->init_tfm) | |
| err = alg->init_tfm(hash); | |
| else if (tfm->__crt_alg->cra_init) | |
| err = tfm->__crt_alg->cra_init(tfm); | |
| else | |
| return 0; | |
| if (err) | |
| goto out_free_sync_hash; | |
| if (!ahash_is_async(hash) && crypto_ahash_reqsize(hash) > | |
| MAX_SYNC_HASH_REQSIZE) | |
| goto out_exit_tfm; | |
| BUILD_BUG_ON(HASH_MAX_DESCSIZE > MAX_SYNC_HASH_REQSIZE); | |
| if (crypto_ahash_reqsize(hash) < HASH_MAX_DESCSIZE) | |
| crypto_ahash_set_reqsize(hash, HASH_MAX_DESCSIZE); | |
| return 0; | |
| out_exit_tfm: | |
| if (alg->exit_tfm) | |
| alg->exit_tfm(hash); | |
| else if (tfm->__crt_alg->cra_exit) | |
| tfm->__crt_alg->cra_exit(tfm); | |
| err = -EINVAL; | |
| out_free_sync_hash: | |
| crypto_free_ahash(fb); | |
| return err; | |
| } | |
| static unsigned int crypto_ahash_extsize(struct crypto_alg *alg) | |
| { | |
| if (alg->cra_type == &crypto_shash_type) | |
| return sizeof(struct crypto_shash *); | |
| return crypto_alg_extsize(alg); | |
| } | |
| static void crypto_ahash_free_instance(struct crypto_instance *inst) | |
| { | |
| struct ahash_instance *ahash = ahash_instance(inst); | |
| ahash->free(ahash); | |
| } | |
| static int __maybe_unused crypto_ahash_report( | |
| struct sk_buff *skb, struct crypto_alg *alg) | |
| { | |
| struct crypto_report_hash rhash = { | |
| .type = "ahash", | |
| }; | |
| rhash.blocksize = alg->cra_blocksize; | |
| rhash.digestsize = __crypto_hash_alg_common(alg)->digestsize; | |
| return nla_put(skb, CRYPTOCFGA_REPORT_HASH, sizeof(rhash), &rhash); | |
| } | |
| static void __maybe_unused crypto_ahash_show(struct seq_file *m, | |
| struct crypto_alg *alg) | |
| { | |
| seq_printf(m, "type : ahash\n"); | |
| seq_printf(m, "async : %s\n", | |
| str_yes_no(alg->cra_flags & CRYPTO_ALG_ASYNC)); | |
| seq_printf(m, "blocksize : %u\n", alg->cra_blocksize); | |
| seq_printf(m, "digestsize : %u\n", | |
| __crypto_hash_alg_common(alg)->digestsize); | |
| } | |
| static const struct crypto_type crypto_ahash_type = { | |
| .extsize = crypto_ahash_extsize, | |
| .init_tfm = crypto_ahash_init_tfm, | |
| .free = crypto_ahash_free_instance, | |
| #ifdef CONFIG_PROC_FS | |
| .show = crypto_ahash_show, | |
| #endif | |
| #if IS_ENABLED(CONFIG_CRYPTO_USER) | |
| .report = crypto_ahash_report, | |
| #endif | |
| .maskclear = ~CRYPTO_ALG_TYPE_MASK, | |
| .maskset = CRYPTO_ALG_TYPE_AHASH_MASK, | |
| .type = CRYPTO_ALG_TYPE_AHASH, | |
| .tfmsize = offsetof(struct crypto_ahash, base), | |
| .algsize = offsetof(struct ahash_alg, halg.base), | |
| }; | |
| int crypto_grab_ahash(struct crypto_ahash_spawn *spawn, | |
| struct crypto_instance *inst, | |
| const char *name, u32 type, u32 mask) | |
| { | |
| spawn->base.frontend = &crypto_ahash_type; | |
| return crypto_grab_spawn(&spawn->base, inst, name, type, mask); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_grab_ahash); | |
| struct crypto_ahash *crypto_alloc_ahash(const char *alg_name, u32 type, | |
| u32 mask) | |
| { | |
| return crypto_alloc_tfm(alg_name, &crypto_ahash_type, type, mask); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_alloc_ahash); | |
| int crypto_has_ahash(const char *alg_name, u32 type, u32 mask) | |
| { | |
| return crypto_type_has_alg(alg_name, &crypto_ahash_type, type, mask); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_has_ahash); | |
| bool crypto_hash_alg_has_setkey(struct hash_alg_common *halg) | |
| { | |
| struct crypto_alg *alg = &halg->base; | |
| if (alg->cra_type == &crypto_shash_type) | |
| return crypto_shash_alg_has_setkey(__crypto_shash_alg(alg)); | |
| return __crypto_ahash_alg(alg)->setkey != ahash_nosetkey; | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_hash_alg_has_setkey); | |
| static int ahash_default_export_core(struct ahash_request *req, void *out) | |
| { | |
| return -ENOSYS; | |
| } | |
| static int ahash_default_import_core(struct ahash_request *req, const void *in) | |
| { | |
| return -ENOSYS; | |
| } | |
| static int ahash_prepare_alg(struct ahash_alg *alg) | |
| { | |
| struct crypto_alg *base = &alg->halg.base; | |
| int err; | |
| if (alg->halg.statesize == 0) | |
| return -EINVAL; | |
| if (base->cra_reqsize && base->cra_reqsize < alg->halg.statesize) | |
| return -EINVAL; | |
| if (!(base->cra_flags & CRYPTO_ALG_ASYNC) && | |
| base->cra_reqsize > MAX_SYNC_HASH_REQSIZE) | |
| return -EINVAL; | |
| if (base->cra_flags & CRYPTO_ALG_NEED_FALLBACK && | |
| base->cra_flags & CRYPTO_ALG_NO_FALLBACK) | |
| return -EINVAL; | |
| err = hash_prepare_alg(&alg->halg); | |
| if (err) | |
| return err; | |
| base->cra_type = &crypto_ahash_type; | |
| base->cra_flags |= CRYPTO_ALG_TYPE_AHASH; | |
| if ((base->cra_flags ^ CRYPTO_ALG_REQ_VIRT) & | |
| (CRYPTO_ALG_ASYNC | CRYPTO_ALG_REQ_VIRT) && | |
| !(base->cra_flags & CRYPTO_ALG_NO_FALLBACK)) | |
| base->cra_flags |= CRYPTO_ALG_NEED_FALLBACK; | |
| if (!alg->setkey) | |
| alg->setkey = ahash_nosetkey; | |
| if (base->cra_flags & CRYPTO_AHASH_ALG_BLOCK_ONLY) { | |
| BUILD_BUG_ON(MAX_ALGAPI_BLOCKSIZE >= 256); | |
| if (!alg->finup) | |
| return -EINVAL; | |
| base->cra_reqsize += base->cra_blocksize + 1; | |
| alg->halg.statesize += base->cra_blocksize + 1; | |
| alg->export_core = alg->export; | |
| alg->import_core = alg->import; | |
| } else if (!alg->export_core || !alg->import_core) { | |
| alg->export_core = ahash_default_export_core; | |
| alg->import_core = ahash_default_import_core; | |
| base->cra_flags |= CRYPTO_AHASH_ALG_NO_EXPORT_CORE; | |
| } | |
| return 0; | |
| } | |
| int crypto_register_ahash(struct ahash_alg *alg) | |
| { | |
| struct crypto_alg *base = &alg->halg.base; | |
| int err; | |
| err = ahash_prepare_alg(alg); | |
| if (err) | |
| return err; | |
| return crypto_register_alg(base); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_register_ahash); | |
| void crypto_unregister_ahash(struct ahash_alg *alg) | |
| { | |
| crypto_unregister_alg(&alg->halg.base); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_unregister_ahash); | |
| int crypto_register_ahashes(struct ahash_alg *algs, int count) | |
| { | |
| int i, ret; | |
| for (i = 0; i < count; i++) { | |
| ret = crypto_register_ahash(&algs[i]); | |
| if (ret) { | |
| crypto_unregister_ahashes(algs, i); | |
| return ret; | |
| } | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_register_ahashes); | |
| void crypto_unregister_ahashes(struct ahash_alg *algs, int count) | |
| { | |
| int i; | |
| for (i = count - 1; i >= 0; --i) | |
| crypto_unregister_ahash(&algs[i]); | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_unregister_ahashes); | |
| int ahash_register_instance(struct crypto_template *tmpl, | |
| struct ahash_instance *inst) | |
| { | |
| int err; | |
| if (WARN_ON(!inst->free)) | |
| return -EINVAL; | |
| err = ahash_prepare_alg(&inst->alg); | |
| if (err) | |
| return err; | |
| return crypto_register_instance(tmpl, ahash_crypto_instance(inst)); | |
| } | |
| EXPORT_SYMBOL_GPL(ahash_register_instance); | |
| void ahash_request_free(struct ahash_request *req) | |
| { | |
| if (unlikely(!req)) | |
| return; | |
| if (!ahash_req_on_stack(req)) { | |
| kfree(req); | |
| return; | |
| } | |
| ahash_request_zero(req); | |
| } | |
| EXPORT_SYMBOL_GPL(ahash_request_free); | |
| int crypto_hash_digest(struct crypto_ahash *tfm, const u8 *data, | |
| unsigned int len, u8 *out) | |
| { | |
| HASH_REQUEST_ON_STACK(req, crypto_ahash_fb(tfm)); | |
| int err; | |
| ahash_request_set_callback(req, 0, NULL, NULL); | |
| ahash_request_set_virt(req, data, out, len); | |
| err = crypto_ahash_digest(req); | |
| ahash_request_zero(req); | |
| return err; | |
| } | |
| EXPORT_SYMBOL_GPL(crypto_hash_digest); | |
| void ahash_free_singlespawn_instance(struct ahash_instance *inst) | |
| { | |
| crypto_drop_spawn(ahash_instance_ctx(inst)); | |
| kfree(inst); | |
| } | |
| EXPORT_SYMBOL_GPL(ahash_free_singlespawn_instance); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_DESCRIPTION("Asynchronous cryptographic hash type"); |