1: <?php
2: /**
3: * Random_* Compatibility Library
4: * for using the new PHP 7 random_* API in PHP 5 projects
5: *
6: * @version 2.0.17
7: * @released 2018-07-04
8: *
9: * The MIT License (MIT)
10: *
11: * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
12: *
13: * Permission is hereby granted, free of charge, to any person obtaining a copy
14: * of this software and associated documentation files (the "Software"), to deal
15: * in the Software without restriction, including without limitation the rights
16: * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17: * copies of the Software, and to permit persons to whom the Software is
18: * furnished to do so, subject to the following conditions:
19: *
20: * The above copyright notice and this permission notice shall be included in
21: * all copies or substantial portions of the Software.
22: *
23: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28: * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29: * SOFTWARE.
30: */
31:
32: if (!defined('PHP_VERSION_ID')) {
33: // This constant was introduced in PHP 5.2.7
34: $RandomCompatversion = array_map('intval', explode('.', PHP_VERSION));
35: define(
36: 'PHP_VERSION_ID',
37: $RandomCompatversion[0] * 10000
38: + $RandomCompatversion[1] * 100
39: + $RandomCompatversion[2]
40: );
41: $RandomCompatversion = null;
42: }
43:
44: /**
45: * PHP 7.0.0 and newer have these functions natively.
46: */
47: if (PHP_VERSION_ID >= 70000) {
48: return;
49: }
50:
51: if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
52: define('RANDOM_COMPAT_READ_BUFFER', 8);
53: }
54:
55: $RandomCompatDIR = dirname(__FILE__);
56:
57: require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'byte_safe_strings.php';
58: require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'cast_to_int.php';
59: require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'error_polyfill.php';
60:
61: if (!is_callable('random_bytes')) {
62: /**
63: * PHP 5.2.0 - 5.6.x way to implement random_bytes()
64: *
65: * We use conditional statements here to define the function in accordance
66: * to the operating environment. It's a micro-optimization.
67: *
68: * In order of preference:
69: * 1. Use libsodium if available.
70: * 2. fread() /dev/urandom if available (never on Windows)
71: * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)
72: * 4. COM('CAPICOM.Utilities.1')->GetRandom()
73: *
74: * See RATIONALE.md for our reasoning behind this particular order
75: */
76: if (extension_loaded('libsodium')) {
77: // See random_bytes_libsodium.php
78: if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) {
79: require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_libsodium.php';
80: } elseif (method_exists('Sodium', 'randombytes_buf')) {
81: require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_libsodium_legacy.php';
82: }
83: }
84:
85: /**
86: * Reading directly from /dev/urandom:
87: */
88: if (DIRECTORY_SEPARATOR === '/') {
89: // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast
90: // way to exclude Windows.
91: $RandomCompatUrandom = true;
92: $RandomCompat_basedir = ini_get('open_basedir');
93:
94: if (!empty($RandomCompat_basedir)) {
95: $RandomCompat_open_basedir = explode(
96: PATH_SEPARATOR,
97: strtolower($RandomCompat_basedir)
98: );
99: $RandomCompatUrandom = (array() !== array_intersect(
100: array('/dev', '/dev/', '/dev/urandom'),
101: $RandomCompat_open_basedir
102: ));
103: $RandomCompat_open_basedir = null;
104: }
105:
106: if (
107: !is_callable('random_bytes')
108: &&
109: $RandomCompatUrandom
110: &&
111: @is_readable('/dev/urandom')
112: ) {
113: // Error suppression on is_readable() in case of an open_basedir
114: // or safe_mode failure. All we care about is whether or not we
115: // can read it at this point. If the PHP environment is going to
116: // panic over trying to see if the file can be read in the first
117: // place, that is not helpful to us here.
118:
119: // See random_bytes_dev_urandom.php
120: require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_dev_urandom.php';
121: }
122: // Unset variables after use
123: $RandomCompat_basedir = null;
124: } else {
125: $RandomCompatUrandom = false;
126: }
127:
128: /**
129: * mcrypt_create_iv()
130: *
131: * We only want to use mcypt_create_iv() if:
132: *
133: * - random_bytes() hasn't already been defined
134: * - the mcrypt extensions is loaded
135: * - One of these two conditions is true:
136: * - We're on Windows (DIRECTORY_SEPARATOR !== '/')
137: * - We're not on Windows and /dev/urandom is readabale
138: * (i.e. we're not in a chroot jail)
139: * - Special case:
140: * - If we're not on Windows, but the PHP version is between
141: * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will
142: * hang indefinitely. This is bad.
143: * - If we're on Windows, we want to use PHP >= 5.3.7 or else
144: * we get insufficient entropy errors.
145: */
146: if (
147: !is_callable('random_bytes')
148: &&
149: // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be.
150: (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307)
151: &&
152: // Prevent this code from hanging indefinitely on non-Windows;
153: // see https://bugs.php.net/bug.php?id=69833
154: (
155: DIRECTORY_SEPARATOR !== '/' ||
156: (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613)
157: )
158: &&
159: extension_loaded('mcrypt')
160: ) {
161: // See random_bytes_mcrypt.php
162: require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_mcrypt.php';
163: }
164: $RandomCompatUrandom = null;
165:
166: /**
167: * This is a Windows-specific fallback, for when the mcrypt extension
168: * isn't loaded.
169: */
170: if (
171: !is_callable('random_bytes')
172: &&
173: extension_loaded('com_dotnet')
174: &&
175: class_exists('COM')
176: ) {
177: $RandomCompat_disabled_classes = preg_split(
178: '#\s*,\s*#',
179: strtolower(ini_get('disable_classes'))
180: );
181:
182: if (!in_array('com', $RandomCompat_disabled_classes)) {
183: try {
184: $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
185: /** @psalm-suppress TypeDoesNotContainType */
186: if (is_callable(array($RandomCompatCOMtest, 'GetRandom'))) {
187: // See random_bytes_com_dotnet.php
188: require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_com_dotnet.php';
189: }
190: } catch (com_exception $e) {
191: // Don't try to use it.
192: }
193: }
194: $RandomCompat_disabled_classes = null;
195: $RandomCompatCOMtest = null;
196: }
197:
198: /**
199: * throw new Exception
200: */
201: if (!is_callable('random_bytes')) {
202: /**
203: * We don't have any more options, so let's throw an exception right now
204: * and hope the developer won't let it fail silently.
205: *
206: * @param mixed $length
207: * @psalm-suppress InvalidReturnType
208: * @throws Exception
209: * @return string
210: */
211: function random_bytes($length)
212: {
213: unset($length); // Suppress "variable not used" warnings.
214: throw new Exception(
215: 'There is no suitable CSPRNG installed on your system'
216: );
217: return '';
218: }
219: }
220: }
221:
222: if (!is_callable('random_int')) {
223: require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_int.php';
224: }
225:
226: $RandomCompatDIR = null;
227: