From fa2637a3beb8677067015df3d9d7b394fa837c2f Mon Sep 17 00:00:00 2001
From: Pieter Wuille <pieter.wuille@gmail.com>
Date: Sat, 16 Apr 2016 12:25:12 +0200
Subject: [PATCH] Always require OS randomness when generating secret keys

---
 src/Makefile.am       |  3 ++-
 src/init.cpp          |  2 --
 src/key.cpp           |  3 +--
 src/main.cpp          |  1 -
 src/random.cpp        | 48 ++++++++++++++++++++++++++++++++++++++++++-
 src/random.h          | 11 ++++++----
 src/wallet/wallet.cpp |  8 ++------
 7 files changed, 59 insertions(+), 17 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am
index 3c056386f..f630ad4aa 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -374,7 +374,8 @@ endif
 bitcoin_cli_LDADD = \
   $(LIBBITCOIN_CLI) \
   $(LIBUNIVALUE) \
-  $(LIBBITCOIN_UTIL)
+  $(LIBBITCOIN_UTIL) \
+  $(LIBBITCOIN_CRYPTO)
 
 bitcoin_cli_LDADD += $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_LIBS)
 #
diff --git a/src/init.cpp b/src/init.cpp
index 9b6943c58..98c089412 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1401,8 +1401,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
     if (!strErrors.str().empty())
         return InitError(strErrors.str());
 
-    RandAddSeedPerfmon();
-
     //// debug print
     LogPrintf("mapBlockIndex.size() = %u\n",   mapBlockIndex.size());
     LogPrintf("nBestHeight = %d\n",                   chainActive.Height());
diff --git a/src/key.cpp b/src/key.cpp
index 6a3d9aa14..79023566c 100644
--- a/src/key.cpp
+++ b/src/key.cpp
@@ -124,9 +124,8 @@ bool CKey::Check(const unsigned char *vch) {
 }
 
 void CKey::MakeNewKey(bool fCompressedIn) {
-    RandAddSeedPerfmon();
     do {
-        GetRandBytes(vch, sizeof(vch));
+        GetStrongRandBytes(vch, sizeof(vch));
     } while (!Check(vch));
     fValid = true;
     fCompressed = fCompressedIn;
diff --git a/src/main.cpp b/src/main.cpp
index ed157b53d..ffc57d48b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4547,7 +4547,6 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam
 
 bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams)
 {
-    RandAddSeedPerfmon();
     LogPrint("net", "received: %s (%u bytes) peer=%d\n", SanitizeString(strCommand), vRecv.size(), pfrom->id);
     if (mapArgs.count("-dropmessagestest") && GetRand(atoi(mapArgs["-dropmessagestest"])) == 0)
     {
diff --git a/src/random.cpp b/src/random.cpp
index 6155c0d8c..8ad0a9b00 100644
--- a/src/random.cpp
+++ b/src/random.cpp
@@ -5,9 +5,11 @@
 
 #include "random.h"
 
+#include "crypto/sha512.h"
 #include "support/cleanse.h"
 #ifdef WIN32
 #include "compat.h" // for Windows API
+#include <wincrypt.h>
 #endif
 #include "serialize.h"        // for begin_ptr(vec)
 #include "util.h"             // for LogPrint()
@@ -43,7 +45,7 @@ void RandAddSeed()
     memory_cleanse((void*)&nCounter, sizeof(nCounter));
 }
 
-void RandAddSeedPerfmon()
+static void RandAddSeedPerfmon()
 {
     RandAddSeed();
 
@@ -83,6 +85,29 @@ void RandAddSeedPerfmon()
 #endif
 }
 
+/** Get 32 bytes of system entropy. */
+static void GetOSRand(unsigned char *ent32)
+{
+#ifdef WIN32
+    HCRYPTPROV hProvider;
+    int ret = CryptAcquireContextW(&hProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
+    assert(ret);
+    ret = CryptGenRandom(hProvider, 32, ent32);
+    assert(ret);
+    CryptReleaseContext(hProvider, 0);
+#else
+    int f = open("/dev/urandom", O_RDONLY);
+    assert(f != -1);
+    int have = 0;
+    do {
+        ssize_t n = read(f, ent32 + have, 32 - have);
+        assert(n > 0 && n <= 32 - have);
+        have += n;
+    } while (have < 32);
+    close(f);
+#endif
+}
+
 void GetRandBytes(unsigned char* buf, int num)
 {
     if (RAND_bytes(buf, num) != 1) {
@@ -91,6 +116,27 @@ void GetRandBytes(unsigned char* buf, int num)
     }
 }
 
+void GetStrongRandBytes(unsigned char* out, int num)
+{
+    assert(num <= 32);
+    CSHA512 hasher;
+    unsigned char buf[64];
+
+    // First source: OpenSSL's RNG
+    RandAddSeedPerfmon();
+    GetRandBytes(buf, 32);
+    hasher.Write(buf, 32);
+
+    // Second source: OS RNG
+    GetOSRand(buf);
+    hasher.Write(buf, 32);
+
+    // Produce output
+    hasher.Finalize(buf);
+    memcpy(out, buf, num);
+    memory_cleanse(buf, 64);
+}
+
 uint64_t GetRand(uint64_t nMax)
 {
     if (nMax == 0)
diff --git a/src/random.h b/src/random.h
index 1a2d3e8ee..31b80bd56 100644
--- a/src/random.h
+++ b/src/random.h
@@ -10,11 +10,8 @@
 
 #include <stdint.h>
 
-/**
- * Seed OpenSSL PRNG with additional entropy data
- */
+/* Seed OpenSSL PRNG with additional entropy data */
 void RandAddSeed();
-void RandAddSeedPerfmon();
 
 /**
  * Functions to gather random data via the OpenSSL PRNG
@@ -24,6 +21,12 @@ uint64_t GetRand(uint64_t nMax);
 int GetRandInt(int nMax);
 uint256 GetRandHash();
 
+/**
+ * Function to gather random data from multiple sources, failing whenever any
+ * of those source fail to provide a result.
+ */
+void GetStrongRandBytes(unsigned char* buf, int num);
+
 /**
  * Seed insecure_rand using the random pool.
  * @param Deterministic Use a deterministic seed
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 5d1a43119..da0d6f272 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -509,16 +509,14 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
         return false;
 
     CKeyingMaterial vMasterKey;
-    RandAddSeedPerfmon();
 
     vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
-    GetRandBytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
+    GetStrongRandBytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
 
     CMasterKey kMasterKey;
-    RandAddSeedPerfmon();
 
     kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
-    GetRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
+    GetStrongRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
 
     CCrypter crypter;
     int64_t nStartTime = GetTimeMillis();
@@ -3147,8 +3145,6 @@ bool CWallet::InitLoadWallet()
     if (fFirstRun)
     {
         // Create new keyUser and set as default key
-        RandAddSeedPerfmon();
-
         CPubKey newDefaultKey;
         if (walletInstance->GetKeyFromPool(newDefaultKey)) {
             walletInstance->SetDefaultKey(newDefaultKey);