chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,100 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#include <gtest/gtest.h>
#include <openssl/crypto.h>
#include "internal.h"
#include "../../../ube/vm_ube_detect.h"
#define MAX_MULTIPLE_FROM_RNG (16)
// In the future this test can be improved by being able to predict whether the
// test is running on hardware that we expect to support RNDR. This will require
// amending the CI with such information.
// For now, simply ensure we exercise all code-paths in the hw rng
// implementations.
TEST(EntropySourceHw, Aarch64) {
uint8_t buf[MAX_MULTIPLE_FROM_RNG*8] = { 0 } ;
#if !defined(OPENSSL_AARCH64) || defined(OPENSSL_NO_ASM)
ASSERT_FALSE(have_hw_rng_aarch64_for_testing());
ASSERT_FALSE(rndr_multiple8(buf, 0));
ASSERT_FALSE(rndr_multiple8(buf, 8));
#else
if (have_hw_rng_aarch64_for_testing() != 1) {
GTEST_SKIP() << "Compiled for Arm64, but Aarch64 hw rng is not available in run-time";
}
// Extracting 0 bytes is never supported.
ASSERT_FALSE(rndr_multiple8(buf, 0));
// Multiples of 8 allowed.
for (size_t i = 8; i < MAX_MULTIPLE_FROM_RNG; i += 8) {
ASSERT_TRUE(rndr_multiple8(buf, i));
}
// Must be multiples of 8.
for (size_t i : {1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15}) {
ASSERT_FALSE(rndr_multiple8(buf, i));
}
#endif
}
TEST(EntropySourceHw, x86_64) {
uint8_t buf[MAX_MULTIPLE_FROM_RNG*8] = { 0 } ;
#if !defined(OPENSSL_X86_64) || defined(OPENSSL_NO_ASM)
ASSERT_FALSE(have_hw_rng_x86_64_for_testing());
ASSERT_FALSE(rdrand_multiple8(buf, 0));
ASSERT_FALSE(rdrand_multiple8(buf, 8));
#else
if (have_hw_rng_x86_64_for_testing() != 1) {
GTEST_SKIP() << "Compiled for x86_64, but x86_64 hw rng is not available in run-time";
}
// Extracting 0 bytes is never supported.
ASSERT_FALSE(rdrand_multiple8(buf, 0));
// Multiples of 8 allowed.
for (size_t i = 8; i < MAX_MULTIPLE_FROM_RNG; i += 8) {
ASSERT_TRUE(rdrand_multiple8(buf, i));
}
// Must be multiples of 8.
for (size_t i : {1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15}) {
ASSERT_FALSE(rdrand_multiple8(buf, i));
}
#endif
}
TEST(EntropySources, Configuration) {
uint8_t buf[1];
ASSERT_TRUE(RAND_bytes(buf, sizeof(buf)));
// VM UBE detection is only defined for Linux. So, only strongly assert on
// that kernel.
#if defined(AWSLC_VM_UBE_TESTING) && defined(OPENSSL_LINUX)
EXPECT_EQ(OPT_OUT_CPU_JITTER_ENTROPY_SOURCE, get_entropy_source_method_id_FOR_TESTING());
// If entropy build configuration choose to explicitly opt-out of CPU Jitter
// Entropy
#elif defined(DISABLE_CPU_JITTER_ENTROPY)
EXPECT_EQ(OPT_OUT_CPU_JITTER_ENTROPY_SOURCE, get_entropy_source_method_id_FOR_TESTING());
#else
int expected_entropy_source_id = TREE_DRBG_JITTER_ENTROPY_SOURCE;
if (CRYPTO_get_vm_ube_supported()) {
expected_entropy_source_id = OPT_OUT_CPU_JITTER_ENTROPY_SOURCE;
}
EXPECT_EQ(expected_entropy_source_id, get_entropy_source_method_id_FOR_TESTING());
// For FIPS build we can strongly assert.
if (FIPS_mode() == 1 && CRYPTO_get_vm_ube_supported() != 1) {
EXPECT_NE(OPT_OUT_CPU_JITTER_ENTROPY_SOURCE, get_entropy_source_method_id_FOR_TESTING());
}
#endif
}

View File

