162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0-or-later */ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * SM4-GCM AEAD Algorithm using ARMv8 Crypto Extensions 462306a36Sopenharmony_ci * as specified in rfc8998 562306a36Sopenharmony_ci * https://datatracker.ietf.org/doc/html/rfc8998 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright (C) 2022 Tianjia Zhang <tianjia.zhang@linux.alibaba.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/crypto.h> 1262306a36Sopenharmony_ci#include <linux/kernel.h> 1362306a36Sopenharmony_ci#include <linux/cpufeature.h> 1462306a36Sopenharmony_ci#include <asm/neon.h> 1562306a36Sopenharmony_ci#include <crypto/b128ops.h> 1662306a36Sopenharmony_ci#include <crypto/scatterwalk.h> 1762306a36Sopenharmony_ci#include <crypto/internal/aead.h> 1862306a36Sopenharmony_ci#include <crypto/internal/skcipher.h> 1962306a36Sopenharmony_ci#include <crypto/sm4.h> 2062306a36Sopenharmony_ci#include "sm4-ce.h" 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ciasmlinkage void sm4_ce_pmull_ghash_setup(const u32 *rkey_enc, u8 *ghash_table); 2362306a36Sopenharmony_ciasmlinkage void pmull_ghash_update(const u8 *ghash_table, u8 *ghash, 2462306a36Sopenharmony_ci const u8 *src, unsigned int nblocks); 2562306a36Sopenharmony_ciasmlinkage void sm4_ce_pmull_gcm_enc(const u32 *rkey_enc, u8 *dst, 2662306a36Sopenharmony_ci const u8 *src, u8 *iv, 2762306a36Sopenharmony_ci unsigned int nbytes, u8 *ghash, 2862306a36Sopenharmony_ci const u8 *ghash_table, const u8 *lengths); 2962306a36Sopenharmony_ciasmlinkage void sm4_ce_pmull_gcm_dec(const u32 *rkey_enc, u8 *dst, 3062306a36Sopenharmony_ci const u8 *src, u8 *iv, 3162306a36Sopenharmony_ci unsigned int nbytes, u8 *ghash, 3262306a36Sopenharmony_ci const u8 *ghash_table, const u8 *lengths); 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define GHASH_BLOCK_SIZE 16 3562306a36Sopenharmony_ci#define GCM_IV_SIZE 12 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistruct sm4_gcm_ctx { 3862306a36Sopenharmony_ci struct sm4_ctx key; 3962306a36Sopenharmony_ci u8 ghash_table[16 * 4]; 4062306a36Sopenharmony_ci}; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic int gcm_setkey(struct crypto_aead *tfm, const u8 *key, 4462306a36Sopenharmony_ci unsigned int key_len) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci struct sm4_gcm_ctx *ctx = crypto_aead_ctx(tfm); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci if (key_len != SM4_KEY_SIZE) 4962306a36Sopenharmony_ci return -EINVAL; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci kernel_neon_begin(); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci sm4_ce_expand_key(key, ctx->key.rkey_enc, ctx->key.rkey_dec, 5462306a36Sopenharmony_ci crypto_sm4_fk, crypto_sm4_ck); 5562306a36Sopenharmony_ci sm4_ce_pmull_ghash_setup(ctx->key.rkey_enc, ctx->ghash_table); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci kernel_neon_end(); 5862306a36Sopenharmony_ci return 0; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic int gcm_setauthsize(struct crypto_aead *tfm, unsigned int authsize) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci switch (authsize) { 6462306a36Sopenharmony_ci case 4: 6562306a36Sopenharmony_ci case 8: 6662306a36Sopenharmony_ci case 12 ... 16: 6762306a36Sopenharmony_ci return 0; 6862306a36Sopenharmony_ci default: 6962306a36Sopenharmony_ci return -EINVAL; 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic void gcm_calculate_auth_mac(struct aead_request *req, u8 ghash[]) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci struct crypto_aead *aead = crypto_aead_reqtfm(req); 7662306a36Sopenharmony_ci struct sm4_gcm_ctx *ctx = crypto_aead_ctx(aead); 7762306a36Sopenharmony_ci u8 __aligned(8) buffer[GHASH_BLOCK_SIZE]; 7862306a36Sopenharmony_ci u32 assoclen = req->assoclen; 7962306a36Sopenharmony_ci struct scatter_walk walk; 8062306a36Sopenharmony_ci unsigned int buflen = 0; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci scatterwalk_start(&walk, req->src); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci do { 8562306a36Sopenharmony_ci u32 n = scatterwalk_clamp(&walk, assoclen); 8662306a36Sopenharmony_ci u8 *p, *ptr; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci if (!n) { 8962306a36Sopenharmony_ci scatterwalk_start(&walk, sg_next(walk.sg)); 9062306a36Sopenharmony_ci n = scatterwalk_clamp(&walk, assoclen); 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci p = ptr = scatterwalk_map(&walk); 9462306a36Sopenharmony_ci assoclen -= n; 9562306a36Sopenharmony_ci scatterwalk_advance(&walk, n); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (n + buflen < GHASH_BLOCK_SIZE) { 9862306a36Sopenharmony_ci memcpy(&buffer[buflen], ptr, n); 9962306a36Sopenharmony_ci buflen += n; 10062306a36Sopenharmony_ci } else { 10162306a36Sopenharmony_ci unsigned int nblocks; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci if (buflen) { 10462306a36Sopenharmony_ci unsigned int l = GHASH_BLOCK_SIZE - buflen; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci memcpy(&buffer[buflen], ptr, l); 10762306a36Sopenharmony_ci ptr += l; 10862306a36Sopenharmony_ci n -= l; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci pmull_ghash_update(ctx->ghash_table, ghash, 11162306a36Sopenharmony_ci buffer, 1); 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci nblocks = n / GHASH_BLOCK_SIZE; 11562306a36Sopenharmony_ci if (nblocks) { 11662306a36Sopenharmony_ci pmull_ghash_update(ctx->ghash_table, ghash, 11762306a36Sopenharmony_ci ptr, nblocks); 11862306a36Sopenharmony_ci ptr += nblocks * GHASH_BLOCK_SIZE; 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci buflen = n % GHASH_BLOCK_SIZE; 12262306a36Sopenharmony_ci if (buflen) 12362306a36Sopenharmony_ci memcpy(&buffer[0], ptr, buflen); 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci scatterwalk_unmap(p); 12762306a36Sopenharmony_ci scatterwalk_done(&walk, 0, assoclen); 12862306a36Sopenharmony_ci } while (assoclen); 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci /* padding with '0' */ 13162306a36Sopenharmony_ci if (buflen) { 13262306a36Sopenharmony_ci memset(&buffer[buflen], 0, GHASH_BLOCK_SIZE - buflen); 13362306a36Sopenharmony_ci pmull_ghash_update(ctx->ghash_table, ghash, buffer, 1); 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci} 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic int gcm_crypt(struct aead_request *req, struct skcipher_walk *walk, 13862306a36Sopenharmony_ci u8 ghash[], int err, 13962306a36Sopenharmony_ci void (*sm4_ce_pmull_gcm_crypt)(const u32 *rkey_enc, 14062306a36Sopenharmony_ci u8 *dst, const u8 *src, u8 *iv, 14162306a36Sopenharmony_ci unsigned int nbytes, u8 *ghash, 14262306a36Sopenharmony_ci const u8 *ghash_table, const u8 *lengths)) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci struct crypto_aead *aead = crypto_aead_reqtfm(req); 14562306a36Sopenharmony_ci struct sm4_gcm_ctx *ctx = crypto_aead_ctx(aead); 14662306a36Sopenharmony_ci u8 __aligned(8) iv[SM4_BLOCK_SIZE]; 14762306a36Sopenharmony_ci be128 __aligned(8) lengths; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci memset(ghash, 0, SM4_BLOCK_SIZE); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci lengths.a = cpu_to_be64(req->assoclen * 8); 15262306a36Sopenharmony_ci lengths.b = cpu_to_be64(walk->total * 8); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci memcpy(iv, req->iv, GCM_IV_SIZE); 15562306a36Sopenharmony_ci put_unaligned_be32(2, iv + GCM_IV_SIZE); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci kernel_neon_begin(); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (req->assoclen) 16062306a36Sopenharmony_ci gcm_calculate_auth_mac(req, ghash); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci while (walk->nbytes) { 16362306a36Sopenharmony_ci unsigned int tail = walk->nbytes % SM4_BLOCK_SIZE; 16462306a36Sopenharmony_ci const u8 *src = walk->src.virt.addr; 16562306a36Sopenharmony_ci u8 *dst = walk->dst.virt.addr; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci if (walk->nbytes == walk->total) { 16862306a36Sopenharmony_ci sm4_ce_pmull_gcm_crypt(ctx->key.rkey_enc, dst, src, iv, 16962306a36Sopenharmony_ci walk->nbytes, ghash, 17062306a36Sopenharmony_ci ctx->ghash_table, 17162306a36Sopenharmony_ci (const u8 *)&lengths); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci kernel_neon_end(); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci return skcipher_walk_done(walk, 0); 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci sm4_ce_pmull_gcm_crypt(ctx->key.rkey_enc, dst, src, iv, 17962306a36Sopenharmony_ci walk->nbytes - tail, ghash, 18062306a36Sopenharmony_ci ctx->ghash_table, NULL); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci kernel_neon_end(); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci err = skcipher_walk_done(walk, tail); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci kernel_neon_begin(); 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci sm4_ce_pmull_gcm_crypt(ctx->key.rkey_enc, NULL, NULL, iv, 19062306a36Sopenharmony_ci walk->nbytes, ghash, ctx->ghash_table, 19162306a36Sopenharmony_ci (const u8 *)&lengths); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci kernel_neon_end(); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci return err; 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic int gcm_encrypt(struct aead_request *req) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci struct crypto_aead *aead = crypto_aead_reqtfm(req); 20162306a36Sopenharmony_ci u8 __aligned(8) ghash[SM4_BLOCK_SIZE]; 20262306a36Sopenharmony_ci struct skcipher_walk walk; 20362306a36Sopenharmony_ci int err; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci err = skcipher_walk_aead_encrypt(&walk, req, false); 20662306a36Sopenharmony_ci err = gcm_crypt(req, &walk, ghash, err, sm4_ce_pmull_gcm_enc); 20762306a36Sopenharmony_ci if (err) 20862306a36Sopenharmony_ci return err; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci /* copy authtag to end of dst */ 21162306a36Sopenharmony_ci scatterwalk_map_and_copy(ghash, req->dst, req->assoclen + req->cryptlen, 21262306a36Sopenharmony_ci crypto_aead_authsize(aead), 1); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci return 0; 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cistatic int gcm_decrypt(struct aead_request *req) 21862306a36Sopenharmony_ci{ 21962306a36Sopenharmony_ci struct crypto_aead *aead = crypto_aead_reqtfm(req); 22062306a36Sopenharmony_ci unsigned int authsize = crypto_aead_authsize(aead); 22162306a36Sopenharmony_ci u8 __aligned(8) ghash[SM4_BLOCK_SIZE]; 22262306a36Sopenharmony_ci u8 authtag[SM4_BLOCK_SIZE]; 22362306a36Sopenharmony_ci struct skcipher_walk walk; 22462306a36Sopenharmony_ci int err; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci err = skcipher_walk_aead_decrypt(&walk, req, false); 22762306a36Sopenharmony_ci err = gcm_crypt(req, &walk, ghash, err, sm4_ce_pmull_gcm_dec); 22862306a36Sopenharmony_ci if (err) 22962306a36Sopenharmony_ci return err; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci /* compare calculated auth tag with the stored one */ 23262306a36Sopenharmony_ci scatterwalk_map_and_copy(authtag, req->src, 23362306a36Sopenharmony_ci req->assoclen + req->cryptlen - authsize, 23462306a36Sopenharmony_ci authsize, 0); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci if (crypto_memneq(authtag, ghash, authsize)) 23762306a36Sopenharmony_ci return -EBADMSG; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci return 0; 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cistatic struct aead_alg sm4_gcm_alg = { 24362306a36Sopenharmony_ci .base = { 24462306a36Sopenharmony_ci .cra_name = "gcm(sm4)", 24562306a36Sopenharmony_ci .cra_driver_name = "gcm-sm4-ce", 24662306a36Sopenharmony_ci .cra_priority = 400, 24762306a36Sopenharmony_ci .cra_blocksize = 1, 24862306a36Sopenharmony_ci .cra_ctxsize = sizeof(struct sm4_gcm_ctx), 24962306a36Sopenharmony_ci .cra_module = THIS_MODULE, 25062306a36Sopenharmony_ci }, 25162306a36Sopenharmony_ci .ivsize = GCM_IV_SIZE, 25262306a36Sopenharmony_ci .chunksize = SM4_BLOCK_SIZE, 25362306a36Sopenharmony_ci .maxauthsize = SM4_BLOCK_SIZE, 25462306a36Sopenharmony_ci .setkey = gcm_setkey, 25562306a36Sopenharmony_ci .setauthsize = gcm_setauthsize, 25662306a36Sopenharmony_ci .encrypt = gcm_encrypt, 25762306a36Sopenharmony_ci .decrypt = gcm_decrypt, 25862306a36Sopenharmony_ci}; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_cistatic int __init sm4_ce_gcm_init(void) 26162306a36Sopenharmony_ci{ 26262306a36Sopenharmony_ci if (!cpu_have_named_feature(PMULL)) 26362306a36Sopenharmony_ci return -ENODEV; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci return crypto_register_aead(&sm4_gcm_alg); 26662306a36Sopenharmony_ci} 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_cistatic void __exit sm4_ce_gcm_exit(void) 26962306a36Sopenharmony_ci{ 27062306a36Sopenharmony_ci crypto_unregister_aead(&sm4_gcm_alg); 27162306a36Sopenharmony_ci} 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_cistatic const struct cpu_feature __maybe_unused sm4_ce_gcm_cpu_feature[] = { 27462306a36Sopenharmony_ci { cpu_feature(PMULL) }, 27562306a36Sopenharmony_ci {} 27662306a36Sopenharmony_ci}; 27762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(cpu, sm4_ce_gcm_cpu_feature); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cimodule_cpu_feature_match(SM4, sm4_ce_gcm_init); 28062306a36Sopenharmony_cimodule_exit(sm4_ce_gcm_exit); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ciMODULE_DESCRIPTION("Synchronous SM4 in GCM mode using ARMv8 Crypto Extensions"); 28362306a36Sopenharmony_ciMODULE_ALIAS_CRYPTO("gcm(sm4)"); 28462306a36Sopenharmony_ciMODULE_AUTHOR("Tianjia Zhang <tianjia.zhang@linux.alibaba.com>"); 28562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 286