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/jitterentropy_collector.h>
20#include <lib/crypto/entropy/hw_rng_collector.h>
21#include <lib/crypto/entropy/quality_test.h>
22#include <lib/crypto/prng.h>
23#include <zxcpp/new.h>
24#include <lk/init.h>
25#include <string.h>
26#include <trace.h>
27
28#define LOCAL_TRACE 0
29
30namespace crypto {
31
32namespace GlobalPRNG {
33
34static PRNG* kGlobalPrng = nullptr;
35
36PRNG* 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.
46static 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.
84static 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.
115static 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.
166static void BecomeThreadSafe(uint level) {
167    GetInstance()->BecomeThreadSafe();
168}
169
170} //namespace GlobalPRNG
171
172} // namespace crypto
173
174
175LK_INIT_HOOK(global_prng_seed, crypto::GlobalPRNG::EarlyBootSeed,
176             LK_INIT_LEVEL_TARGET_EARLY);
177
178LK_INIT_HOOK(global_prng_thread_safe, crypto::GlobalPRNG::BecomeThreadSafe,
179             LK_INIT_LEVEL_THREADING - 1)
180