Subversion Repositories php_utils

Rev

Rev 31 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
30 daniel-mar 1
<?php
2
 
3
/*
4
 * OpenSSL php functions implemented using phpseclib
5
 * Copyright 2022 Daniel Marschall, ViaThinkSoft
34 daniel-mar 6
 * Version 2022-07-17
30 daniel-mar 7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
 
21
// How to use this supplement:
22
// 1. Include phpseclib using composer and include the autoloader
23
// 2. Then, include this file. The openssl functions are now available.
24
 
25
// ATTENTION: This supplement/polyfill does only implement a few openssl_*() functions,
26
// and only a few algorithms: AES and RSA! Feel free to extend this library!
27
// The sign/verify and encrypt/decrypt functions should be binary compatible with
28
// the actual openssl functions.
29
 
30
if (!function_exists('openssl_pkey_new') && class_exists('\\phpseclib3\\Crypt\\RSA')) {
31
 
32
        define('OPENSSL_SUPPLEMENT', 1);
33
 
34 daniel-mar 34
        // ---------------------------------------------------------------------
30 daniel-mar 35
 
34 daniel-mar 36
        // https://www.php.net/manual/en/openssl.purpose-check.php
37
        if (!defined('X509_PURPOSE_SSL_CLIENT')) define('X509_PURPOSE_SSL_CLIENT', 1);
38
        if (!defined('X509_PURPOSE_SSL_SERVER')) define('X509_PURPOSE_SSL_SERVER', 2);
39
        if (!defined('X509_PURPOSE_NS_SSL_SERVER')) define('X509_PURPOSE_NS_SSL_SERVER', 3);
40
        if (!defined('X509_PURPOSE_SMIME_SIGN')) define('X509_PURPOSE_SMIME_SIGN', 4);
41
        if (!defined('X509_PURPOSE_SMIME_ENCRYPT')) define('X509_PURPOSE_SMIME_ENCRYPT', 5);
42
        if (!defined('X509_PURPOSE_CRL_SIGN')) define('X509_PURPOSE_CRL_SIGN', 6);
43
        if (!defined('X509_PURPOSE_ANY')) define('X509_PURPOSE_ANY', 7);
44
 
45
        // https://www.php.net/manual/en/openssl.padding.php
46
        if (!defined('OPENSSL_PKCS1_PADDING')) define('OPENSSL_PKCS1_PADDING', 1);
47
        if (!defined('OPENSSL_SSLV23_PADDING')) define('OPENSSL_SSLV23_PADDING', 2);
48
        if (!defined('OPENSSL_NO_PADDING')) define('OPENSSL_NO_PADDING', 3);
49
        if (!defined('OPENSSL_PKCS1_OAEP_PADDING')) define('OPENSSL_PKCS1_OAEP_PADDING', 4);
50
 
51
        // https://www.php.net/manual/en/openssl.key-types.php
30 daniel-mar 52
        if (!defined('OPENSSL_KEYTYPE_RSA')) define('OPENSSL_KEYTYPE_RSA', 0);
34 daniel-mar 53
        if (!defined('OPENSSL_KEYTYPE_DSA')) define('OPENSSL_KEYTYPE_DSA', 1);
54
        if (!defined('OPENSSL_KEYTYPE_DH')) define('OPENSSL_KEYTYPE_DH', 2);
55
        if (!defined('OPENSSL_KEYTYPE_EC')) define('OPENSSL_KEYTYPE_EC', 3);
30 daniel-mar 56
 
34 daniel-mar 57
        // https://www.php.net/manual/en/openssl.pkcs7.flags.php
58
        if (!defined('PKCS7_TEXT')) define('PKCS7_TEXT', 1);
59
        if (!defined('PKCS7_BINARY')) define('PKCS7_BINARY', 128);
60
        if (!defined('PKCS7_NOINTERN')) define('PKCS7_NOINTERN', 16);
61
        if (!defined('PKCS7_NOVERIFY')) define('PKCS7_NOVERIFY', 32);
62
        if (!defined('PKCS7_NOCHAIN')) define('PKCS7_NOCHAIN', 8);
63
        if (!defined('PKCS7_NOCERTS')) define('PKCS7_NOCERTS', 2);
64
        if (!defined('PKCS7_NOATTR')) define('PKCS7_NOATTR', 256);
65
        if (!defined('PKCS7_DETACHED')) define('PKCS7_DETACHED', 64);
66
        if (!defined('PKCS7_NOSIGS')) define('PKCS7_NOSIGS', 4);
30 daniel-mar 67
 
34 daniel-mar 68
        // https://www.php.net/manual/en/openssl.cms.flags.php
69
        if (!defined('OPENSSL_CMS_TEXT')) define('OPENSSL_CMS_TEXT', 1);
70
        if (!defined('OPENSSL_CMS_BINARY')) define('OPENSSL_CMS_BINARY', 128);
71
        if (!defined('OPENSSL_CMS_NOINTERN')) define('OPENSSL_CMS_NOINTERN', 16);
72
        if (!defined('OPENSSL_CMS_NOVERIFY')) define('OPENSSL_CMS_NOVERIFY', 32);
73
        if (!defined('OPENSSL_CMS_NOCERTS')) define('OPENSSL_CMS_NOCERTS', 2);
74
        if (!defined('OPENSSL_CMS_NOATTR')) define('OPENSSL_CMS_NOATTR', 256);
75
        if (!defined('OPENSSL_CMS_DETACHED')) define('OPENSSL_CMS_DETACHED', 64);
76
        if (!defined('OPENSSL_CMS_NOSIGS')) define('OPENSSL_CMS_NOSIGS', 12);
77
 
78
        // https://www.php.net/manual/en/openssl.signature-algos.php
79
        if (!defined('OPENSSL_ALGO_DSS1')) define('OPENSSL_ALGO_DSS1', 5); // Only defined when php/openssl compiled with MD2 support
30 daniel-mar 80
        if (!defined('OPENSSL_ALGO_SHA1')) define('OPENSSL_ALGO_SHA1', 1);
81
        if (!defined('OPENSSL_ALGO_SHA224')) define('OPENSSL_ALGO_SHA224', 6);
82
        if (!defined('OPENSSL_ALGO_SHA256')) define('OPENSSL_ALGO_SHA256', 7);
83
        if (!defined('OPENSSL_ALGO_SHA384')) define('OPENSSL_ALGO_SHA384', 8);
84
        if (!defined('OPENSSL_ALGO_SHA512')) define('OPENSSL_ALGO_SHA512', 9);
85
        if (!defined('OPENSSL_ALGO_RMD160')) define('OPENSSL_ALGO_RMD160', 10);
86
        if (!defined('OPENSSL_ALGO_MD5')) define('OPENSSL_ALGO_MD5', 2);
87
        if (!defined('OPENSSL_ALGO_MD4')) define('OPENSSL_ALGO_MD4', 3);
34 daniel-mar 88
        if (!defined('OPENSSL_ALGO_MD2')) define('OPENSSL_ALGO_MD2', 4); // Only defined when php/openssl compiled with MD2 support
30 daniel-mar 89
 
34 daniel-mar 90
        // https://www.php.net/manual/en/openssl.ciphers.php
91
        if (!defined('OPENSSL_CIPHER_RC2_40')) define('OPENSSL_CIPHER_RC2_40', 0);
92
        if (!defined('OPENSSL_CIPHER_RC2_128')) define('OPENSSL_CIPHER_RC2_128', 1);
93
        if (!defined('OPENSSL_CIPHER_RC2_64')) define('OPENSSL_CIPHER_RC2_64', 2);
94
        if (!defined('OPENSSL_CIPHER_DES')) define('OPENSSL_CIPHER_DES', 3);
95
        if (!defined('OPENSSL_CIPHER_3DES')) define('OPENSSL_CIPHER_3DES', 4);
96
        if (!defined('OPENSSL_CIPHER_AES_128_CBC')) define('OPENSSL_CIPHER_AES_128_CBC', 5);
97
        if (!defined('OPENSSL_CIPHER_AES_192_CBC')) define('OPENSSL_CIPHER_AES_192_CBC', 6);
98
        if (!defined('OPENSSL_CIPHER_AES_256_CBC')) define('OPENSSL_CIPHER_AES_256_CBC', 7);
99
 
100
        // https://www.php.net/manual/en/openssl.constversion.php
101
        // OPENSSL_VERSION_TEXT (string)
102
        // OPENSSL_VERSION_NUMBER (int)
103
 
104
        // https://www.php.net/manual/en/openssl.constsni.php
105
        // OPENSSL_TLSEXT_SERVER_NAME (string)
106
 
107
        // https://www.php.net/manual/en/openssl.constants.other.php
108
        if (!defined('OPENSSL_RAW_DATA')) define('OPENSSL_RAW_DATA', 1);
109
        if (!defined('OPENSSL_ZERO_PADDING')) define('OPENSSL_ZERO_PADDING', 2);
110
        if (!defined('OPENSSL_ENCODING_SMIME')) define('OPENSSL_ENCODING_SMIME', 1);
111
        if (!defined('OPENSSL_ENCODING_DER')) define('OPENSSL_ENCODING_DER', 0);
112
        if (!defined('OPENSSL_ENCODING_PEM')) define('OPENSSL_ENCODING_PEM', 2);
113
 
114
        // ---------------------------------------------------------------------
115
 
116
        $openssl_supplement_last_error = '';
117
 
30 daniel-mar 118
        function openssl_pkey_new($pkey_config=null) {
119
                try {
120
                        $algo = $pkey_config && isset($pkey_config["private_key_type"]) ? $pkey_config["private_key_type"] : OPENSSL_KEYTYPE_RSA;
121
                        $bits = $pkey_config && isset($pkey_config["private_key_bits"]) ? $pkey_config["private_key_bits"] : 2048;
122
 
31 daniel-mar 123
                        // TODO: Also support $pkey_config['encrypt_key'] and $pkey_config['encrypt_key_cipher'] ?
124
 
30 daniel-mar 125
                        if ($algo == OPENSSL_KEYTYPE_RSA) {
126
                                $private = \phpseclib3\Crypt\RSA::createKey($bits);
127
                        } else {
128
                                throw new Exception("Algo not implemented");
129
                        }
130
 
131
                        $private = $private->withPadding(\phpseclib3\Crypt\RSA::ENCRYPTION_PKCS1 | \phpseclib3\Crypt\RSA::SIGNATURE_PKCS1);
132
 
133
                        $public = $private->getPublicKey()->withPadding(\phpseclib3\Crypt\RSA::ENCRYPTION_PKCS1 | \phpseclib3\Crypt\RSA::SIGNATURE_PKCS1);
134
 
135
                        return array($algo, $bits, $private, $public);
136
                } catch (Exception $e) {
137
                        global $openssl_supplement_last_error;
138
                        $openssl_supplement_last_error = $e->getMessage();
139
                        return false;
140
                }
141
        }
142
 
143
        function openssl_pkey_export($res, &$privKey, $passphrase = null, $options = null) {
144
                try {
31 daniel-mar 145
                        if ($res instanceof \phpseclib3\Crypt\Common\PrivateKey /*\phpseclib3\Crypt\RSA\PrivateKey*/ ) {
146
                                $privKey = $res;
147
                                if (!is_null($passphrase)) {
148
                                        $privKey = $res->withPassword($passphrase);
149
                                }
150
                                $privKey = $privKey."";
151
                                return true;
152
                        } else if (is_string($res)) {
153
                                $privKey = $res;
154
                                if (!is_null($passphrase)) {
155
                                        $privKey = \phpseclib3\Crypt\RSA::load($privKey);
156
                                        $privKey = $res->withPassword($passphrase);
157
                                        $privKey = $privKey."";
158
                                }
159
                                return true;
160
                        } else if (is_array($res)) {
161
                                $privKey = $res[2]."";
162
                                if (!is_null($passphrase)) {
163
                                        $privKey = \phpseclib3\Crypt\RSA::load($privKey);
164
                                        $privKey = $res->withPassword($passphrase);
165
                                        $privKey = $privKey."";
166
                                }
167
                                return true;
168
                        } else {
169
                                throw new Exception("Invalid input datatype");
170
                        }