@@ -0,0 +1,218 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#include <openssl/base.h>
#include <openssl/target.h>
#include "internal.h"
#include "../internal.h"
#include "../../delocate.h"
#include "../../../rand_extra/internal.h"
#include "../../../ube/vm_ube_detect.h"
DEFINE_BSS_GET(const struct entropy_source_methods *, entropy_source_methods_override)
DEFINE_BSS_GET(int, allow_entropy_source_methods_override)
DEFINE_STATIC_MUTEX(global_entropy_source_lock)
static int entropy_cpu_get_entropy(uint8_t *entropy, size_t entropy_len) {
#if defined(OPENSSL_X86_64)
if (rdrand_multiple8(entropy, entropy_len) == 1) {
return 1;
}
#elif defined(OPENSSL_AARCH64)
if (rndr_multiple8(entropy, entropy_len) == 1) {
return 1;
}
#endif
return 0;
}
static int entropy_cpu_get_prediction_resistance(
const struct entropy_source_t *entropy_source,
uint8_t pred_resistance[RAND_PRED_RESISTANCE_LEN]) {
return entropy_cpu_get_entropy(pred_resistance, RAND_PRED_RESISTANCE_LEN);
}
static int entropy_cpu_get_extra_entropy(
const struct entropy_source_t *entropy_source,
uint8_t extra_entropy[CTR_DRBG_ENTROPY_LEN]) {
return entropy_cpu_get_entropy(extra_entropy, CTR_DRBG_ENTROPY_LEN);
}
static int entropy_os_get_extra_entropy(
const struct entropy_source_t *entropy_source,
uint8_t extra_entropy[CTR_DRBG_ENTROPY_LEN]) {
CRYPTO_sysrand(extra_entropy, CTR_DRBG_ENTROPY_LEN);
return 1;
}
// Tree-DRBG entropy source configuration.
// - Tree DRBG with Jitter Entropy as root for seeding.
// - OS as personalization string source.
// - If run-time is on an x86_64 or Arm64 CPU and it supports rdrand
// or rndr respectively, use it as a source for prediction resistance.
// Otherwise, no source.
DEFINE_LOCAL_DATA(struct entropy_source_methods, tree_jitter_entropy_source_methods) {
out->initialize = tree_jitter_initialize;
out->zeroize_thread = tree_jitter_zeroize_thread_drbg;
out->free_thread = tree_jitter_free_thread_drbg;
out->get_seed = tree_jitter_get_seed;
out->get_extra_entropy = entropy_os_get_extra_entropy;
if (have_hw_rng_x86_64() == 1 ||
have_hw_rng_aarch64() == 1) {
out->get_prediction_resistance = entropy_cpu_get_prediction_resistance;
} else {
out->get_prediction_resistance = NULL;
}
out->id = TREE_DRBG_JITTER_ENTROPY_SOURCE;
}
static int opt_out_cpu_jitter_initialize(
struct entropy_source_t *entropy_source) {
return 1;
}
static void opt_out_cpu_jitter_zeroize_thread(struct entropy_source_t *entropy_source) {}
static void opt_out_cpu_jitter_free_thread(struct entropy_source_t *entropy_source) {}
static int opt_out_cpu_jitter_get_seed_wrap(
const struct entropy_source_t *entropy_source, uint8_t seed[CTR_DRBG_ENTROPY_LEN]) {
return vm_ube_fallback_get_seed(seed);
}
// Define conditions for not using CPU Jitter
static int is_vm_ube_environment(void) {
return CRYPTO_get_vm_ube_supported();
}
static int has_explicitly_opted_out_of_cpu_jitter(void) {
#if defined(DISABLE_CPU_JITTER_ENTROPY)
return 1;
#else
return 0;
#endif
}
static int use_opt_out_cpu_jitter_entropy(void) {
if (has_explicitly_opted_out_of_cpu_jitter() == 1 ||
is_vm_ube_environment() == 1) {
return 1;
}
return 0;
}
// Out-out CPU Jitter configurations. CPU source required for rule-of-two.
// - OS as seed source source.
// - Uses rdrand or rndr, if supported, for personalization string. Otherwise
// falls back to OS source.
DEFINE_LOCAL_DATA(struct entropy_source_methods, opt_out_cpu_jitter_entropy_source_methods) {
out->initialize = opt_out_cpu_jitter_initialize;
out->zeroize_thread = opt_out_cpu_jitter_zeroize_thread;
out->free_thread = opt_out_cpu_jitter_free_thread;
out->get_seed = opt_out_cpu_jitter_get_seed_wrap;
if (have_hw_rng_x86_64() == 1 ||
have_hw_rng_aarch64() == 1) {
out->get_extra_entropy = entropy_cpu_get_extra_entropy;
} else {
// Fall back to seed source because a second source must always be present.
out->get_extra_entropy = opt_out_cpu_jitter_get_seed_wrap;
}
out->get_prediction_resistance = NULL;
out->id = OPT_OUT_CPU_JITTER_ENTROPY_SOURCE;
}
static const struct entropy_source_methods * get_entropy_source_methods(void) {
if (*allow_entropy_source_methods_override_bss_get() == 1) {
return *entropy_source_methods_override_bss_get();
}
if (use_opt_out_cpu_jitter_entropy()) {
return opt_out_cpu_jitter_entropy_source_methods();
}
return tree_jitter_entropy_source_methods();
}
struct entropy_source_t * get_entropy_source(void) {
struct entropy_source_t *entropy_source = OPENSSL_zalloc(sizeof(struct entropy_source_t));
if (entropy_source == NULL) {
return NULL;
}
entropy_source->methods = get_entropy_source_methods();
// Make sure that the function table contains the minimal number of callbacks
// that we expect. Also make sure that the entropy source is initialized such
// that calling code can assume that.
if (entropy_source->methods == NULL ||
entropy_source->methods->zeroize_thread == NULL ||
entropy_source->methods->free_thread == NULL ||
entropy_source->methods->get_seed == NULL ||
entropy_source->methods->initialize == NULL ||
entropy_source->methods->initialize(entropy_source) != 1) {
OPENSSL_free(entropy_source);
return NULL;
}
return entropy_source;
}
int rndr_multiple8(uint8_t *buf, const size_t len) {
if (len == 0 || ((len & 0x7) != 0)) {
return 0;
}
return CRYPTO_rndr_multiple8(buf, len);
}
int have_hw_rng_aarch64_for_testing(void) {
return have_hw_rng_aarch64();
}
// rdrand maximum retries as suggested by:
// Intel® Digital Random Number Generator (DRNG) Software Implementation Guide
// Revision 2.1
// https://software.intel.com/content/www/us/en/develop/articles/intel-digital-random-number-generator-drng-software-implementation-guide.html
#define RDRAND_MAX_RETRIES 10
OPENSSL_STATIC_ASSERT(RDRAND_MAX_RETRIES > 0, rdrand_max_retries_must_be_positive)
// rdrand_multiple8 should only be called if |have_hw_rng_x86_64| returned true.
int rdrand_multiple8(uint8_t *buf, size_t len) {
if (len == 0 || ((len & 0x7) != 0)) {
return 0;
}
// This retries all rdrand calls for the requested |len|.
// |CRYPTO_rdrand_multiple8| will typically execute rdrand multiple times. But
// it's easier to implement on the C-level and it should be a very rare event.
for (size_t tries = 0; tries < RDRAND_MAX_RETRIES; tries++) {
if (CRYPTO_rdrand_multiple8(buf, len) == 1) {
return 1;
}
}
return 0;
}
int have_hw_rng_x86_64_for_testing(void) {
return have_hw_rng_x86_64();
}
void override_entropy_source_method_FOR_TESTING(
const struct entropy_source_methods *override_entropy_source_methods) {
CRYPTO_STATIC_MUTEX_lock_write(global_entropy_source_lock_bss_get());
*allow_entropy_source_methods_override_bss_get() = 1;
*entropy_source_methods_override_bss_get() = override_entropy_source_methods;
CRYPTO_STATIC_MUTEX_unlock_write(global_entropy_source_lock_bss_get());
}
int get_entropy_source_method_id_FOR_TESTING(void) {
int id;
CRYPTO_STATIC_MUTEX_lock_read(global_entropy_source_lock_bss_get());
const struct entropy_source_methods *entropy_source_method = get_entropy_source_methods();
id = entropy_source_method->id;
CRYPTO_STATIC_MUTEX_unlock_read(global_entropy_source_lock_bss_get());
return id;
}

View File

