mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-06 01:49:46 +00:00
The phmac implementation used the req->nbytes field on combined
operations (finup, digest) to track the state:
with req->nbytes > 0 the update needs to be processed,
while req->nbytes == 0 means to do the final operation. For
this purpose the req->nbytes field was set to 0 after successful
update operation. However, aead uses the req->nbytes field after a
successful hash operation to determine the amount of data to
en/decrypt. So an implementation must not modify the nbytes field.
Fixed by a slight rework on the phmac implementation. There is
now a new field async_op in the request context which tracks
the (asynch) operation to process. So the 'state' via req->nbytes
is not needed any more and now this field is untouched and may
be evaluated even after a request is processed by the phmac
implementation.
Fixes: cbbc675506 ("crypto: s390 - New s390 specific protected key hash phmac")
Reported-by: Ingo Franzki <ifranzki@linux.ibm.com>
Signed-off-by: Harald Freudenberger <freude@linux.ibm.com>
Tested-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Holger Dengler <dengler@linux.ibm.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
1065 lines
27 KiB
C
1065 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright IBM Corp. 2025
|
|
*
|
|
* s390 specific HMAC support for protected keys.
|
|
*/
|
|
|
|
#define KMSG_COMPONENT "phmac_s390"
|
|
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
|
|
|
|
#include <asm/cpacf.h>
|
|
#include <asm/pkey.h>
|
|
#include <crypto/engine.h>
|
|
#include <crypto/hash.h>
|
|
#include <crypto/internal/hash.h>
|
|
#include <crypto/sha2.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/cpufeature.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
static struct crypto_engine *phmac_crypto_engine;
|
|
#define MAX_QLEN 10
|
|
|
|
/*
|
|
* A simple hash walk helper
|
|
*/
|
|
|
|
struct hash_walk_helper {
|
|
struct crypto_hash_walk walk;
|
|
const u8 *walkaddr;
|
|
int walkbytes;
|
|
};
|
|
|
|
/*
|
|
* Prepare hash walk helper.
|
|
* Set up the base hash walk, fill walkaddr and walkbytes.
|
|
* Returns 0 on success or negative value on error.
|
|
*/
|
|
static inline int hwh_prepare(struct ahash_request *req,
|
|
struct hash_walk_helper *hwh)
|
|
{
|
|
hwh->walkbytes = crypto_hash_walk_first(req, &hwh->walk);
|
|
if (hwh->walkbytes < 0)
|
|
return hwh->walkbytes;
|
|
hwh->walkaddr = hwh->walk.data;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Advance hash walk helper by n bytes.
|
|
* Progress the walkbytes and walkaddr fields by n bytes.
|
|
* If walkbytes is then 0, pull next hunk from hash walk
|
|
* and update walkbytes and walkaddr.
|
|
* If n is negative, unmap hash walk and return error.
|
|
* Returns 0 on success or negative value on error.
|
|
*/
|
|
static inline int hwh_advance(struct hash_walk_helper *hwh, int n)
|
|
{
|
|
if (n < 0)
|
|
return crypto_hash_walk_done(&hwh->walk, n);
|
|
|
|
hwh->walkbytes -= n;
|
|
hwh->walkaddr += n;
|
|
if (hwh->walkbytes > 0)
|
|
return 0;
|
|
|
|
hwh->walkbytes = crypto_hash_walk_done(&hwh->walk, 0);
|
|
if (hwh->walkbytes < 0)
|
|
return hwh->walkbytes;
|
|
|
|
hwh->walkaddr = hwh->walk.data;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* KMAC param block layout for sha2 function codes:
|
|
* The layout of the param block for the KMAC instruction depends on the
|
|
* blocksize of the used hashing sha2-algorithm function codes. The param block
|
|
* contains the hash chaining value (cv), the input message bit-length (imbl)
|
|
* and the hmac-secret (key). To prevent code duplication, the sizes of all
|
|
* these are calculated based on the blocksize.
|
|
*
|
|
* param-block:
|
|
* +-------+
|
|
* | cv |
|
|
* +-------+
|
|
* | imbl |
|
|
* +-------+
|
|
* | key |
|
|
* +-------+
|
|
*
|
|
* sizes:
|
|
* part | sh2-alg | calculation | size | type
|
|
* -----+---------+-------------+------+--------
|
|
* cv | 224/256 | blocksize/2 | 32 | u64[8]
|
|
* | 384/512 | | 64 | u128[8]
|
|
* imbl | 224/256 | blocksize/8 | 8 | u64
|
|
* | 384/512 | | 16 | u128
|
|
* key | 224/256 | blocksize | 96 | u8[96]
|
|
* | 384/512 | | 160 | u8[160]
|
|
*/
|
|
|
|
#define MAX_DIGEST_SIZE SHA512_DIGEST_SIZE
|
|
#define MAX_IMBL_SIZE sizeof(u128)
|
|
#define MAX_BLOCK_SIZE SHA512_BLOCK_SIZE
|
|
|
|
#define SHA2_CV_SIZE(bs) ((bs) >> 1)
|
|
#define SHA2_IMBL_SIZE(bs) ((bs) >> 3)
|
|
|
|
#define SHA2_IMBL_OFFSET(bs) (SHA2_CV_SIZE(bs))
|
|
#define SHA2_KEY_OFFSET(bs) (SHA2_CV_SIZE(bs) + SHA2_IMBL_SIZE(bs))
|
|
|
|
#define PHMAC_MAX_KEYSIZE 256
|
|
#define PHMAC_SHA256_PK_SIZE (SHA256_BLOCK_SIZE + 32)
|
|
#define PHMAC_SHA512_PK_SIZE (SHA512_BLOCK_SIZE + 32)
|
|
#define PHMAC_MAX_PK_SIZE PHMAC_SHA512_PK_SIZE
|
|
|
|
/* phmac protected key struct */
|
|
struct phmac_protkey {
|
|
u32 type;
|
|
u32 len;
|
|
u8 protkey[PHMAC_MAX_PK_SIZE];
|
|
};
|
|
|
|
#define PK_STATE_NO_KEY 0
|
|
#define PK_STATE_CONVERT_IN_PROGRESS 1
|
|
#define PK_STATE_VALID 2
|
|
|
|
/* phmac tfm context */
|
|
struct phmac_tfm_ctx {
|
|
/* source key material used to derive a protected key from */
|
|
u8 keybuf[PHMAC_MAX_KEYSIZE];
|
|
unsigned int keylen;
|
|
|
|
/* cpacf function code to use with this protected key type */
|
|
long fc;
|
|
|
|
/* nr of requests enqueued via crypto engine which use this tfm ctx */
|
|
atomic_t via_engine_ctr;
|
|
|
|
/* spinlock to atomic read/update all the following fields */
|
|
spinlock_t pk_lock;
|
|
|
|
/* see PK_STATE* defines above, < 0 holds convert failure rc */
|
|
int pk_state;
|
|
/* if state is valid, pk holds the protected key */
|
|
struct phmac_protkey pk;
|
|
};
|
|
|
|
union kmac_gr0 {
|
|
unsigned long reg;
|
|
struct {
|
|
unsigned long : 48;
|
|
unsigned long ikp : 1;
|
|
unsigned long iimp : 1;
|
|
unsigned long ccup : 1;
|
|
unsigned long : 6;
|
|
unsigned long fc : 7;
|
|
};
|
|
};
|
|
|
|
struct kmac_sha2_ctx {
|
|
u8 param[MAX_DIGEST_SIZE + MAX_IMBL_SIZE + PHMAC_MAX_PK_SIZE];
|
|
union kmac_gr0 gr0;
|
|
u8 buf[MAX_BLOCK_SIZE];
|
|
u64 buflen[2];
|
|
};
|
|
|
|
enum async_op {
|
|
OP_NOP = 0,
|
|
OP_UPDATE,
|
|
OP_FINAL,
|
|
OP_FINUP,
|
|
};
|
|
|
|
/* phmac request context */
|
|
struct phmac_req_ctx {
|
|
struct hash_walk_helper hwh;
|
|
struct kmac_sha2_ctx kmac_ctx;
|
|
enum async_op async_op;
|
|
};
|
|
|
|
/*
|
|
* Pkey 'token' struct used to derive a protected key value from a clear key.
|
|
*/
|
|
struct hmac_clrkey_token {
|
|
u8 type;
|
|
u8 res0[3];
|
|
u8 version;
|
|
u8 res1[3];
|
|
u32 keytype;
|
|
u32 len;
|
|
u8 key[];
|
|
} __packed;
|
|
|
|
static int hash_key(const u8 *in, unsigned int inlen,
|
|
u8 *digest, unsigned int digestsize)
|
|
{
|
|
unsigned long func;
|
|
union {
|
|
struct sha256_paramblock {
|
|
u32 h[8];
|
|
u64 mbl;
|
|
} sha256;
|
|
struct sha512_paramblock {
|
|
u64 h[8];
|
|
u128 mbl;
|
|
} sha512;
|
|
} __packed param;
|
|
|
|
#define PARAM_INIT(x, y, z) \
|
|
param.sha##x.h[0] = SHA##y ## _H0; \
|
|
param.sha##x.h[1] = SHA##y ## _H1; \
|
|
param.sha##x.h[2] = SHA##y ## _H2; \
|
|
param.sha##x.h[3] = SHA##y ## _H3; \
|
|
param.sha##x.h[4] = SHA##y ## _H4; \
|
|
param.sha##x.h[5] = SHA##y ## _H5; \
|
|
param.sha##x.h[6] = SHA##y ## _H6; \
|
|
param.sha##x.h[7] = SHA##y ## _H7; \
|
|
param.sha##x.mbl = (z)
|
|
|
|
switch (digestsize) {
|
|
case SHA224_DIGEST_SIZE:
|
|
func = CPACF_KLMD_SHA_256;
|
|
PARAM_INIT(256, 224, inlen * 8);
|
|
break;
|
|
case SHA256_DIGEST_SIZE:
|
|
func = CPACF_KLMD_SHA_256;
|
|
PARAM_INIT(256, 256, inlen * 8);
|
|
break;
|
|
case SHA384_DIGEST_SIZE:
|
|
func = CPACF_KLMD_SHA_512;
|
|
PARAM_INIT(512, 384, inlen * 8);
|
|
break;
|
|
case SHA512_DIGEST_SIZE:
|
|
func = CPACF_KLMD_SHA_512;
|
|
PARAM_INIT(512, 512, inlen * 8);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
#undef PARAM_INIT
|
|
|
|
cpacf_klmd(func, ¶m, in, inlen);
|
|
|
|
memcpy(digest, ¶m, digestsize);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* make_clrkey_token() - wrap the clear key into a pkey clearkey token.
|
|
*/
|
|
static inline int make_clrkey_token(const u8 *clrkey, size_t clrkeylen,
|
|
unsigned int digestsize, u8 *dest)
|
|
{
|
|
struct hmac_clrkey_token *token = (struct hmac_clrkey_token *)dest;
|
|
unsigned int blocksize;
|
|
int rc;
|
|
|
|
token->type = 0x00;
|
|
token->version = 0x02;
|
|
switch (digestsize) {
|
|
case SHA224_DIGEST_SIZE:
|
|
case SHA256_DIGEST_SIZE:
|
|
token->keytype = PKEY_KEYTYPE_HMAC_512;
|
|
blocksize = 64;
|
|
break;
|
|
case SHA384_DIGEST_SIZE:
|
|
case SHA512_DIGEST_SIZE:
|
|
token->keytype = PKEY_KEYTYPE_HMAC_1024;
|
|
blocksize = 128;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
token->len = blocksize;
|
|
|
|
if (clrkeylen > blocksize) {
|
|
rc = hash_key(clrkey, clrkeylen, token->key, digestsize);
|
|
if (rc)
|
|
return rc;
|
|
} else {
|
|
memcpy(token->key, clrkey, clrkeylen);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* phmac_tfm_ctx_setkey() - Set key value into tfm context, maybe construct
|
|
* a clear key token digestible by pkey from a clear key value.
|
|
*/
|
|
static inline int phmac_tfm_ctx_setkey(struct phmac_tfm_ctx *tfm_ctx,
|
|
const u8 *key, unsigned int keylen)
|
|
{
|
|
if (keylen > sizeof(tfm_ctx->keybuf))
|
|
return -EINVAL;
|
|
|
|
memcpy(tfm_ctx->keybuf, key, keylen);
|
|
tfm_ctx->keylen = keylen;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Convert the raw key material into a protected key via PKEY api.
|
|
* This function may sleep - don't call in non-sleeping context.
|
|
*/
|
|
static inline int convert_key(const u8 *key, unsigned int keylen,
|
|
struct phmac_protkey *pk)
|
|
{
|
|
int rc, i;
|
|
|
|
pk->len = sizeof(pk->protkey);
|
|
|
|
/*
|
|
* In case of a busy card retry with increasing delay
|
|
* of 200, 400, 800 and 1600 ms - in total 3 s.
|
|
*/
|
|
for (rc = -EIO, i = 0; rc && i < 5; i++) {
|
|
if (rc == -EBUSY && msleep_interruptible((1 << i) * 100)) {
|
|
rc = -EINTR;
|
|
goto out;
|
|
}
|
|
rc = pkey_key2protkey(key, keylen,
|
|
pk->protkey, &pk->len, &pk->type,
|
|
PKEY_XFLAG_NOMEMALLOC);
|
|
}
|
|
|
|
out:
|
|
pr_debug("rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* (Re-)Convert the raw key material from the tfm ctx into a protected
|
|
* key via convert_key() function. Update the pk_state, pk_type, pk_len
|
|
* and the protected key in the tfm context.
|
|
* Please note this function may be invoked concurrently with the very
|
|
* same tfm context. The pk_lock spinlock in the context ensures an
|
|
* atomic update of the pk and the pk state but does not guarantee any
|
|
* order of update. So a fresh converted valid protected key may get
|
|
* updated with an 'old' expired key value. As the cpacf instructions
|
|
* detect this, refuse to operate with an invalid key and the calling
|
|
* code triggers a (re-)conversion this does no harm. This may lead to
|
|
* unnecessary additional conversion but never to invalid data on the
|
|
* hash operation.
|
|
*/
|
|
static int phmac_convert_key(struct phmac_tfm_ctx *tfm_ctx)
|
|
{
|
|
struct phmac_protkey pk;
|
|
int rc;
|
|
|
|
spin_lock_bh(&tfm_ctx->pk_lock);
|
|
tfm_ctx->pk_state = PK_STATE_CONVERT_IN_PROGRESS;
|
|
spin_unlock_bh(&tfm_ctx->pk_lock);
|
|
|
|
rc = convert_key(tfm_ctx->keybuf, tfm_ctx->keylen, &pk);
|
|
|
|
/* update context */
|
|
spin_lock_bh(&tfm_ctx->pk_lock);
|
|
if (rc) {
|
|
tfm_ctx->pk_state = rc;
|
|
} else {
|
|
tfm_ctx->pk_state = PK_STATE_VALID;
|
|
tfm_ctx->pk = pk;
|
|
}
|
|
spin_unlock_bh(&tfm_ctx->pk_lock);
|
|
|
|
memzero_explicit(&pk, sizeof(pk));
|
|
pr_debug("rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* kmac_sha2_set_imbl - sets the input message bit-length based on the blocksize
|
|
*/
|
|
static inline void kmac_sha2_set_imbl(u8 *param, u64 buflen_lo,
|
|
u64 buflen_hi, unsigned int blocksize)
|
|
{
|
|
u8 *imbl = param + SHA2_IMBL_OFFSET(blocksize);
|
|
|
|
switch (blocksize) {
|
|
case SHA256_BLOCK_SIZE:
|
|
*(u64 *)imbl = buflen_lo * BITS_PER_BYTE;
|
|
break;
|
|
case SHA512_BLOCK_SIZE:
|
|
*(u128 *)imbl = (((u128)buflen_hi << 64) + buflen_lo) << 3;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int phmac_kmac_update(struct ahash_request *req, bool maysleep)
|
|
{
|
|
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
|
|
struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm);
|
|
struct phmac_req_ctx *req_ctx = ahash_request_ctx(req);
|
|
struct kmac_sha2_ctx *ctx = &req_ctx->kmac_ctx;
|
|
struct hash_walk_helper *hwh = &req_ctx->hwh;
|
|
unsigned int bs = crypto_ahash_blocksize(tfm);
|
|
unsigned int offset, k, n;
|
|
int rc = 0;
|
|
|
|
/*
|
|
* The walk is always mapped when this function is called.
|
|
* Note that in case of partial processing or failure the walk
|
|
* is NOT unmapped here. So a follow up task may reuse the walk
|
|
* or in case of unrecoverable failure needs to unmap it.
|
|
*/
|
|
|
|
while (hwh->walkbytes > 0) {
|
|
/* check sha2 context buffer */
|
|
offset = ctx->buflen[0] % bs;
|
|
if (offset + hwh->walkbytes < bs)
|
|
goto store;
|
|
|
|
if (offset) {
|
|
/* fill ctx buffer up to blocksize and process this block */
|
|
n = bs - offset;
|
|
memcpy(ctx->buf + offset, hwh->walkaddr, n);
|
|
ctx->gr0.iimp = 1;
|
|
for (;;) {
|
|
k = _cpacf_kmac(&ctx->gr0.reg, ctx->param, ctx->buf, bs);
|
|
if (likely(k == bs))
|
|
break;
|
|
if (unlikely(k > 0)) {
|
|
/*
|
|
* Can't deal with hunks smaller than blocksize.
|
|
* And kmac should always return the nr of
|
|
* processed bytes as 0 or a multiple of the
|
|
* blocksize.
|
|
*/
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
/* protected key is invalid and needs re-conversion */
|
|
if (!maysleep) {
|
|
rc = -EKEYEXPIRED;
|
|
goto out;
|
|
}
|
|
rc = phmac_convert_key(tfm_ctx);
|
|
if (rc)
|
|
goto out;
|
|
spin_lock_bh(&tfm_ctx->pk_lock);
|
|
memcpy(ctx->param + SHA2_KEY_OFFSET(bs),
|
|
tfm_ctx->pk.protkey, tfm_ctx->pk.len);
|
|
spin_unlock_bh(&tfm_ctx->pk_lock);
|
|
}
|
|
ctx->buflen[0] += n;
|
|
if (ctx->buflen[0] < n)
|
|
ctx->buflen[1]++;
|
|
rc = hwh_advance(hwh, n);
|
|
if (unlikely(rc))
|
|
goto out;
|
|
offset = 0;
|
|
}
|
|
|
|
/* process as many blocks as possible from the walk */
|
|
while (hwh->walkbytes >= bs) {
|
|
n = (hwh->walkbytes / bs) * bs;
|
|
ctx->gr0.iimp = 1;
|
|
k = _cpacf_kmac(&ctx->gr0.reg, ctx->param, hwh->walkaddr, n);
|
|
if (likely(k > 0)) {
|
|
ctx->buflen[0] += k;
|
|
if (ctx->buflen[0] < k)
|
|
ctx->buflen[1]++;
|
|
rc = hwh_advance(hwh, k);
|
|
if (unlikely(rc))
|
|
goto out;
|
|
}
|
|
if (unlikely(k < n)) {
|
|
/* protected key is invalid and needs re-conversion */
|
|
if (!maysleep) {
|
|
rc = -EKEYEXPIRED;
|
|
goto out;
|
|
}
|
|
rc = phmac_convert_key(tfm_ctx);
|
|
if (rc)
|
|
goto out;
|
|
spin_lock_bh(&tfm_ctx->pk_lock);
|
|
memcpy(ctx->param + SHA2_KEY_OFFSET(bs),
|
|
tfm_ctx->pk.protkey, tfm_ctx->pk.len);
|
|
spin_unlock_bh(&tfm_ctx->pk_lock);
|
|
}
|
|
}
|
|
|
|
store:
|
|
/* store incomplete block in context buffer */
|
|
if (hwh->walkbytes) {
|
|
memcpy(ctx->buf + offset, hwh->walkaddr, hwh->walkbytes);
|
|
ctx->buflen[0] += hwh->walkbytes;
|
|
if (ctx->buflen[0] < hwh->walkbytes)
|
|
ctx->buflen[1]++;
|
|
rc = hwh_advance(hwh, hwh->walkbytes);
|
|
if (unlikely(rc))
|
|
goto out;
|
|
}
|
|
|
|
} /* end of while (hwh->walkbytes > 0) */
|
|
|
|
out:
|
|
pr_debug("rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int phmac_kmac_final(struct ahash_request *req, bool maysleep)
|
|
{
|
|
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
|
|
struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm);
|
|
struct phmac_req_ctx *req_ctx = ahash_request_ctx(req);
|
|
struct kmac_sha2_ctx *ctx = &req_ctx->kmac_ctx;
|
|
unsigned int ds = crypto_ahash_digestsize(tfm);
|
|
unsigned int bs = crypto_ahash_blocksize(tfm);
|
|
unsigned int k, n;
|
|
int rc = 0;
|
|
|
|
n = ctx->buflen[0] % bs;
|
|
ctx->gr0.iimp = 0;
|
|
kmac_sha2_set_imbl(ctx->param, ctx->buflen[0], ctx->buflen[1], bs);
|
|
for (;;) {
|
|
k = _cpacf_kmac(&ctx->gr0.reg, ctx->param, ctx->buf, n);
|
|
if (likely(k == n))
|
|
break;
|
|
if (unlikely(k > 0)) {
|
|
/* Can't deal with hunks smaller than blocksize. */
|
|
rc = -EIO;
|
|
goto out;
|
|
}
|
|
/* protected key is invalid and needs re-conversion */
|
|
if (!maysleep) {
|
|
rc = -EKEYEXPIRED;
|
|
goto out;
|
|
}
|
|
rc = phmac_convert_key(tfm_ctx);
|
|
if (rc)
|
|
goto out;
|
|
spin_lock_bh(&tfm_ctx->pk_lock);
|
|
memcpy(ctx->param + SHA2_KEY_OFFSET(bs),
|
|
tfm_ctx->pk.protkey, tfm_ctx->pk.len);
|
|
spin_unlock_bh(&tfm_ctx->pk_lock);
|
|
}
|
|
|
|
memcpy(req->result, ctx->param, ds);
|
|
|
|
out:
|
|
pr_debug("rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int phmac_init(struct ahash_request *req)
|
|
{
|
|
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
|
|
struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm);
|
|
struct phmac_req_ctx *req_ctx = ahash_request_ctx(req);
|
|
struct kmac_sha2_ctx *kmac_ctx = &req_ctx->kmac_ctx;
|
|
unsigned int bs = crypto_ahash_blocksize(tfm);
|
|
int rc = 0;
|
|
|
|
/* zero request context (includes the kmac sha2 context) */
|
|
memset(req_ctx, 0, sizeof(*req_ctx));
|
|
|
|
/*
|
|
* setkey() should have set a valid fc into the tfm context.
|
|
* Copy this function code into the gr0 field of the kmac context.
|
|
*/
|
|
if (!tfm_ctx->fc) {
|
|
rc = -ENOKEY;
|
|
goto out;
|
|
}
|
|
kmac_ctx->gr0.fc = tfm_ctx->fc;
|
|
|
|
/*
|
|
* Copy the pk from tfm ctx into kmac ctx. The protected key
|
|
* may be outdated but update() and final() will handle this.
|
|
*/
|
|
spin_lock_bh(&tfm_ctx->pk_lock);
|
|
memcpy(kmac_ctx->param + SHA2_KEY_OFFSET(bs),
|
|
tfm_ctx->pk.protkey, tfm_ctx->pk.len);
|
|
spin_unlock_bh(&tfm_ctx->pk_lock);
|
|
|
|
out:
|
|
pr_debug("rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int phmac_update(struct ahash_request *req)
|
|
{
|
|
struct phmac_req_ctx *req_ctx = ahash_request_ctx(req);
|
|
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
|
|
struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm);
|
|
struct kmac_sha2_ctx *kmac_ctx = &req_ctx->kmac_ctx;
|
|
struct hash_walk_helper *hwh = &req_ctx->hwh;
|
|
int rc;
|
|
|
|
/* prep the walk in the request context */
|
|
rc = hwh_prepare(req, hwh);
|
|
if (rc)
|
|
goto out;
|
|
|
|
/* Try synchronous operation if no active engine usage */
|
|
if (!atomic_read(&tfm_ctx->via_engine_ctr)) {
|
|
rc = phmac_kmac_update(req, false);
|
|
if (rc == 0)
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* If sync operation failed or key expired or there are already
|
|
* requests enqueued via engine, fallback to async. Mark tfm as
|
|
* using engine to serialize requests.
|
|
*/
|
|
if (rc == 0 || rc == -EKEYEXPIRED) {
|
|
req_ctx->async_op = OP_UPDATE;
|
|
atomic_inc(&tfm_ctx->via_engine_ctr);
|
|
rc = crypto_transfer_hash_request_to_engine(phmac_crypto_engine, req);
|
|
if (rc != -EINPROGRESS)
|
|
atomic_dec(&tfm_ctx->via_engine_ctr);
|
|
}
|
|
|
|
if (rc != -EINPROGRESS) {
|
|
hwh_advance(hwh, rc);
|
|
memzero_explicit(kmac_ctx, sizeof(*kmac_ctx));
|
|
}
|
|
|
|
out:
|
|
pr_debug("rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int phmac_final(struct ahash_request *req)
|
|
{
|
|
struct phmac_req_ctx *req_ctx = ahash_request_ctx(req);
|
|
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
|
|
struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm);
|
|
struct kmac_sha2_ctx *kmac_ctx = &req_ctx->kmac_ctx;
|
|
int rc = 0;
|
|
|
|
/* Try synchronous operation if no active engine usage */
|
|
if (!atomic_read(&tfm_ctx->via_engine_ctr)) {
|
|
rc = phmac_kmac_final(req, false);
|
|
if (rc == 0)
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* If sync operation failed or key expired or there are already
|
|
* requests enqueued via engine, fallback to async. Mark tfm as
|
|
* using engine to serialize requests.
|
|
*/
|
|
if (rc == 0 || rc == -EKEYEXPIRED) {
|
|
req_ctx->async_op = OP_FINAL;
|
|
atomic_inc(&tfm_ctx->via_engine_ctr);
|
|
rc = crypto_transfer_hash_request_to_engine(phmac_crypto_engine, req);
|
|
if (rc != -EINPROGRESS)
|
|
atomic_dec(&tfm_ctx->via_engine_ctr);
|
|
}
|
|
|
|
out:
|
|
if (rc != -EINPROGRESS)
|
|
memzero_explicit(kmac_ctx, sizeof(*kmac_ctx));
|
|
pr_debug("rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int phmac_finup(struct ahash_request *req)
|
|
{
|
|
struct phmac_req_ctx *req_ctx = ahash_request_ctx(req);
|
|
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
|
|
struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm);
|
|
struct kmac_sha2_ctx *kmac_ctx = &req_ctx->kmac_ctx;
|
|
struct hash_walk_helper *hwh = &req_ctx->hwh;
|
|
int rc;
|
|
|
|
/* prep the walk in the request context */
|
|
rc = hwh_prepare(req, hwh);
|
|
if (rc)
|
|
goto out;
|
|
|
|
req_ctx->async_op = OP_FINUP;
|
|
|
|
/* Try synchronous operations if no active engine usage */
|
|
if (!atomic_read(&tfm_ctx->via_engine_ctr)) {
|
|
rc = phmac_kmac_update(req, false);
|
|
if (rc == 0)
|
|
req_ctx->async_op = OP_FINAL;
|
|
}
|
|
if (!rc && req_ctx->async_op == OP_FINAL &&
|
|
!atomic_read(&tfm_ctx->via_engine_ctr)) {
|
|
rc = phmac_kmac_final(req, false);
|
|
if (rc == 0)
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* If sync operation failed or key expired or there are already
|
|
* requests enqueued via engine, fallback to async. Mark tfm as
|
|
* using engine to serialize requests.
|
|
*/
|
|
if (rc == 0 || rc == -EKEYEXPIRED) {
|
|
/* req->async_op has been set to either OP_FINUP or OP_FINAL */
|
|
atomic_inc(&tfm_ctx->via_engine_ctr);
|
|
rc = crypto_transfer_hash_request_to_engine(phmac_crypto_engine, req);
|
|
if (rc != -EINPROGRESS)
|
|
atomic_dec(&tfm_ctx->via_engine_ctr);
|
|
}
|
|
|
|
if (rc != -EINPROGRESS)
|
|
hwh_advance(hwh, rc);
|
|
|
|
out:
|
|
if (rc != -EINPROGRESS)
|
|
memzero_explicit(kmac_ctx, sizeof(*kmac_ctx));
|
|
pr_debug("rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int phmac_digest(struct ahash_request *req)
|
|
{
|
|
int rc;
|
|
|
|
rc = phmac_init(req);
|
|
if (rc)
|
|
goto out;
|
|
|
|
rc = phmac_finup(req);
|
|
|
|
out:
|
|
pr_debug("rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int phmac_setkey(struct crypto_ahash *tfm,
|
|
const u8 *key, unsigned int keylen)
|
|
{
|
|
struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm);
|
|
unsigned int ds = crypto_ahash_digestsize(tfm);
|
|
unsigned int bs = crypto_ahash_blocksize(tfm);
|
|
unsigned int tmpkeylen;
|
|
u8 *tmpkey = NULL;
|
|
int rc = 0;
|
|
|
|
if (!crypto_ahash_tested(tfm)) {
|
|
/*
|
|
* selftest running: key is a raw hmac clear key and needs
|
|
* to get embedded into a 'clear key token' in order to have
|
|
* it correctly processed by the pkey module.
|
|
*/
|
|
tmpkeylen = sizeof(struct hmac_clrkey_token) + bs;
|
|
tmpkey = kzalloc(tmpkeylen, GFP_KERNEL);
|
|
if (!tmpkey) {
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
rc = make_clrkey_token(key, keylen, ds, tmpkey);
|
|
if (rc)
|
|
goto out;
|
|
keylen = tmpkeylen;
|
|
key = tmpkey;
|
|
}
|
|
|
|
/* copy raw key into tfm context */
|
|
rc = phmac_tfm_ctx_setkey(tfm_ctx, key, keylen);
|
|
if (rc)
|
|
goto out;
|
|
|
|
/* convert raw key into protected key */
|
|
rc = phmac_convert_key(tfm_ctx);
|
|
if (rc)
|
|
goto out;
|
|
|
|
/* set function code in tfm context, check for valid pk type */
|
|
switch (ds) {
|
|
case SHA224_DIGEST_SIZE:
|
|
if (tfm_ctx->pk.type != PKEY_KEYTYPE_HMAC_512)
|
|
rc = -EINVAL;
|
|
else
|
|
tfm_ctx->fc = CPACF_KMAC_PHMAC_SHA_224;
|
|
break;
|
|
case SHA256_DIGEST_SIZE:
|
|
if (tfm_ctx->pk.type != PKEY_KEYTYPE_HMAC_512)
|
|
rc = -EINVAL;
|
|
else
|
|
tfm_ctx->fc = CPACF_KMAC_PHMAC_SHA_256;
|
|
break;
|
|
case SHA384_DIGEST_SIZE:
|
|
if (tfm_ctx->pk.type != PKEY_KEYTYPE_HMAC_1024)
|
|
rc = -EINVAL;
|
|
else
|
|
tfm_ctx->fc = CPACF_KMAC_PHMAC_SHA_384;
|
|
break;
|
|
case SHA512_DIGEST_SIZE:
|
|
if (tfm_ctx->pk.type != PKEY_KEYTYPE_HMAC_1024)
|
|
rc = -EINVAL;
|
|
else
|
|
tfm_ctx->fc = CPACF_KMAC_PHMAC_SHA_512;
|
|
break;
|
|
default:
|
|
tfm_ctx->fc = 0;
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
out:
|
|
kfree(tmpkey);
|
|
pr_debug("rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int phmac_export(struct ahash_request *req, void *out)
|
|
{
|
|
struct phmac_req_ctx *req_ctx = ahash_request_ctx(req);
|
|
struct kmac_sha2_ctx *ctx = &req_ctx->kmac_ctx;
|
|
|
|
memcpy(out, ctx, sizeof(*ctx));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phmac_import(struct ahash_request *req, const void *in)
|
|
{
|
|
struct phmac_req_ctx *req_ctx = ahash_request_ctx(req);
|
|
struct kmac_sha2_ctx *ctx = &req_ctx->kmac_ctx;
|
|
|
|
memset(req_ctx, 0, sizeof(*req_ctx));
|
|
memcpy(ctx, in, sizeof(*ctx));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phmac_init_tfm(struct crypto_ahash *tfm)
|
|
{
|
|
struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm);
|
|
|
|
memset(tfm_ctx, 0, sizeof(*tfm_ctx));
|
|
spin_lock_init(&tfm_ctx->pk_lock);
|
|
|
|
crypto_ahash_set_reqsize(tfm, sizeof(struct phmac_req_ctx));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void phmac_exit_tfm(struct crypto_ahash *tfm)
|
|
{
|
|
struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm);
|
|
|
|
memzero_explicit(tfm_ctx->keybuf, sizeof(tfm_ctx->keybuf));
|
|
memzero_explicit(&tfm_ctx->pk, sizeof(tfm_ctx->pk));
|
|
}
|
|
|
|
static int phmac_do_one_request(struct crypto_engine *engine, void *areq)
|
|
{
|
|
struct ahash_request *req = ahash_request_cast(areq);
|
|
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
|
|
struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm);
|
|
struct phmac_req_ctx *req_ctx = ahash_request_ctx(req);
|
|
struct kmac_sha2_ctx *kmac_ctx = &req_ctx->kmac_ctx;
|
|
struct hash_walk_helper *hwh = &req_ctx->hwh;
|
|
int rc = -EINVAL;
|
|
|
|
/*
|
|
* Three kinds of requests come in here:
|
|
* 1. req->async_op == OP_UPDATE with req->nbytes > 0
|
|
* 2. req->async_op == OP_FINUP with req->nbytes > 0
|
|
* 3. req->async_op == OP_FINAL
|
|
* For update and finup the hwh walk has already been prepared
|
|
* by the caller. For final there is no hwh walk needed.
|
|
*/
|
|
|
|
switch (req_ctx->async_op) {
|
|
case OP_UPDATE:
|
|
case OP_FINUP:
|
|
rc = phmac_kmac_update(req, true);
|
|
if (rc == -EKEYEXPIRED) {
|
|
/*
|
|
* Protected key expired, conversion is in process.
|
|
* Trigger a re-schedule of this request by returning
|
|
* -ENOSPC ("hardware queue full") to the crypto engine.
|
|
* To avoid immediately re-invocation of this callback,
|
|
* tell scheduler to voluntarily give up the CPU here.
|
|
*/
|
|
pr_debug("rescheduling request\n");
|
|
cond_resched();
|
|
return -ENOSPC;
|
|
} else if (rc) {
|
|
hwh_advance(hwh, rc);
|
|
goto out;
|
|
}
|
|
if (req_ctx->async_op == OP_UPDATE)
|
|
break;
|
|
req_ctx->async_op = OP_FINAL;
|
|
fallthrough;
|
|
case OP_FINAL:
|
|
rc = phmac_kmac_final(req, true);
|
|
if (rc == -EKEYEXPIRED) {
|
|
/*
|
|
* Protected key expired, conversion is in process.
|
|
* Trigger a re-schedule of this request by returning
|
|
* -ENOSPC ("hardware queue full") to the crypto engine.
|
|
* To avoid immediately re-invocation of this callback,
|
|
* tell scheduler to voluntarily give up the CPU here.
|
|
*/
|
|
pr_debug("rescheduling request\n");
|
|
cond_resched();
|
|
return -ENOSPC;
|
|
}
|
|
break;
|
|
default:
|
|
/* unknown/unsupported/unimplemented asynch op */
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
out:
|
|
if (rc || req_ctx->async_op == OP_FINAL)
|
|
memzero_explicit(kmac_ctx, sizeof(*kmac_ctx));
|
|
pr_debug("request complete with rc=%d\n", rc);
|
|
local_bh_disable();
|
|
atomic_dec(&tfm_ctx->via_engine_ctr);
|
|
crypto_finalize_hash_request(engine, req, rc);
|
|
local_bh_enable();
|
|
return rc;
|
|
}
|
|
|
|
#define S390_ASYNC_PHMAC_ALG(x) \
|
|
{ \
|
|
.base = { \
|
|
.init = phmac_init, \
|
|
.update = phmac_update, \
|
|
.final = phmac_final, \
|
|
.finup = phmac_finup, \
|
|
.digest = phmac_digest, \
|
|
.setkey = phmac_setkey, \
|
|
.import = phmac_import, \
|
|
.export = phmac_export, \
|
|
.init_tfm = phmac_init_tfm, \
|
|
.exit_tfm = phmac_exit_tfm, \
|
|
.halg = { \
|
|
.digestsize = SHA##x##_DIGEST_SIZE, \
|
|
.statesize = sizeof(struct kmac_sha2_ctx), \
|
|
.base = { \
|
|
.cra_name = "phmac(sha" #x ")", \
|
|
.cra_driver_name = "phmac_s390_sha" #x, \
|
|
.cra_blocksize = SHA##x##_BLOCK_SIZE, \
|
|
.cra_priority = 400, \
|
|
.cra_flags = CRYPTO_ALG_ASYNC | \
|
|
CRYPTO_ALG_NO_FALLBACK, \
|
|
.cra_ctxsize = sizeof(struct phmac_tfm_ctx), \
|
|
.cra_module = THIS_MODULE, \
|
|
}, \
|
|
}, \
|
|
}, \
|
|
.op = { \
|
|
.do_one_request = phmac_do_one_request, \
|
|
}, \
|
|
}
|
|
|
|
static struct phmac_alg {
|
|
unsigned int fc;
|
|
struct ahash_engine_alg alg;
|
|
bool registered;
|
|
} phmac_algs[] = {
|
|
{
|
|
.fc = CPACF_KMAC_PHMAC_SHA_224,
|
|
.alg = S390_ASYNC_PHMAC_ALG(224),
|
|
}, {
|
|
.fc = CPACF_KMAC_PHMAC_SHA_256,
|
|
.alg = S390_ASYNC_PHMAC_ALG(256),
|
|
}, {
|
|
.fc = CPACF_KMAC_PHMAC_SHA_384,
|
|
.alg = S390_ASYNC_PHMAC_ALG(384),
|
|
}, {
|
|
.fc = CPACF_KMAC_PHMAC_SHA_512,
|
|
.alg = S390_ASYNC_PHMAC_ALG(512),
|
|
}
|
|
};
|
|
|
|
static struct miscdevice phmac_dev = {
|
|
.name = "phmac",
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
};
|
|
|
|
static void s390_phmac_exit(void)
|
|
{
|
|
struct phmac_alg *phmac;
|
|
int i;
|
|
|
|
if (phmac_crypto_engine) {
|
|
crypto_engine_stop(phmac_crypto_engine);
|
|
crypto_engine_exit(phmac_crypto_engine);
|
|
}
|
|
|
|
for (i = ARRAY_SIZE(phmac_algs) - 1; i >= 0; i--) {
|
|
phmac = &phmac_algs[i];
|
|
if (phmac->registered)
|
|
crypto_engine_unregister_ahash(&phmac->alg);
|
|
}
|
|
|
|
misc_deregister(&phmac_dev);
|
|
}
|
|
|
|
static int __init s390_phmac_init(void)
|
|
{
|
|
struct phmac_alg *phmac;
|
|
int i, rc;
|
|
|
|
/* for selftest cpacf klmd subfunction is needed */
|
|
if (!cpacf_query_func(CPACF_KLMD, CPACF_KLMD_SHA_256))
|
|
return -ENODEV;
|
|
if (!cpacf_query_func(CPACF_KLMD, CPACF_KLMD_SHA_512))
|
|
return -ENODEV;
|
|
|
|
/* register a simple phmac pseudo misc device */
|
|
rc = misc_register(&phmac_dev);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* with this pseudo device alloc and start a crypto engine */
|
|
phmac_crypto_engine =
|
|
crypto_engine_alloc_init_and_set(phmac_dev.this_device,
|
|
true, false, MAX_QLEN);
|
|
if (!phmac_crypto_engine) {
|
|
rc = -ENOMEM;
|
|
goto out_err;
|
|
}
|
|
rc = crypto_engine_start(phmac_crypto_engine);
|
|
if (rc) {
|
|
crypto_engine_exit(phmac_crypto_engine);
|
|
phmac_crypto_engine = NULL;
|
|
goto out_err;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(phmac_algs); i++) {
|
|
phmac = &phmac_algs[i];
|
|
if (!cpacf_query_func(CPACF_KMAC, phmac->fc))
|
|
continue;
|
|
rc = crypto_engine_register_ahash(&phmac->alg);
|
|
if (rc)
|
|
goto out_err;
|
|
phmac->registered = true;
|
|
pr_debug("%s registered\n", phmac->alg.base.halg.base.cra_name);
|
|
}
|
|
|
|
return 0;
|
|
|
|
out_err:
|
|
s390_phmac_exit();
|
|
return rc;
|
|
}
|
|
|
|
module_init(s390_phmac_init);
|
|
module_exit(s390_phmac_exit);
|
|
|
|
MODULE_ALIAS_CRYPTO("phmac(sha224)");
|
|
MODULE_ALIAS_CRYPTO("phmac(sha256)");
|
|
MODULE_ALIAS_CRYPTO("phmac(sha384)");
|
|
MODULE_ALIAS_CRYPTO("phmac(sha512)");
|
|
|
|
MODULE_DESCRIPTION("S390 HMAC driver for protected keys");
|
|
MODULE_LICENSE("GPL");
|