crypto: xilinx-trng - Replace crypto_drbg_ctr_df() with HMAC-SHA512

This code is just trying to condition 48 bytes of random data.  This can
be done easily using HKDF-SHA512-Extract, saving 300 lines of code.

This commit also fixes forward security (in this particular case) by
clearing the entropy from memory after it's used.

Signed-off-by: Eric Biggers <ebiggers@kernel.org>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
This commit is contained in:
Eric Biggers
2026-05-31 12:17:37 -07:00
committed by Herbert Xu
parent 2dd6777422
commit 5f1d444e08
6 changed files with 16 additions and 312 deletions

View File

@@ -1246,11 +1246,6 @@ config CRYPTO_KDF800108_CTR
select CRYPTO_HMAC
select CRYPTO_SHA256
config CRYPTO_DF80090A
tristate
select CRYPTO_AES
select CRYPTO_CTR
endmenu
menu "Userspace interface (deprecated)"

View File

@@ -208,6 +208,4 @@ obj-$(CONFIG_CRYPTO_SIMD) += crypto_simd.o
#
obj-$(CONFIG_CRYPTO_KDF800108_CTR) += kdf_sp800108.o
obj-$(CONFIG_CRYPTO_DF80090A) += df_sp80090a.o
obj-$(CONFIG_CRYPTO_KRB5) += krb5/

View File

