Skip to content

Commit b96bbe5

Browse files
committed
New osrandom_engine in C (WIP)
Inspired by Python/random.c and the old implementation. Signed-off-by: Christian Heimes <[email protected]>
1 parent fb0e719 commit b96bbe5

File tree

6 files changed

+418
-43
lines changed

6 files changed

+418
-43
lines changed

src/_cffi_src/build_openssl.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def _osx_libraries(build_static):
6868
"objects",
6969
"ocsp",
7070
"opensslv",
71+
"osrandom_engine",
7172
"pem",
7273
"pkcs12",
7374
"rand",
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# This file is dual licensed under the terms of the Apache License, Version
2+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3+
# for complete details.
4+
5+
from __future__ import absolute_import, division, print_function
6+
7+
import os
8+
9+
HERE = os.path.dirname(os.path.abspath(__file__))
10+
11+
with open(os.path.join(HERE, "src/osrandom_engine.h")) as f:
12+
INCLUDES = f.read()
13+
14+
TYPES = """
15+
static const char *const Cryptography_osrandom_engine_name;
16+
static const char *const Cryptography_osrandom_engine_id;
17+
"""
18+
19+
FUNCTIONS = """
20+
int Cryptography_add_osrandom_engine(void);
21+
"""
22+
23+
MACROS = """
24+
"""
25+
26+
with open(os.path.join(HERE, "src/osrandom_engine.c")) as f:
27+
CUSTOMIZATIONS = f.read()
28+
29+
CONDITIONAL_NAMES = {}
Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
/* Largely inspired by Python/random.c and the old implementation of osrandom_engine.c */
2+
3+
static const char *Cryptography_osrandom_engine_id = "osrandom";
4+
static const char *Cryptography_osrandom_engine_name = CRYPTOGRAPHY_OSRANDOM_ENGINE_NAME;
5+
6+
/****************************************************************************
7+
* Windows
8+
*/
9+
#if defined(_WIN32)
10+
#define RANDOM_ENGINE 1
11+
12+
static HCRYPTPROV hCryptProv = 0;
13+
14+
static int osrandom_init(ENGINE *e) {
15+
if (hCryptProv != 0) {
16+
return 1;
17+
}
18+
if (CryptAcquireContext(&hCryptProv, NULL, NULL,
19+
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
20+
return 1;
21+
} else {
22+
return 0;
23+
}
24+
}
25+
26+
static int osrandom_rand_bytes(unsigned char *buffer, int size) {
27+
if (hCryptProv == 0) {
28+
return 0;
29+
}
30+
31+
if (!CryptGenRandom(hCryptProv, (DWORD)size, buffer)) {
32+
ERR_put_error(
33+
ERR_LIB_RAND, 0, ERR_R_RAND_LIB,
34+
"osrandom_engine.py:CryptGenRandom()", 0
35+
);
36+
return 0;
37+
}
38+
return 1;
39+
}
40+
41+
static int osrandom_finish(ENGINE *e) {
42+
if (CryptReleaseContext(hCryptProv, 0)) {
43+
hCryptProv = 0;
44+
return 1;
45+
} else {
46+
return 0;
47+
}
48+
}
49+
50+
static int osrandom_rand_status(void) {
51+
if (hCryptProv == 0) {
52+
return 0;
53+
} else {
54+
return 1;
55+
}
56+
}
57+
#elif defined(CRYPTOGRAPHY_HAVE_GETENTROPY)
58+
59+
/****************************************************************************
60+
* BSD getentropy
61+
*/
62+
static int osrandom_init(ENGINE *e) {
63+
return 1;
64+
}
65+
66+
static int osrandom_rand_bytes(unsigned char *buffer, int size) {
67+
Py_ssize_t len;
68+
int res;
69+
while (size > 0) {
70+
len = Py_MIN(size, 256);
71+
res = getentropy(buffer, len);
72+
if (res < 0) {
73+
ERR_put_error(
74+
ERR_LIB_RAND, 0, ERR_R_RAND_LIB,
75+
"osrandom_engine.py:getentropy()", 0
76+
);
77+
return 0;
78+
}
79+
buffer += len;
80+
size -= len;
81+
}
82+
return 1;
83+
}
84+
85+
static int osrandom_finish(ENGINE *e) {
86+
return 1;
87+
}
88+
89+
static int osrandom_rand_status(void) {
90+
return 1;
91+
}
92+
93+
#else /* not _WIN32 and not BSD CRYPTOGRAPHY_HAVE_GETENTROPY */
94+
95+
/****************************************************************************
96+
* /dev/urandom helpers for all non-BSD Unix platforms
97+
*/
98+
99+
static struct {
100+
int fd;
101+
dev_t st_dev;
102+
ino_t st_ino;
103+
} urandom_cache = { -1 };
104+
105+
/* return -1 on error */
106+
static int dev_urandom_fd() {
107+
int fd, n, flags;
108+
struct stat st;
109+
110+
/* Check that fd still points to the correct device */
111+
if (urandom_cache.fd >= 0) {
112+
if (fstat(urandom_cache.fd, &st)
113+
|| st.st_dev != urandom_cache.st_dev
114+
|| st.st_ino != urandom_cache.st_ino) {
115+
urandom_cache.fd = -1;
116+
}
117+
}
118+
if (urandom_cache.fd < 0) {
119+
fd = open("/dev/urandom", O_RDONLY);
120+
if (fd < 0) {
121+
goto error;
122+
}
123+
if (fstat(fd, &st)) {
124+
goto error;
125+
}
126+
/* Another thread initialized the fd */
127+
if (urandom_cache.fd >= 0) {
128+
do {
129+
n = close(fd);
130+
} while (n < 0 && errno == EINTR);
131+
return urandom_cache.fd;
132+
}
133+
flags = fcntl(fd, F_GETFD);
134+
if (flags == -1) {
135+
goto error;
136+
} else if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
137+
goto error;
138+
}
139+
urandom_cache.st_dev = st.st_dev;
140+
urandom_cache.st_ino = st.st_ino;
141+
urandom_cache.fd = fd;
142+
}
143+
return urandom_cache.fd;
144+
145+
error:
146+
if (fd != -1) {
147+
do {
148+
n = close(fd);
149+
} while (n < 0 && errno == EINTR);
150+
}
151+
ERR_put_error(
152+
ERR_LIB_RAND, 0, ERR_R_RAND_LIB,
153+
"osrandom_engine.py:dev_urandom_fd()", 0);
154+
return -1;
155+
}
156+
157+
static int dev_urandom_read(unsigned char *buffer, int size) {
158+
int fd, n;
159+
160+
fd = dev_urandom_fd();
161+
if (fd < 0) {
162+
return 0;
163+
}
164+
165+
while (size > 0) {
166+
do {
167+
n = read(fd, buffer, (size_t)size);
168+
} while (n < 0 && errno == EINTR);
169+
170+
if (n <= 0) {
171+
ERR_put_error(
172+
ERR_LIB_RAND, 0, ERR_R_RAND_LIB,
173+
"osrandom_engine.py:dev_urandom_read()", 0);
174+
return 0;
175+
}
176+
buffer += n;
177+
size -= n;
178+
}
179+
return 1;
180+
}
181+
182+
static void dev_urandom_close() {
183+
if (urandom_cache.fd >= 0) {
184+
int fd, n;
185+
struct stat st;
186+
187+
if (fstat(urandom_cache.fd, &st)
188+
&& st.st_dev == urandom_cache.st_dev
189+
&& st.st_ino == urandom_cache.st_ino) {
190+
fd = urandom_cache.fd;
191+
urandom_cache.fd = -1;
192+
do {
193+
n = close(fd);
194+
} while (n < 0 && errno == EINTR);
195+
}
196+
}
197+
}
198+
199+
/****************************************************************************
200+
* Linux getrandom engine with fallback to dev_urandom
201+
*/
202+
203+
#ifdef CRYPTOGRAPHY_HAVE_SYS_GETRANDOM
204+
static int getrandom_works = -1;
205+
206+
static int osrandom_init(ENGINE *e) {
207+
if (getrandom_works == -1) {
208+
long n;
209+
char dest[1];
210+
n = syscall(SYS_getrandom, dest, sizeof(dest), GRND_NONBLOCK);
211+
/* TODO: EAGAIN when Kernel RNG is not initialized. */
212+
if (n < 0 && (errno == ENOSYS || errno == EPERM)) {
213+
getrandom_works = 0;
214+
} else {
215+
getrandom_works = 1;
216+
}
217+
}
218+
/* fallback to dev urandom */
219+
if (getrandom_works == 0) {
220+
int fd = dev_urandom_fd();
221+
if (fd < 0) {
222+
return 0;
223+
}
224+
}
225+
return 1;
226+
}
227+
228+
static int osrandom_rand_bytes(unsigned char *buffer, int size) {
229+
if (getrandom_works == 1) {
230+
long n;
231+
while (size > 0) {
232+
do {
233+
n = syscall(SYS_getrandom, buffer, size, GRND_NONBLOCK);
234+
} while (n < 0 && errno == EINTR);
235+
236+
if (n <= 0) {
237+
ERR_put_error(
238+
ERR_LIB_RAND, 0, ERR_R_RAND_LIB,
239+
"osrandom_engine.py:SYS_getrandom", 0);
240+
return 0;
241+
}
242+
buffer += n;
243+
size -= n;
244+
}
245+
return 1;
246+
} else {
247+
return dev_urandom_read(buffer, size);
248+
}
249+
}
250+
251+
static int osrandom_finish(ENGINE *e) {
252+
dev_urandom_close();
253+
return 1;
254+
}
255+
256+
static int osrandom_rand_status(void) {
257+
if ((getrandom_works != 1) && (urandom_cache.fd < 0)) {
258+
return 0;
259+
}
260+
return 1;
261+
}
262+
#endif /* CRYPTOGRAPHY_HAVE_SYS_GETRANDOM */
263+
264+
/****************************************************************************
265+
* dev_urandom engine for all remaining platforms
266+
*/
267+
268+
#ifdef CRYPTOGRAPHY_USE_DEV_URANDOM
269+
270+
static int osrandom_init(ENGINE *e) {
271+
int fd = dev_urandom_fd();
272+
if (fd < 0) {
273+
return 0;
274+
}
275+
return 1;
276+
}
277+
278+
static int osrandom_rand_bytes(unsigned char *buffer, int size) {
279+
return dev_urandom_read(buffer, size);
280+
}
281+
282+
static int osrandom_finish(ENGINE *e) {
283+
dev_urandom_close();
284+
return 1;
285+
}
286+
287+
static int osrandom_rand_status(void) {
288+
if (urandom_cache.fd < 0) {
289+
return 0;
290+
}
291+
return 1;
292+
}
293+
294+
#endif /* CRYPTOGRAPHY_USE_DEV_URANDOM */
295+
#endif /* _WIN32 */
296+
297+
/* This replicates the behavior of the OpenSSL FIPS RNG, which returns a
298+
-1 in the event that there is an error when calling RAND_pseudo_bytes. */
299+
static int osrandom_pseudo_rand_bytes(unsigned char *buffer, int size) {
300+
int res = osrandom_rand_bytes(buffer, size);
301+
if (res == 0) {
302+
return -1;
303+
} else {
304+
return res;
305+
}
306+
}
307+
308+
static RAND_METHOD osrandom_rand = {
309+
NULL,
310+
osrandom_rand_bytes,
311+
NULL,
312+
NULL,
313+
osrandom_pseudo_rand_bytes,
314+
osrandom_rand_status,
315+
};
316+
317+
/* Returns 1 if successfully added, 2 if engine has previously been added,
318+
and 0 for error. */
319+
int Cryptography_add_osrandom_engine(void) {
320+
ENGINE *e;
321+
e = ENGINE_by_id(Cryptography_osrandom_engine_id);
322+
if (e != NULL) {
323+
ENGINE_free(e);
324+
return 2;
325+
} else {
326+
ERR_clear_error();
327+
}
328+
329+
e = ENGINE_new();
330+
if (e == NULL) {
331+
return 0;
332+
}
333+
if(!ENGINE_set_id(e, Cryptography_osrandom_engine_id) ||
334+
!ENGINE_set_name(e, Cryptography_osrandom_engine_name) ||
335+
!ENGINE_set_RAND(e, &osrandom_rand) ||
336+
!ENGINE_set_init_function(e, osrandom_init) ||
337+
!ENGINE_set_finish_function(e, osrandom_finish)) {
338+
ENGINE_free(e);
339+
return 0;
340+
}
341+
if (!ENGINE_add(e)) {
342+
ENGINE_free(e);
343+
return 0;
344+
}
345+
if (!ENGINE_free(e)) {
346+
return 0;
347+
}
348+
349+
return 1;
350+
}

0 commit comments

Comments
 (0)