blob: 4d7a5a7b3f52c3af8e677d3c4d758d4d235f1dd6 [file] [log] [blame]
/*
* Copyright (c) 2012-2014, Intel Corporation
* Authors: Richard B. Hill <richard.b.hill@intel.com>,
* H. Peter Anvin <hpa@linux.intel.com>,
* John P. Mechalas <john.p.mechalas@intel.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#define _GNU_SOURCE
#ifndef HAVE_CONFIG_H
#error Invalid or missing autoconf build environment
#endif
#include "rng-tools-config.h"
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <syslog.h>
#include <string.h>
#include <stddef.h>
#ifdef HAVE_LIBGCRYPT
#include <gcrypt.h>
#endif
#include "rngd.h"
#include "fips.h"
#include "exits.h"
#include "rngd_entsource.h"
#if defined(__i386__) || defined(__x86_64__)
/* Struct for CPUID return values */
struct cpuid {
uint32_t eax, ecx, edx, ebx;
};
/*
* Get data from RDRAND. The count is in bytes, but the function can
* round *up* the count to the nearest 4- or 8-byte boundary. The caller
* needs to take that into account. count must not be zero.
*
* The function returns the number of bytes actually written.
*/
extern unsigned int x86_rdrand_bytes(void *ptr, unsigned int count);
/*
* Get data from RDSEED (preferentially) or RDRAND into separate
* buffers. Returns when either buffer is full. Same conditions
* apply as for x86_rdrand_bytes().
*/
extern void x86_rdseed_or_rdrand_bytes(void *seed_ptr, unsigned int *seed_cnt,
void *rand_ptr, unsigned int *rand_cnt);
/* Condition RDRAND for seed-grade entropy */
extern void x86_aes_mangle(void *data, void *state);
/* Expand an AES key for future use */
extern void x86_aes_expand_key(const void *key);
#ifdef __x86_64__
typedef uint64_t unative_t; /* x86-64 or x32 */
#else
typedef uint32_t unative_t; /* i386 */
#endif
/* Checking eflags to confirm cpuid instruction available */
static inline int x86_has_eflag(unative_t flag)
{
unative_t f0, f1;
asm("pushf ; "
"pushf ; "
"pop %0 ; "
"mov %0,%1 ; "
"xor %2,%1 ; "
"push %1 ; "
"popf ; "
"pushf ; "
"pop %1 ; "
"popf"
: "=&r" (f0), "=&r" (f1)
: "ri" (flag));
return !!((f0^f1) & flag);
}
static inline int x86_has_cpuid(void)
{
#ifdef __i386__
return x86_has_eflag(1 << 21); /* ID flag */
#else
return 1; /* x86-64 always has CPUID */
#endif
}
/* Calling cpuid instruction to verify rdrand and aes-ni capability */
static void cpuid(unsigned int leaf, unsigned int subleaf, struct cpuid *out)
{
#ifdef __i386__
/* %ebx is a forbidden register if we compile with -fPIC or -fPIE */
asm volatile("movl %%ebx,%0 ; cpuid ; xchgl %%ebx,%0"
: "=r" (out->ebx),
"=a" (out->eax),
"=c" (out->ecx),
"=d" (out->edx)
: "a" (leaf), "c" (subleaf));
#else
asm volatile("cpuid"
: "=b" (out->ebx),
"=a" (out->eax),
"=c" (out->ecx),
"=d" (out->edx)
: "a" (leaf), "c" (subleaf));
#endif
}
/* Read data from the drng in chunks of 128 bytes for AES scrambling */
#define AES_BLOCK 16
#define CHUNK_SIZE (AES_BLOCK*8) /* 8 parallel streams */
#define RDRAND_ROUNDS 512 /* 512:1 data reduction */
static unsigned char iv_buf[CHUNK_SIZE] __attribute__((aligned(128)));
static int have_aesni, have_rdseed;
/* Necessary if we have RDRAND but not AES-NI */
#ifdef HAVE_LIBGCRYPT
#define MIN_GCRYPT_VERSION "1.0.0"
static gcry_cipher_hd_t gcry_cipher_hd;
#endif
static inline int gcrypt_mangle(unsigned char *tmp)
{
#ifdef HAVE_LIBGCRYPT
gcry_error_t gcry_error;
/* Encrypt tmp in-place. */
gcry_error = gcry_cipher_encrypt(gcry_cipher_hd, tmp,
AES_BLOCK * RDRAND_ROUNDS,
NULL, 0);
if (gcry_error) {
message(LOG_DAEMON|LOG_ERR,
"gcry_cipher_encrypt error: %s\n",
gcry_strerror(gcry_error));
return -1;
}
return 0;
#else
(void)tmp;
return -1;
#endif
}
int xread_drng(void *buf, size_t size, struct rng *ent_src)
{
static unsigned char rdrand_buf[CHUNK_SIZE * RDRAND_ROUNDS]
__attribute__((aligned(128)));
static unsigned int rdrand_bytes = 0;
unsigned char rdseed_buf[CHUNK_SIZE]
__attribute__((aligned(128)));
char *p = buf;
size_t chunk;
unsigned char *rdrand_ptr, *data;
unsigned int rand_bytes, seed_bytes;
(void)ent_src;
while (size) {
rand_bytes = (have_aesni
? CHUNK_SIZE * RDRAND_ROUNDS
: AES_BLOCK * RDRAND_ROUNDS)
- rdrand_bytes;
if (rand_bytes == 0) {
/* We already have a full rdrand_buf */
if (have_aesni) {
x86_aes_mangle(rdrand_buf, iv_buf);
data = iv_buf;
chunk = CHUNK_SIZE;
} else if (!gcrypt_mangle(rdrand_buf)) {
data = rdrand_buf +
AES_BLOCK * (RDRAND_ROUNDS - 1);
chunk = AES_BLOCK;
} else {
return -1;
}
rdrand_bytes = 0;
goto have_data;
}
rdrand_ptr = rdrand_buf + rdrand_bytes;
if (have_rdseed) {
seed_bytes = sizeof rdseed_buf;
x86_rdseed_or_rdrand_bytes(rdseed_buf, &seed_bytes,
rdrand_ptr, &rand_bytes);
} else {
rand_bytes = x86_rdrand_bytes(rdrand_ptr, rand_bytes);
seed_bytes = 0;
}
rdrand_bytes += rand_bytes;
if (seed_bytes) {
data = rdseed_buf;
chunk = seed_bytes;
goto have_data;
}
continue; /* No data ready yet */
have_data:
chunk = (chunk > size) ? size : chunk;
memcpy(p, data, chunk);
p += chunk;
size -= chunk;
}
return 0;
}
static int init_aesni(const void *key)
{
if (!have_aesni)
return 1;
x86_aes_expand_key(key);
return 0;
}
static int init_gcrypt(const void *key)
{
#ifdef HAVE_LIBGCRYPT
gcry_error_t gcry_error;
if (!gcry_check_version(MIN_GCRYPT_VERSION)) {
message(LOG_DAEMON|LOG_ERR,
"libgcrypt version mismatch: have %s, require >= %s\n",
gcry_check_version(NULL), MIN_GCRYPT_VERSION);
return 1;
}
gcry_error = gcry_cipher_open(&gcry_cipher_hd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_CBC, 0);
if (!gcry_error)
gcry_error = gcry_cipher_setkey(gcry_cipher_hd, key, AES_BLOCK);
if (!gcry_error) {
/*
* Only need the first 16 bytes of iv_buf. AES-NI can
* encrypt multiple blocks in parallel but we can't.
*/
gcry_error = gcry_cipher_setiv(gcry_cipher_hd, iv_buf, AES_BLOCK);
}
if (gcry_error) {
message(LOG_DAEMON|LOG_ERR,
"could not set key or IV: %s\n",
gcry_strerror(gcry_error));
gcry_cipher_close(gcry_cipher_hd);
return 1;
}
return 0;
#else
(void)key;
return 1;
#endif
}
/*
* Confirm RDRAND capabilities for drng entropy source
*/
int init_drng_entropy_source(struct rng *ent_src)
{
struct cpuid info;
/* We need RDRAND, but AESni is optional */
const uint32_t features_ecx1_rdrand = 1 << 30;
const uint32_t features_ecx1_aesni = 1 << 25;
const uint32_t features_ebx7_rdseed = 1 << 18;
uint32_t max_cpuid_leaf;
static unsigned char key[AES_BLOCK] = {
0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70,
0x80,0x90,0xa0,0xb0,0xc0,0xd0,0xe0,0xf0
}; /* AES data reduction key */
unsigned char xkey[AES_BLOCK]; /* Material to XOR into the key */
int fd;
int i;
if (!x86_has_cpuid())
return 1; /* No CPUID instruction */
cpuid(0, 0, &info);
max_cpuid_leaf = info.eax;
if (max_cpuid_leaf < 1)
return 1;
cpuid(1, 0, &info);
if (!(info.ecx & features_ecx1_rdrand))
return 1;
have_aesni = !!(info.ecx & features_ecx1_aesni);
have_rdseed = 0;
if (max_cpuid_leaf >= 7) {
cpuid(7, 0, &info);
if (info.ebx & features_ebx7_rdseed)
have_rdseed = 1;
}
/* Randomize the AES data reduction key the best we can */
if (x86_rdrand_bytes(xkey, sizeof xkey) != sizeof xkey)
return 1;
fd = open("/dev/urandom", O_RDONLY);
if (fd >= 0) {
read(fd, key, sizeof key);
close(fd);
}
for (i = 0; i < (int)sizeof key; i++)
key[i] ^= xkey[i];
/* Initialize the IV buffer */
if (x86_rdrand_bytes(iv_buf, CHUNK_SIZE) != CHUNK_SIZE)
return 1;
if (init_aesni(key) && init_gcrypt(key))
return 1; /* We need one crypto or the other... */
src_list_add(ent_src);
/* Bootstrap FIPS tests */
ent_src->fipsctx = malloc(sizeof(fips_ctx_t));
fips_init(ent_src->fipsctx, 0);
return 0;
}
#else /* Not i386 or x86-64 */
int init_drng_entropy_source(struct rng *ent_src)
{
(void)ent_src;
return 1;
}
int xread_drng(void *buf, size_t size, struct rng *ent_src)
{
(void)buf;
(void)size;
(void)ent_src;
return -1;
}
#endif /* Not i386 or x86-64 */