@@ -1,222 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NIST SP800-90A DRBG derivation function
*
* Copyright (C) 2014, Stephan Mueller <smueller@chronox.de>
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/unaligned.h>
#include <crypto/aes.h>
#include <crypto/df_sp80090a.h>
static void drbg_kcapi_sym(struct aes_enckey *aeskey, unsigned char *outval,
const struct drbg_string *in, u8 blocklen_bytes)
{
/* there is only component in *in */
BUG_ON(in->len < blocklen_bytes);
aes_encrypt(aeskey, outval, in->buf);
}
/* BCC function for CTR DRBG as defined in 10.4.3 */
static void drbg_ctr_bcc(struct aes_enckey *aeskey,
unsigned char *out, const unsigned char *key,
struct list_head *in,
u8 blocklen_bytes,
u8 keylen)
{
struct drbg_string *curr = NULL;
struct drbg_string data;
short cnt = 0;
drbg_string_fill(&data, out, blocklen_bytes);
/* 10.4.3 step 2 / 4 */
aes_prepareenckey(aeskey, key, keylen);
list_for_each_entry(curr, in, list) {
const unsigned char *pos = curr->buf;
size_t len = curr->len;
/* 10.4.3 step 4.1 */
while (len) {
/* 10.4.3 step 4.2 */
if (blocklen_bytes == cnt) {
cnt = 0;
drbg_kcapi_sym(aeskey, out, &data, blocklen_bytes);
}
out[cnt] ^= *pos;
pos++;
cnt++;
len--;
}
}
/* 10.4.3 step 4.2 for last block */
if (cnt)
drbg_kcapi_sym(aeskey, out, &data, blocklen_bytes);
}
/*
* scratchpad usage: drbg_ctr_update is interlinked with crypto_drbg_ctr_df
* (and drbg_ctr_bcc, but this function does not need any temporary buffers),
* the scratchpad is used as follows:
* drbg_ctr_update:
* temp
* start: drbg->scratchpad
* length: drbg_statelen(drbg) + drbg_blocklen(drbg)
* note: the cipher writing into this variable works
* blocklen-wise. Now, when the statelen is not a multiple
* of blocklen, the generateion loop below "spills over"
* by at most blocklen. Thus, we need to give sufficient
* memory.
* df_data
* start: drbg->scratchpad +
* drbg_statelen(drbg) + drbg_blocklen(drbg)
* length: drbg_statelen(drbg)
*
* crypto_drbg_ctr_df:
* pad
* start: df_data + drbg_statelen(drbg)
* length: drbg_blocklen(drbg)
* iv
* start: pad + drbg_blocklen(drbg)
* length: drbg_blocklen(drbg)
* temp
* start: iv + drbg_blocklen(drbg)
* length: drbg_satelen(drbg) + drbg_blocklen(drbg)
* note: temp is the buffer that the BCC function operates
* on. BCC operates blockwise. drbg_statelen(drbg)
* is sufficient when the DRBG state length is a multiple
* of the block size. For AES192 (and maybe other ciphers)
* this is not correct and the length for temp is
* insufficient (yes, that also means for such ciphers,
* the final output of all BCC rounds are truncated).
* Therefore, add drbg_blocklen(drbg) to cover all
* possibilities.
* refer to crypto_drbg_ctr_df_datalen() to get required length
*/
/* Derivation Function for CTR DRBG as defined in 10.4.2 */
int crypto_drbg_ctr_df(struct aes_enckey *aeskey,
unsigned char *df_data, size_t bytes_to_return,
struct list_head *seedlist,
u8 blocklen_bytes,
u8 statelen)
{
unsigned char L_N[8];
/* S3 is input */
struct drbg_string S1, S2, S4, cipherin;
LIST_HEAD(bcc_list);
unsigned char *pad = df_data + statelen;
unsigned char *iv = pad + blocklen_bytes;
unsigned char *temp = iv + blocklen_bytes;
size_t padlen = 0;
unsigned int templen = 0;
/* 10.4.2 step 7 */
unsigned int i = 0;
/* 10.4.2 step 8 */
const unsigned char *K = (unsigned char *)
"\x00\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17"
"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f";
unsigned char *X;
size_t generated_len = 0;
size_t inputlen = 0;
struct drbg_string *seed = NULL;
u8 keylen;
memset(pad, 0, blocklen_bytes);
memset(iv, 0, blocklen_bytes);
keylen = statelen - blocklen_bytes;
/* 10.4.2 step 1 is implicit as we work byte-wise */
/* 10.4.2 step 2 */
if ((512 / 8) < bytes_to_return)
return -EINVAL;
/* 10.4.2 step 2 -- calculate the entire length of all input data */
list_for_each_entry(seed, seedlist, list)
inputlen += seed->len;
put_unaligned_be32(inputlen, &L_N[0]);
/* 10.4.2 step 3 */
put_unaligned_be32(bytes_to_return, &L_N[4]);
/* 10.4.2 step 5: length is L_N, input_string, one byte, padding */
padlen = (inputlen + sizeof(L_N) + 1) % (blocklen_bytes);
/* wrap the padlen appropriately */
if (padlen)
padlen = blocklen_bytes - padlen;
/*
* pad / padlen contains the 0x80 byte and the following zero bytes.
* As the calculated padlen value only covers the number of zero
* bytes, this value has to be incremented by one for the 0x80 byte.
*/
padlen++;
pad[0] = 0x80;
/* 10.4.2 step 4 -- first fill the linked list and then order it */
drbg_string_fill(&S1, iv, blocklen_bytes);
list_add_tail(&S1.list, &bcc_list);
drbg_string_fill(&S2, L_N, sizeof(L_N));
list_add_tail(&S2.list, &bcc_list);
list_splice_tail(seedlist, &bcc_list);
drbg_string_fill(&S4, pad, padlen);
list_add_tail(&S4.list, &bcc_list);
/* 10.4.2 step 9 */
while (templen < (keylen + (blocklen_bytes))) {
/*
* 10.4.2 step 9.1 - the padding is implicit as the buffer
* holds zeros after allocation -- even the increment of i
* is irrelevant as the increment remains within length of i
*/
put_unaligned_be32(i, iv);
/* 10.4.2 step 9.2 -- BCC and concatenation with temp */
drbg_ctr_bcc(aeskey, temp + templen, K, &bcc_list,
blocklen_bytes, keylen);
/* 10.4.2 step 9.3 */
i++;
templen += blocklen_bytes;
}
/* 10.4.2 step 11 */
X = temp + (keylen);
drbg_string_fill(&cipherin, X, blocklen_bytes);
/* 10.4.2 step 12: overwriting of outval is implemented in next step */
/* 10.4.2 step 13 */
aes_prepareenckey(aeskey, temp, keylen);
while (generated_len < bytes_to_return) {
short blocklen = 0;
/*
* 10.4.2 step 13.1: the truncation of the key length is
* implicit as the key is only drbg_blocklen in size based on
* the implementation of the cipher function callback
*/
drbg_kcapi_sym(aeskey, X, &cipherin, blocklen_bytes);
blocklen = (blocklen_bytes <
(bytes_to_return - generated_len)) ?
blocklen_bytes :
(bytes_to_return - generated_len);
/* 10.4.2 step 13.2 and 14 */
memcpy(df_data + generated_len, X, blocklen);
generated_len += blocklen;
}
memset(iv, 0, blocklen_bytes);
memset(temp, 0, statelen + blocklen_bytes);
memset(pad, 0, blocklen_bytes);
return 0;
}
EXPORT_SYMBOL_GPL(crypto_drbg_ctr_df);
MODULE_IMPORT_NS("CRYPTO_INTERNAL");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Stephan Mueller <smueller@chronox.de>");
MODULE_DESCRIPTION("Derivation Function conformant to SP800-90A");

View File

@@ -702,7 +702,7 @@ config CRYPTO_DEV_TEGRA
config CRYPTO_DEV_XILINX_TRNG
tristate "Support for Xilinx True Random Generator"
depends on ZYNQMP_FIRMWARE || COMPILE_TEST
select CRYPTO_DF80090A
select CRYPTO_LIB_SHA512
select HW_RANDOM
help
Xilinx Versal SoC driver provides kernel-side support for True Random Number

View File