@@ -0,0 +1,170 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#ifndef OPENSSL_HEADER_CRYPTO_RAND_ENTROPY_INTERNAL_H
#define OPENSSL_HEADER_CRYPTO_RAND_ENTROPY_INTERNAL_H
#include <openssl/ctrdrbg.h>
#include <openssl/rand.h>
#include "../../cpucap/internal.h"
#if defined(__cplusplus)
extern "C" {
#endif
#define OVERRIDDEN_ENTROPY_SOURCE 0
#define TREE_DRBG_JITTER_ENTROPY_SOURCE 1
#define OPT_OUT_CPU_JITTER_ENTROPY_SOURCE 2
#define ENTROPY_JITTER_MAX_NUM_TRIES (3)
// TREE_JITTER_GLOBAL_DRBG_MAX_GENERATE = 2^24
#define TREE_JITTER_GLOBAL_DRBG_MAX_GENERATE 0x1000000
// TREE_JITTER_THREAD_DRBG_MAX_GENERATE = 2^20
#define TREE_JITTER_THREAD_DRBG_MAX_GENERATE 0x100000
struct entropy_source_t {
void *state;
const struct entropy_source_methods *methods;
};
struct entropy_source_methods {
int (*initialize)(struct entropy_source_t *entropy_source);
void (*zeroize_thread)(struct entropy_source_t *entropy_source);
void (*free_thread)(struct entropy_source_t *entropy_source);
int (*get_seed)(const struct entropy_source_t *entropy_source,
uint8_t seed[CTR_DRBG_ENTROPY_LEN]);
int (*get_extra_entropy)(const struct entropy_source_t *entropy_source,
uint8_t extra_entropy[CTR_DRBG_ENTROPY_LEN]);
int (*get_prediction_resistance)(const struct entropy_source_t *entropy_source,
uint8_t pred_resistance[RAND_PRED_RESISTANCE_LEN]);
int id;
};
// get_entropy_source will return an entropy source configured for the platform.
struct entropy_source_t * get_entropy_source(void);
// override_entropy_source_method_FOR_TESTING will override the global
// entropy source that is assigned when calling |get_entropy_source|.
// |override_entropy_source_method_FOR_TESTING| can be called multiple times but
// it's designed to allow overriding the entropy source for testing purposes at
// the start of a process.
OPENSSL_EXPORT void override_entropy_source_method_FOR_TESTING(
const struct entropy_source_methods *override_entropy_source_methods);
OPENSSL_EXPORT int get_entropy_source_method_id_FOR_TESTING(void);
#if !defined(DISABLE_CPU_JITTER_ENTROPY)
OPENSSL_EXPORT int tree_jitter_initialize(struct entropy_source_t *entropy_source);
OPENSSL_EXPORT void tree_jitter_zeroize_thread_drbg(struct entropy_source_t *entropy_source);
OPENSSL_EXPORT void tree_jitter_free_thread_drbg(struct entropy_source_t *entropy_source);
OPENSSL_EXPORT int tree_jitter_get_seed(
const struct entropy_source_t *entropy_source, uint8_t seed[CTR_DRBG_ENTROPY_LEN]);
#else // !defined(DISABLE_CPU_JITTER_ENTROPY)
// Define stubs for tree-DRBG functions that implements the entropy source
// interface.
static inline int tree_jitter_initialize(struct entropy_source_t *entropy_source) { return 0; }
static inline void tree_jitter_zeroize_thread_drbg(struct entropy_source_t *entropy_source) { abort(); }
static inline void tree_jitter_free_thread_drbg(struct entropy_source_t *entropy_source) { abort(); }
static inline int tree_jitter_get_seed(const struct entropy_source_t *entropy_source, uint8_t seed[CTR_DRBG_ENTROPY_LEN]) { return 0; }
#endif // !defined(DISABLE_CPU_JITTER_ENTROPY)
// rndr_multiple8 writes |len| number of bytes to |buf| generated using the
// rndr instruction. |len| must be a multiple of 8.
// Outputs 1 on success, 0 otherwise.
OPENSSL_EXPORT int rndr_multiple8(uint8_t *buf, const size_t len);
// have_hw_rng_aarch64_for_testing wraps |have_hw_rng_aarch64| to allow usage
// in testing.
OPENSSL_EXPORT int have_hw_rng_aarch64_for_testing(void);
#if defined(OPENSSL_AARCH64) && !defined(OPENSSL_NO_ASM)
// CRYPTO_rndr_multiple8 writes |len| number of bytes to |buf| generated using
// the rndr instruction. |len| must be a multiple of 8 and positive.
// Outputs 1 on success, 0 otherwise.
int CRYPTO_rndr_multiple8(uint8_t *out, size_t out_len);
// Returns 1 if Armv8-A instruction rndr is available, 0 otherwise.
OPENSSL_INLINE int have_hw_rng_aarch64(void) {
return CRYPTO_is_ARMv8_RNDR_capable();
}
#else // defined(OPENSSL_AARCH64) && !defined(OPENSSL_NO_ASM)
OPENSSL_INLINE int CRYPTO_rndr_multiple8(uint8_t *out, size_t out_len) {
return 0;
}
OPENSSL_INLINE int have_hw_rng_aarch64(void) {
return 0;
}
#endif // defined(OPENSSL_AARCH64) && !defined(OPENSSL_NO_ASM)
// rdrand_multiple8 writes |len| number of bytes to |buf| generated using the
// rdrand instruction. |len| must be a multiple of 8. Retries
// Outputs 1 on success, 0 otherwise.
OPENSSL_EXPORT int rdrand_multiple8(uint8_t *buf, size_t len);
// have_hw_rng_x86_64_for_testing wraps |have_hw_rng_x86_64| to allow usage
// in testing.
OPENSSL_EXPORT int have_hw_rng_x86_64_for_testing(void);
#if defined(OPENSSL_X86_64) && !defined(OPENSSL_NO_ASM)
// Certain operating environments will disable RDRAND for both security and
// performance reasons. See initialization of CPU capability vector for details.
// At the moment, we must implement this logic there because the CPU capability
// vector does not carry CPU family/model information which is required to
// determine restrictions.
OPENSSL_INLINE int have_hw_rng_x86_64(void) {
return CRYPTO_is_RDRAND_capable();
}
// CRYPTO_rdrand_multiple8 writes |len| number of bytes to |buf| generated using
// the rdrand instruction. |len| must be a multiple of 8 and positive.
// Outputs 1 on success, 0 otherwise.
int CRYPTO_rdrand_multiple8(uint8_t *buf, size_t len);
#else // defined(OPENSSL_X86_64) && !defined(OPENSSL_NO_ASM)
OPENSSL_INLINE int CRYPTO_rdrand_multiple8(uint8_t *buf, size_t len) {
return 0;
}
OPENSSL_INLINE int have_hw_rng_x86_64(void) {
return 0;
}
#endif // defined(OPENSSL_X86_64) && !defined(OPENSSL_NO_ASM)
struct test_tree_drbg_t {
uint64_t thread_generate_calls_since_seed;
uint64_t thread_reseed_calls_since_initialization;
uint64_t global_generate_calls_since_seed;
uint64_t global_reseed_calls_since_initialization;
};
// get_thread_and_global_tree_drbg_calls_FOR_TESTING returns the number of
// generate calls since seed/reseed for the tread-local and global tree-DRBG.
// In addition, it returns the number of reseeds applied on the tread-local and
// global tree-DRBG. These values of copied to |test_tree_drbg|.
OPENSSL_EXPORT int get_thread_and_global_tree_drbg_calls_FOR_TESTING(
const struct entropy_source_t *entropy_source,
struct test_tree_drbg_t *test_tree_drbg);
// set_thread_and_global_tree_drbg_reseed_counter_FOR_TESTING sets the reseed
// counter for either the tread-local and/or global tree-DRBG. If either of
// |thread_reseed_calls| or |global_reseed_calls| are equal to 0, their
// reseed counter is not set.
OPENSSL_EXPORT int set_thread_and_global_tree_drbg_reseed_counter_FOR_TESTING(
struct entropy_source_t *entropy_source, uint64_t thread_reseed_calls,
uint64_t global_reseed_calls);
#if defined(__cplusplus)
} // extern C
#endif
#endif // OPENSSL_HEADER_CRYPTO_RAND_ENTROPY_INTERNAL_H

View File

