1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2013-2016 Jakub Zelenka                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Author: Jakub Zelenka <bukka@php.net>                                |
16   +----------------------------------------------------------------------+
17 */
18 
19 #include "php.h"
20 #include "php_crypto.h"
21 #include "php_crypto_rand.h"
22 #include "zend_exceptions.h"
23 
24 #include <openssl/rand.h>
25 #include <openssl/err.h>
26 
27 PHP_CRYPTO_EXCEPTION_DEFINE(Rand)
28 PHP_CRYPTO_ERROR_INFO_BEGIN(Rand)
29 PHP_CRYPTO_ERROR_INFO_ENTRY(
30 	GENERATE_PREDICTABLE,
31 	"The PRNG state is not yet unpredictable"
32 )
33 PHP_CRYPTO_ERROR_INFO_ENTRY(
34 	FILE_WRITE_PREDICTABLE,
35 	"The bytes written were generated without appropriate seed"
36 )
37 PHP_CRYPTO_ERROR_INFO_ENTRY(
38 	REQUESTED_BYTES_NUMBER_TOO_HIGH,
39 	"The requested number of bytes is too high"
40 )
41 PHP_CRYPTO_ERROR_INFO_ENTRY(
42 	SEED_LENGTH_TOO_HIGH,
43 	"The supplied seed length is too high"
44 )
45 PHP_CRYPTO_ERROR_INFO_END()
46 
47 ZEND_BEGIN_ARG_INFO_EX(arginfo_crypto_rand_generate, 0, 0, 1)
48 ZEND_ARG_INFO(0, num)
49 ZEND_ARG_INFO(0, must_be_strong)
50 ZEND_ARG_INFO(1, returned_strong_result)
51 ZEND_END_ARG_INFO()
52 
53 ZEND_BEGIN_ARG_INFO_EX(arginfo_crypto_rand_seed, 0, 0, 1)
54 ZEND_ARG_INFO(0, buf)
55 ZEND_ARG_INFO(0, entropy)
56 ZEND_END_ARG_INFO()
57 
58 ZEND_BEGIN_ARG_INFO_EX(arginfo_crypto_rand_load_file, 0, 0, 1)
59 ZEND_ARG_INFO(0, filename)
60 ZEND_ARG_INFO(0, max_bytes)
61 ZEND_END_ARG_INFO()
62 
63 ZEND_BEGIN_ARG_INFO(arginfo_crypto_rand_write_file, 0)
64 ZEND_ARG_INFO(0, filename)
65 ZEND_END_ARG_INFO()
66 
67 static const zend_function_entry php_crypto_rand_object_methods[] = {
68 	PHP_CRYPTO_ME(
69 		Rand, generate,
70 		arginfo_crypto_rand_generate,
71 		ZEND_ACC_STATIC|ZEND_ACC_PUBLIC
72 	)
73 	PHP_CRYPTO_ME(
74 		Rand, seed,
75 		arginfo_crypto_rand_seed,
76 		ZEND_ACC_STATIC|ZEND_ACC_PUBLIC
77 	)
78 	PHP_CRYPTO_ME(
79 		Rand, cleanup,
80 		NULL,
81 		ZEND_ACC_STATIC|ZEND_ACC_PUBLIC
82 	)
83 	PHP_CRYPTO_ME(
84 		Rand, loadFile,
85 		arginfo_crypto_rand_load_file,
86 		ZEND_ACC_STATIC|ZEND_ACC_PUBLIC
87 	)
88 	PHP_CRYPTO_ME(
89 		Rand, writeFile,
90 		arginfo_crypto_rand_write_file,
91 		ZEND_ACC_STATIC|ZEND_ACC_PUBLIC
92 	)
93 	PHPC_FE_END
94 };
95 
96 /* class entry */
97 PHP_CRYPTO_API zend_class_entry *php_crypto_rand_ce;
98 
99 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(crypto_rand)100 PHP_MINIT_FUNCTION(crypto_rand)
101 {
102 	zend_class_entry ce;
103 
104 	/* Rand class */
105 	INIT_CLASS_ENTRY(ce, PHP_CRYPTO_CLASS_NAME(Rand),
106 			php_crypto_rand_object_methods);
107 	php_crypto_rand_ce = PHPC_CLASS_REGISTER(ce);
108 
109 	/* RandException class */
110 	PHP_CRYPTO_EXCEPTION_REGISTER(ce, Rand);
111 	PHP_CRYPTO_ERROR_INFO_REGISTER(Rand);
112 
113 	return SUCCESS;
114 }
115 /* }}} */
116 
117 /* {{{ proto static string Crypto\Rand::generate(
118 			int $num, bool $must_be_strong = true,
119 			&bool $returned_strong_result = true)
120 	Generates pseudo random bytes */
PHP_CRYPTO_METHOD(Rand,generate)121 PHP_CRYPTO_METHOD(Rand, generate)
122 {
123 	phpc_long_t num_long;
124 	int num;
125 	PHPC_STR_DECLARE(buf);
126 	zval *zstrong_result = NULL;
127 	zend_bool strong_result, must_be_strong = 1;
128 
129 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|bz/",
130 				&num_long, &must_be_strong, &zstrong_result) == FAILURE) {
131 		return;
132 	}
133 
134 	if (php_crypto_long_to_int(num_long, &num) == FAILURE) {
135 		php_crypto_error(PHP_CRYPTO_ERROR_ARGS(Rand, REQUESTED_BYTES_NUMBER_TOO_HIGH));
136 		RETURN_FALSE;
137 	}
138 
139 
140 	PHPC_STR_ALLOC(buf, num);
141 
142 	if (must_be_strong) {
143 		if (!RAND_bytes((unsigned char *) PHPC_STR_VAL(buf), num)) {
144 			php_crypto_error(PHP_CRYPTO_ERROR_ARGS(Rand, GENERATE_PREDICTABLE));
145 			PHPC_STR_RELEASE(buf);
146 			RETURN_FALSE;
147 		}
148 		strong_result = 1;
149 	} else {
150 		strong_result = RAND_pseudo_bytes((unsigned char *) PHPC_STR_VAL(buf), num);
151 	}
152 	if (zstrong_result) {
153 		ZVAL_BOOL(zstrong_result, strong_result);
154 	}
155 	PHPC_STR_VAL(buf)[num] = '\0';
156 	PHPC_STR_RETURN(buf);
157 }
158 /* }}} */
159 
160 /* {{{ proto static void Crypto\Rand::seed(
161 			string $buf, float $entropy = (float) strlen($buf))
162 	Mixes bytes in $buf into PRNG state */
PHP_CRYPTO_METHOD(Rand,seed)163 PHP_CRYPTO_METHOD(Rand, seed)
164 {
165 	char *buf;
166 	phpc_str_size_t buf_str_size;
167 	int buf_len;
168 	double entropy;
169 
170 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|d",
171 				&buf, &buf_str_size, &entropy) == FAILURE) {
172 		return;
173 	}
174 
175 	if (php_crypto_str_size_to_int(buf_str_size, &buf_len) == FAILURE) {
176 		php_crypto_error(PHP_CRYPTO_ERROR_ARGS(Rand, SEED_LENGTH_TOO_HIGH));
177 		RETURN_NULL();
178 	}
179 
180 	if (ZEND_NUM_ARGS() == 1) {
181 		entropy = (double) buf_len;
182 	}
183 
184 	RAND_add(buf, buf_len, entropy);
185 }
186 /* }}} */
187 
188 /* {{{ proto static void Crypto\Rand::cleanup()
189    Cleans up PRNG state */
PHP_CRYPTO_METHOD(Rand,cleanup)190 PHP_CRYPTO_METHOD(Rand, cleanup)
191 {
192 	if (zend_parse_parameters_none() == FAILURE) {
193 		return;
194 	}
195 	RAND_cleanup();
196 	RETURN_NULL();
197 }
198 /* }}} */
199 
200 /* {{{ proto static int Crypto\Rand::loadFile(string $filename, int $max_bytes = -1)
201 	Reads a number of bytes from file $filename and adds them
202 	to the PRNG. If max_bytes is non-negative, up to to max_bytes
203 	are read; if $max_bytes is negative, the complete file is read */
PHP_CRYPTO_METHOD(Rand,loadFile)204 PHP_CRYPTO_METHOD(Rand, loadFile)
205 {
206 	char *path;
207 	phpc_str_size_t path_len;
208 	phpc_long_t max_bytes_len = -1;
209 	int max_bytes;
210 
211 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, PHPC_PATH_ZPP_FLAG"|l",
212 			&path, &path_len, &max_bytes_len) == FAILURE) {
213 		return;
214 	}
215 
216 	if (php_crypto_long_to_int(max_bytes_len, &max_bytes) == FAILURE) {
217 		php_crypto_error(PHP_CRYPTO_ERROR_ARGS(Rand, REQUESTED_BYTES_NUMBER_TOO_HIGH));
218 		RETURN_FALSE;
219 	}
220 
221 	if (max_bytes < -1) {
222 		max_bytes = -1;
223 	}
224 
225 	RETURN_LONG(RAND_load_file(path, max_bytes));
226 }
227 /* }}} */
228 
229 
230 /* {{{ proto static int Crypto\Rand::writeFile(string $filename)
231 	Writes a number of random bytes (currently 1024) to file $filename
232 	which can be used to initializethe PRNG by calling
233 	Crypto\Rand::loadFile() in a later session */
PHP_CRYPTO_METHOD(Rand,writeFile)234 PHP_CRYPTO_METHOD(Rand, writeFile)
235 {
236 	char *path;
237 	phpc_str_size_t path_len;
238 	int bytes_written;
239 
240 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, PHPC_PATH_ZPP_FLAG,
241 			&path, &path_len) == FAILURE) {
242 		return;
243 	}
244 
245 	bytes_written = RAND_write_file(path);
246 	if (bytes_written < 0) {
247 		php_crypto_error(PHP_CRYPTO_ERROR_ARGS(Rand, FILE_WRITE_PREDICTABLE));
248 		RETURN_FALSE;
249 	} else {
250 		RETURN_LONG((phpc_long_t) bytes_written);
251 	}
252 }
253 /* }}} */
254