30 daniel-mar 171
                } catch (Exception $e) {
172
                        global $openssl_supplement_last_error;
173
                        $openssl_supplement_last_error = $e->getMessage();
174
                        return false;
175
                }
176
        }
177
 
178
        function openssl_pkey_get_details($res) {
179
                return array(
180
                        "bits" => $res[1],
181
                        "key" => $res[3]."",
182
                        "type" => $res[0]
183
                );
184
        }
185
 
186
        function openssl_public_encrypt($data, &$encrypted, $pubKey) {
187
                try {
188
                        if (is_string($pubKey)) $pubKey = openssl_pkey_get_public($pubKey);
31 daniel-mar 189
                        if (!is_object($pubKey) || !method_exists($pubKey,'encrypt'))
190
                                throw new Exception("Invalid input datatype");
30 daniel-mar 191
                        $encrypted = $pubKey->encrypt($data);
192
                        return true;
193
                } catch (Exception $e) {
194
                        global $openssl_supplement_last_error;
195
                        $openssl_supplement_last_error = $e->getMessage();
196
                        return false;
197
                }
198
        }
199
 
200
        function openssl_private_decrypt($encrypted, &$decrypted, $privKey) {
201
                try {
202
                        if (is_string($privKey)) $privKey = openssl_pkey_get_private($privKey);
31 daniel-mar 203
                        if (!is_object($privKey) || !method_exists($privKey,'decrypt'))
204
                                throw new Exception("Invalid input datatype");
30 daniel-mar 205
                        $decrypted = $privKey->decrypt($encrypted);
206
                        return true;
207
                } catch (Exception $e) {
208
                        global $openssl_supplement_last_error;
209
                        $openssl_supplement_last_error = $e->getMessage();
210
                        return false;
211
                }
212
        }
