2003-08-13 05:53:07 +04:00
/*
Unix SMB / CIFS implementation .
Functions to create reasonable random numbers for crypto use .
Copyright ( C ) Jeremy Allison 2001
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
2007-07-10 06:07:03 +04:00
the Free Software Foundation ; either version 3 of the License , or
2003-08-13 05:53:07 +04:00
( at your option ) any later version .
This program 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 General Public License for more details .
You should have received a copy of the GNU General Public License
2007-07-10 06:07:03 +04:00
along with this program . If not , see < http : //www.gnu.org/licenses/>.
2003-08-13 05:53:07 +04:00
*/
# include "includes.h"
2005-02-10 08:09:35 +03:00
# include "system/filesys.h"
2008-09-24 17:30:23 +04:00
# include "../lib/crypto/crypto.h"
2006-05-13 23:14:12 +04:00
# include "system/locale.h"
2003-08-13 05:53:07 +04:00
2006-02-28 16:12:39 +03:00
/**
* @ file
* @ brief Random number generation
*/
2005-01-11 01:56:51 +03:00
static unsigned char hash [ 258 ] ;
2005-02-10 08:09:35 +03:00
static uint32_t counter ;
2004-07-14 16:14:07 +04:00
2007-08-27 21:21:16 +04:00
static bool done_reseed = false ;
2007-10-16 03:27:15 +04:00
static unsigned int bytes_since_reseed = 0 ;
static int urand_fd = - 1 ;
2008-04-01 17:17:18 +04:00
static void ( * reseed_callback ) ( void * userdata , int * newseed ) ;
static void * reseed_callback_userdata = NULL ;
2003-08-13 05:53:07 +04:00
2006-02-28 16:12:39 +03:00
/**
2003-08-13 05:53:07 +04:00
Copy any user given reseed data .
2006-02-28 16:12:39 +03:00
* */
2003-08-13 05:53:07 +04:00
2008-04-01 17:17:18 +04:00
_PUBLIC_ void set_rand_reseed_callback ( void ( * fn ) ( void * , int * ) , void * userdata )
2004-07-14 16:14:07 +04:00
{
reseed_callback = fn ;
2008-04-01 17:17:18 +04:00
reseed_callback_userdata = userdata ;
2004-07-14 16:14:07 +04:00
set_need_random_reseed ( ) ;
}
2006-03-06 03:24:51 +03:00
/**
* Tell the random number generator it needs to reseed .
*/
2006-03-05 20:15:19 +03:00
_PUBLIC_ void set_need_random_reseed ( void )
2003-08-13 05:53:07 +04:00
{
2007-08-27 21:21:16 +04:00
done_reseed = false ;
2007-10-16 03:27:15 +04:00
bytes_since_reseed = 0 ;
2004-07-14 16:14:07 +04:00
}
2003-08-13 05:53:07 +04:00
2004-07-14 16:14:07 +04:00
static void get_rand_reseed_data ( int * reseed_data )
{
if ( reseed_callback ) {
2008-04-01 17:17:18 +04:00
reseed_callback ( reseed_callback_userdata , reseed_data ) ;
2004-07-14 16:14:07 +04:00
} else {
* reseed_data = 0 ;
}
2003-08-13 05:53:07 +04:00
}
2005-01-11 01:56:51 +03:00
/****************************************************************
Setup the seed .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void seed_random_stream ( unsigned char * seedval , size_t seedlen )
{
unsigned char j = 0 ;
size_t ind ;
for ( ind = 0 ; ind < 256 ; ind + + )
hash [ ind ] = ( unsigned char ) ind ;
for ( ind = 0 ; ind < 256 ; ind + + ) {
unsigned char tc ;
j + = ( hash [ ind ] + seedval [ ind % seedlen ] ) ;
tc = hash [ ind ] ;
hash [ ind ] = hash [ j ] ;
hash [ j ] = tc ;
}
hash [ 256 ] = 0 ;
hash [ 257 ] = 0 ;
}
2003-08-13 05:53:07 +04:00
/****************************************************************
Get datasize bytes worth of random data .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2005-01-11 01:56:51 +03:00
static void get_random_stream ( unsigned char * data , size_t datasize )
2003-08-13 05:53:07 +04:00
{
2005-01-11 01:56:51 +03:00
unsigned char index_i = hash [ 256 ] ;
unsigned char index_j = hash [ 257 ] ;
size_t ind ;
for ( ind = 0 ; ind < datasize ; ind + + ) {
unsigned char tc ;
unsigned char t ;
index_i + + ;
index_j + = hash [ index_i ] ;
tc = hash [ index_i ] ;
hash [ index_i ] = hash [ index_j ] ;
hash [ index_j ] = tc ;
t = hash [ index_i ] + hash [ index_j ] ;
data [ ind ] = hash [ t ] ;
}
hash [ 256 ] = index_i ;
hash [ 257 ] = index_j ;
2003-08-13 05:53:07 +04:00
}
/****************************************************************
2005-05-16 03:20:58 +04:00
Get a 16 byte hash from the contents of a file .
Note that the hash is initialised , because the extra entropy is not
worth the valgrind pain .
2003-08-13 05:53:07 +04:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2004-07-14 16:14:07 +04:00
static void do_filehash ( const char * fname , unsigned char * the_hash )
2003-08-13 05:53:07 +04:00
{
2004-07-14 16:14:07 +04:00
unsigned char buf [ 1011 ] ; /* deliberate weird size */
unsigned char tmp_md4 [ 16 ] ;
2003-08-13 05:53:07 +04:00
int fd , n ;
2005-05-16 03:20:58 +04:00
ZERO_STRUCT ( tmp_md4 ) ;
2004-11-01 23:21:54 +03:00
fd = open ( fname , O_RDONLY , 0 ) ;
2003-08-13 05:53:07 +04:00
if ( fd = = - 1 )
return ;
while ( ( n = read ( fd , ( char * ) buf , sizeof ( buf ) ) ) > 0 ) {
mdfour ( tmp_md4 , buf , n ) ;
for ( n = 0 ; n < 16 ; n + + )
the_hash [ n ] ^ = tmp_md4 [ n ] ;
}
close ( fd ) ;
}
/**************************************************************
Try and get a good random number seed . Try a number of
different factors . Firstly , try / dev / urandom - use if exists .
We use / dev / urandom as a read of / dev / random can block if
the entropy pool dries up . This leads clients to timeout
or be very slow on connect .
If we can ' t use / dev / urandom then seed the stream random generator
above . . .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2007-08-27 21:21:16 +04:00
static int do_reseed ( bool use_fd , int fd )
2003-08-13 05:53:07 +04:00
{
2004-07-14 16:14:07 +04:00
unsigned char seed_inbuf [ 40 ] ;
2005-02-10 08:09:35 +03:00
uint32_t v1 , v2 ; struct timeval tval ; pid_t mypid ;
2004-07-14 16:14:07 +04:00
int reseed_data = 0 ;
2003-08-13 05:53:07 +04:00
if ( use_fd ) {
2007-10-16 03:27:15 +04:00
if ( fd = = - 1 ) {
fd = open ( " /dev/urandom " , O_RDONLY , 0 ) ;
}
if ( fd ! = - 1
& & ( read ( fd , seed_inbuf , sizeof ( seed_inbuf ) ) = = sizeof ( seed_inbuf ) ) ) {
seed_random_stream ( seed_inbuf , sizeof ( seed_inbuf ) ) ;
2003-08-13 05:53:07 +04:00
return fd ;
2007-10-16 03:27:15 +04:00
}
2003-08-13 05:53:07 +04:00
}
/* Add in some secret file contents */
do_filehash ( " /etc/shadow " , & seed_inbuf [ 0 ] ) ;
/*
* Add the counter , time of day , and pid .
*/
GetTimeOfDay ( & tval ) ;
mypid = getpid ( ) ;
v1 = ( counter + + ) + mypid + tval . tv_sec ;
v2 = ( counter + + ) * mypid + tval . tv_usec ;
SIVAL ( seed_inbuf , 32 , v1 ^ IVAL ( seed_inbuf , 32 ) ) ;
SIVAL ( seed_inbuf , 36 , v2 ^ IVAL ( seed_inbuf , 36 ) ) ;
/*
* Add any user - given reseed data .
*/
2004-07-14 16:14:07 +04:00
get_rand_reseed_data ( & reseed_data ) ;
2003-08-13 05:53:07 +04:00
if ( reseed_data ) {
size_t i ;
for ( i = 0 ; i < sizeof ( seed_inbuf ) ; i + + )
2004-07-14 16:14:07 +04:00
seed_inbuf [ i ] ^ = ( ( char * ) ( & reseed_data ) ) [ i % sizeof ( reseed_data ) ] ;
2003-08-13 05:53:07 +04:00
}
2005-01-11 01:56:51 +03:00
seed_random_stream ( seed_inbuf , sizeof ( seed_inbuf ) ) ;
2003-08-13 05:53:07 +04:00
return - 1 ;
}
2006-02-28 16:12:39 +03:00
/**
2003-08-13 05:53:07 +04:00
Interface to the ( hopefully ) good crypto random number generator .
2007-10-16 03:27:15 +04:00
Will use our internal PRNG if more than 40 bytes of random generation
has been requested , otherwise tries to read from / dev / random
2006-02-28 16:12:39 +03:00
* */
2006-03-05 20:15:19 +03:00
_PUBLIC_ void generate_random_buffer ( uint8_t * out , int len )
2003-08-13 05:53:07 +04:00
{
2004-07-14 16:14:07 +04:00
unsigned char md4_buf [ 64 ] ;
unsigned char tmp_buf [ 16 ] ;
unsigned char * p ;
2003-08-13 05:53:07 +04:00
2004-07-14 16:14:07 +04:00
if ( ! done_reseed ) {
2007-10-16 03:27:15 +04:00
bytes_since_reseed + = len ;
/* Magic constant to try and avoid reading 40 bytes
* and setting up the PRNG if the app only ever wants
* a few bytes */
if ( bytes_since_reseed < 40 ) {
if ( urand_fd = = - 1 ) {
urand_fd = open ( " /dev/urandom " , O_RDONLY , 0 ) ;
}
if ( urand_fd ! = - 1 & & ( read ( urand_fd , out , len ) = = len ) ) {
return ;
}
}
2003-08-13 05:53:07 +04:00
2007-10-16 03:27:15 +04:00
urand_fd = do_reseed ( true , urand_fd ) ;
2007-08-27 21:21:16 +04:00
done_reseed = true ;
2003-08-13 05:53:07 +04:00
}
/*
* Generate random numbers in chunks of 64 bytes ,
* then md4 them & copy to the output buffer .
* This way the raw state of the stream is never externally
* seen .
*/
p = out ;
while ( len > 0 ) {
int copy_len = len > 16 ? 16 : len ;
2005-01-11 01:56:51 +03:00
get_random_stream ( md4_buf , sizeof ( md4_buf ) ) ;
2003-08-13 05:53:07 +04:00
mdfour ( tmp_buf , md4_buf , sizeof ( md4_buf ) ) ;
memcpy ( p , tmp_buf , copy_len ) ;
p + = copy_len ;
len - = copy_len ;
}
}
2007-10-16 03:27:15 +04:00
/**
Interface to the ( hopefully ) good crypto random number generator .
Will always use / dev / urandom if available .
* */
_PUBLIC_ void generate_secret_buffer ( uint8_t * out , int len )
{
if ( urand_fd = = - 1 ) {
urand_fd = open ( " /dev/urandom " , O_RDONLY , 0 ) ;
}
if ( urand_fd ! = - 1 & & ( read ( urand_fd , out , len ) = = len ) ) {
return ;
}
generate_random_buffer ( out , len ) ;
}
2006-02-28 16:12:39 +03:00
/**
2005-01-21 16:13:24 +03:00
generate a single random uint32_t
2006-02-28 16:12:39 +03:00
* */
2006-03-05 20:15:19 +03:00
_PUBLIC_ uint32_t generate_random ( void )
2005-01-21 16:13:24 +03:00
{
uint8_t v [ 4 ] ;
generate_random_buffer ( v , 4 ) ;
return IVAL ( v , 0 ) ;
}
2006-02-28 16:12:39 +03:00
/**
2004-04-22 10:17:50 +04:00
very basic password quality checker
2006-02-28 16:12:39 +03:00
* */
2007-08-27 21:21:16 +04:00
_PUBLIC_ bool check_password_quality ( const char * s )
2004-04-22 10:17:50 +04:00
{
2007-08-08 06:41:12 +04:00
int has_digit = 0 , has_capital = 0 , has_lower = 0 , has_special = 0 , has_high = 0 ;
2004-04-22 10:17:50 +04:00
while ( * s ) {
2005-07-13 02:22:59 +04:00
if ( isdigit ( ( unsigned char ) * s ) ) {
2007-08-08 06:41:12 +04:00
has_digit | = 1 ;
2005-07-13 02:22:59 +04:00
} else if ( isupper ( ( unsigned char ) * s ) ) {
2007-08-08 06:41:12 +04:00
has_capital | = 1 ;
2005-07-13 02:22:59 +04:00
} else if ( islower ( ( unsigned char ) * s ) ) {
2007-08-08 06:41:12 +04:00
has_lower | = 1 ;
} else if ( isascii ( ( unsigned char ) * s ) ) {
has_special | = 1 ;
} else {
has_high + + ;
2004-04-22 10:17:50 +04:00
}
s + + ;
}
2007-08-08 06:41:12 +04:00
return ( ( has_digit + has_lower + has_capital + has_special ) > = 3
| | ( has_high > strlen ( s ) / 2 ) ) ;
2004-04-22 10:17:50 +04:00
}
2006-02-28 16:12:39 +03:00
/**
2003-08-13 05:53:07 +04:00
Use the random number generator to generate a random string .
2006-02-28 16:12:39 +03:00
* */
2003-08-13 05:53:07 +04:00
2006-03-05 20:15:19 +03:00
_PUBLIC_ char * generate_random_str_list ( TALLOC_CTX * mem_ctx , size_t len , const char * list )
2003-08-13 05:53:07 +04:00
{
size_t i ;
2004-10-26 17:16:50 +04:00
size_t list_len = strlen ( list ) ;
2003-08-13 05:53:07 +04:00
2005-01-27 10:08:20 +03:00
char * retstr = talloc_array ( mem_ctx , char , len + 1 ) ;
2004-10-26 17:16:50 +04:00
if ( ! retstr ) return NULL ;
2003-08-13 05:53:07 +04:00
2004-11-25 22:02:42 +03:00
generate_random_buffer ( ( uint8_t * ) retstr , len ) ;
2004-10-26 17:16:50 +04:00
for ( i = 0 ; i < len ; i + + ) {
retstr [ i ] = list [ retstr [ i ] % list_len ] ;
}
2003-08-13 05:53:07 +04:00
retstr [ i ] = ' \0 ' ;
2004-10-26 17:16:50 +04:00
return retstr ;
}
2006-03-06 03:24:51 +03:00
/**
* Generate a random text string consisting of the specified length .
* The returned string will be allocated .
*
* Characters used are : ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 + _ - # . ,
*/
2006-03-05 20:15:19 +03:00
_PUBLIC_ char * generate_random_str ( TALLOC_CTX * mem_ctx , size_t len )
2004-10-26 17:16:50 +04:00
{
char * retstr ;
const char * c_list = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_-#., " ;
again :
retstr = generate_random_str_list ( mem_ctx , len , c_list ) ;
if ( ! retstr ) return NULL ;
2004-04-22 10:17:50 +04:00
/* we need to make sure the random string passes basic quality tests
or it might be rejected by windows as a password */
if ( len > = 7 & & ! check_password_quality ( retstr ) ) {
2004-10-26 17:16:50 +04:00
talloc_free ( retstr ) ;
2004-04-22 10:17:50 +04:00
goto again ;
}
2004-05-15 11:51:38 +04:00
return retstr ;
2003-08-13 05:53:07 +04:00
}