#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: subr_cprng.c,v 1.44 2023/08/05 11:21:24 riastradh Exp $");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/cprng.h>
#include <sys/cpu.h>
#include <sys/entropy.h>
#include <sys/errno.h>
#include <sys/evcnt.h>
#include <sys/intr.h>
#include <sys/kmem.h>
#include <sys/percpu.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <crypto/nist_hash_drbg/nist_hash_drbg.h>
struct cprng_strong {
struct percpu *cs_percpu;
ipl_cookie_t cs_iplcookie;
};
struct cprng_cpu {
struct nist_hash_drbg *cc_drbg;
struct {
struct evcnt reseed;
} *cc_evcnt;
unsigned cc_epoch;
};
static int sysctl_kern_urandom(SYSCTLFN_ARGS);
static int sysctl_kern_arandom(SYSCTLFN_ARGS);
static void cprng_init_cpu(void *, void *, struct cpu_info *);
static void cprng_fini_cpu(void *, void *, struct cpu_info *);
struct cprng_strong *kern_cprng __read_mostly;
struct cprng_strong *user_cprng __read_mostly;
static struct sysctllog *cprng_sysctllog __read_mostly;
void
cprng_init(void)
{
if (__predict_false(nist_hash_drbg_initialize() != 0))
panic("NIST Hash_DRBG failed self-test");
kern_cprng = cprng_strong_create("kern", IPL_SOFTSERIAL, 0);
user_cprng = cprng_strong_create("user", IPL_NONE, 0);
sysctl_createv(&cprng_sysctllog, 0, NULL, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READONLY, CTLTYPE_INT, "urandom",
SYSCTL_DESCR("Independent uniform random 32-bit integer"),
sysctl_kern_urandom, 0, NULL, 0, CTL_KERN, KERN_URND, CTL_EOL);
sysctl_createv(&cprng_sysctllog, 0, NULL, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READONLY, CTLTYPE_INT , "arandom",
SYSCTL_DESCR("Independent uniform random bytes, up to 256 bytes"),
sysctl_kern_arandom, 0, NULL, 0, CTL_KERN, KERN_ARND, CTL_EOL);
}
static int
sysctl_kern_urandom(SYSCTLFN_ARGS)
{
struct sysctlnode node = *rnode;
int v;
int error;
cprng_strong(user_cprng, &v, sizeof v, 0);
node.sysctl_data = &v;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
explicit_memset(&v, 0, sizeof v);
return error;
}
static int
sysctl_kern_arandom(SYSCTLFN_ARGS)
{
struct sysctlnode node = *rnode;
uint8_t buf[256];
int error;
if (*oldlenp > 256)
*oldlenp = 256;
cprng_strong(user_cprng, buf, *oldlenp, 0);
node.sysctl_data = buf;
node.sysctl_size = *oldlenp;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
explicit_memset(buf, 0, sizeof buf);
return error;
}
struct cprng_strong *
cprng_strong_create(const char *name, int ipl, int flags)
{
struct cprng_strong *cprng;
cprng = kmem_alloc(sizeof(*cprng), KM_SLEEP);
cprng->cs_iplcookie = makeiplcookie(ipl);
cprng->cs_percpu = percpu_create(sizeof(struct cprng_cpu),
cprng_init_cpu, cprng_fini_cpu, __UNCONST(name));
return cprng;
}
void
cprng_strong_destroy(struct cprng_strong *cprng)
{
percpu_free(cprng->cs_percpu, sizeof(struct cprng_cpu));
kmem_free(cprng, sizeof(*cprng));
}
static void
cprng_init_cpu(void *ptr, void *cookie, struct cpu_info *ci)
{
struct cprng_cpu *cc = ptr;
const char *name = cookie;
const char *cpuname;
uint8_t zero[NIST_HASH_DRBG_SEEDLEN_BYTES] = {0};
char namebuf[64];
snprintf(namebuf, sizeof namebuf, "%s/%u", name, cpu_index(ci));
cc->cc_drbg = kmem_zalloc(sizeof(*cc->cc_drbg), KM_SLEEP);
cc->cc_evcnt = kmem_alloc(sizeof(*cc->cc_evcnt), KM_SLEEP);
if (__predict_false(nist_hash_drbg_instantiate(cc->cc_drbg,
zero, sizeof zero, NULL, 0, namebuf, strlen(namebuf))))
panic("nist_hash_drbg_instantiate");
cpuname = ci->ci_cpuname[0] == '\0' ? "cpu0" : ci->ci_cpuname;
evcnt_attach_dynamic(&cc->cc_evcnt->reseed, EVCNT_TYPE_MISC, NULL,
cpuname, "cprng_strong reseed");
cc->cc_epoch = 0;
}
static void
cprng_fini_cpu(void *ptr, void *cookie, struct cpu_info *ci)
{
struct cprng_cpu *cc = ptr;
evcnt_detach(&cc->cc_evcnt->reseed);
if (__predict_false(nist_hash_drbg_destroy(cc->cc_drbg)))
panic("nist_hash_drbg_destroy");
kmem_free(cc->cc_evcnt, sizeof(*cc->cc_evcnt));
kmem_free(cc->cc_drbg, sizeof(*cc->cc_drbg));
}
static void
cprng_strong_reseed(struct cprng_strong *cprng, unsigned epoch,
struct cprng_cpu **ccp, int *sp)
{
uint8_t seed[NIST_HASH_DRBG_SEEDLEN_BYTES];
splx(*sp);
percpu_putref(cprng->cs_percpu);
entropy_extract(seed, sizeof seed, 0);
*ccp = percpu_getref(cprng->cs_percpu);
*sp = splraiseipl(cprng->cs_iplcookie);
(*ccp)->cc_evcnt->reseed.ev_count++;
if (__predict_false(nist_hash_drbg_reseed((*ccp)->cc_drbg,
seed, sizeof seed, NULL, 0)))
panic("nist_hash_drbg_reseed");
explicit_memset(seed, 0, sizeof seed);
(*ccp)->cc_epoch = epoch;
}
size_t
cprng_strong(struct cprng_strong *cprng, void *buf, size_t len, int flags)
{
struct cprng_cpu *cc;
unsigned epoch;
int s;
KASSERT(!cpu_intr_p());
KASSERT(len <= CPRNG_MAX_LEN);
KASSERT(flags == 0);
cc = percpu_getref(cprng->cs_percpu);
s = splraiseipl(cprng->cs_iplcookie);
epoch = entropy_epoch();
if (__predict_false(epoch != cc->cc_epoch))
cprng_strong_reseed(cprng, epoch, &cc, &s);
if (__predict_false(nist_hash_drbg_generate(cc->cc_drbg, buf, len,
NULL, 0))) {
cprng_strong_reseed(cprng, epoch, &cc, &s);
if (__predict_false(nist_hash_drbg_generate(cc->cc_drbg,
buf, len, NULL, 0)))
panic("nist_hash_drbg_generate");
}
splx(s);
percpu_putref(cprng->cs_percpu);
return len;
}
uint32_t
cprng_strong32(void)
{
uint32_t r;
cprng_strong(kern_cprng, &r, sizeof(r), 0);
return r;
}
uint64_t
cprng_strong64(void)
{
uint64_t r;
cprng_strong(kern_cprng, &r, sizeof(r), 0);
return r;
}