@@ -4,6 +4,7 @@
* Copyright (c) 2024 - 2025 Advanced Micro Devices, Inc.
*/
#include <crypto/sha2.h>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
@@ -15,9 +16,6 @@
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <crypto/aes.h>
#include <crypto/df_sp80090a.h>
#include <crypto/internal/cipher.h>
/* TRNG Registers Offsets */
#define TRNG_STATUS_OFFSET 0x4U
@@ -43,7 +41,6 @@
/* Sizes in bytes */
#define TRNG_SEED_LEN_BYTES 48U
#define TRNG_ENTROPY_SEED_LEN_BYTES 64U
#define TRNG_SEC_STRENGTH_SHIFT 5U
#define TRNG_SEC_STRENGTH_BYTES BIT(TRNG_SEC_STRENGTH_SHIFT)
#define TRNG_BYTES_PER_REG 4U
@@ -55,8 +52,6 @@
struct xilinx_rng {
void __iomem *rng_base;
struct device *dev;
unsigned char *scratchpadbuf;
struct aes_enckey *aeskey;
struct hwrng trng;
};
@@ -172,29 +167,30 @@ static void xtrng_enable_entropy(struct xilinx_rng *rng)
static int xtrng_reseed_internal(struct xilinx_rng *rng)
{
u8 entropy[TRNG_ENTROPY_SEED_LEN_BYTES];
struct drbg_string data;
LIST_HEAD(seedlist);
static const u8 default_salt[SHA512_DIGEST_SIZE];
u8 entropy[SHA512_DIGEST_SIZE] __aligned(4);
u32 val;
int ret;
drbg_string_fill(&data, entropy, TRNG_SEED_LEN_BYTES);
list_add_tail(&data.list, &seedlist);
memset(entropy, 0, sizeof(entropy));
xtrng_enable_entropy(rng);
/* collect random data to use it as entropy (input for DF) */
/* Collect some output from the TRNG. */
static_assert(sizeof(entropy) >= TRNG_SEED_LEN_BYTES);
ret = xtrng_collect_random_data(rng, entropy, TRNG_SEED_LEN_BYTES, true);
if (ret != TRNG_SEED_LEN_BYTES)
return -EINVAL;
ret = crypto_drbg_ctr_df(rng->aeskey, rng->scratchpadbuf,
TRNG_SEED_LEN_BYTES, &seedlist, AES_BLOCK_SIZE,
TRNG_SEED_LEN_BYTES);
if (ret)
return ret;
/* Extract entropy from the TRNG output using HKDF-SHA512-Extract. */
hmac_sha512_usingrawkey(default_salt, sizeof(default_salt), entropy,
TRNG_SEED_LEN_BYTES, entropy);
/* Write the extracted entropy to the hardware. */
xtrng_write_multiple_registers(rng->rng_base + TRNG_EXT_SEED_OFFSET,
(u32 *)rng->scratchpadbuf, TRNG_NUM_INIT_REGS);
(u32 *)entropy, TRNG_NUM_INIT_REGS);
/* Clear the entropy from the stack. */
memzero_explicit(entropy, sizeof(entropy));
/* select reseed operation */
iowrite32(TRNG_CTRL_PRNGXS_MASK, rng->rng_base + TRNG_CTRL_OFFSET);
@@ -278,7 +274,6 @@ static void xtrng_hwrng_unregister(struct hwrng *trng)
static int xtrng_probe(struct platform_device *pdev)
{
struct xilinx_rng *rng;
size_t sb_size;
int ret;
rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL);
@@ -292,15 +287,6 @@ static int xtrng_probe(struct platform_device *pdev)
return PTR_ERR(rng->rng_base);
}
rng->aeskey = devm_kzalloc(&pdev->dev, sizeof(*rng->aeskey), GFP_KERNEL);
if (!rng->aeskey)
return -ENOMEM;
sb_size = crypto_drbg_ctr_df_datalen(TRNG_SEED_LEN_BYTES, AES_BLOCK_SIZE);
rng->scratchpadbuf = devm_kzalloc(&pdev->dev, sb_size, GFP_KERNEL);
if (!rng->scratchpadbuf)
return -ENOMEM;
xtrng_trng_reset(rng->rng_base);
ret = xtrng_reseed_internal(rng);
if (ret) {

View File

@@ -1,53 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright Stephan Mueller <smueller@chronox.de>, 2014
*/
#ifndef _CRYPTO_DF80090A_H
#define _CRYPTO_DF80090A_H
#include <crypto/internal/cipher.h>
#include <crypto/aes.h>
#include <linux/list.h>
/*
* Concatenation Helper and string operation helper
*
* SP800-90A requires the concatenation of different data. To avoid copying
* buffers around or allocate additional memory, the following data structure
* is used to point to the original memory with its size. In addition, it
* is used to build a linked list. The linked list defines the concatenation
* of individual buffers. The order of memory block referenced in that
* linked list determines the order of concatenation.
*/
struct drbg_string {
const unsigned char *buf;
size_t len;
struct list_head list;
};
static inline void drbg_string_fill(struct drbg_string *string,
const unsigned char *buf, size_t len)
{
string->buf = buf;
string->len = len;
INIT_LIST_HEAD(&string->list);
}
static inline int crypto_drbg_ctr_df_datalen(u8 statelen, u8 blocklen)
{
return statelen + /* df_data */
blocklen + /* pad */
blocklen + /* iv */
statelen + blocklen; /* temp */
}
int crypto_drbg_ctr_df(struct aes_enckey *aes,
unsigned char *df_data,
size_t bytes_to_return,
struct list_head *seedlist,
u8 blocklen_bytes,
u8 statelen);
#endif /* _CRYPTO_DF80090A_H */