@@ -0,0 +1,541 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#if !defined(DISABLE_CPU_JITTER_ENTROPY)
#include <openssl/ctrdrbg.h>
#include <openssl/mem.h>
#include <openssl/type_check.h>
#include "internal.h"
#include "../internal.h"
#include "../../delocate.h"
#include "../../../internal.h"
#include "../../../rand_extra/internal.h"
#include "../../../ube/internal.h"
#include "../../../../third_party/jitterentropy/jitterentropy-library/jitterentropy.h"
// Randomness generation implements thread-local "frontend" DRBGs that serve
// requests for randomness from consumers through exported functions such as
// RAND_bytes(). This file implements a tree-DRBG from SP800-90C as a seed
// entropy source for the frontend DRBGs. The implemented tree-DRBG has the
// following characteristics:
// - A per-thread seed DRBG that serves seed requests for a thread-local
// frontend DRBG.
// - A global seed DRBG that serves seed requests from the thread-local seed
// DRBGs.
// - A root seed source that serves seed requests from the global seed DRBG.
// The root seed source is a global instance of Jitter Entropy.
//
// The dependency tree looks as follows:
//
// entropy_source
// interface
// |
// rand.c | tree_drbg_jitter_entropy.c
// |
// front-end | tree-DRBG
// per-thread | per-thread
// +-----------+ | +-----------+
// | CTR-DRBG | --> | CTR-DRBG | -|
// +-----------+ | +-----------+ -|
// +-----------+ | +-----------+ --| per-process per-process
// | CTR-DRBG | --> | CTR-DRBG | ---| --> +-----------+ +----------------+
// +-----------+ | +-----------+ -----> | CTR-DRBG | --> | Jitter Entropy |
// ... | ... --> +-----------+ +----------------+
// +-----------+ | +-----------+ -----|
// | CTR-DRBG | --> | CTR-DRBG |-|
// +-----------+ | +-----------+
// |
//
// Memory life-cycle: The thread-local DRBGs have the same storage duration as
// their corresponding thread-local frontend DRBGs. The per-process DRBG and
// Jitter Entropy instance has a storage duration that extends to the duration
// of AWS-LC being loaded into the process. The per-process memory is lazily
// allocated.
// To serve seed requests from the frontend DRBGs the following
// |struct entropy_source_methods| interface functions are implemented:
// - tree_jitter_initialize
// - tree_jitter_zeroize_thread_drbg
// - tree_jitter_free_thread_drbg
// - tree_jitter_get_seed
struct tree_jitter_drbg_t {
// is_global is 1 if this object is the per-process seed DRBG. Otherwise 0.
uint8_t is_global;
// drbg is the DRBG state.
CTR_DRBG_STATE drbg;
// max_generate_calls is the maximum number of generate calls that can be
// invoked on |drbg| without a reseed.
uint64_t max_generate_calls;
// reseed_calls_since_initialization is the number of seed/reseed calls made
// on |drbg| since its initialization.
// We assume 2^64 - 1 is an upper bound on the number of reseeds. Type must
// support that.
uint64_t reseed_calls_since_initialization;
// generation_number caches the UBE generation number.
uint64_t generation_number;
// ube_protection denotes whether this object is protected from UBEs.
uint8_t ube_protection;
// Jitter entropy state. NULL if not the per-process seed DRBG.
struct rand_data *jitter_ec;
};
// Per-process seed DRBG locks.
DEFINE_BSS_GET(struct tree_jitter_drbg_t *, global_seed_drbg)
DEFINE_STATIC_ONCE(global_seed_drbg_once)
DEFINE_STATIC_ONCE(global_seed_drbg_zeroize_once)
DEFINE_STATIC_MUTEX(global_seed_drbg_lock)
// tree_jitter_get_root_seed generates |CTR_DRBG_ENTROPY_LEN| bytes of output
// from the Jitter Entropy instance configured in |tree_jitter_drbg|. The output
// is returned in |seed_out|.
// Access to this function must be synchronized.
static void tree_jitter_get_root_seed(
struct tree_jitter_drbg_t *tree_jitter_drbg,
uint8_t seed_out[CTR_DRBG_ENTROPY_LEN]) {
if (tree_jitter_drbg->jitter_ec == NULL) {
abort();
}
// |jent_read_entropy| has a false positive health test failure rate of 2^-22.
// To avoid aborting so frequently, we retry 3 times.
char jitter_generated_output = 0;
for (size_t num_tries = 1; num_tries <= ENTROPY_JITTER_MAX_NUM_TRIES; num_tries++) {
// Try to generate the required number of bytes with Jitter.
// If successful break out from the loop, otherwise try again.
if (jent_read_entropy(tree_jitter_drbg->jitter_ec, (char *) seed_out,
CTR_DRBG_ENTROPY_LEN) == (ssize_t) CTR_DRBG_ENTROPY_LEN) {
jitter_generated_output = 1;
break;
}
// If Jitter entropy failed to produce entropy we need to reset it.
jent_entropy_collector_free(tree_jitter_drbg->jitter_ec);
tree_jitter_drbg->jitter_ec = NULL;
tree_jitter_drbg->jitter_ec = jent_entropy_collector_alloc(0, JENT_FORCE_FIPS);
if (tree_jitter_drbg->jitter_ec == NULL) {
abort();
}
}
if (jitter_generated_output != 1) {
abort();
}
}
// tree_jitter_drbg_maybe_get_pred_resistance generates RAND_PRED_RESISTANCE_LEN
// bytes for prediction resistance and returns them in |pred_resistance|.
// However, it only generate bytes if |tree_jitter_drbg| meets the conditions:
// 1) is not the global seed DRBG 2) is not protected from UBEs. If bytes are
// generated, |pred_resistance_len| is set to RAND_PRED_RESISTANCE_LEN and is
// otherwise not mutated.
static void tree_jitter_drbg_maybe_get_pred_resistance(
struct tree_jitter_drbg_t *tree_jitter_drbg,
uint8_t pred_resistance[RAND_PRED_RESISTANCE_LEN],
size_t *pred_resistance_len) {
if (tree_jitter_drbg->is_global == 0 &&
tree_jitter_drbg->ube_protection != 1) {
CRYPTO_sysrand(pred_resistance, RAND_PRED_RESISTANCE_LEN);
*pred_resistance_len = RAND_PRED_RESISTANCE_LEN;
}
}
// tree_jitter_check_drbg_must_reseed computes whether |state| must be
// randomized to ensure uniqueness.
//
// Return 1 if |state| must be randomized. 0 otherwise.
static int tree_jitter_check_drbg_must_reseed(
struct tree_jitter_drbg_t *tree_jitter_drbg) {
uint64_t current_generation_number = 0;
if (CRYPTO_get_ube_generation_number(&current_generation_number) == 1 &&
current_generation_number != tree_jitter_drbg->generation_number) {
tree_jitter_drbg->generation_number = current_generation_number;
return 1;
}
// drbg.reseed_counter is initialized to 1, incremented after a generate call
// on |drbg|. |max_generate_calls| is the maximum allowed invocation of the
// generate function on |drbg|.
if (tree_jitter_drbg->drbg.reseed_counter > tree_jitter_drbg->max_generate_calls) {
return 1;
}
return 0;
}
// tree_jitter_drbg_derive_seed generates a CTR_DRBG_ENTROPY_LEN byte seed from
// the DRBG configured in |tree_jitter_drbg|. The generated bytes are returned
// in |seed_out|.
//
// |tree_jitter_drbg_derive_seed| automatically handles reseeding the
// associated DRBG if required. In addition, if UBE detection is not supported
// prediction resistance is used to ensure bytes are generated safely.
static void tree_jitter_drbg_derive_seed(
struct tree_jitter_drbg_t *tree_jitter_drbg,
uint8_t seed_out[CTR_DRBG_ENTROPY_LEN]) {
if (tree_jitter_drbg == NULL) {
abort();
}
if (tree_jitter_check_drbg_must_reseed(tree_jitter_drbg) == 1) {
uint8_t seed_drbg[CTR_DRBG_ENTROPY_LEN];
if (tree_jitter_drbg->is_global == 1) {
tree_jitter_get_root_seed(tree_jitter_drbg, seed_drbg);
} else {
CRYPTO_STATIC_MUTEX_lock_write(global_seed_drbg_lock_bss_get());
tree_jitter_drbg_derive_seed(*global_seed_drbg_bss_get(), seed_drbg);
CRYPTO_STATIC_MUTEX_unlock_write(global_seed_drbg_lock_bss_get());
}
if (CTR_DRBG_reseed(&(tree_jitter_drbg->drbg), seed_drbg, NULL, 0) != 1) {
abort();
}
OPENSSL_cleanse(seed_drbg, CTR_DRBG_ENTROPY_LEN);
tree_jitter_drbg->reseed_calls_since_initialization += 1;
}
uint8_t pred_resistance[RAND_PRED_RESISTANCE_LEN];
size_t pred_resistance_len = 0;
tree_jitter_drbg_maybe_get_pred_resistance(tree_jitter_drbg,
pred_resistance, &pred_resistance_len);
OPENSSL_STATIC_ASSERT(CTR_DRBG_ENTROPY_LEN <= CTR_DRBG_MAX_GENERATE_LENGTH,
CTR_DRBG_ENTROPY_LEN_is_too_large_compared_to_CTR_DRBG_MAX_GENERATE_LENGTH)
if (!CTR_DRBG_generate(&(tree_jitter_drbg->drbg), seed_out, CTR_DRBG_ENTROPY_LEN,
pred_resistance, pred_resistance_len)) {
abort();
}
OPENSSL_cleanse(pred_resistance, RAND_PRED_RESISTANCE_LEN);
}
// tree_jitter_get_seed generates a CTR_DRBG_ENTROPY_LEN byte seed from the DRBG
// configured in |entropy_source|. The generated bytes are returned in
// |seed_out|. This function is the entry point for generating output from the
// tree DRBG.
//
// Return 1 on success and 0 otherwise.
int tree_jitter_get_seed(const struct entropy_source_t *entropy_source,
uint8_t seed_out[CTR_DRBG_ENTROPY_LEN]) {
GUARD_PTR_ABORT(entropy_source);
GUARD_PTR_ABORT(seed_out);
struct tree_jitter_drbg_t *tree_jitter_drbg_thread =
(struct tree_jitter_drbg_t *) entropy_source->state;
tree_jitter_drbg_derive_seed(tree_jitter_drbg_thread, seed_out);
return 1;
}
// tree_jitter_initialize initializes the global seed DRBG.
static void tree_jitter_initialize_once(void) {
struct tree_jitter_drbg_t *tree_jitter_drbg_global =
OPENSSL_zalloc(sizeof(struct tree_jitter_drbg_t));
if (tree_jitter_drbg_global == NULL) {
abort();
}
tree_jitter_drbg_global->is_global = 1;
tree_jitter_drbg_global->max_generate_calls = TREE_JITTER_GLOBAL_DRBG_MAX_GENERATE;
tree_jitter_drbg_global->reseed_calls_since_initialization = 0;
uint64_t current_generation_number = 0;
if (CRYPTO_get_ube_generation_number(&current_generation_number) != 1) {
tree_jitter_drbg_global->generation_number = 0;
} else {
tree_jitter_drbg_global->generation_number = current_generation_number;
}
// The first parameter passed to |jent_entropy_collector_alloc| function is
// the desired oversampling rate. Passing a 0 tells Jitter module to use
// the default rate (which is 3 in Jitter v3.6.3).
tree_jitter_drbg_global->jitter_ec = jent_entropy_collector_alloc(0, JENT_FORCE_FIPS);
if (tree_jitter_drbg_global->jitter_ec == NULL) {
abort();
}
uint8_t seed_drbg[CTR_DRBG_ENTROPY_LEN];
tree_jitter_get_root_seed(tree_jitter_drbg_global, seed_drbg);
if (!CTR_DRBG_init(&(tree_jitter_drbg_global->drbg), seed_drbg, NULL, 0)) {
abort();
}
tree_jitter_drbg_global->reseed_calls_since_initialization += 1;
OPENSSL_cleanse(seed_drbg, CTR_DRBG_ENTROPY_LEN);
*global_seed_drbg_bss_get() = tree_jitter_drbg_global;
}
// tree_jitter_initialize initializes a thread-local seed DRBG and configures
// it in |entropy_source|. If the global seed DRBG has not been initialized yet
// it's also initialized.
//
// Returns 1 on success and 0 otherwise.
int tree_jitter_initialize(struct entropy_source_t *entropy_source) {
GUARD_PTR_ABORT(entropy_source);
// Initialize the per-thread seed drbg.
struct tree_jitter_drbg_t *tree_jitter_drbg =
OPENSSL_zalloc(sizeof(struct tree_jitter_drbg_t));
if (tree_jitter_drbg == NULL) {
abort();
}
// Initialize the global seed DRBG if haven't already.
CRYPTO_once(global_seed_drbg_once_bss_get(), tree_jitter_initialize_once);
// Initialize the per-thread seed DRBG.
uint8_t seed_drbg[CTR_DRBG_ENTROPY_LEN];
CRYPTO_STATIC_MUTEX_lock_write(global_seed_drbg_lock_bss_get());
tree_jitter_drbg_derive_seed(*global_seed_drbg_bss_get(), seed_drbg);
CRYPTO_STATIC_MUTEX_unlock_write(global_seed_drbg_lock_bss_get());
if (!CTR_DRBG_init(&(tree_jitter_drbg->drbg), seed_drbg, NULL, 0)) {
abort();
}
tree_jitter_drbg->reseed_calls_since_initialization = 1;
OPENSSL_cleanse(seed_drbg, CTR_DRBG_ENTROPY_LEN);
tree_jitter_drbg->is_global = 0;
tree_jitter_drbg->max_generate_calls = TREE_JITTER_THREAD_DRBG_MAX_GENERATE;
uint64_t current_generation_number = 0;
if (CRYPTO_get_ube_generation_number(&current_generation_number) != 1) {
tree_jitter_drbg->ube_protection = 0;
tree_jitter_drbg->generation_number = 0;
} else {
tree_jitter_drbg->ube_protection = 1;
tree_jitter_drbg->generation_number = current_generation_number;
}
entropy_source->state = tree_jitter_drbg;
return 1;
}
#if defined(_MSC_VER)
#pragma section(".CRT$XCU", read)
static void tree_jitter_free_global_drbg(void);
static void windows_install_tree_jitter_free_global_drbg(void) {
atexit(&tree_jitter_free_global_drbg);
}
__declspec(allocate(".CRT$XCU")) void(*tree_jitter_drbg_destructor)(void) =
windows_install_tree_jitter_free_global_drbg;
#else
static void tree_jitter_free_global_drbg(void) __attribute__ ((destructor));
#endif
// The memory life-time for thread-local seed DRBGs is handled differently
// compared to the global seed DRBG (and Jitter Entropy instance). The frontend
// DRBG thread-local destuctors will invoke |tree_jitter_free_thread_drbg| using
// their reference to it. The global seed DRBG and Jitter Entropy instance will
// be released by a destructor. This ensures that the global seed DRBG life-time
// extends to the entire process life-time if the lazy initialization happened.
// Obviously, any dlclose on AWS-LC will release the memory early but that's
// correct behaviour.
// tree_jitter_free_global_drbg frees the memory allocated for the global seed
// DRBG and Jitter Entropy instance.
static void tree_jitter_free_global_drbg(void) {
CRYPTO_STATIC_MUTEX_lock_write(global_seed_drbg_lock_bss_get());
struct tree_jitter_drbg_t *global_tree_jitter_drbg = *global_seed_drbg_bss_get();
if (global_tree_jitter_drbg == NULL) {
CRYPTO_STATIC_MUTEX_unlock_write(global_seed_drbg_lock_bss_get());
return;
}
if (global_tree_jitter_drbg->is_global != 1) {
// Should not happen.
abort();
}
jent_entropy_collector_free(global_tree_jitter_drbg->jitter_ec);
OPENSSL_free(global_tree_jitter_drbg);
*global_seed_drbg_bss_get() = NULL;
CRYPTO_STATIC_MUTEX_unlock_write(global_seed_drbg_lock_bss_get());
}
// tree_jitter_free_thread_drbg frees the thread-local seed DRBG
// associated with the entropy source |entropy_source|.
void tree_jitter_free_thread_drbg(struct entropy_source_t *entropy_source) {
GUARD_PTR_ABORT(entropy_source);
struct tree_jitter_drbg_t *tree_jitter_drbg =
(struct tree_jitter_drbg_t *) entropy_source->state;
if (tree_jitter_drbg == NULL) {
return;
}
OPENSSL_free(tree_jitter_drbg);
entropy_source->state = NULL;
}
// Per ISO/IEC 19790-2012 7.9.7 "zeroization" can be random data just not other
// SSP/CSP's. The Jitter Entropy instance doesn't have any practical state; it's
// a live entropy source. The zeroization strategy used for the DRBG's is to
// reseed with random data, that in turn, will override all states in the tree
// with random data. The zeroization of the tree DRBG executes after the
// frontend DRBGs have been locked - they can't release any generated output.
// Therefore, the randomness generation layer ensures that no output from the
// tree DRBG is used to generate any output that is later released. Randomizing
// the tree DRBG states therefore effectively "zeroize" the state.
//
// If there aren't any threads running, the zeroizer for the global seed DRBG
// won't execute. But the destructor responsible for releasing the memory
// allocated for the global seed DRBG and Jitter Entropy instance, will still
// execute, in turn, zeroize it.
//
// One could override the DRBG states with zero's. However, doing the small
// extra work to use random data (from the OS source) ensures that even if some
// output were to escape from the randomness generation, it will still be sound
// practically.
// tree_jitter_zeroize_drbg zeroizes the DRBG state configured in
// |tree_jitter_drbg|.
static void tree_jitter_zeroize_drbg(
struct tree_jitter_drbg_t *tree_jitter_drbg) {
uint8_t random_data[CTR_DRBG_ENTROPY_LEN];
CRYPTO_sysrand_if_available(random_data, CTR_DRBG_ENTROPY_LEN);
if (CTR_DRBG_reseed(&(tree_jitter_drbg->drbg), random_data, NULL, 0) != 1) {
abort();
}
OPENSSL_cleanse(random_data, CTR_DRBG_ENTROPY_LEN);
tree_jitter_drbg->reseed_calls_since_initialization += 1;
}
// tree_jitter_zeroize_global_drbg is similar to |tree_jitter_zeroize_drbg| but
// also handles synchronizing access to the global seed DRBG
static void tree_jitter_zeroize_global_drbg(void) {
CRYPTO_STATIC_MUTEX_lock_write(global_seed_drbg_lock_bss_get());
struct tree_jitter_drbg_t *tree_jitter_drbg = *global_seed_drbg_bss_get();
if (tree_jitter_drbg == NULL) {
CRYPTO_STATIC_MUTEX_unlock_write(global_seed_drbg_lock_bss_get());
return;
}
if (tree_jitter_drbg->is_global != 1) {
// Should not happen.
abort();
}
tree_jitter_zeroize_drbg(tree_jitter_drbg);
CRYPTO_STATIC_MUTEX_unlock_write(global_seed_drbg_lock_bss_get());
}
// tree_jitter_zeroize_thread_drbg zeroizes the thread-local seed DRBG
// associated with the entropy source |entropy_source|. It also executes
// zeroization of the global seed DRBG if applicable.
void tree_jitter_zeroize_thread_drbg(struct entropy_source_t *entropy_source) {
GUARD_PTR_ABORT(entropy_source);
CRYPTO_once(global_seed_drbg_zeroize_once_bss_get(), tree_jitter_zeroize_global_drbg);
struct tree_jitter_drbg_t *tree_jitter_drbg =
(struct tree_jitter_drbg_t *) entropy_source->state;
if (tree_jitter_drbg == NULL) {
return;
}
if (tree_jitter_drbg->is_global == 1) {
// Should not happen.
abort();
}
tree_jitter_zeroize_drbg(tree_jitter_drbg);
}
int get_thread_and_global_tree_drbg_calls_FOR_TESTING(
const struct entropy_source_t *entropy_source,
struct test_tree_drbg_t *test_tree_drbg) {
if (test_tree_drbg == NULL || entropy_source == NULL) {
return 0;
}
int ret = 0;
CRYPTO_STATIC_MUTEX_lock_read(global_seed_drbg_lock_bss_get());
struct tree_jitter_drbg_t *global_tree_jitter_drbg = *global_seed_drbg_bss_get();
struct tree_jitter_drbg_t *thread_tree_jitter_drbg =
(struct tree_jitter_drbg_t *) entropy_source->state;
if (global_tree_jitter_drbg == NULL || thread_tree_jitter_drbg == NULL) {
goto out;
}
// Note that |drbg.reseed_counter| is initialized to 1.
test_tree_drbg->thread_generate_calls_since_seed = thread_tree_jitter_drbg->drbg.reseed_counter;
test_tree_drbg->thread_reseed_calls_since_initialization = thread_tree_jitter_drbg->reseed_calls_since_initialization;
test_tree_drbg->global_generate_calls_since_seed = global_tree_jitter_drbg->drbg.reseed_counter;
test_tree_drbg->global_reseed_calls_since_initialization = global_tree_jitter_drbg->reseed_calls_since_initialization;
ret = 1;
out:
CRYPTO_STATIC_MUTEX_unlock_read(global_seed_drbg_lock_bss_get());
return ret;
}
OPENSSL_EXPORT int set_thread_and_global_tree_drbg_reseed_counter_FOR_TESTING(
struct entropy_source_t *entropy_source, uint64_t thread_reseed_calls,
uint64_t global_reseed_calls) {
if (entropy_source == NULL) {
return 0;
}
int ret = 0;
CRYPTO_STATIC_MUTEX_lock_write(global_seed_drbg_lock_bss_get());
struct tree_jitter_drbg_t *global_tree_jitter_drbg = *global_seed_drbg_bss_get();
struct tree_jitter_drbg_t *thread_tree_jitter_drbg =
(struct tree_jitter_drbg_t *) entropy_source->state;
if (global_tree_jitter_drbg == NULL || thread_tree_jitter_drbg == NULL) {
goto out;
}
if (thread_reseed_calls != 0) {
thread_tree_jitter_drbg->drbg.reseed_counter = thread_reseed_calls;
}
if (global_reseed_calls != 0) {
global_tree_jitter_drbg->drbg.reseed_counter = global_reseed_calls;
}
ret = 1;
out:
CRYPTO_STATIC_MUTEX_unlock_write(global_seed_drbg_lock_bss_get());
return ret;
}
#endif // !defined(DISABLE_CPU_JITTER_ENTROPY)

