1 // Copyright 2016 The Fuchsia Authors
2 //
3 // Use of this source code is governed by a MIT-style
4 // license that can be found in the LICENSE file or at
5 // https://opensource.org/licenses/MIT
6 
7 #include <lib/crypto/global_prng.h>
8 
9 #include <assert.h>
10 #include <ctype.h>
11 #include <err.h>
12 #include <explicit-memory/bytes.h>
13 #include <fbl/algorithm.h>
14 #include <kernel/auto_lock.h>
15 #include <kernel/cmdline.h>
16 #include <kernel/mutex.h>
17 #include <lib/crypto/cryptolib.h>
18 #include <lib/crypto/entropy/collector.h>
19 #include <lib/crypto/entropy/hw_rng_collector.h>
20 #include <lib/crypto/entropy/jitterentropy_collector.h>
21 #include <lib/crypto/entropy/quality_test.h>
22 #include <lib/crypto/prng.h>
23 #include <lk/init.h>
24 #include <new>
25 #include <string.h>
26 #include <trace.h>
27 
28 #define LOCAL_TRACE 0
29 
30 namespace crypto {
31 
32 namespace GlobalPRNG {
33 
34 static PRNG* kGlobalPrng = nullptr;
35 
GetInstance()36 PRNG* GetInstance() {
37     ASSERT(kGlobalPrng);
38     return kGlobalPrng;
39 }
40 
41 // Returns true if the kernel cmdline provided at least PRNG::kMinEntropy bytes
42 // of entropy, and false otherwise.
43 //
44 // TODO(security): Remove this in favor of virtio-rng once it is available and
45 // we decide we don't need it for getting entropy from elsewhere.
IntegrateCmdlineEntropy()46 static bool IntegrateCmdlineEntropy() {
47     const char* entropy = cmdline_get("kernel.entropy-mixin");
48     if (!entropy) {
49         return false;
50     }
51 
52     const size_t kMaxEntropyArgumentLen = 128;
53     const size_t hex_len = fbl::min(strlen(entropy), kMaxEntropyArgumentLen);
54 
55     for (size_t i = 0; i < hex_len; ++i) {
56         if (!isxdigit(entropy[i])) {
57             panic("Invalid entropy string: idx %zu is not an ASCII hex digit\n", i);
58         }
59     }
60 
61     uint8_t digest[clSHA256_DIGEST_SIZE];
62     clSHA256(entropy, static_cast<int>(hex_len), digest);
63     kGlobalPrng->AddEntropy(digest, sizeof(digest));
64 
65     // We have a pointer to const, but it's actually a pointer to the
66     // mutable global state in __kernel_cmdline that is still live (it
67     // will be copied into the userboot bootstrap message later).  So
68     // it's fully well-defined to cast away the const and mutate this
69     // here so the bits can't leak to userboot.  While we're at it,
70     // prettify the result a bit so it's obvious what one is looking at.
71     mandatory_memset(const_cast<char*>(entropy), 'x', hex_len);
72     if (hex_len >= sizeof(".redacted=") - 1) {
73         memcpy(const_cast<char*>(entropy) - 1,
74                ".redacted=", sizeof(".redacted=") - 1);
75     }
76 
77     const size_t entropy_added = fbl::max(hex_len / 2, sizeof(digest));
78     LTRACEF("Collected %zu bytes of entropy from the kernel cmdline.\n",
79             entropy_added);
80     return (entropy_added >= PRNG::kMinEntropy);
81 }
82 
83 // Returns true on success, false on failure.
SeedFrom(entropy::Collector * collector)84 static bool SeedFrom(entropy::Collector* collector) {
85     uint8_t buf[PRNG::kMinEntropy] = {0};
86     size_t remaining = collector->BytesNeeded(8 * PRNG::kMinEntropy);
87 #if LOCAL_TRACE
88     {
89         char name[ZX_MAX_NAME_LEN];
90         collector->get_name(name, sizeof(name));
91         LTRACEF("About to collect %zu bytes of entropy from '%s'.\n",
92                 remaining, name);
93     }
94 #endif
95     while (remaining > 0) {
96         size_t result = collector->DrawEntropy(
97                 buf, fbl::min(sizeof(buf), remaining));
98         if (result == 0) {
99             LTRACEF("Collected 0 bytes; aborting. "
100                     "There were %zu bytes remaining to collect.\n",
101                     remaining);
102             return false;
103         }
104         // TODO(ZX-1007): don't assume that every byte of entropy that's added
105         // has a full 8 bits worth of entropy
106         kGlobalPrng->AddEntropy(buf, result);
107         mandatory_memset(buf, 0, sizeof(buf));
108         remaining -= result;
109     }
110     LTRACEF("Successfully collected entropy.\n");
111     return true;
112 }
113 
114 // Instantiates the global PRNG (in non-thread-safe mode) and seeds it.
EarlyBootSeed(uint level)115 static void EarlyBootSeed(uint level) {
116     ASSERT(kGlobalPrng == nullptr);
117 
118     // Before doing anything else, test our entropy collector. This is
119     // explicitly called here rather than in another init hook to ensure
120     // ordering (at level LK_INIT_LEVEL_TARGET_EARLY, but before the rest of
121     // EarlyBootSeed).
122     entropy::EarlyBootTest();
123 
124     // Statically allocate an array of bytes to put the PRNG into.  We do this
125     // to control when the PRNG constructor is called.
126     // TODO(security): This causes the PRNG state to be in a fairly predictable
127     // place.  Some aspects of KASLR will help with this, but we may
128     // additionally want to remap where this is later.
129     alignas(alignof(PRNG))static uint8_t prng_space[sizeof(PRNG)];
130     kGlobalPrng = new (&prng_space) PRNG(nullptr, 0, PRNG::NonThreadSafeTag());
131 
132     // TODO(security): Have the PRNG reseed based on usage
133 
134     unsigned int successful = 0; // number of successful entropy sources
135     entropy::Collector* collector;
136     if (entropy::HwRngCollector::GetInstance(&collector) == ZX_OK &&
137         SeedFrom(collector)) {
138         successful++;
139     }
140     if (entropy::JitterentropyCollector::GetInstance(&collector) == ZX_OK &&
141         SeedFrom(collector)) {
142         successful++;
143     }
144 
145     if (IntegrateCmdlineEntropy()) {
146         successful++;
147     }
148     if (successful == 0) {
149         printf("WARNING: System has insufficient randomness.  It is completely "
150                "unsafe to use this system for any cryptographic applications."
151                "\n");
152         // TODO(security): *CRITICAL* This is a fallback for systems without RNG
153         // hardware that we should remove and attempt to do better.  If this
154         // fallback is used, it breaks all cryptography used on the system.
155         // *CRITICAL*
156         uint8_t buf[PRNG::kMinEntropy] = {0};
157         kGlobalPrng->AddEntropy(buf, sizeof(buf));
158         return;
159     } else {
160         LTRACEF("Successfully collected entropy from %u sources.\n",
161                 successful);
162     }
163 }
164 
165 // Migrate the global PRNG to enter thread-safe mode.
BecomeThreadSafe(uint level)166 static void BecomeThreadSafe(uint level) {
167     GetInstance()->BecomeThreadSafe();
168 }
169 
170 } //namespace GlobalPRNG
171 
172 } // namespace crypto
173 
174 
175 LK_INIT_HOOK(global_prng_seed, crypto::GlobalPRNG::EarlyBootSeed,
176              LK_INIT_LEVEL_TARGET_EARLY);
177 
178 LK_INIT_HOOK(global_prng_thread_safe, crypto::GlobalPRNG::BecomeThreadSafe,
179              LK_INIT_LEVEL_THREADING - 1)
180