#include "k5-int.h"
#include <string.h>
#include <limits.h>
#ifdef _WIN32
#include "port-sockets.h"
#else
# include <unistd.h>
# include <netinet/in.h>
#endif
#if !defined(YARROW_NO_MATHLIB)
#include <math.h>
#endif
#define YARROW_IMPL
#include "yarrow.h"
#include "yhash.h"
#include "ycipher.h"
#include "ylock.h"
#include "ystate.h"
#include "yexcep.h"
#if defined( YARROW_DEBUG ) || defined( YARROW_TRACE )
# include <stdio.h>
#endif
#if defined( YARROW_TRACE )
extern int yarrow_verbose;
#define TRACE( x ) do { if (yarrow_verbose) { x } } while (0)
#else
#define TRACE( x )
#endif
#if defined(macintosh)
# define make_big_endian32(x) (x)
#else
# define make_big_endian32(x) htonl(x)
#endif
#if defined( YARROW_DEBUG )
static void hex_print(FILE* f, const char* var, void* data, size_t size);
#endif
static void block_increment( void* block, const int sz );
#if defined( YARROW_SAVE_STATE )
static int Yarrow_Load_State( Yarrow_CTX *y );
static int Yarrow_Save_State( Yarrow_CTX *y );
#endif
static int yarrow_gate_locked(Yarrow_CTX* y);
static const byte zero_block[CIPHER_BLOCK_SIZE] = { 0, };
static const char* const yarrow_str_error[] = {
"ok",
"failed",
"failed: uninitialized",
"failed: already initialized",
"failed: no driver",
"failed: can't open driver",
"failed: invalid source id",
"failed: no more source ids available",
"failed: invalid argument",
"failed: insufficient privileges",
"failed: out of memory",
"failed: resource exhausted",
"failed: not enough entropy to generate output",
"failed: locking error",
"failed: no state to load",
"failed: state load or save failed",
"failed: not implemented"
};
static void krb5int_yarrow_init_Limits(Yarrow_CTX* y)
{
double tmp1, tmp2, limit;
tmp1 = POW_CIPHER_BLOCK_SIZE / y->Pg;
tmp2 = POW_CIPHER_KEY_SIZE;
limit = min(tmp1, tmp2);
if (limit < COUNTER_MAX)
{
y->gates_limit = limit;
}
else
{
y->gates_limit = COUNTER_MAX;
}
}
static int yarrow_reseed_locked( Yarrow_CTX* y, int pool );
#ifdef YARROW_DETECT_FORK
static int
yarrow_input_locked( Yarrow_CTX* y, unsigned source_id,
const void *sample,
size_t size, size_t entropy_bits );
static int Yarrow_detect_fork(Yarrow_CTX *y)
{
pid_t newpid;
EXCEP_DECL;
newpid = getpid();
if ( y->pid != newpid )
{
y->pid = newpid;
TRY (yarrow_input_locked (y, 0, &newpid,
sizeof (newpid), 0));
TRY (yarrow_input_locked (y, 0, &newpid,
sizeof (newpid), 0));
TRY (yarrow_reseed_locked (y, YARROW_FAST_POOL));
}
CATCH:
EXCEP_RET;
}
#else
#define Yarrow_detect_fork(x) (YARROW_OK)
#endif
static void Yarrow_Make_Seeded( Yarrow_CTX* y )
{
TRACE( printf( "SEEDED," ); );
y->seeded = 1;
y->slow_thresh = YARROW_SLOW_THRESH;
y->fast_thresh = YARROW_FAST_THRESH;
y->slow_k_of_n_thresh = YARROW_K_OF_N_THRESH;
}
YARROW_DLL
int krb5int_yarrow_init(Yarrow_CTX* y, const char *filename)
{
EXCEP_DECL;
int locked = 0;
if (!y) { THROW( YARROW_BAD_ARG ); }
TRY( LOCK() );
locked = 1;
y->seeded = 0;
y->saved = 0;
#if defined( YARROW_DETECT_FORK )
y->pid = getpid();
#endif
y->entropyfile = filename;
y->num_sources = 0;
mem_zero(y->C, sizeof(y->C));
HASH_Init(&y->pool[YARROW_FAST_POOL]);
HASH_Init(&y->pool[YARROW_SLOW_POOL]);
mem_zero(y->K, sizeof(y->K));
mem_zero(&y->cipher, sizeof(y->cipher));
TRY (krb5int_yarrow_cipher_init(&y->cipher, y->K));
y->out_left = 0;
y->out_count = 0;
y->gate_count = 0;
y->Pg = YARROW_OUTPUTS_PER_GATE;
y->Pt[YARROW_FAST_POOL] = YARROW_FAST_PT;
y->Pt[YARROW_SLOW_POOL] = YARROW_SLOW_PT;
y->slow_k_of_n = 0;
y->slow_thresh = YARROW_SLOW_INIT_THRESH;
y->fast_thresh = YARROW_FAST_INIT_THRESH;
y->slow_k_of_n_thresh = YARROW_K_OF_N_INIT_THRESH;
krb5int_yarrow_init_Limits(y);
#if defined( YARROW_SAVE_STATE )
if ( y->entropyfile != NULL )
{
int ret = Yarrow_Load_State( y );
if ( ret != YARROW_OK && ret != YARROW_NO_STATE )
{
THROW( ret );
}
if ( ret == YARROW_OK && !y->saved )
{
TRY( Yarrow_Save_State( y ) );
}
}
#endif
if ( !y->seeded )
{
THROW( YARROW_NOT_SEEDED );
}
CATCH:
if ( locked ) { TRY( UNLOCK() ); }
EXCEP_RET;
}
static
int yarrow_input_maybe_locking( Yarrow_CTX* y, unsigned source_id,
const void* sample,
size_t size, size_t entropy_bits,
int do_lock )
{
EXCEP_DECL;
int ret;
int locked = 0;
Source* source;
size_t new_entropy;
size_t estimate;
if (do_lock) {
TRY( LOCK() );
locked = 1;
}
k5_assert_locked(&krb5int_yarrow_lock);
if (!y) { THROW( YARROW_BAD_ARG ); }
if (source_id >= y->num_sources) { THROW( YARROW_BAD_SOURCE ); }
source = &y->source[source_id];
if(source->pool != YARROW_FAST_POOL && source->pool != YARROW_SLOW_POOL)
{
THROW( YARROW_BAD_SOURCE );
}
HASH_Update(&y->pool[source->pool], (const void*)sample, size);
if ( (source->pool == YARROW_FAST_POOL &&
source->entropy[source->pool] < y->fast_thresh) ||
(source->pool == YARROW_SLOW_POOL &&
source->entropy[source->pool] < y->slow_thresh) )
{
new_entropy = min(entropy_bits, size * 8 * YARROW_ENTROPY_MULTIPLIER);
if (source->estimator)
{
estimate = source->estimator(sample, size);
new_entropy = min(new_entropy, estimate);
}
source->entropy[source->pool] += new_entropy;
if ( source->entropy[source->pool] > YARROW_POOL_SIZE )
{
source->entropy[source->pool] = YARROW_POOL_SIZE;
}
if (source->pool == YARROW_FAST_POOL)
{
if (source->entropy[YARROW_FAST_POOL] >= y->fast_thresh)
{
ret = yarrow_reseed_locked(y, YARROW_FAST_POOL);
if ( ret != YARROW_OK && ret != YARROW_NOT_SEEDED )
{
THROW( ret );
}
}
}
else
{
if (!source->reached_slow_thresh &&
source->entropy[YARROW_SLOW_POOL] >= y->slow_thresh)
{
source->reached_slow_thresh = 1;
y->slow_k_of_n++;
if (y->slow_k_of_n >= y->slow_k_of_n_thresh)
{
y->slow_k_of_n = 0;
ret = yarrow_reseed_locked(y, YARROW_SLOW_POOL);
if ( ret != YARROW_OK && ret != YARROW_NOT_SEEDED )
{
THROW( ret );
}
}
}
}
}
source->pool = (source->pool + 1) % 2;
CATCH:
if ( locked ) { TRY( UNLOCK() ); }
EXCEP_RET;
}
YARROW_DLL
int krb5int_yarrow_input( Yarrow_CTX* y, unsigned source_id,
const void* sample,
size_t size, size_t entropy_bits )
{
return yarrow_input_maybe_locking(y, source_id, sample, size,
entropy_bits, 1);
}
static int
yarrow_input_locked( Yarrow_CTX* y, unsigned source_id,
const void *sample,
size_t size, size_t entropy_bits )
{
return yarrow_input_maybe_locking(y, source_id, sample, size,
entropy_bits, 0);
}
YARROW_DLL
int krb5int_yarrow_new_source(Yarrow_CTX* y, unsigned* source_id)
{
EXCEP_DECL;
int locked = 0;
Source* source;
if (!y) { THROW( YARROW_BAD_ARG ); }
TRY( LOCK() );
locked = 1;
if (y->num_sources + 1 > YARROW_MAX_SOURCES)
{
THROW( YARROW_TOO_MANY_SOURCES );
}
*source_id = y->num_sources;
source = &y->source[*source_id];
source->pool = YARROW_FAST_POOL;
source->entropy[YARROW_FAST_POOL] = 0;
source->entropy[YARROW_SLOW_POOL] = 0;
source->reached_slow_thresh = 0;
source->estimator = 0;
y->num_sources++;
CATCH:
if ( locked ) { TRY( UNLOCK() ); }
EXCEP_RET;
}
int krb5int_yarrow_register_source_estimator(Yarrow_CTX* y, unsigned source_id,
estimator_fn* fptr)
{
EXCEP_DECL;
Source* source;
if (!y) { THROW( YARROW_BAD_ARG ); }
if (source_id >= y->num_sources) { THROW( YARROW_BAD_SOURCE ); }
source = &y->source[source_id];
source->estimator = fptr;
CATCH:
EXCEP_RET;
}
static int krb5int_yarrow_output_Block( Yarrow_CTX* y, void* out )
{
EXCEP_DECL;
if (!y || !out) { THROW( YARROW_BAD_ARG ); }
TRACE( printf( "OUT," ); );
y->out_count++;
if (y->out_count >= y->Pg)
{
y->out_count = 0;
TRY( yarrow_gate_locked( y ) );
y->gate_count++;
if ( y->gate_count >= y->gates_limit )
{
y->gate_count = 0;
TRACE( printf( "OUTPUT LIMIT REACHED," ); );
TRY( yarrow_reseed_locked( y, YARROW_SLOW_POOL ) );
}
}
block_increment( y->C, CIPHER_BLOCK_SIZE );
TRY ( krb5int_yarrow_cipher_encrypt_block ( &y->cipher, y->C, out ));
#if defined(YARROW_DEBUG)
printf("===\n");
hex_print( stdout, "output: C", y->C, CIPHER_BLOCK_SIZE );
hex_print( stdout, "output: K", y->K, CIPHER_KEY_SIZE );
hex_print( stdout, "output: O", out, CIPHER_BLOCK_SIZE );
#endif
CATCH:
EXCEP_RET;
}
YARROW_DLL
int krb5int_yarrow_status( Yarrow_CTX* y, int *num_sources, unsigned *source_id,
size_t *entropy_bits, size_t *entropy_max )
{
EXCEP_DECL;
int num = y->slow_k_of_n_thresh;
int source = -1;
int emax = y->slow_thresh;
size_t entropy = 0;
unsigned i;
if (!y) { THROW( YARROW_BAD_ARG ); }
TRY( Yarrow_detect_fork( y ) );
if (num_sources) { *num_sources = num; }
if (source_id) { *source_id = -1; }
if (entropy_bits) { *entropy_bits = 0; }
if (entropy_max) { *entropy_max = emax; }
if (y->seeded)
{
if (num_sources) { *num_sources = 0; }
if (entropy_bits) { *entropy_bits = emax; }
THROW( YARROW_OK );
}
for (i = 0; i < y->num_sources; i++)
{
if (y->source[i].entropy[YARROW_SLOW_POOL] >= y->slow_thresh)
{
num--;
}
else if (y->source[i].entropy[YARROW_SLOW_POOL] > entropy)
{
source = i;
entropy = y->source[i].entropy[YARROW_SLOW_POOL];
}
}
if (num_sources) { *num_sources = num; }
if (source_id) { *source_id = source; }
if (entropy_bits) { *entropy_bits = entropy; }
THROW( YARROW_NOT_SEEDED );
CATCH:
EXCEP_RET;
}
static int yarrow_output_locked(Yarrow_CTX*, void*, size_t);
YARROW_DLL
int krb5int_yarrow_output( Yarrow_CTX* y, void* out, size_t size )
{
EXCEP_DECL;
TRY( LOCK() );
TRY( yarrow_output_locked(y, out, size));
CATCH:
UNLOCK();
EXCEP_RET;
}
static
int yarrow_output_locked( Yarrow_CTX* y, void* out, size_t size )
{
EXCEP_DECL;
size_t left;
char* outp;
size_t use;
if (!y || !out) { THROW( YARROW_BAD_ARG ); }
TRY( Yarrow_detect_fork( y ) );
if (!y->seeded) { THROW( YARROW_NOT_SEEDED ); }
left = size;
outp = out;
if (y->out_left > 0)
{
use = min(left, y->out_left);
mem_copy(outp, y->out + CIPHER_BLOCK_SIZE - y->out_left, use);
left -= use;
y->out_left -= use;
outp += use;
}
for ( ;
left >= CIPHER_BLOCK_SIZE;
left -= CIPHER_BLOCK_SIZE, outp += CIPHER_BLOCK_SIZE)
{
TRY( krb5int_yarrow_output_Block(y, outp) );
}
if (left > 0)
{
TRY( krb5int_yarrow_output_Block(y, y->out) );
mem_copy(outp, y->out, left);
y->out_left = CIPHER_BLOCK_SIZE - left;
}
CATCH:
EXCEP_RET;
}
static int yarrow_gate_locked(Yarrow_CTX* y)
{
EXCEP_DECL;
byte new_K[CIPHER_KEY_SIZE];
if (!y) { THROW( YARROW_BAD_ARG ); }
TRACE( printf( "GATE[" ); );
TRY( yarrow_output_locked(y, new_K, CIPHER_KEY_SIZE) );
mem_copy(y->K, new_K, CIPHER_KEY_SIZE);
TRY (krb5int_yarrow_cipher_init(&y->cipher, y->K));
CATCH:
TRACE( printf( "]," ); );
mem_zero(new_K, sizeof(new_K));
EXCEP_RET;
}
int krb5int_yarrow_gate(Yarrow_CTX* y)
{
EXCEP_DECL;
byte new_K[CIPHER_KEY_SIZE];
if (!y) { THROW( YARROW_BAD_ARG ); }
TRACE( printf( "GATE[" ); );
TRY( krb5int_yarrow_output(y, new_K, CIPHER_KEY_SIZE) );
mem_copy(y->K, new_K, CIPHER_KEY_SIZE);
TRY (krb5int_yarrow_cipher_init(&y->cipher, y->K));
CATCH:
TRACE( printf( "]," ); );
mem_zero(new_K, sizeof(new_K));
EXCEP_RET;
}
#if defined( YARROW_SAVE_STATE )
static int Yarrow_Load_State( Yarrow_CTX *y )
{
EXCEP_DECL;
Yarrow_STATE state;
if ( !y ) { THROW( YARROW_BAD_ARG ); }
if ( y->entropyfile )
{
TRY( STATE_Load(y->entropyfile, &state) );
TRACE( printf( "LOAD STATE," ); );
#if defined( YARROW_DEBUG )
hex_print( stderr, "state.load", state.seed, sizeof(state.seed));
#endif
HASH_Update( &y->pool[YARROW_FAST_POOL],
state.seed, sizeof(state.seed) );
Yarrow_Make_Seeded( y );
TRY( krb5int_yarrow_reseed(y, YARROW_FAST_POOL) );
}
CATCH:
mem_zero(state.seed, sizeof(state.seed));
EXCEP_RET;
}
static int Yarrow_Save_State( Yarrow_CTX *y )
{
EXCEP_DECL;
Yarrow_STATE state;
if ( !y ) { THROW( YARROW_BAD_ARG ); }
if ( y->entropyfile && y->seeded )
{
TRACE( printf( "SAVE STATE[" ); );
TRY( krb5int_yarrow_output( y, state.seed, sizeof(state.seed) ) );
TRY( STATE_Save(y->entropyfile, &state) );
}
y->saved = 1;
# if defined(YARROW_DEBUG)
hex_print(stdout, "state.save", state.seed, sizeof(state.seed));
# endif
CATCH:
TRACE( printf( "]," ); );
mem_zero(state.seed, sizeof(state.seed));
EXCEP_RET;
}
#endif
static int yarrow_reseed_locked(Yarrow_CTX* y, int pool)
{
EXCEP_DECL;
HASH_CTX* fast_pool;
HASH_CTX* slow_pool;
byte digest[HASH_DIGEST_SIZE];
HASH_CTX hash;
byte v_0[HASH_DIGEST_SIZE];
byte v_i[HASH_DIGEST_SIZE];
krb5_ui_4 big_endian_int32;
COUNTER i;
k5_assert_locked(&krb5int_yarrow_lock);
if (!y) { THROW( YARROW_BAD_ARG ); }
fast_pool = &y->pool[YARROW_FAST_POOL];
slow_pool = &y->pool[YARROW_SLOW_POOL];
if( pool != YARROW_FAST_POOL && pool != YARROW_SLOW_POOL )
{
THROW( YARROW_BAD_ARG );
}
TRACE( printf( "%s RESEED,",
pool == YARROW_SLOW_POOL ? "SLOW" : "FAST" ); );
if (pool == YARROW_SLOW_POOL)
{
HASH_Final(slow_pool, digest);
HASH_Init(slow_pool);
HASH_Update(fast_pool, digest, sizeof(digest));
if (y->seeded == 0)
{
Yarrow_Make_Seeded( y );
}
}
HASH_Final(fast_pool, &v_0);
HASH_Init(fast_pool);
mem_copy( v_i, v_0, sizeof(v_0) );
for ( i = 0; i < y->Pt[pool]; i++ )
{
HASH_Init(&hash);
HASH_Update(&hash, v_i, sizeof(v_i));
HASH_Update(&hash, v_0, sizeof(v_0));
big_endian_int32 = make_big_endian32(0);
HASH_Update(&hash, &big_endian_int32, sizeof(krb5_ui_4));
big_endian_int32 = make_big_endian32(i & 0xFFFFFFFF);
HASH_Update(&hash, &big_endian_int32, sizeof(krb5_ui_4));
HASH_Final(&hash, &v_i);
}
HASH_Init(&hash);
HASH_Update(&hash, v_i, sizeof(v_i));
HASH_Update(&hash, y->K, sizeof(y->K));
HASH_Final(&hash, v_i);
#if defined(YARROW_DEBUG)
hex_print(stdout, "old K", y->K, sizeof(y->K));
#endif
TRY( krb5int_yarrow_stretch(v_i, HASH_DIGEST_SIZE, y->K, CIPHER_KEY_SIZE) );
TRY(krb5int_yarrow_cipher_init(&y->cipher, y->K));
#if defined(YARROW_DEBUG)
hex_print(stdout, "new K", y->K, sizeof(y->K));
#endif
#if defined(YARROW_DEBUG)
hex_print(stdout, "old C", y->C, sizeof(y->C));
#endif
TRY (krb5int_yarrow_cipher_encrypt_block (&y->cipher, zero_block, y->C));
#if defined(YARROW_DEBUG)
hex_print(stdout, "new C", y->C, sizeof(y->C));
#endif
y->out_left = 0;
for (i = 0; i < y->num_sources; i++)
{
y->source[i].entropy[pool] = 0;
if (pool == YARROW_SLOW_POOL)
{
y->source[i].entropy[YARROW_FAST_POOL] = 0;
y->source[i].reached_slow_thresh = 0;
}
}
#if defined( YARROW_SAVE_STATE )
if ( y->seeded && y->entropyfile )
{
TRY( Yarrow_Save_State( y ) );
}
#endif
CATCH:
mem_zero( digest, sizeof(digest) );
mem_zero( &hash, sizeof(hash) );
mem_zero( v_0, sizeof(v_0) );
mem_zero( v_i, sizeof(v_i) );
EXCEP_RET;
}
int krb5int_yarrow_reseed(Yarrow_CTX* y, int pool)
{
int r;
LOCK();
r = yarrow_reseed_locked(y, pool);
UNLOCK();
return r;
}
int krb5int_yarrow_stretch(const byte* m, size_t size, byte* out, size_t out_size)
{
EXCEP_DECL;
const byte* s_i;
byte* outp;
int left;
unsigned int use;
HASH_CTX hash, save;
byte digest[HASH_DIGEST_SIZE];
if (m == NULL || size == 0 || out == NULL || out_size == 0)
{
THROW( YARROW_BAD_ARG );
}
outp = out;
left = out_size;
use = min(out_size, size);
mem_copy(outp, m, use);
s_i = (const byte*)m;
outp += use;
left -= use;
HASH_Init(&hash);
for ( ;
left > 0;
left -= HASH_DIGEST_SIZE)
{
HASH_Update(&hash, s_i, use);
mem_copy(&save, &hash, sizeof(hash));
HASH_Final(&hash, digest);
use = min(HASH_DIGEST_SIZE, left);
mem_copy(outp, digest, use);
mem_copy(&hash, &save, sizeof(hash));
s_i = outp;
outp += use;
}
CATCH:
mem_zero(&hash, sizeof(hash));
mem_zero(digest, sizeof(digest));
EXCEP_RET;
}
static void block_increment(void* block, const int sz)
{
byte* b = block;
int i;
for (i = sz-1; (++b[i]) == 0 && i > 0; i--)
{
;
}
}
YARROW_DLL
int krb5int_yarrow_final(Yarrow_CTX* y)
{
EXCEP_DECL;
int locked = 0;
if (!y) { THROW( YARROW_BAD_ARG ); }
TRY( LOCK() );
locked = 1;
#if defined( YARROW_SAVE_STATE )
if ( y->seeded && y->entropyfile )
{
TRY( Yarrow_Save_State( y ) );
}
#endif
CATCH:
krb5int_yarrow_cipher_final(&y->cipher);
mem_zero( y, sizeof(Yarrow_CTX) );
if ( locked ) { TRY( UNLOCK() ); }
EXCEP_RET;
}
YARROW_DLL
const char* krb5int_yarrow_str_error( int err )
{
err = 1-err;
if ( err < 0 || err >= sizeof( yarrow_str_error ) / sizeof( char* ) )
{
err = 1-YARROW_FAIL;
}
return yarrow_str_error[ err ];
}
#if defined(YARROW_DEBUG)
static void hex_print(FILE* f, const char* var, void* data, size_t size)
{
const char* conv = "0123456789abcdef";
size_t i;
char* p = (char*) data;
char c, d;
fprintf(f, var);
fprintf(f, " = ");
for (i = 0; i < size; i++)
{
c = conv[(p[i] >> 4) & 0xf];
d = conv[p[i] & 0xf];
fprintf(f, "%c%c", c, d);
}
fprintf(f, "\n");
}
#endif