213
 
214
        function openssl_verify($msg, $signature, $public, $algorithm=OPENSSL_ALGO_SHA1) {
215
                try {
216
                        if ($algorithm == OPENSSL_ALGO_SHA1) $algorithm = 'SHA1';
217
                        if ($algorithm == OPENSSL_ALGO_SHA224) $algorithm = 'SHA224';
34 daniel-mar 218
                        if ($algorithm == OPENSSL_ALGO_SHA256) $algorithm = 'SHA256';
30 daniel-mar 219
                        if ($algorithm == OPENSSL_ALGO_SHA384) $algorithm = 'SHA384';
220
                        if ($algorithm == OPENSSL_ALGO_SHA512) $algorithm = 'SHA512';
221
                        if ($algorithm == OPENSSL_ALGO_RMD160) $algorithm = 'RMD160';
222
                        if ($algorithm == OPENSSL_ALGO_MD5) $algorithm = 'MD5';
223
                        if ($algorithm == OPENSSL_ALGO_MD4) $algorithm = 'MD4';
224
                        if (is_string($public)) $public = openssl_pkey_get_public($public);
31 daniel-mar 225
                        if (!is_object($public) || !method_exists($public,'verify'))
226
                                throw new Exception("Invalid input datatype");
30 daniel-mar 227
                        return $public->withHash($algorithm)->verify($msg, $signature) ? 1 : 0;
228
                } catch (Exception $e) {
229
                        global $openssl_supplement_last_error;
230
                        $openssl_supplement_last_error = $e->getMessage();
231
                        return false;
232
                }
233
        }
