diff --git a/configure.ac b/configure.ac index 75103662..736b54cb 100644 --- a/configure.ac +++ b/configure.ac @@ -90,8 +90,15 @@ AC_CHECK_LIB([uuid], [uuid_generate]) # needed to statically link libparted, PKG_CHECK_MODULES([PARTED], [libparted]) AC_CHECK_LIB([pthread], [main], ,[AC_MSG_ERROR([pthread development library not found])]) +# Überprüft die Math-Bibliothek. +AC_CHECK_LIB([m], [log2], [ + LIBS="$LIBS -lm" +], [ + AC_MSG_ERROR([Math library not found. Please install the math library (libm).]) +]) + # Checks for header files. -AC_CHECK_HEADERS([libconfig.h fcntl.h inttypes.h netinet/in.h stddef.h stdint.h stdlib.h string.h sys/file.h sys/ioctl.h unistd.h]) +AC_CHECK_HEADERS([libconfig.h fcntl.h inttypes.h netinet/in.h stddef.h stdint.h stdlib.h string.h sys/file.h sys/ioctl.h unistd.h math.h]) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_SIZE_T @@ -99,6 +106,7 @@ AC_CHECK_MEMBERS([struct stat.st_blksize]) # Checks for library functions. AC_FUNC_MALLOC -AC_CHECK_FUNCS([fdatasync memset regcomp strdup strerror]) +AC_CHECK_FUNCS([fdatasync memset regcomp strdup strerror log2 fabs]) AC_OUTPUT + diff --git a/src/aes/aes_ctr_prng.c b/src/aes/aes_ctr_prng.c index e244a0a6..8a92d51b 100644 --- a/src/aes/aes_ctr_prng.c +++ b/src/aes/aes_ctr_prng.c @@ -30,20 +30,25 @@ #include #include #include +#include typedef enum { NWIPE_LOG_NONE = 0, - NWIPE_LOG_DEBUG, // Debugging messages, detailed for troubleshooting - NWIPE_LOG_INFO, // Informative logs, for regular operation insights + NWIPE_LOG_DEBUG, // Debugging messages, detailed for troubleshooting + NWIPE_LOG_INFO, // Informative logs, for regular operation insights NWIPE_LOG_NOTICE, // Notices for significant but non-critical events - NWIPE_LOG_WARNING, // Warnings about potential errors - NWIPE_LOG_ERROR, // Error messages, significant issues that affect operation - NWIPE_LOG_FATAL, // Fatal errors, require immediate termination of the program + NWIPE_LOG_WARNING, // Warnings about potential errors + NWIPE_LOG_ERROR, // Error messages, significant issues that affect operation + NWIPE_LOG_FATAL, // Fatal errors, require immediate termination of the program NWIPE_LOG_SANITY, // Sanity checks, used primarily in debugging phases NWIPE_LOG_NOTIMESTAMP // Log entries without timestamp information } nwipe_log_t; -extern void nwipe_log( nwipe_log_t level, const char* format, ... ); +extern void nwipe_log(nwipe_log_t level, const char* format, ...); + +/* Function prototypes */ +int aes_ctr_prng_validate(aes_ctr_state_t* state); +static double calculate_shannon_entropy(const unsigned int* byte_counts, size_t data_length); /* Initializes the AES CTR pseudorandom number generator state. This function sets up the cryptographic context necessary for generating @@ -53,108 +58,256 @@ extern void nwipe_log( nwipe_log_t level, const char* format, ... ); - state: Pointer to the AES CTR PRNG state structure. - init_key: Array containing the seed for key generation. - key_length: Length of the seed array. */ -int aes_ctr_prng_init( aes_ctr_state_t* state, unsigned long init_key[], unsigned long key_length ) +int aes_ctr_prng_init(aes_ctr_state_t* state, unsigned long init_key[], unsigned long key_length) { - assert( state != NULL && init_key != NULL && key_length > 0 ); // Validate inputs + assert(state != NULL && init_key != NULL && key_length > 0); // Validate inputs unsigned char key[32]; // Storage for the 256-bit key - memset( state->ivec, 0, AES_BLOCK_SIZE ); // Clear IV buffer + memset(state->ivec, 0, AES_BLOCK_SIZE); // Clear IV buffer state->num = 0; // Reset the block counter - memset( state->ecount, 0, AES_BLOCK_SIZE ); // Clear encryption count buffer + memset(state->ecount, 0, AES_BLOCK_SIZE); // Clear encryption count buffer - nwipe_log( NWIPE_LOG_DEBUG, "Initializing AES CTR PRNG with provided seed." ); // Log initialization + nwipe_log(NWIPE_LOG_DEBUG, "Initializing AES CTR PRNG with provided seed."); // Log initialization EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); // Create new SHA-256 context - if( !mdctx ) + if (!mdctx) { - nwipe_log( NWIPE_LOG_FATAL, - "Failed to allocate EVP_MD_CTX for SHA-256, return code: %d.", - ERR_get_error() ); // Log context allocation failure + nwipe_log(NWIPE_LOG_FATAL, + "Failed to allocate EVP_MD_CTX for SHA-256, error code: %lu.", + ERR_get_error()); // Log context allocation failure return -1; // Handle error } - if( EVP_DigestInit_ex( mdctx, EVP_sha256(), NULL ) != 1 ) + if (EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) { - nwipe_log( NWIPE_LOG_FATAL, - "SHA-256 context initialization failed, return code: %d.", - ERR_get_error() ); // Log init failure + nwipe_log(NWIPE_LOG_FATAL, + "SHA-256 context initialization failed, error code: %lu.", + ERR_get_error()); // Log init failure + EVP_MD_CTX_free(mdctx); return -1; // Handle error } EVP_DigestUpdate( - mdctx, (const unsigned char*) init_key, key_length * sizeof( unsigned long ) ); // Process the seed + mdctx, (const unsigned char*)init_key, key_length * sizeof(unsigned long)); // Process the seed - if( EVP_DigestFinal_ex( mdctx, key, NULL ) != 1 ) + if (EVP_DigestFinal_ex(mdctx, key, NULL) != 1) { - nwipe_log( NWIPE_LOG_FATAL, - "SHA-256 hash finalization failed, return code: %d.", - ERR_get_error() ); // Log finalization failure + nwipe_log(NWIPE_LOG_FATAL, + "SHA-256 hash finalization failed, error code: %lu.", + ERR_get_error()); // Log finalization failure + EVP_MD_CTX_free(mdctx); return -1; // Handle error } - EVP_MD_CTX_free( mdctx ); + EVP_MD_CTX_free(mdctx); mdctx = NULL; // Clean up SHA-256 context state->ctx = EVP_CIPHER_CTX_new(); // Create new AES-256-CTR context - if( !state->ctx ) + if (!state->ctx) { - nwipe_log( NWIPE_LOG_FATAL, - "Failed to allocate EVP_CIPHER_CTX, return code: %d.", - ERR_get_error() ); // Log cipher context failure + nwipe_log(NWIPE_LOG_FATAL, + "Failed to allocate EVP_CIPHER_CTX, error code: %lu.", + ERR_get_error()); // Log cipher context failure return -1; // Handle error } - if( EVP_EncryptInit_ex( state->ctx, EVP_aes_256_ctr(), NULL, key, state->ivec ) != 1 ) + if (EVP_EncryptInit_ex(state->ctx, EVP_aes_256_ctr(), NULL, key, state->ivec) != 1) { - nwipe_log( NWIPE_LOG_FATAL, - "AES-256-CTR encryption context initialization failed, return code: %d.", - ERR_get_error() ); // Log encryption init failure + nwipe_log(NWIPE_LOG_FATAL, + "AES-256-CTR encryption context initialization failed, error code: %lu.", + ERR_get_error()); // Log encryption init failure + EVP_CIPHER_CTX_free(state->ctx); return -1; // Handle error } - nwipe_log( NWIPE_LOG_DEBUG, "AES CTR PRNG successfully initialized." ); // Log successful initialization + // Validate the pseudorandom data generation + if (aes_ctr_prng_validate(state) != 0) + { + nwipe_log(NWIPE_LOG_FATAL, "AES CTR PRNG validation failed."); + EVP_CIPHER_CTX_free(state->ctx); + return -1; // Handle validation failure + } + + nwipe_log(NWIPE_LOG_DEBUG, "AES CTR PRNG successfully initialized and validated."); // Log success return 0; // Exit successfully } + +/* Validates the pseudorandom data generation by generating 4KB of data and performing statistical tests. + This function generates 4KB of pseudorandom data and performs: + - Bit Frequency Test + - Byte Frequency Test + - Entropy Calculation + - Checks for any obvious patterns + - Thresholds are set to detect non-random behavior. + - state: Pointer to the initialized AES CTR PRNG state. + Returns 0 on success, -1 on failure. */ +int aes_ctr_prng_validate(aes_ctr_state_t* state) +{ + assert(state != NULL); + + const size_t test_data_size = 4096; // 4KB of data + unsigned char* test_buffer = malloc(test_data_size); + if (!test_buffer) + { + nwipe_log(NWIPE_LOG_ERROR, "Validation failed: Unable to allocate memory for test buffer."); + return -1; + } + memset(test_buffer, 0, test_data_size); // Zero out the buffer + int outlen = 0; // Length of data produced by encryption + int total_generated = 0; + + // Generate 4KB of pseudorandom data + while (total_generated < test_data_size) + { + int chunk_size = 512; // Generate data in chunks to avoid large allocations + if (EVP_EncryptUpdate(state->ctx, test_buffer + total_generated, &outlen, + test_buffer + total_generated, chunk_size) != 1) + { + nwipe_log(NWIPE_LOG_ERROR, + "Failed to generate pseudorandom numbers during validation, error code: %lu.", + ERR_get_error()); // Log generation failure + free(test_buffer); + return -1; // Handle error + } + total_generated += outlen; + } + + // Bit Frequency Test + unsigned long bit_count = 0; + for (size_t i = 0; i < test_data_size; i++) + { + unsigned char byte = test_buffer[i]; + for (int j = 0; j < 8; j++) + { + if (byte & (1 << j)) + bit_count++; + } + } + + unsigned long total_bits = test_data_size * 8; + double ones_ratio = (double)bit_count / total_bits; + double zeros_ratio = 1.0 - ones_ratio; + + // Acceptable deviation from 50% + const double frequency_threshold = 0.02; // 2% + + if (fabs(ones_ratio - 0.5) > frequency_threshold) + { + nwipe_log(NWIPE_LOG_ERROR, + "Validation failed: Bit frequency test failed. Ones ratio: %.4f, Zeros ratio: %.4f", + ones_ratio, zeros_ratio); + free(test_buffer); + return -1; + } + + // Byte Frequency Test and Entropy Calculation + unsigned int byte_counts[256] = {0}; + for (size_t i = 0; i < test_data_size; i++) + { + byte_counts[test_buffer[i]]++; + } + + // Calculate Shannon entropy + double entropy = calculate_shannon_entropy(byte_counts, test_data_size); + + // Adjusted entropy threshold + const double entropy_threshold = 7.5; // Target entropy ≥ 7.5 bits per byte + if (entropy < entropy_threshold) + { + nwipe_log(NWIPE_LOG_ERROR, + "Validation failed: Entropy too low. Calculated entropy: %.4f bits per byte", + entropy); + free(test_buffer); + return -1; + } + + // Check for repeating patterns (e.g., all bytes are the same) + int is_repeating = 1; + for (size_t i = 1; i < test_data_size; i++) + { + if (test_buffer[i] != test_buffer[0]) + { + is_repeating = 0; + break; + } + } + + if (is_repeating) + { + nwipe_log(NWIPE_LOG_ERROR, "Validation failed: Generated data contains repeating patterns."); + free(test_buffer); + return -1; + } + + nwipe_log(NWIPE_LOG_DEBUG, "AES CTR PRNG validation passed. Entropy: %.4f bits per byte", entropy); + free(test_buffer); + return 0; // Validation successful +} + +/* Calculates the Shannon entropy of the data based on byte frequencies. + - byte_counts: Array of counts for each byte value (0-255). + - data_length: Total number of bytes in the data. + Returns the calculated entropy in bits per byte. */ +static double calculate_shannon_entropy(const unsigned int* byte_counts, size_t data_length) +{ + double entropy = 0.0; + for (int i = 0; i < 256; i++) + { + if (byte_counts[i] > 0) + { + double probability = (double)byte_counts[i] / data_length; + entropy -= probability * log2(probability); + } + } + return entropy; +} + /* Generates pseudorandom numbers and writes them to a buffer. This function performs the core operation of producing pseudorandom data. It directly updates the buffer provided, filling it with pseudorandom bytes generated using the AES-256-CTR mode of operation. - state: Pointer to the initialized AES CTR PRNG state. - - bufpos: Target buffer where the pseudorandom numbers will be written. */ -int aes_ctr_prng_genrand_uint256_to_buf( aes_ctr_state_t* state, unsigned char* bufpos ) + - bufpos: Target buffer where the pseudorandom numbers will be written. + Returns 0 on success, -1 on failure. */ +int aes_ctr_prng_genrand_uint256_to_buf(aes_ctr_state_t* state, unsigned char* bufpos) { - assert( state != NULL && bufpos != NULL ); // Validate inputs + assert(state != NULL && bufpos != NULL); // Validate inputs unsigned char temp_buffer[32]; // Temporary storage for pseudorandom bytes - memset( temp_buffer, 0, sizeof( temp_buffer ) ); // Zero out temporary buffer + memset(temp_buffer, 0, sizeof(temp_buffer)); // Zero out temporary buffer int outlen; // Length of data produced by encryption - if( EVP_EncryptUpdate( state->ctx, temp_buffer, &outlen, temp_buffer, sizeof( temp_buffer ) ) != 1 ) + if (EVP_EncryptUpdate(state->ctx, temp_buffer, &outlen, temp_buffer, sizeof(temp_buffer)) != 1) { - nwipe_log( NWIPE_LOG_ERROR, - "Failed to generate pseudorandom numbers, return code: %d.", - ERR_get_error() ); // Log generation failure + nwipe_log(NWIPE_LOG_ERROR, + "Failed to generate pseudorandom numbers, error code: %lu.", + ERR_get_error()); // Log generation failure return -1; // Handle error } - memcpy( bufpos, temp_buffer, sizeof( temp_buffer ) ); // Copy pseudorandom bytes to buffer + memcpy(bufpos, temp_buffer, sizeof(temp_buffer)); // Copy pseudorandom bytes to buffer return 0; // Exit successfully } -// General cleanup function for AES CTR PRNG -int aes_ctr_prng_general_cleanup( aes_ctr_state_t* state ) + +/* General cleanup function for AES CTR PRNG. + Frees allocated resources and clears sensitive data. + - state: Pointer to the AES CTR PRNG state structure. + Returns 0 on success. */ +int aes_ctr_prng_general_cleanup(aes_ctr_state_t* state) { - if( state != NULL ) + if (state != NULL) { // Free the EVP_CIPHER_CTX if it has been allocated - if( state->ctx ) + if (state->ctx) { - EVP_CIPHER_CTX_free( state->ctx ); + EVP_CIPHER_CTX_free(state->ctx); state->ctx = NULL; // Nullify the pointer after free } // Clear sensitive information from the state - memset( state->ivec, 0, AES_BLOCK_SIZE ); - memset( state->ecount, 0, AES_BLOCK_SIZE ); + memset(state->ivec, 0, AES_BLOCK_SIZE); + memset(state->ecount, 0, AES_BLOCK_SIZE); state->num = 0; } return 0; } + diff --git a/src/aes/aes_ctr_prng.h b/src/aes/aes_ctr_prng.h index a04abc0a..33ee9486 100644 --- a/src/aes/aes_ctr_prng.h +++ b/src/aes/aes_ctr_prng.h @@ -23,8 +23,8 @@ * ensuring compliance with OpenSSL's licensing terms. */ -#ifndef AES_CTR_RNG_H -#define AES_CTR_RNG_H +#ifndef AES_CTR_PRNG_H +#define AES_CTR_PRNG_H #include #include @@ -39,13 +39,40 @@ typedef struct unsigned char ecount[AES_BLOCK_SIZE]; } aes_ctr_state_t; -// Initializes the AES-CTR random number generator -int aes_ctr_prng_init( aes_ctr_state_t* state, unsigned long init_key[], unsigned long key_length ); +/** + * Initializes the AES-CTR random number generator. + * + * @param state Pointer to the AES CTR PRNG state structure. + * @param init_key Array containing the seed for key generation. + * @param key_length Length of the seed array. + * @return int Returns 0 on success, -1 on failure. + */ +int aes_ctr_prng_init(aes_ctr_state_t* state, unsigned long init_key[], unsigned long key_length); + +/** + * Generates a 256-bit random number using AES-CTR and stores it directly in the output buffer. + * + * @param state Pointer to the initialized AES CTR PRNG state. + * @param bufpos Target buffer where the pseudorandom numbers will be written. + * @return int Returns 0 on success, -1 on failure. + */ +int aes_ctr_prng_genrand_uint256_to_buf(aes_ctr_state_t* state, unsigned char* bufpos); -// Generates a 256-bit random number using AES-CTR and stores it directly in the output buffer -int aes_ctr_prng_genrand_uint256_to_buf( aes_ctr_state_t* state, unsigned char* bufpos ); +/** + * Validates the pseudorandom data generation by generating test data and performing statistical tests. + * + * @param state Pointer to the initialized AES CTR PRNG state. + * @return int Returns 0 on success, -1 on failure. + */ +int aes_ctr_prng_validate(aes_ctr_state_t* state); + +/** + * General cleanup function for AES CTR PRNG. + * + * @param state Pointer to the AES CTR PRNG state structure. + * @return int Returns 0 on success. + */ +int aes_ctr_prng_general_cleanup(aes_ctr_state_t* state); -// General cleanup function for AES CTR PRNG -int aes_ctr_prng_general_cleanup( aes_ctr_state_t* state ); +#endif // AES_CTR_PRNG_H -#endif // AES_CTR_RNG_H diff --git a/src/prng.c b/src/prng.c index 8ddaa778..74d20f67 100644 --- a/src/prng.c +++ b/src/prng.c @@ -347,18 +347,17 @@ int nwipe_xoroshiro256_prng_read( NWIPE_PRNG_READ_SIGNATURE ) } /** - * EPERIMENTAL implementation of AES-256 in counter mode to provide high-quality random numbers. - * Initializes the AES CTR PRNG state. + * Initializes the AES-256-CTR PRNG using OpenSSL and validates its output. + * * @param state A double pointer to the PRNG state structure. If the pointed state is NULL, * memory will be allocated and initialized for it. * @param seed A pointer to a seed structure containing the seed data and its length. - * @return int Returns 0 on success, -1 on failure (e.g., memory allocation failure). + * @return int Returns 0 on success, -1 on failure. */ - int nwipe_aes_ctr_prng_init( NWIPE_PRNG_INIT_SIGNATURE ) { // Log the start of the PRNG initialization process. - nwipe_log( NWIPE_LOG_DEBUG, "Initialising AES CTR PRNG" ); + nwipe_log( NWIPE_LOG_NOTICE, "Initializing AES-256-CTR PRNG" ); // Check if the state pointer is NULL, indicating that it hasn't been allocated yet. if( *state == NULL ) @@ -376,13 +375,28 @@ int nwipe_aes_ctr_prng_init( NWIPE_PRNG_INIT_SIGNATURE ) } // Initialize the PRNG state with the provided seed. - if( aes_ctr_prng_init( (aes_ctr_state_t*) *state, (uint64_t*) ( seed->s ), seed->length / sizeof( uint64_t ) ) + if( aes_ctr_prng_init( + (aes_ctr_state_t*) *state, (unsigned long*) ( seed->s ), seed->length / sizeof( unsigned long ) ) != 0 ) { - nwipe_log( NWIPE_LOG_SANITY, "Fatal error occured during PRNG init in OpenSSL." ); + nwipe_log( NWIPE_LOG_FATAL, "Fatal error occurred during PRNG initialization in OpenSSL." ); + aes_ctr_prng_general_cleanup( (aes_ctr_state_t*) *state ); + free( *state ); + *state = NULL; return -1; } + // Validate the PRNG output to ensure it is functioning correctly. + if( aes_ctr_prng_validate( (aes_ctr_state_t*) *state ) != 0 ) + { + nwipe_log( NWIPE_LOG_FATAL, "AES CTR PRNG validation failed." ); + aes_ctr_prng_general_cleanup( (aes_ctr_state_t*) *state ); + free( *state ); + *state = NULL; + return -1; + } + + nwipe_log( NWIPE_LOG_NOTICE, "AES CTR PRNG successfully initialized and validated." ); return 0; // Indicate success. } @@ -392,7 +406,7 @@ int nwipe_aes_ctr_prng_init( NWIPE_PRNG_INIT_SIGNATURE ) * @param state A double pointer to the PRNG state structure. * @param buffer A pointer to the buffer where the generated random data will be stored. * @param count The number of bytes of random data to generate. - * @return int Returns 0 on success. + * @return int Returns 0 on success, -1 on failure. */ int nwipe_aes_ctr_prng_read( NWIPE_PRNG_READ_SIGNATURE ) { @@ -408,7 +422,7 @@ int nwipe_aes_ctr_prng_read( NWIPE_PRNG_READ_SIGNATURE ) // Generate a 256-bit block and write it directly to the buffer. if( aes_ctr_prng_genrand_uint256_to_buf( (aes_ctr_state_t*) *state, bufpos ) != 0 ) { - nwipe_log( NWIPE_LOG_SANITY, "Fatal error occured during RNG generation in OpenSSL." ); + nwipe_log( NWIPE_LOG_ERROR, "Error occurred during RNG generation in OpenSSL." ); return -1; } @@ -423,11 +437,15 @@ int nwipe_aes_ctr_prng_read( NWIPE_PRNG_READ_SIGNATURE ) if( remain > 0 ) { // Temporary buffer for the last block of random data. - unsigned char temp_output[32]; + unsigned char temp_output[SIZE_OF_AES_CTR_PRNG]; memset( temp_output, 0, sizeof( temp_output ) ); // Generate one more block of random data. - aes_ctr_prng_genrand_uint256_to_buf( (aes_ctr_state_t*) *state, temp_output ); + if( aes_ctr_prng_genrand_uint256_to_buf( (aes_ctr_state_t*) *state, temp_output ) != 0 ) + { + nwipe_log( NWIPE_LOG_ERROR, "Error occurred during RNG generation in OpenSSL." ); + return -1; + } // Copy only the necessary remaining bytes to the output buffer. memcpy( bufpos, temp_output, remain );