| // 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 <lib/crypto/prng.h> |
| |
| #include <assert.h> |
| #include <string.h> |
| |
| #include <err.h> |
| #include <lib/crypto/cryptolib.h> |
| #include <zircon/compiler.h> |
| #include <zircon/types.h> |
| #include <fbl/auto_lock.h> |
| #include <openssl/chacha.h> |
| #include <pow2.h> |
| |
| namespace crypto { |
| |
| PRNG::PRNG(const void* data, size_t size) |
| : PRNG(data, size, NonThreadSafeTag()) { |
| BecomeThreadSafe(); |
| } |
| |
| PRNG::PRNG(const void* data, size_t size, NonThreadSafeTag tag) |
| : is_thread_safe_(false), lock_(), total_entropy_added_(0) { |
| memset(key_, 0, sizeof(key_)); |
| memset(nonce_.u8, 0, sizeof(nonce_.u8)); |
| AddEntropy(data, size); |
| } |
| |
| void PRNG::AddEntropy(const void* data, size_t size) { |
| DEBUG_ASSERT(data || size == 0); |
| if (likely(is_thread_safe_)) { |
| uint64_t total; |
| { |
| fbl::AutoLock guard(&lock_); |
| AddEntropyInternal(data, size); |
| total = total_entropy_added_; |
| } |
| if (total >= kMinEntropy) { |
| event_signal(&ready_, true); |
| } |
| } else { |
| AddEntropyInternal(data, size); |
| } |
| } |
| |
| static_assert(PRNG::kMaxEntropy <= INT_MAX, "bad entropy limit"); |
| static_assert(sizeof(uint32_t) * 2 <= clSHA256_DIGEST_SIZE, "digest too small"); |
| void PRNG::AddEntropyInternal(const void* data, size_t size) { |
| ASSERT(size < kMaxEntropy); |
| clSHA256_CTX ctx; |
| clSHA256_init(&ctx); |
| // 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. |
| clHASH_update(&ctx, data, static_cast<int>(size)); |
| clHASH_update(&ctx, key_, sizeof(key_)); |
| static_assert(clSHA256_DIGEST_SIZE <= sizeof(key_), "key too small"); |
| memcpy(key_, clHASH_final(&ctx), clSHA256_DIGEST_SIZE); |
| total_entropy_added_ += size; |
| } |
| |
| void PRNG::Draw(void* out, size_t size) { |
| DEBUG_ASSERT(out || size == 0); |
| if (likely(is_thread_safe_)) { |
| fbl::AutoLock guard(&lock_); |
| if (unlikely(total_entropy_added_ < kMinEntropy)) { |
| lock_.Release(); |
| zx_status_t status = event_wait(&ready_); |
| ASSERT(status == ZX_OK); |
| lock_.Acquire(); |
| } |
| DrawInternal(out, size); |
| } else { |
| DrawInternal(out, size); |
| } |
| } |
| |
| void PRNG::DrawInternal(void* out, size_t size) { |
| ASSERT(size < kMaxDrawLen); |
| uint8_t* buf = static_cast<uint8_t*>(out); |
| // CRYPTO_chacha_20 XORs the cipher stream with the contents of the 'in' |
| // buffer (which can't be null). Zero it to get just the cipher stream. |
| // TODO(aarongreen): try to get BoringSSL to take a patch which allows 'in' |
| // to be null. |
| memset(buf, 0, size); |
| // We use a unique nonce for each request, meaning we can reset the |
| // counter to 0 each time. The counter is guaranteed not to overflow within |
| // the call below because of the above assertion on the overall size. |
| CRYPTO_chacha_20(buf, buf, size, key_, nonce_.u8, 0); |
| // We use a different 12-byte nonce for each request by treating it as the |
| // concatenation of an 8-byte and 4-byte counter. Every time the 8-byte |
| // counter overflows, we increment the 4-byte counter. The 4-byte counter |
| // must not overflow, meaning we can make a total of 2**96 requests. Even |
| // at 1000 requests per nanosecond, this would take 2.5 billion years. |
| ++nonce_.u64; |
| if (unlikely(nonce_.u64 == 0)) { |
| ++nonce_.u32[2]; |
| ASSERT(nonce_.u32[2] != 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 |
| // |is_thread_safe_| and |total_entropy_added_| are initialized. |
| void PRNG::BecomeThreadSafe() { |
| ASSERT(!is_thread_safe_); |
| |
| const bool enough_entropy = (total_entropy_added_ >= kMinEntropy); |
| ready_ = EVENT_INITIAL_VALUE(ready_, enough_entropy, 0); |
| |
| is_thread_safe_ = true; |
| } |
| |
| PRNG::~PRNG() {} |
| |
| } // namespace crypto |