234
 
235
        function openssl_sign($msg, &$signature, $private, $algorithm=OPENSSL_ALGO_SHA1) {
236
                try {
237
                        if ($algorithm == OPENSSL_ALGO_SHA1) $algorithm = 'SHA1';
238
                        if ($algorithm == OPENSSL_ALGO_SHA224) $algorithm = 'SHA224';
34 daniel-mar 239
                        if ($algorithm == OPENSSL_ALGO_SHA256) $algorithm = 'SHA256';
30 daniel-mar 240
                        if ($algorithm == OPENSSL_ALGO_SHA384) $algorithm = 'SHA384';
241
                        if ($algorithm == OPENSSL_ALGO_SHA512) $algorithm = 'SHA512';
242
                        if ($algorithm == OPENSSL_ALGO_RMD160) $algorithm = 'RMD160';
243
                        if ($algorithm == OPENSSL_ALGO_MD5) $algorithm = 'MD5';
244
                        if ($algorithm == OPENSSL_ALGO_MD4) $algorithm = 'MD4';
245
                        if (is_string($private)) $private = openssl_pkey_get_private($private);
31 daniel-mar 246
                        if (!is_object($private) || !method_exists($private,'sign'))
247
                                throw new Exception("Invalid input datatype");
30 daniel-mar 248
                        $signature = $private->withHash($algorithm)->sign($msg);
249
                        return true;
250
                } catch (Exception $e) {
251
                        global $openssl_supplement_last_error;
252
                        $openssl_supplement_last_error = $e->getMessage();
253
                        return false;
254
                }
255
        }
