blob: af9993b78b572121bd43463937236e6751ada0dd [file] [log] [blame]
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// BoringCrypto Jitter Entropy version 20250725.
#include <openssl/base.h>
#if defined(OPENSSL_LINUX) || defined(OPENSSL_MACOS)
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <array>
#include <type_traits>
#include <utility>
#include <openssl/mem.h>
#include "internal.h"
#include "sha512.cc.inc"
#if defined(__x86_64__)
#include <x86intrin.h>
#endif
BSSL_NAMESPACE_BEGIN
namespace entropy {
namespace {
#if defined(__x86_64__)
static inline uint64_t GetTimestamp() { return _rdtsc(); }
#elif defined(__aarch64__)
static inline uint64_t GetTimestamp() {
// Ideally this would use __arm_rsr64 from <arm_acle.h>. Clang has supported
// it Clang 3.7 (2016), but GCC did not add it until GCC 14.1.0 (2024). See
// https://crbug.com/440670941. When our minimum GCC is past that point,
// switch this back to __arm_rsr64.
uint64_t ret;
__asm__ volatile("mrs %0, cntvct_el0" : "=r"(ret));
return ret;
}
#else
static inline uint64_t GetTimestamp() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
}
#endif
class MemoryOffsetLCG {
public:
MemoryOffsetLCG() : state(GetTimestamp() & 0xFFFFFFFF) {}
uint32_t Next() {
state = state * 1664525 + 1013904223;
return state;
}
private:
uint32_t state;
};
class MemoryAccessSampler {
public:
MemoryAccessSampler(size_t array_size, unsigned num_samples)
: array_size_(array_size),
num_samples_(num_samples),
array_(reinterpret_cast<uint8_t *>(OPENSSL_malloc(array_size_))) {
if (array_ == nullptr || //
array_size_ == 0 || //
array_size_ > (1u << 26) || //
array_size_ & (array_size_ - 1)) {
abort();
}
}
~MemoryAccessSampler() { OPENSSL_free(const_cast<uint8_t *>(array_)); }
MemoryAccessSampler(const MemoryAccessSampler &) = delete;
MemoryAccessSampler &operator=(const MemoryAccessSampler &) = delete;
MemoryAccessSampler(MemoryAccessSampler &&other)
: array_size_(other.array_size_),
num_samples_(other.num_samples_),
lcg_(other.lcg_),
array_(other.array_) {
other.array_ = nullptr;
}
bool Next(uint64_t *out) {
// Perform some memory accesses and measure how long it took. The LCG is
// intended to defeat any CPU predictors and thus expose this code to as
// much system entropy as possible.
for (unsigned i = 0; i < num_samples_; i++) {
// The lower bits of an LCG tend to fall into short cycles and so are
// discarded here.
array_[(lcg_.Next() >> 6) & (array_size_ - 1)] += 1;
}
*out = GetTimestamp();
return true;
}
private:
const size_t array_size_;
const unsigned num_samples_;
MemoryOffsetLCG lcg_;
volatile uint8_t *array_;
};
template <typename T>
class DeltaSampler {
public:
explicit DeltaSampler(T &&sub_sampler)
: sub_sampler_(std::forward<T>(sub_sampler)) {}
// Next function to return the delta between two subsequent samples
bool Next(uint64_t *out) {
uint64_t sample;
if (!sub_sampler_.Next(&sample)) {
return false;
}
if (!initialized_) {
last_sample_ = sample;
if (!sub_sampler_.Next(&sample)) {
return false;
}
initialized_ = true;
}
*out = sample - last_sample_;
last_sample_ = sample;
return true;
}
private:
bool initialized_ = false;
T sub_sampler_;
uint64_t last_sample_;
};
template <typename U>
DeltaSampler(U &&sub_sampler) -> DeltaSampler<std::decay_t<U>>;
template <typename T>
class MaskSampler {
public:
explicit MaskSampler(uint8_t mask, T &&sub_sampler)
: mask_(mask), sub_sampler_(std::forward<T>(sub_sampler)) {}
bool Next(uint8_t *out) {
uint64_t sample;
if (!sub_sampler_.Next(&sample)) {
return false;
}
*out = sample & mask_;
return true;
}
private:
const uint8_t mask_;
T sub_sampler_;
};
template <typename U>
MaskSampler(uint8_t mask, U &&sub_sampler) -> MaskSampler<std::decay_t<U>>;
// The estimated entropy per sample from MaskSampler.
constexpr float kH = 0.8;
// kAlphaLog2 is log_2(alpha), where alpha is the standard false-positive
// probability from SP 800-90B.
static constexpr float kAlphaLog2 = -20;
// kAlpha is the variable of the same name from section 4.4.1 of SP 800-90B.
constexpr float kAlpha = 1.0 / (1 << static_cast<unsigned>(-kAlphaLog2));
// Ceil rounds up its non-negative argument to the next integer. (std::ceil
// isn't constexpr until C++23.)
constexpr unsigned Ceil(float val) {
auto truncated = static_cast<unsigned>(val);
if (val == static_cast<float>(truncated)) {
return truncated;
}
if (val > 0) {
return truncated + 1;
}
__builtin_unreachable();
}
template <typename T>
class RepetitionCountTest {
public:
static constexpr unsigned kThreshold = 1 + Ceil(-kAlphaLog2 / kH);
static_assert(kThreshold == 26);
explicit RepetitionCountTest(T &&sub_sampler)
: sub_sampler_(std::forward<T>(sub_sampler)) {}
bool Next(uint8_t *out) {
uint8_t sample;
if (!sub_sampler_.Next(&sample)) {
return false;
}
if (sample == last_sample_) {
count_++;
} else {
count_ = 1;
last_sample_ = sample;
}
if (count_ >= kThreshold) {
return false;
}
*out = sample;
return true;
}
private:
T sub_sampler_;
unsigned count_ = 0;
uint8_t last_sample_ = 0;
};
template <typename U>
RepetitionCountTest(U &&sub_sampler) -> RepetitionCountTest<std::decay_t<U>>;
constexpr double BinomialPMF(int64_t k, int64_t n, double p) {
if (k < 0 || k > n) {
return 0.0;
}
double result = 1.0;
for (int64_t i = 0; i < k; ++i) {
result *= (n - i);
result /= (i + 1);
}
for (int64_t i = 0; i < k; ++i) {
result *= p;
}
for (int64_t i = 0; i < n - k; ++i) {
result *= (1 - p);
}
return result;
}
// CritBinom implements the Excel function of the same name.
constexpr unsigned CritBinom(unsigned trials, double probability_s,
double alpha) {
if (probability_s < 0.0 || probability_s > 1.0 || alpha < 0.0 ||
alpha > 1.0) {
__builtin_unreachable();
}
double cumulative = 0.0;
for (unsigned k = 0; k <= trials; ++k) {
cumulative += BinomialPMF(k, trials, probability_s);
if (cumulative >= alpha) {
return k;
}
}
return trials;
}
// ExpTaylor calculates e^x using the Taylor series: e^x = 1 + x + x²/2! +
// x³/3! + ...
constexpr double ExpTaylor(double x) {
double sum = 1.0;
double term = 1.0;
for (int i = 1; i < 25; ++i) {
term *= x / i;
sum += term;
}
return sum;
}
// Power2 calculates 2^exp by calculating e^(ln(2) * exp) = e^(ln(2)) ^ exp =
// 2^exp. (std::pow isn't constexpr until C++26.)
constexpr double Power2(double exp) {
constexpr double ln2 = 0.693147180559945309417232121458;
return ExpTaylor(exp * ln2);
}
// AdaptiveProportionTestCutoff implements the function from the footnote on
// page 27 of SP 800-90B.
constexpr unsigned AdaptiveProportionTestCutoff(unsigned W, float H,
float alpha) {
return 1 + CritBinom(W, Power2(-H), 1.0 - alpha);
}
// These are the example values from table 2 of SP 800-90B, to show that
// we're calculating the values correctly.
static_assert(AdaptiveProportionTestCutoff(512, 0.5, kAlpha) == 410);
static_assert(AdaptiveProportionTestCutoff(512, 1, kAlpha) == 311);
static_assert(AdaptiveProportionTestCutoff(512, 2, kAlpha) == 177);
static_assert(AdaptiveProportionTestCutoff(512, 4, kAlpha) == 62);
static_assert(AdaptiveProportionTestCutoff(512, 8, kAlpha) == 13);
template <typename T>
class AdaptiveProportionTest {
public:
// The size of the sliding window, representing the number of recent samples
// to analyze.
static constexpr unsigned kWindowSize = 512;
// The maximum number of times any single byte value is allowed to appear
// within the sliding window.
static constexpr unsigned kThreshold =
AdaptiveProportionTestCutoff(kWindowSize, kH, kAlpha);
static_assert(kThreshold == 348);
explicit AdaptiveProportionTest(T &&sub_sampler)
: sub_sampler_(std::forward<T>(sub_sampler)) {
counts_.fill(0);
}
bool Next(uint8_t *out) {
uint8_t sample;
if (!sub_sampler_.Next(&sample)) {
return false;
}
*out = sample;
if (samples_processed_ >= kWindowSize) {
const uint8_t evicted_sample = buffer_[buffer_idx_];
counts_[evicted_sample]--;
}
buffer_[buffer_idx_] = sample;
const uint16_t new_count = ++counts_[sample];
if (new_count > kThreshold) {
return false;
}
buffer_idx_ = (buffer_idx_ + 1) % kWindowSize;
samples_processed_++;
return true;
}
private:
T sub_sampler_;
// A circular buffer to store the most recent `kWindowSize` samples.
std::array<uint8_t, kWindowSize> buffer_{};
// An array to store the frequency counts of each possible byte value (0-255)
// within the current window.
std::array<uint16_t, 256> counts_{};
// The current index for writing into the circular buffer.
size_t buffer_idx_ = 0;
// The total number of samples processed. Used to determine when the buffer
// is full and eviction should begin.
size_t samples_processed_ = 0;
};
template <typename U>
AdaptiveProportionTest(U &&sub_sampler)
-> AdaptiveProportionTest<std::decay_t<U>>;
template <typename T>
class SeedSampler {
public:
// NIST requires 1024 samples at start-up time. This code is structured so
// that the entropy generator is considered to be starting afresh for each
// seed.
static constexpr unsigned kNumSamples = 1024;
explicit SeedSampler(T &&sub_sampler)
: sub_sampler_(std::forward<T>(sub_sampler)) {}
bool Next(uint8_t out_seed[48]) {
// HMAC-SHA384 `kNumSamples` samples with an all-zero key:
SHA512_CTX ctx;
SHA384_Init(&ctx);
uint8_t block[kSHA384Block];
memset(block, 0x36, sizeof(block));
SHA384_Update(&ctx, block, sizeof(block));
static_assert(kNumSamples % sizeof(block) == 0);
for (unsigned i = 0; i < kNumSamples / sizeof(block); i++) {
for (unsigned j = 0; j < sizeof(block); j++) {
if (!sub_sampler_.Next(&block[j])) {
return false;
}
}
SHA384_Update(&ctx, block, sizeof(block));
}
SHA384_Final(out_seed, &ctx);
SHA384_Init(&ctx);
memset(block, 0x5c, sizeof(block));
SHA384_Update(&ctx, block, sizeof(block));
SHA384_Update(&ctx, out_seed, kSHA384DigestLength);
SHA384_Final(out_seed, &ctx);
return true;
}
private:
T sub_sampler_;
};
template <typename U>
SeedSampler(U &&sub_sampler) -> SeedSampler<std::decay_t<U>>;
constexpr size_t kMemorySize = 1u << 25;
constexpr size_t kMemoryAccessesPerSample = 16;
constexpr size_t kBitsPerSample = 8;
constexpr uint8_t kMask = (1u << kBitsPerSample) - 1;
} // namespace
int GetVersion() { return 20250725; }
bool GetSeed(uint8_t out[48]) {
auto sampler(SeedSampler(AdaptiveProportionTest(RepetitionCountTest(
MaskSampler(kMask, DeltaSampler(MemoryAccessSampler(
kMemorySize, kMemoryAccessesPerSample)))))));
return sampler.Next(out);
}
bool GetSamples(uint64_t *out, size_t n) {
auto sampler(
DeltaSampler(MemoryAccessSampler(kMemorySize, kMemoryAccessesPerSample)));
for (size_t i = 0; i < n; i++) {
if (!sampler.Next(&out[i])) {
return false;
}
}
return true;
}
} // namespace entropy
BSSL_NAMESPACE_END
#endif // LINUX || MACOS