blob: 586e26c0163e93b5656f410da69ec66719db3b34 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <assert.h>
#include <lib/crypto/entropy_pool.h>
#include <lib/crypto/prng.h>
#include <lib/fit/defer.h>
#include <pow2.h>
#include <string.h>
#include <zircon/compiler.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <explicit-memory/bytes.h>
#include <kernel/mutex.h>
#include <ktl/atomic.h>
#include <ktl/move.h>
#include <openssl/chacha.h>
#include <openssl/sha.h>
#include <ktl/enforce.h>
namespace crypto {
namespace {
const uint128_t kNonceOverflow = ((uint128_t)1ULL) << 96;
static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__, "must be LE");
} // namespace
Prng::Prng(const void* data, size_t size) : Prng(data, size, NonThreadSafeTag()) {
BecomeThreadSafe();
}
Prng::Prng(const void* data, size_t size, NonThreadSafeTag tag) : nonce_(0), accumulated_(0) {
AddEntropy(data, size);
}
Prng::~Prng() {
Guard<SpinLock, IrqSave> spinlock_guard(&pool_lock_);
nonce_ = 0;
}
void Prng::AddEntropy(const void* data, size_t size) {
DEBUG_ASSERT(data || size == 0);
ASSERT(size <= kMaxEntropy);
// Concurrent calls to |AddEntropy| must run sequentially.
Guard<Mutex> add_entropy_guard(&add_entropy_lock_);
EntropyPool pool;
{
Guard<SpinLock, IrqSave> pool_guard(&pool_lock_);
pool = pool_.Clone();
}
pool.Add(ktl::span<const uint8_t>(reinterpret_cast<const uint8_t*>(data), size));
{
Guard<SpinLock, IrqSave> pool_guard(&pool_lock_);
pool_ = ktl::move(pool);
}
// Increment how much entropy has been added, and signal if we have enough.
size_t total_entropy = accumulated_.fetch_add(size) + size;
if (is_thread_safe() && total_entropy >= kMinEntropy) {
ready_->Signal();
}
}
// AddEntropy() with NULL input effectively reseeds with hash of current key.
void Prng::SelfReseed() { AddEntropy(nullptr, 0); }
void Prng::Draw(void* out, size_t size) {
DEBUG_ASSERT(out || size == 0);
ASSERT(size <= kMaxDrawLen);
// Wait if other threads should add entropy.
if (is_thread_safe() && accumulated_.load() < kMinEntropy) {
ready_->Wait();
}
// Save these on the stack, but guarantee we clean them up
EntropyPool pool;
uint128_t nonce;
auto cleanup = fit::defer([&] { mandatory_memset(&nonce, 0, sizeof(nonce)); });
{
Guard<SpinLock, IrqSave> pool_guard(&pool_lock_);
nonce = ++nonce_;
pool = pool_.Clone();
}
ASSERT(nonce < kNonceOverflow);
uint8_t* nonce_u8 = reinterpret_cast<uint8_t*>(&nonce);
uint8_t* buf = reinterpret_cast<uint8_t*>(out);
// We randomize |buf| by encrypting it with a key that is never exposed to
// the caller, and a 96-bit nonce that changes on each call. We don't zero
// |buf| because the encrypted output meets the criteria of the PRNG
// regardless of its original contents. We reset the counter to 0 on each
// request; it can't overflow because of the limit on the overall size.
CRYPTO_chacha_20(buf, buf, size, pool.contents().data(), nonce_u8, 0);
}
uint64_t Prng::RandInt(uint64_t exclusive_upper_bound) {
ASSERT(exclusive_upper_bound != 0);
const uint log2 = static_cast<uint>(log2_ceil<uint64_t>(exclusive_upper_bound));
const size_t mask =
(log2 != sizeof(uint64_t) * CHAR_BIT) ? (uint64_t(1) << log2) - 1 : UINT64_MAX;
DEBUG_ASSERT(exclusive_upper_bound - 1 <= mask);
// This loop should terminate very fast, since the probability that the
// drawn value is >= exclusive_upper_bound is less than 0.5. This is the
// classic discard out-of-range values approach.
while (true) {
uint64_t v;
Draw(reinterpret_cast<uint8_t*>(&v), sizeof(uint64_t) / sizeof(uint8_t));
v &= mask;
if (v < exclusive_upper_bound) {
return v;
}
}
}
// It is safe to call this function from PRNG's constructor provided
// |ready_| and |accumulated_| initialized.
void Prng::BecomeThreadSafe() {
ASSERT(!is_thread_safe());
ready_.Initialize(accumulated_.load() >= kMinEntropy);
is_thread_safe_ = true;
}
bool Prng::is_thread_safe() const {
// Safe to read |is_thread_safe_|; it is read-only in a threaded context.
return is_thread_safe_;
}
} // namespace crypto