summaryrefslogtreecommitdiffstats
path: root/libc-top-half/sources/arc4random.c
blob: 9898206bb78ee87c829603e3edfca202a69c8057 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define RNG_RESERVE_LEN 512

#define CHACHA20_KEYBYTES 32
#define CHACHA20_BLOCKBYTES 64

#define ROTL32(x, b) (uint32_t)(((x) << (b)) | ((x) >> (32 - (b))))

#define CHACHA20_QUARTERROUND(A, B, C, D) \
    A += B;                               \
    D = ROTL32(D ^ A, 16);                \
    C += D;                               \
    B = ROTL32(B ^ C, 12);                \
    A += B;                               \
    D = ROTL32(D ^ A, 8);                 \
    C += D;                               \
    B = ROTL32(B ^ C, 7)

static void CHACHA20_ROUNDS(uint32_t st[16])
{
    int i;

    for (i = 0; i < 20; i += 2) {
        CHACHA20_QUARTERROUND(st[0], st[4], st[8], st[12]);
        CHACHA20_QUARTERROUND(st[1], st[5], st[9], st[13]);
        CHACHA20_QUARTERROUND(st[2], st[6], st[10], st[14]);
        CHACHA20_QUARTERROUND(st[3], st[7], st[11], st[15]);
        CHACHA20_QUARTERROUND(st[0], st[5], st[10], st[15]);
        CHACHA20_QUARTERROUND(st[1], st[6], st[11], st[12]);
        CHACHA20_QUARTERROUND(st[2], st[7], st[8], st[13]);
        CHACHA20_QUARTERROUND(st[3], st[4], st[9], st[14]);
    }
}

static void chacha20_update(uint8_t out[CHACHA20_BLOCKBYTES], uint32_t st[16])
{
    uint32_t ks[16];
    int i;

    memcpy(ks, st, 4 * 16);
    CHACHA20_ROUNDS(st);
    for (i = 0; i < 16; i++) {
        ks[i] += st[i];
    }
    memcpy(out, ks, CHACHA20_BLOCKBYTES);
    st[12]++;
}

static void chacha20_init(uint32_t st[16], const uint8_t key[CHACHA20_KEYBYTES])
{
    static const uint32_t constants[4] = { 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 };
    memcpy(&st[0], constants, 4 * 4);
    memcpy(&st[4], key, CHACHA20_KEYBYTES);
    memset(&st[12], 0, 4 * 4);
}

static int chacha20_rng(uint8_t* out, size_t len, uint8_t key[CHACHA20_KEYBYTES])
{
    uint32_t st[16];
    size_t off;

    chacha20_init(st, key);
    chacha20_update(&out[0], st);
    memcpy(key, out, CHACHA20_KEYBYTES);
    off = 0;
    while (len >= CHACHA20_BLOCKBYTES) {
        chacha20_update(&out[off], st);
        len -= CHACHA20_BLOCKBYTES;
        off += CHACHA20_BLOCKBYTES;
    }
    if (len > 0) {
        uint8_t tmp[CHACHA20_BLOCKBYTES];
        chacha20_update(tmp, st);
        memcpy(&out[off], tmp, len);
    }
    return 0;
}

struct rng_state {
    int initialized;
    size_t off;
    uint8_t key[CHACHA20_KEYBYTES];
    uint8_t reserve[RNG_RESERVE_LEN];
};

void arc4random_buf(void* buffer, size_t len)
{
    static _Thread_local struct rng_state rng_state;

    unsigned char* buffer_ = (unsigned char*)buffer;
    size_t off;
    size_t remaining;
    size_t partial;

    if (!rng_state.initialized) {
        if (getentropy(rng_state.key, sizeof rng_state.key) != 0) {
            assert(0);
        }
        rng_state.off = RNG_RESERVE_LEN;
        rng_state.initialized = 1;
    }
    off = 0;
    remaining = len;
    while (remaining > 0) {
        if (rng_state.off == RNG_RESERVE_LEN) {
            while (remaining >= RNG_RESERVE_LEN) {
                chacha20_rng(&buffer_[off], RNG_RESERVE_LEN, rng_state.key);
                off += RNG_RESERVE_LEN;
                remaining -= RNG_RESERVE_LEN;
            }
            if (remaining == 0) {
                break;
            }
            chacha20_rng(&rng_state.reserve[0], RNG_RESERVE_LEN, rng_state.key);
            rng_state.off = 0;
        }
        partial = RNG_RESERVE_LEN - rng_state.off;
        if (remaining < partial) {
            partial = remaining;
        }
        memcpy(&buffer_[off], &rng_state.reserve[rng_state.off], partial);
        memset(&rng_state.reserve[rng_state.off], 0, partial);
        rng_state.off += partial;
        remaining -= partial;
        off += partial;
    }
}

uint32_t arc4random(void)
{
    uint32_t v;

    arc4random_buf(&v, sizeof v);

    return v;
}

uint32_t arc4random_uniform(const uint32_t upper_bound)
{
    uint32_t min;
    uint32_t r;

    if (upper_bound < 2U) {
        return 0;
    }
    min = (1U + ~upper_bound) % upper_bound; // = 2**32 mod upper_bound
    do {
        r = arc4random();
    } while (r < min);

    // r is now clamped to a set whose size mod upper_bound == 0.
    // The worst case (2**31+1) requires 2 attempts on average.

    return r % upper_bound;
}