256
 
257
        function openssl_error_string() {
258
                global $openssl_supplement_last_error;
34 daniel-mar 259
                $res = $openssl_supplement_last_error;
260
                $openssl_supplement_last_error = '';
261
                return $res;
30 daniel-mar 262
        }
263
 
264
        function openssl_random_pseudo_bytes($len) {
265
                /*
266
                if (function_exists('openssl_random_pseudo_bytes')) {
267
                        $a = openssl_random_pseudo_bytes($len);
268
                        if ($a) return $a;
269
                }
270
                */
271
 
272
                if (function_exists('mcrypt_create_iv')) {
273
                        $a = bin2hex(mcrypt_create_iv($len, MCRYPT_DEV_URANDOM));
274
                        if ($a) return $a;
275
                }
276
 
277
                if (function_exists('random_bytes')) {
278
                        $a = random_bytes($len);
279
                        if ($a) return $a;
280
                }
281
 
282
                // Fallback to non-secure RNG
283
                $a = '';
284
                while (strlen($a) < $len*2) {
285
                        $a .= sha1(uniqid((string)mt_rand(), true));
286
                }
287
                $a = substr($a, 0, $len*2);
288
                return hex2bin($a);
289
        }
290
 
291
        function openssl_encrypt($data, $cipher_algo, $passphrase, $options=0, $iv="", &$tag=null, $aad="", $tag_length=16) {
292
                try {
293
                        if (!is_null($tag)) throw new Exception("tag not implemented");
294
                        if ($aad != "") throw new Exception("aad not implemented");
295
                        if ($tag_length != 16) throw new Exception("tag_length not implemented");
296
                        if (!preg_match('@AES\\-(.+)\\-(.+)@i', $cipher_algo, $m)) throw new Exception("Algo not implemented");
297
                        if (($options & OPENSSL_ZERO_PADDING) != 0) throw new Exception("OPENSSL_ZERO_PADDING not implemented");
298
                        $aes = new \phpseclib3\Crypt\AES($m[2]);
299
                        $aes->setKeyLength($m[1]);
300
                        $passphrase = substr($passphrase, 0, $m[1]/8);
301
                        $passphrase = str_pad($passphrase, $m[1]/8, "\0", STR_PAD_RIGHT);
302
                        $aes->setKey($passphrase);
303
                        $aes->setIV($iv);
304
                        $res = $aes->encrypt($data);
305
                        if (($options & OPENSSL_RAW_DATA) == 0) $res = base64_encode($res);
306
                        return $res;
307
                } catch (Exception $e) {
308
                        global $openssl_supplement_last_error;
309
                        $openssl_supplement_last_error = $e->getMessage();
310
                        return false;
311
                }
312
        }