View File

@@ -0,0 +1,557 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#if !defined(DISABLE_CPU_JITTER_ENTROPY)
#include <gtest/gtest.h>
#include "internal.h"
#include "../internal.h"
#include "../../../ube/internal.h"
#include "../../../test/ube_test.h"
#include "../../../test/test_util.h"
#include <cstdio>
#if defined(GTEST_HAS_DEATH_TEST)
#define TEST_IN_FORK_ASSERT_TRUE(condition) if (!condition) { std::cerr << __FILE__ << ":" << __LINE__ << ": Assertion failed: " << #condition << std::endl;; exit(1);}
#define TEST_IN_FORK_ASSERT_FALSE(condition) if (condition) { std::cerr << __FILE__ << ":" << __LINE__ << ": Assertion failed: " << #condition << std::endl;; exit(1);}
static const size_t number_of_threads = 8;
class treeDrbgJitterentropyTest : public::testing::Test {
private:
UbeBase ube_base_;
protected:
void SetUp() override {
ube_base_.SetUp();
}
void TearDown() override {
ube_base_.TearDown();
}
bool UbeIsSupported() const {
return ube_base_.UbeIsSupported();
}
void allowMockedUbe() const {
ube_base_.allowMockedUbe();
}
};
static bool get_tree_drbg_call(const struct entropy_source_t *entropy_source,
struct test_tree_drbg_t *test_tree_drbg) {
if (get_thread_and_global_tree_drbg_calls_FOR_TESTING(
entropy_source, test_tree_drbg)) {
return true;
}
return false;
}
TEST_F(treeDrbgJitterentropyTest, BasicInitialization) {
if (runtimeEmulationIsIntelSde() && addressSanitizerIsEnabled()) {
GTEST_SKIP() << "Test not supported under Intel SDE + ASAN";
}
// Test only one seed occurs on initialization.
auto testFunc = []() {
struct entropy_source_t entropy_source = {0, 0};
struct test_tree_drbg_t new_test_tree_drbg = {0, 0, 0, 0};
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_initialize(&entropy_source))
TEST_IN_FORK_ASSERT_TRUE(get_tree_drbg_call(&entropy_source, &new_test_tree_drbg))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_reseed_calls_since_initialization == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_reseed_calls_since_initialization == 1))
// Calling tree_jitter_initialize before thread test would set
// |thread_generate_calls_since_seed| equal to 1 and
// |global_generate_calls_since_seed| equal to 2. The latter because the
// initial value 1 and we perform a generate call on the global tree-DRBG
// to seed the thread-local tree-DRBG.
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_generate_calls_since_seed == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_generate_calls_since_seed == 2))
tree_jitter_zeroize_thread_drbg(&entropy_source);
tree_jitter_free_thread_drbg(&entropy_source);
exit(0);
};
EXPECT_EXIT(testFunc(), ::testing::ExitedWithCode(0), "");
}
TEST_F(treeDrbgJitterentropyTest, BasicThread) {
// Test seeds are observed when spawning new threads.
auto testFunc = []() {
struct entropy_source_t entropy_source = {0, 0};
struct test_tree_drbg_t new_test_tree_drbg = {0, 0, 0, 0};
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_initialize(&entropy_source))
std::function<void(bool*)> threadFunc = [](bool *result) {
struct entropy_source_t entropy_source_thread = {0, 0};
struct test_tree_drbg_t new_test_tree_drbg_thread = {0, 0, 0, 0};
bool test = tree_jitter_initialize(&entropy_source_thread);
test = test && get_tree_drbg_call(&entropy_source_thread, &new_test_tree_drbg_thread);
test = test && new_test_tree_drbg_thread.thread_reseed_calls_since_initialization == 1;
test = test && new_test_tree_drbg_thread.global_reseed_calls_since_initialization == 1;
*result = test;
tree_jitter_free_thread_drbg(&entropy_source_thread);
};
bool exit_code = threadTest(number_of_threads, threadFunc);
TEST_IN_FORK_ASSERT_TRUE(exit_code)
TEST_IN_FORK_ASSERT_TRUE(get_tree_drbg_call(&entropy_source, &new_test_tree_drbg))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_reseed_calls_since_initialization == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_reseed_calls_since_initialization == 1))
// Calling tree_jitter_initialize before thread test would set
// |global_generate_calls_since_seed| equal to 2. We then expect an
// additional |number_of_threads| thread-local tree-DRBGs to seed using the
// global tree-DRBG.
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_generate_calls_since_seed == (number_of_threads+2)))
tree_jitter_zeroize_thread_drbg(&entropy_source);
tree_jitter_free_thread_drbg(&entropy_source);
exit(0);
};
EXPECT_EXIT(testFunc(), ::testing::ExitedWithCode(0), "");
}
TEST_F(treeDrbgJitterentropyTest, BasicReseed) {
if (runtimeEmulationIsIntelSde() && addressSanitizerIsEnabled()) {
GTEST_SKIP() << "Test not supported under Intel SDE + ASAN";
}
// Test reseeding happens as expected
auto testFunc = []() {
struct entropy_source_t entropy_source = {0, 0};
struct test_tree_drbg_t new_test_tree_drbg = {0, 0, 0, 0};
uint8_t seed_out[CTR_DRBG_ENTROPY_LEN];
const uint64_t tree_drbg_thread_reseed_limit = TREE_JITTER_THREAD_DRBG_MAX_GENERATE;
const uint64_t tree_drbg_global_reseed_limit = TREE_JITTER_GLOBAL_DRBG_MAX_GENERATE;
// Similar to initialization test above.
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_initialize(&entropy_source))
TEST_IN_FORK_ASSERT_TRUE(get_tree_drbg_call(&entropy_source, &new_test_tree_drbg))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_reseed_calls_since_initialization == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_generate_calls_since_seed == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_reseed_calls_since_initialization == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_generate_calls_since_seed == 2))
// Set reseed counter for thread-local tree-DRBG to max value + 1 (because
// the reseed interval condition uses strict inequality and
// drbg.reseed_counter is initialized to 1).
TEST_IN_FORK_ASSERT_TRUE(set_thread_and_global_tree_drbg_reseed_counter_FOR_TESTING(
&entropy_source, tree_drbg_thread_reseed_limit + 1, 0))
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_get_seed(&entropy_source, seed_out))
TEST_IN_FORK_ASSERT_TRUE(get_tree_drbg_call(&entropy_source, &new_test_tree_drbg))
// Thread-local tree-DRBG should generate a seed from global tree-DRBG
// causing its generate call counter to increment by 1. Thread-local
// tree-DRBG reseed counter should also go increment by 1.
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_reseed_calls_since_initialization == 2)) // changed
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_generate_calls_since_seed == 2)) // changed
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_reseed_calls_since_initialization == 1)) // unchanged
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_generate_calls_since_seed == 3)) // changed
// Set reseed counter for global tree-DRBG to max value + 1. Thread-local
// tree-DRBG is unchanged
TEST_IN_FORK_ASSERT_TRUE(set_thread_and_global_tree_drbg_reseed_counter_FOR_TESTING(
&entropy_source, 0, tree_drbg_global_reseed_limit + 1))
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_get_seed(&entropy_source, seed_out))
TEST_IN_FORK_ASSERT_TRUE(get_tree_drbg_call(&entropy_source, &new_test_tree_drbg))
// We generated a seed from the tread-local tree-DRBG which should not
// reseed. Hence, we do not expect a generate call made to the global
// tree-DRBG. The value of the latter will change though because the reseed
// counter is equal to the number of generate calls. Since we are generating
// a seed from the thread-local tree-DRBG its generate counter should increment by 1
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_reseed_calls_since_initialization == 2)) // unchanged
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_generate_calls_since_seed == 3)) // changed
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_reseed_calls_since_initialization == 1)) // unchanged
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_generate_calls_since_seed == (tree_drbg_global_reseed_limit + 1))) // changed
// Set reseed counter for both thread-local and global tree-DRBG to
// max value + 1.
TEST_IN_FORK_ASSERT_TRUE(set_thread_and_global_tree_drbg_reseed_counter_FOR_TESTING(
&entropy_source, tree_drbg_thread_reseed_limit + 1, tree_drbg_global_reseed_limit + 1))
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_get_seed(&entropy_source, seed_out))
TEST_IN_FORK_ASSERT_TRUE(get_tree_drbg_call(&entropy_source, &new_test_tree_drbg))
// When generating a seed from from the thread-local tree-DRBG it should
// reseed by getting a seed from the global tree-DRBG. The global tree-DRBG
// should itself reseed. In both cases, their generate calls (since last
// seed/reseed) should be reset.
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_reseed_calls_since_initialization == 3)) // changed
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_generate_calls_since_seed == 2)) // changed
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_reseed_calls_since_initialization == 2)) // changed
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_generate_calls_since_seed == 2)) // changed
// Try without calling zeroize thread-local tree-DRBG first.
tree_jitter_free_thread_drbg(&entropy_source);
exit(0);
};
EXPECT_EXIT(testFunc(), ::testing::ExitedWithCode(0), "");
}
#if !defined(OPENSSL_WINDOWS)
static bool veryifySeedOrReseed(const struct test_tree_drbg_t *test_tree_drbg,
const struct test_tree_drbg_t *cached_test_tree_drbg,
size_t expect_reseed_thread, size_t expect_reseed_global,
const char *error_text) {
if (cached_test_tree_drbg->thread_reseed_calls_since_initialization + expect_reseed_thread != test_tree_drbg->thread_reseed_calls_since_initialization ||
cached_test_tree_drbg->global_reseed_calls_since_initialization + expect_reseed_global != test_tree_drbg->global_reseed_calls_since_initialization) {
std::cerr << "Tree-DRBG expected count mismatch " << error_text << '\n'
<< " Thread DRBG: expected=" << (cached_test_tree_drbg->thread_reseed_calls_since_initialization + expect_reseed_thread)
<< ", actual=" << test_tree_drbg->thread_reseed_calls_since_initialization << '\n'
<< " Global DRBG: expected=" << (cached_test_tree_drbg->global_reseed_calls_since_initialization + expect_reseed_global)
<< ", actual=" << test_tree_drbg->global_reseed_calls_since_initialization << '\n';
return false;
}
return true;
}
static bool assertSeedOrReseed(const struct entropy_source_t *entropy_source,
size_t expect_reseed_thread, size_t expect_reseed_global,
std::function<bool()> func, const char *error_text = "") {
struct test_tree_drbg_t cached_test_tree_drbg = {0, 0, 0, 0};
TEST_IN_FORK_ASSERT_TRUE(get_tree_drbg_call(entropy_source, &cached_test_tree_drbg))
TEST_IN_FORK_ASSERT_TRUE(func())
struct test_tree_drbg_t test_tree_drbg = {0, 0, 0, 0};
TEST_IN_FORK_ASSERT_TRUE(get_tree_drbg_call(entropy_source, &test_tree_drbg))
return veryifySeedOrReseed(&test_tree_drbg, &cached_test_tree_drbg,
expect_reseed_thread, expect_reseed_global, error_text);
}
TEST_F(treeDrbgJitterentropyTest, BasicFork) {
if (runtimeEmulationIsIntelSde() && addressSanitizerIsEnabled()) {
GTEST_SKIP() << "Test not supported under Intel SDE + ASAN";
}
auto testFuncSingleFork = [this]() {
struct entropy_source_t entropy_source = {0, 0};
uint8_t seed_out[CTR_DRBG_ENTROPY_LEN];
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_initialize(&entropy_source))
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_get_seed(&entropy_source, seed_out))
bool exit_code = forkAndRunTest(
[this, entropy_source]() {
// In child. If UBE detection is supported, we expect a reseed.
// No UBE detection is handled via prediction resistance.
size_t expect_reseed = 0;
if (UbeIsSupported()) {
expect_reseed = 1;
}
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&entropy_source, expect_reseed, expect_reseed, [entropy_source]() {
uint8_t child_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&entropy_source, child_out);
}, "child")
)
return true;
},
[entropy_source]() {
// In Parent we expect no reseed, even if UBE detection is not supported.
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&entropy_source, 0, 0, [entropy_source]() {
uint8_t parent_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&entropy_source, parent_out);
}, "parent")
)
return true;
}
);
tree_jitter_free_thread_drbg(&entropy_source);
exit(exit_code ? 0 : 1);
};
EXPECT_EXIT(testFuncSingleFork(), ::testing::ExitedWithCode(0), "");
auto testFuncSingleForkThenThread = [this]() {
struct entropy_source_t entropy_source = {0, 0};
uint8_t seed_out[CTR_DRBG_ENTROPY_LEN];
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_initialize(&entropy_source))
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_get_seed(&entropy_source, seed_out))
bool exit_code = forkAndRunTest(
[this, entropy_source]() {
// In child. Spawn a number of threads before generating randomness.
// If fork detection is supported, we expect a seed in each thread.
// If fork detection is not enabled, we also expect a seed in each
// thread. However, this seed should occur when calling
// tree_jitter_initialize.
std::function<void(bool*)> threadFunc = [](bool *result) {
struct entropy_source_t thread_entropy_source = {0, 0};
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_initialize(&thread_entropy_source))
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&thread_entropy_source, 0, 0, [thread_entropy_source]() {
uint8_t child_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&thread_entropy_source, child_out);
}, "child")
)
tree_jitter_free_thread_drbg(&thread_entropy_source);
*result = true;
};
TEST_IN_FORK_ASSERT_TRUE(threadTest(number_of_threads, threadFunc))
// Now back to original thread.
size_t expect_reseed = 0;
if (UbeIsSupported()) {
expect_reseed = 1;
}
// Global would have been reseeded above.
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&entropy_source, expect_reseed, 0, [entropy_source]() {
uint8_t child_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&entropy_source, child_out);
}, "child")
)
return true;
},
[entropy_source]() {
// In Parent we expect no reseed, even if UBE detection is not supported.
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&entropy_source, 0, 0, [entropy_source]() {
uint8_t child_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&entropy_source, child_out);
}, "parent")
)
return true;
}
);
tree_jitter_free_thread_drbg(&entropy_source);
exit(exit_code ? 0 : 1);
};
EXPECT_EXIT(testFuncSingleForkThenThread(), ::testing::ExitedWithCode(0), "");
// Test reseed is observed when forking and then forking again before
// generating any randomness.
auto testFuncDoubleFork = [this]() {
struct entropy_source_t entropy_source = {0, 0};
uint8_t seed_out[CTR_DRBG_ENTROPY_LEN];
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_initialize(&entropy_source))
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_get_seed(&entropy_source, seed_out))
bool exit_code = forkAndRunTest(
[this, entropy_source]() {
// Fork again. In both child and parent we expect a reseed if UBE
// detection is supported.
bool exit_code_child = forkAndRunTest(
[this, entropy_source]() {
size_t expect_reseed = 0;
if (UbeIsSupported()) {
expect_reseed = 1;
}
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&entropy_source, expect_reseed, expect_reseed, [entropy_source]() {
uint8_t child_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&entropy_source, child_out);
}, "child-child")
)
return true;
},
[this, entropy_source]() {
size_t expect_reseed = 0;
if (UbeIsSupported()) {
expect_reseed = 1;
}
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&entropy_source, expect_reseed, expect_reseed, [entropy_source]() {
uint8_t child_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&entropy_source, child_out);
}, "child-parent")
)
return true;
}
);
return exit_code_child;
},
[entropy_source]() {
// In Parent we expect no reseed, even if UBE detection is not supported.
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&entropy_source, 0, 0, [entropy_source]() {
uint8_t parent_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&entropy_source, parent_out);
}, "parent")
)
return true;
}
);
tree_jitter_free_thread_drbg(&entropy_source);
exit(exit_code ? 0 : 1);
};
EXPECT_EXIT(testFuncDoubleFork(), ::testing::ExitedWithCode(0), "");
// Test reseed is observed when forking, generate randomness, and then fork
// again.
auto testFuncForkGenerateFork = [this]() {
struct entropy_source_t entropy_source = {0, 0};
uint8_t seed_out[CTR_DRBG_ENTROPY_LEN];
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_initialize(&entropy_source))
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_get_seed(&entropy_source, seed_out))
bool exit_code = forkAndRunTest(
[this, entropy_source]() {
size_t expect_reseed = 0;
if (UbeIsSupported()) {
expect_reseed = 1;
}
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&entropy_source, expect_reseed, expect_reseed, [entropy_source]() {
uint8_t parent_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&entropy_source, parent_out);
}, "child-parent")
)
bool exit_code_child = forkAndRunTest(
[this, entropy_source]() {
size_t expect_reseed_child = 0;
if (UbeIsSupported()) {
expect_reseed_child = 1;
}
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&entropy_source, expect_reseed_child, expect_reseed_child, [entropy_source]() {
uint8_t parent_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&entropy_source, parent_out);
}, "child-child")
)
return true;
},
[entropy_source]() {
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&entropy_source, 0, 0, [entropy_source]() {
uint8_t parent_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&entropy_source, parent_out);
}, "child-parent")
)
return true;
}
);
return exit_code_child;
},
[entropy_source]() {
TEST_IN_FORK_ASSERT_TRUE(
assertSeedOrReseed(&entropy_source, 0, 0, [entropy_source]() {
uint8_t parent_out[CTR_DRBG_ENTROPY_LEN];
return tree_jitter_get_seed(&entropy_source, parent_out);
}, "parent")
)
return true;
}
);
exit(exit_code ? 0 : 1);
};
EXPECT_EXIT(testFuncForkGenerateFork(), ::testing::ExitedWithCode(0), "");
}
#endif // !defined(OPENSSL_WINDOWS)
TEST_F(treeDrbgJitterentropyTest, TreeDRBGThreadReseedInterval) {
if (runtimeEmulationIsIntelSde() && addressSanitizerIsEnabled()) {
GTEST_SKIP() << "Test not supported under Intel SDE + ASAN";
}
// Test reseeding happens as expected
auto testFunc = []() {
struct entropy_source_t entropy_source = {0, 0};
struct test_tree_drbg_t new_test_tree_drbg = {0, 0, 0, 0};
uint8_t seed_out[CTR_DRBG_ENTROPY_LEN];
const uint64_t tree_drbg_thread_reseed_limit = TREE_JITTER_THREAD_DRBG_MAX_GENERATE;
// Similar to initialization test above.
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_initialize(&entropy_source))
TEST_IN_FORK_ASSERT_TRUE(get_tree_drbg_call(&entropy_source, &new_test_tree_drbg))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_reseed_calls_since_initialization == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_generate_calls_since_seed == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_reseed_calls_since_initialization == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_generate_calls_since_seed == 2))
// Must allow |tree_drbg_thread_reseed_limit| generate calls before
// reseeding. For the tree-DRBG, not having UBE detection does not trigger
// a pre-invocation reseed. Instead, prediction resistance is used. Hence,
// we do not need to cater for UBE in the logic below.
for (size_t i = 1; i <= tree_drbg_thread_reseed_limit; i++) {
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_get_seed(&entropy_source, seed_out))
TEST_IN_FORK_ASSERT_TRUE(get_tree_drbg_call(&entropy_source, &new_test_tree_drbg))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_reseed_calls_since_initialization == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_generate_calls_since_seed == (1 + i)))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_reseed_calls_since_initialization == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_generate_calls_since_seed == 2))
}
// Now reseed should happen.
TEST_IN_FORK_ASSERT_TRUE(tree_jitter_get_seed(&entropy_source, seed_out))
TEST_IN_FORK_ASSERT_TRUE(get_tree_drbg_call(&entropy_source, &new_test_tree_drbg))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_reseed_calls_since_initialization == 2))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.thread_generate_calls_since_seed == 2)) // Because drbg.reseed_counter is initialized to 1
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_reseed_calls_since_initialization == 1))
TEST_IN_FORK_ASSERT_TRUE((new_test_tree_drbg.global_generate_calls_since_seed == 3))
// Try without calling zeroize thread-local tree-DRBG first.
tree_jitter_free_thread_drbg(&entropy_source);
exit(0);
};
EXPECT_EXIT(testFunc(), ::testing::ExitedWithCode(0), "");
}
#else // GTEST_HAS_DEATH_TEST
TEST(treeDrbgJitterentropyTest, SkippedALL) {
GTEST_SKIP() << "All treeDrbgJitterentropyTest tests are not supported due to Death Tests not supported on this platform";
}
#endif
#endif // !defined(DISABLE_CPU_JITTER_ENTROPY)