1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include "secure.h"
5
6#include "dbg.h"
7#include "types.h"
8#include "windows.h"
9
10#include <cstddef> // size_t
11#include <mutex> // std::call_once
12
13#if defined(CONF_FAMILY_WINDOWS)
14#include <wtypes.h>
15// Need to include wtypes.h before wincrypt.h because the latter is missing the former include
16#include <wincrypt.h>
17#else
18#include "system.h" // io_open etc.
19#endif
20
21struct SECURE_RANDOM_DATA
22{
23 std::once_flag initialized_once_flag;
24#if defined(CONF_FAMILY_WINDOWS)
25 HCRYPTPROV provider;
26#else
27 IOHANDLE urandom;
28#endif
29};
30
31static struct SECURE_RANDOM_DATA secure_random_data = {};
32
33static void ensure_secure_random_init()
34{
35 std::call_once(once&: secure_random_data.initialized_once_flag, f: []() {
36#if defined(CONF_FAMILY_WINDOWS)
37 if(!CryptAcquireContext(&secure_random_data.provider, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
38 {
39 const DWORD LastError = GetLastError();
40 dbg_assert_failed("Failed to initialize secure random: CryptAcquireContext failure (%ld '%s')", LastError, windows_format_system_message(LastError).c_str());
41 }
42#else
43 secure_random_data.urandom = io_open(filename: "/dev/urandom", flags: IOFLAG_READ);
44 dbg_assert(secure_random_data.urandom != nullptr, "Failed to initialize secure random: failed to open /dev/urandom");
45#endif
46 });
47}
48
49void generate_password(char *buffer, unsigned length, const unsigned short *random, unsigned random_length)
50{
51 static const char VALUES[] = "ABCDEFGHKLMNPRSTUVWXYZabcdefghjkmnopqt23456789";
52 static const size_t NUM_VALUES = sizeof(VALUES) - 1; // Disregard the '\0'.
53 unsigned i;
54 dbg_assert(length >= random_length * 2 + 1, "too small buffer");
55 dbg_assert(NUM_VALUES * NUM_VALUES >= 2048, "need at least 2048 possibilities for 2-character sequences");
56
57 buffer[random_length * 2] = 0;
58
59 for(i = 0; i < random_length; i++)
60 {
61 unsigned short random_number = random[i] % 2048;
62 buffer[2 * i + 0] = VALUES[random_number / NUM_VALUES];
63 buffer[2 * i + 1] = VALUES[random_number % NUM_VALUES];
64 }
65}
66
67static constexpr unsigned MAX_PASSWORD_LENGTH = 128;
68
69void secure_random_password(char *buffer, unsigned length, unsigned pw_length)
70{
71 unsigned short random[MAX_PASSWORD_LENGTH / 2];
72 // With 6 characters, we get a password entropy of log(2048) * 6/2 = 33bit.
73 dbg_assert(length >= pw_length + 1, "too small buffer");
74 dbg_assert(pw_length >= 6, "too small password length");
75 dbg_assert(pw_length % 2 == 0, "need an even password length");
76 dbg_assert(pw_length <= MAX_PASSWORD_LENGTH, "too large password length");
77
78 secure_random_fill(bytes: random, length: pw_length);
79
80 generate_password(buffer, length, random, random_length: pw_length / 2);
81}
82
83void secure_random_fill(void *bytes, unsigned length)
84{
85 ensure_secure_random_init();
86#if defined(CONF_FAMILY_WINDOWS)
87 if(!CryptGenRandom(secure_random_data.provider, length, (unsigned char *)bytes))
88 {
89 const DWORD LastError = GetLastError();
90 dbg_assert_failed("CryptGenRandom failure (%ld '%s')", LastError, windows_format_system_message(LastError).c_str());
91 }
92#else
93 dbg_assert(length == io_read(secure_random_data.urandom, bytes, length), "io_read returned with a short read");
94#endif
95}
96
97// From https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2.
98static unsigned int find_next_power_of_two_minus_one(unsigned int n)
99{
100 n--;
101 n |= n >> 1;
102 n |= n >> 2;
103 n |= n >> 4;
104 n |= n >> 4;
105 n |= n >> 16;
106 return n;
107}
108
109int secure_rand_below(int below)
110{
111 unsigned int mask = find_next_power_of_two_minus_one(n: below);
112 dbg_assert(below > 0, "below must be positive");
113 while(true)
114 {
115 unsigned int n;
116 secure_random_fill(bytes: &n, length: sizeof(n));
117 n &= mask;
118 if((int)n < below)
119 {
120 return n;
121 }
122 }
123}
124