313
 
314
        function openssl_decrypt($data, $cipher_algo, $passphrase, $options=0, $iv="", $tag=null, $aad="") {
315
                try {
316
                        if (!is_null($tag)) throw new Exception("tag not implemented");
317
                        if ($aad != "") throw new Exception("aad not implemented");
318
                        if (!preg_match('@AES\\-(.+)\\-(.+)@i', $cipher_algo, $m)) throw new Exception("Algo not implemented");
319
                        if (($options & OPENSSL_ZERO_PADDING) != 0) throw new Exception("OPENSSL_ZERO_PADDING not implemented");
320
                        $aes = new \phpseclib3\Crypt\AES($m[2]);
321
                        $aes->setKeyLength($m[1]);
322
                        $passphrase = substr($passphrase, 0, $m[1]/8);
323
                        $passphrase = str_pad($passphrase, $m[1]/8, "\0", STR_PAD_RIGHT);
324
                        $aes->setKey($passphrase);
325
                        $aes->setIV($iv);
326
                        if (($options & OPENSSL_RAW_DATA) == 0) $data = base64_decode($data);
327
                        return $aes->decrypt($data);
328
                } catch (Exception $e) {
329
                        global $openssl_supplement_last_error;
330
                        $openssl_supplement_last_error = $e->getMessage();
331
                        return false;
332
                }
333
        }
334
 
335
        function openssl_free_key($key) {
336
                // does nothing
337
        }
338
 
34 daniel-mar 339
        function openssl_get_privatekey($key, $passphrase=null) {
340
                return openssl_pkey_get_private($key, $passphrase=null);
341
        }
342
 
30 daniel-mar 343
        function openssl_pkey_get_private($key, $passphrase=null) {
344
                try {
345
                        if (substr($key,0,7) === 'file://') {
346
                                if (!file_exists($file = substr($key, 7))) throw new Exception("file not found");
347
                                $key = file_get_contents($file);
348
                        }
349
                        if (is_null($passphrase)) $passphrase = false;
350
                        $privKey = \phpseclib3\Crypt\RSA::load($key, $passphrase);
31 daniel-mar 351
                        return $privKey->withPassword(false)->withPadding(\phpseclib3\Crypt\RSA::ENCRYPTION_PKCS1 | \phpseclib3\Crypt\RSA::SIGNATURE_PKCS1); /** @phpstan-ignore-line */ // Call to an undefined method phpseclib3\Crypt\Common\AsymmetricKey::withPadding().
30 daniel-mar 352
                } catch (Exception $e) {
353
                        global $openssl_supplement_last_error;
354
                        $openssl_supplement_last_error = $e->getMessage();
355
                        return false;
356
                }
357
        }
358
 
34 daniel-mar 359
        function openssl_get_publickey($public_key) {
360
                return openssl_pkey_get_public($public_key);
361
        }
362
 
30 daniel-mar 363
        function openssl_pkey_get_public($public_key) {
364
                try {
365
                        if (substr($public_key,0,7) === 'file://') {
366
                                if (!file_exists($file = substr($public_key, 7))) throw new Exception("file not found");
367
                                $public_key = file_get_contents($file);
368
                        }
369
                        $pubKey = \phpseclib3\Crypt\RSA::load($public_key);
370
                        return $pubKey->withPadding(\phpseclib3\Crypt\RSA::ENCRYPTION_PKCS1 | \phpseclib3\Crypt\RSA::SIGNATURE_PKCS1); /** @phpstan-ignore-line */ // Call to an undefined method phpseclib3\Crypt\Common\AsymmetricKey::withPadding().
371
                } catch (Exception $e) {
372
                        global $openssl_supplement_last_error;
373
                        $openssl_supplement_last_error = $e->getMessage();
374
                        return false;
375
                }
376
        }
377
 
34 daniel-mar 378
        function openssl_pkey_free($key) {
379
        }
380
 
30 daniel-mar 381
}