blob: ef09a8904e1dcd871c9d64393cd4648d1b78d66f [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 <string.h>
#include <err.h>
#include <explicit-memory/bytes.h>
#include <fbl/atomic.h>
#include <fbl/auto_call.h>
#include <fbl/mutex.h>
#include <kernel/auto_lock.h>
#include <lib/crypto/prng.h>
#include <pow2.h>
#include <zircon/compiler.h>
#include <zircon/types.h>
// See note in //zircon/third_party/ulib/uboringssl/rules.mk
#define BORINGSSL_NO_CXX
#include <openssl/chacha.h>
#include <openssl/sha.h>
namespace crypto {
namespace {
const uint128_t kNonceOverflow = ((uint128_t)1ULL) << 96;
} // namespace
PRNG::PRNG(const void* data, size_t size)
: PRNG(data, size, NonThreadSafeTag()) {
static_assert(sizeof(key_) == SHA256_DIGEST_LENGTH, "Bad key length");
BecomeThreadSafe();
}
PRNG::PRNG(const void* data, size_t size, NonThreadSafeTag tag)
: nonce_(0), accumulated_(0) {
memset(key_, 0, sizeof(key_));
memset(&ready_, 0, sizeof(ready_));
AddEntropy(data, size);
}
PRNG::~PRNG() {
mandatory_memset(key_, 0, sizeof(key_));
nonce_ = 0;
event_destroy(&ready_);
}
void PRNG::AddEntropy(const void* data, size_t size) {
DEBUG_ASSERT(data || size == 0);
ASSERT(size <= kMaxEntropy);
// Concurrent calls to |AddEntropy| must run sequentially.
fbl::AutoLock guard(&mutex_);
// Save the key on the stack, but guarantee we clean them up
uint8_t key[sizeof(key_)];
auto cleanup =
fbl::MakeAutoCall([&] { mandatory_memset(key, 0, sizeof(key)); });
// We mix all of the entropy with the previous key to make the PRNG state
// depend on both the entropy added and the sequence in which it was added.
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, data, size);
{
AutoSpinLock guard(&spinlock_);
memcpy(key, key_, sizeof(key));
}
SHA256_Update(&ctx, key, sizeof(key));
SHA256_Final(key, &ctx);
{
AutoSpinLock guard(&spinlock_);
memcpy(key_, key, sizeof(key_));
}
// Increment how much entropy has been added, and signal if we have enough.
if (is_thread_safe() &&
accumulated_.fetch_add(size) + size >= kMinEntropy) {
event_signal(&ready_, true /* reschedule */);
}
}
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) {
event_wait(&ready_);
}
// Save these on the stack, but guarantee we clean them up
uint8_t key[sizeof(key_)];
uint128_t nonce;
auto cleanup = fbl::MakeAutoCall([&] {
mandatory_memset(key, 0, sizeof(key));
nonce = 0;
});
{
AutoSpinLock guard(&spinlock_);
nonce = ++nonce_;
memcpy(key, key_, sizeof(key));
}
ASSERT(nonce < kNonceOverflow);
static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__, "must be LE");
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, key, nonce_u8, 0);
}
uint64_t PRNG::RandInt(uint64_t exclusive_upper_bound) {
ASSERT(exclusive_upper_bound != 0);
const uint log2 = log2_ulong_ceil(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(!event_initialized(&ready_));
event_init(&ready_, accumulated_.load() < kMinEntropy, 0);
}
bool PRNG::is_thread_safe() const {
// Safe to read event.magic; it is read-only in a threaded context
return event_initialized(&ready_);
}
} // namespace crypto