Commit da1ede1b authored by Philipp Schafft's avatar Philipp Schafft 🦁
Browse files

Merge branch 'feature-prng' into devel-phschafft

parents 383da7ba f7e138bf
Pipeline #2747 failed with stages
in 37 seconds
......@@ -11,6 +11,7 @@ libigloo_la_SOURCES = \
src/feature.c \
src/list.c \
src/digest.c \
src/prng.c \
src/cs.c \
src/sp.c \
src/ro.c \
......@@ -36,6 +37,7 @@ IGLOO_BUILT_TESTS = \
tests/ro \
tests/digest \
tests/error \
tests/prng \
tests/cs \
tests/time
......@@ -62,6 +64,7 @@ pkginclude_HEADERS = \
include/igloo/feature.h \
include/igloo/list.h \
include/igloo/digest.h \
include/igloo/prng.h \
include/igloo/cs.h \
include/igloo/sp.h \
include/igloo/tap.h
......
......@@ -9,6 +9,7 @@ libigloo 0.9.1 (2022-??-??)
* TAP test framework: test framework for testing libigloo and other C projects
* Portable time API: simple API for most common time related functions
* Digest and HMAC: support for digest and HMAC calculation (limited to SHA3)
* PRNG: support for pseudo random number generation
libigloo 0.9.0 (2020-12-14)
* Initial release.
......@@ -80,22 +80,52 @@ AS_IF([test "$ice_found_std_headers" != "yes"], [
AC_MSG_ERROR([Unable to find the standard headers])
])
AC_CHECK_HEADERS([sys/select.h errno.h])
AC_CHECK_HEADERS([sys/select.h errno.h fcntl.h sys/random.h])
AC_CHECK_FUNCS([open close read write fopen fclose fread fwrite], [], [AC_MSG_ERROR([Required I/O functions missing])])
AC_CHECK_FUNCS_ONCE([clock_gettime gettimeofday ftime clock_nanosleep nanosleep usleep select])
AC_CHECK_FUNCS_ONCE([sysconf])
AC_CHECK_FUNCS_ONCE([uname getuid getpid getppid])
AC_CHECK_FUNCS_ONCE([getrandom getentropy])
AC_TYPE_SIZE_T
AC_TYPE_SSIZE_T
AC_TYPE_UINTPTR_T
ACX_PTHREAD([], [AC_MSG_ERROR([POSIX threads missing])])
AC_SEARCH_LIBS([log2], [m], [], [AC_MSG_ERROR([unable to find the log2() function])])
CFLAGS="${CFLAGS} ${PTHREAD_CFLAGS}"
CPPFLAGS="${CPPFLAGS} ${PTHREAD_CPPFLAGS}"
LIBS="${LIBS} ${PTHREAD_LIBS}"
AC_DEFUN([IC_CHECK_PATH], [
AC_ARG_WITH([$1],
AS_HELP_STRING([--with-$1=PATH], [Sets the path to the $1 file]),
[],
[AS_IF([test "$build" = "$host"],
[AC_CHECK_FILE([$4],
[with_$2="$4"],
[])],
[])])
AS_IF([test "$with_$2" != "no"],
[
AS_IF([test "$with_$2" = "yes"], [AC_MSG_ERROR([You need to supply a path as value for --with-$1])])
AC_DEFINE_UNQUOTED([PATH_$3], ["$with_$2"], [Define to $1 path])
])
])
IC_CHECK_PATH([machine-id], [machine_id], [MACHINE_ID], [/etc/machine-id])
IC_CHECK_PATH([boot-id], [boot_id], [BOOT_ID], [/proc/sys/kernel/random/boot_id])
IC_CHECK_PATH([urandom], [urandom], [URANDOM], [/dev/urandom])
AC_DEFINE(igloo_INTERNAL,1,[Set to signal that we are building libigloo in contrast to linking against libigloo.])
AC_REQUIRE_AUX_FILE([tap-driver.sh])
......
/* Copyright (C) 2020-2021 Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _LIBIGLOO__PRNG_H_
#define _LIBIGLOO__PRNG_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <sys/types.h>
#include <igloo/types.h>
typedef uint64_t igloo_prng_flags_t;
#define igloo_PRNG_FLAG_NONE ((igloo_prng_flags_t)0)
/* Change options */
igloo_error_t igloo_prng_configure(igloo_ro_t instance, igloo_prng_flags_t addflags, igloo_prng_flags_t removeflags, ssize_t overcommitment);
/* Run a reseed now, useful to be run at times of idle */
igloo_error_t igloo_prng_auto_reseed(igloo_ro_t instance, igloo_prng_flags_t flags);
/* write data to the PRNG to seed it */
igloo_error_t igloo_prng_write(igloo_ro_t instance, const void *buffer, size_t len, ssize_t bits, igloo_prng_flags_t flags);
/* read pseudo random bytes from the PRNG */
ssize_t igloo_prng_read(igloo_ro_t instance, void *buffer, size_t len, igloo_prng_flags_t flags);
/* write len pseudo random bytes to a file. If len is -1 a default value is used. */
igloo_error_t igloo_prng_write_file(igloo_ro_t instance, const char *filename, ssize_t len, igloo_prng_flags_t flags);
/* read at max len bytes from the file and see the PRNG with it. if len is -1 all of the file is read. */
igloo_error_t igloo_prng_read_file(igloo_ro_t instance, const char *filename, ssize_t len, ssize_t bits, igloo_prng_flags_t flags);
#ifdef __cplusplus
}
#endif
#endif /* ! _LIBIGLOO__PRNG_H_ */
......@@ -41,6 +41,7 @@ struct igloo_instance_tag {
igloo_ro_t logger;
igloo_instance_log_handler_t *logger_handler;
igloo_sp_state_t stringpool_state;
igloo_prng_state_t prng_state;
};
static igloo_error_t igloo_instance__new(igloo_ro_t self, const igloo_ro_type_t *type, va_list ap);
......@@ -70,6 +71,10 @@ igloo_error_t igloo_instance__new(igloo_ro_t self, const igloo_ro_type_t *type,
if (error != igloo_ERROR_NONE)
return error;
error = igloo_instance_prng_init(&(instance->prng_state));
if (error != igloo_ERROR_NONE)
return error;
return igloo_ERROR_NONE;
}
......@@ -79,6 +84,7 @@ static void igloo_instance__free(igloo_ro_t self)
igloo_ro_weak_unref(&(instance->logger));
igloo_instance_prng_destroy(&(instance->prng_state));
igloo_instance_sp_destroy(&(instance->stringpool_state));
}
......@@ -109,7 +115,7 @@ static igloo_error_t render_error(igloo_error_t error, char *buffer, size_t len)
static igloo_error_t igloo_instance__stringify(igloo_ro_t self, char **result, igloo_ro_sy_t flags)
{
#define STRINGIFY_FORMAT "%s, error=%s, stringpool=%s}", from_parent, errorbuffer, stringpool
#define STRINGIFY_FORMAT "%s, error=%s, stringpool=%s, prng=%s}", from_parent, errorbuffer, stringpool, prng
igloo_instance_t *instance = igloo_ro_to_type(self, igloo_instance_t);
igloo_error_t error;
char *from_parent;
......@@ -117,6 +123,7 @@ static igloo_error_t igloo_instance__stringify(igloo_ro_t self, char **result, i
int ret, res;
char errorbuffer[80];
char stringpool[256];
char prng[80];
if (!(flags & igloo_RO_SY_DEBUG))
return igloo_ERROR_NOSYS;
......@@ -129,6 +136,10 @@ static igloo_error_t igloo_instance__stringify(igloo_ro_t self, char **result, i
if (error != igloo_ERROR_NONE)
return error;
error = igloo_instance_prng_stringify(&(instance->prng_state), prng, sizeof(prng));
if (error != igloo_ERROR_NONE)
return error;
error = igloo_ro_stringify_parent(self, &from_parent, flags, igloo_ro_full_t);
if (error != igloo_ERROR_NONE)
return error;
......@@ -294,3 +305,16 @@ igloo_sp_state_t *igloo_instance_get_stringpool_state(igloo_ro_t self)
return &(instance->stringpool_state);
}
igloo_prng_state_t *igloo_instance_get_prng_state(igloo_ro_t self, size_t *instancelen)
{
igloo_instance_t *instance = get_instance(self);
if (instancelen)
*instancelen = sizeof(*instance);
if (!instance)
return NULL;
return &(instance->prng_state);
}
......@@ -24,6 +24,7 @@ extern "C" {
#endif
#include <stdbool.h>
#include <pthread.h>
#include <igloo/types.h>
#include <igloo/rwlock.h>
......@@ -49,16 +50,37 @@ typedef struct {
igloo_sp_area_t *areas[3];
} igloo_sp_state_t;
typedef char igloo_digest_512_t[512/8];
typedef struct {
uint64_t state;
uint64_t flags;
size_t bits;
size_t overcommitment;
size_t callcount;
pthread_mutex_t lock;
igloo_digest_512_t initial;
igloo_digest_512_t auto_seed0;
igloo_digest_512_t auto_seed1;
igloo_digest_512_t manual_seed0;
igloo_digest_512_t manual_seed1;
igloo_digest_512_t last_result;
} igloo_prng_state_t;
extern const igloo_ro_type_t * igloo_instance_type;
igloo_error_t igloo_ro_bootstrap(igloo_ro_t *instance, const igloo_ro_type_t *type);
igloo_ro_t igloo_ro_get_instance_unsafe(igloo_ro_t self, const igloo_ro_type_t *type);
igloo_sp_state_t *igloo_instance_get_stringpool_state(igloo_ro_t self);
igloo_prng_state_t *igloo_instance_get_prng_state(igloo_ro_t self, size_t *instancelen);
igloo_error_t igloo_instance_sp_init(igloo_sp_state_t *state);
void igloo_instance_sp_destroy(igloo_sp_state_t *state);
igloo_error_t igloo_instance_sp_stringify(igloo_sp_state_t *state, char *buf, size_t len);
igloo_error_t igloo_instance_prng_init(igloo_prng_state_t *state);
void igloo_instance_prng_destroy(igloo_prng_state_t *state);
igloo_error_t igloo_instance_prng_stringify(igloo_prng_state_t *state, char *buf, size_t len);
#ifdef __cplusplus
}
#endif
......
/* Copyright (C) 2020-2021 Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <pthread.h>
#ifdef HAVE_UNAME
#include <sys/utsname.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYS_RANDOM_H
#include <sys/random.h>
#endif
#include <igloo/prng.h>
#include <igloo/error.h>
#include <igloo/digest.h>
#include <igloo/ro.h>
#include <igloo/time.h>
#include "private.h"
#define HASHFUNC "SHA3-512" // must be a 512 bit function!
#define BLOCK_LENGTH (512/8)
#define MAX_BITS_PER_HASH 192
#define MAX_BITS_PER_STATE (MAX_BITS_PER_HASH*3)
#define STATE_INITED 0x01U
#define STATE_AUTO_SEED_0 0x02U
#define STATE_MAN_SEED_0 0x04U
typedef struct {
igloo_ctime_t ctime;
pthread_t thread;
const void *stack;
const void *instance;
const void *buffer;
size_t bufferlen;
} igloo_prng_buffer_extra_t;
static inline void buffer_extra_init(igloo_prng_buffer_extra_t *extra, const void *instance)
{
igloo_error_t error;
memset(extra, 0, sizeof(*extra));
extra->thread = pthread_self();
extra->stack = &error; // any object on the current stack.
extra->instance = instance;
error = igloo_ctime_from_now(&(extra->ctime), igloo_CLOCK_MONOTONIC);
if (error != igloo_ERROR_NONE)
igloo_ctime_from_now(&(extra->ctime), igloo_CLOCK_REALTIME);
}
igloo_error_t igloo_instance_prng_init(igloo_prng_state_t *state)
{
if (pthread_mutex_init(&(state->lock), NULL) != 0)
return igloo_ERROR_GENERIC;
state->overcommitment = 32;
return igloo_ERROR_NONE;
}
void igloo_instance_prng_destroy(igloo_prng_state_t *state)
{
pthread_mutex_destroy(&(state->lock));
}
igloo_error_t igloo_instance_prng_stringify(igloo_prng_state_t *state, char *buf, size_t len)
{
(void)state;
pthread_mutex_lock(&(state->lock));
snprintf(buf, len, "{bits=%zu}", state->bits);
pthread_mutex_unlock(&(state->lock));
return igloo_ERROR_NONE;
}
static igloo_error_t get_inited_state(igloo_prng_state_t **state, size_t *instancelen, igloo_ro_t instance)
{
igloo_prng_state_t *self;
size_t len;
*state = self = igloo_instance_get_prng_state(instance, &len);
if (!*state)
return igloo_ERROR_BADSTATE;
if (instancelen)
*instancelen = len;
pthread_mutex_lock(&(self->lock));
if (!(self->state & STATE_INITED)) {
igloo_digest_t *digest = NULL;
igloo_error_t error;
const void *instanceraw = igloo_ro_to_type(instance, igloo_ro_stub_t);
struct {
igloo_prng_buffer_extra_t extra;
int debian;
#ifdef HAVE_UNAME
struct utsname utsname;
#endif
#ifdef HAVE_GETUID
uid_t uid;
#endif
#ifdef HAVE_GETPID
pid_t pid;
#endif
#ifdef HAVE_GETPPID
pid_t ppid;
#endif
} seed;
buffer_extra_init(&(seed.extra), instanceraw);
seed.extra.buffer = instanceraw;
seed.extra.bufferlen = len;
memset(&seed, 0, sizeof(seed));
seed.debian = 4;
#ifdef HAVE_UNAME
uname(&seed.utsname);
#endif
#ifdef HAVE_GETUID
seed.uid = getuid();
#endif
#ifdef HAVE_GETPID
seed.pid = getpid();
#endif
#ifdef HAVE_GETPPID
seed.ppid = getppid();
#endif
error = igloo_digest_new(&digest, instance, HASHFUNC);
if (error != igloo_ERROR_NONE)
return error;
if (igloo_digest_write(digest, instanceraw, len) != (ssize_t)len) {
igloo_ro_unref(&digest);
return igloo_ERROR_GENERIC;
}
if (igloo_digest_write(digest, &seed, sizeof(seed)) != (ssize_t)sizeof(seed)) {
igloo_ro_unref(&digest);
return igloo_ERROR_GENERIC;
}
if (igloo_digest_read(digest, self->initial, sizeof(self->initial)) != sizeof(self->initial)) {
igloo_ro_unref(&digest);
return igloo_ERROR_GENERIC;
}
error = igloo_ro_unref(&digest);
if (error != igloo_ERROR_NONE)
return error;
self->bits = 5;
self->state |= STATE_INITED;
#if defined(PATH_BOOT_ID) || defined(PATH_MACHINE_ID)
// this is safe as we are inited now. In worst case we read initial seeds twice.
pthread_mutex_unlock(&(self->lock));
#ifdef PATH_BOOT_ID
igloo_prng_read_file(instance, PATH_BOOT_ID, -1, -1, igloo_PRNG_FLAG_NONE);
#endif
#ifdef PATH_MACHINE_ID
igloo_prng_read_file(instance, PATH_MACHINE_ID, -1, 0, igloo_PRNG_FLAG_NONE);
#endif
pthread_mutex_lock(&(self->lock));
#endif
}
return igloo_ERROR_NONE;
}
static ssize_t igloo_prng_estimate_bits(const void *buffer, size_t len)
{
size_t counts[256];
size_t i;
double entpb = 0;
if (!buffer)
return -1;
if (len < 2)
return 0;
memset(counts, 0, sizeof(counts));
for (i = 0; i < len; i++)
counts[((const unsigned char *)buffer)[i]]++;
for (i = 0; i < 256; i++) {
double probability;
if (!counts[i])
continue;
probability = counts[i]/(double)len;
entpb += probability * log2(1. / probability);
}
return entpb * len;
}
igloo_error_t igloo_prng_configure(igloo_ro_t instance, igloo_prng_flags_t addflags, igloo_prng_flags_t removeflags, ssize_t overcommitment)
{
igloo_prng_state_t *self;
self = igloo_instance_get_prng_state(instance, NULL);
if (!self)
return igloo_ERROR_BADSTATE;
pthread_mutex_lock(&(self->lock));
self->flags |= addflags|removeflags;
self->flags -= removeflags;
if (overcommitment > 0)
self->overcommitment = overcommitment;
pthread_mutex_unlock(&(self->lock));
return igloo_ERROR_NONE;
}
static igloo_error_t digest_instance_extra_once(const void *instanceraw, size_t instancelen, igloo_ro_t instance, igloo_prng_buffer_extra_t *extra, const void *mid, size_t midlen, igloo_digest_512_t out)
{
igloo_digest_t *digest = NULL;
igloo_error_t error;
error = igloo_digest_new(&digest, instance, HASHFUNC);
if (error != igloo_ERROR_NONE)
return error;
if (igloo_digest_write(digest, instanceraw, instancelen) != (ssize_t)instancelen) {
igloo_ro_unref(&digest);
return igloo_ERROR_GENERIC;
}
if (mid && midlen) {
if (igloo_digest_write(digest, mid, midlen) != (ssize_t)midlen) {
igloo_ro_unref(&digest);
return igloo_ERROR_GENERIC;
}
}
if (igloo_digest_write(digest, extra, sizeof(*extra)) != (ssize_t)sizeof(*extra)) {
igloo_ro_unref(&digest);
return igloo_ERROR_GENERIC;
}
if (igloo_digest_read(digest, out, sizeof(igloo_digest_512_t)) != sizeof(igloo_digest_512_t)) {
igloo_ro_unref(&digest);
return igloo_ERROR_GENERIC;
}
error = igloo_ro_unref(&digest);
if (error != igloo_ERROR_NONE)
return error;
return igloo_ERROR_NONE;
}
static inline size_t igloo_prng_auto_reseed_unlocked_urandom_buffer(void *buffer, size_t bufferlen)
{
#if defined(PATH_URANDOM) || defined(HAVE_GETRANDOM)
ssize_t ret;
#endif
#ifdef PATH_URANDOM
int fh;
#endif
#ifdef HAVE_GETRANDOM
ret = getrandom(buffer, bufferlen, 0);
if (ret > 0)
return ret;
#endif
#ifdef HAVE_GETENTROPY
if (getentropy(buffer, bufferlen) == 0) {
return bufferlen;
}
#endif
#ifdef PATH_URANDOM
#ifdef O_CLOEXEC
fh = open(PATH_URANDOM, O_RDONLY|O_CLOEXEC, 0);
#else
fh = open(PATH_URANDOM, O_RDONLY, 0);
#endif
if (fh >= 0) {
ret = read(fh, buffer, bufferlen);
close(fh);
if (ret > 0)
return ret;
}
#endif
return 0;
}
igloo_error_t igloo_prng_auto_reseed_unlocked(igloo_prng_state_t *self, const void *instanceraw, size_t instancelen, igloo_ro_t instance)
{
igloo_prng_buffer_extra_t extra;
igloo_error_t error;
char buffer[32];
size_t extrabytes;
buffer_extra_init(&extra, instanceraw);
self->callcount++;
extrabytes = igloo_prng_auto_reseed_unlocked_urandom_buffer(buffer, sizeof(buffer));
if (self->state & STATE_AUTO_SEED_0) {
error = digest_instance_extra_once(instanceraw, instancelen, instance, &extra, buffer, extrabytes, self->auto_seed0);
} else {
error = digest_instance_extra_once(instanceraw, instancelen, instance, &extra, buffer, extrabytes, self->auto_seed1);
}
if (error != igloo_ERROR_NONE) {
pthread_mutex_unlock(&(self->lock));
return error;
}
self->state ^= STATE_AUTO_SEED_0;
self->bits += 3 + (extrabytes * 8 / 2);
return igloo_ERROR_NONE;
}
igloo_error_t igloo_prng_auto_reseed(igloo_ro_t instance, igloo_prng_flags_t flags)
{
igloo_prng_state_t *self;
size_t instancelen;
igloo_error_t error = get_inited_state(&self, &instancelen, instance);
const void *instanceraw = igloo_ro_to_type(instance, igloo_ro_stub_t);
(void)flags;
if (error != igloo_ERROR_NONE)