]> git.mxchange.org Git - friendica.git/blob - src/Util/Crypto.php
port hubzillas OpenWebAuth - remote authentification
[friendica.git] / src / Util / Crypto.php
1 <?php
2 /**
3  * @file src/Util/Crypto.php
4  */
5 namespace Friendica\Util;
6
7 use Friendica\Core\Addon;
8 use Friendica\Core\Config;
9 use ASN_BASE;
10 use ASNValue;
11
12 /**
13  * @brief Crypto class
14  */
15 class Crypto
16 {
17         // supported algorithms are 'sha256', 'sha1'
18         /**
19          * @param string $data data
20          * @param string $key  key
21          * @param string $alg  algorithm
22          * @return string
23          */
24         public static function rsaSign($data, $key, $alg = 'sha256')
25         {
26                 openssl_sign($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
27                 return $sig;
28         }
29
30         /**
31          * @param string $data data
32          * @param string $sig  signature
33          * @param string $key  key
34          * @param string $alg  algorithm
35          * @return boolean
36          */
37         public static function rsaVerify($data, $sig, $key, $alg = 'sha256')
38         {
39                 return openssl_verify($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
40         }
41
42         /**
43          * @param string $Der     der formatted string
44          * @param string $Private key type optional, default false
45          * @return string
46          */
47         private static function DerToPem($Der, $Private = false)
48         {
49                 //Encode:
50                 $Der = base64_encode($Der);
51                 //Split lines:
52                 $lines = str_split($Der, 65);
53                 $body = implode("\n", $lines);
54                 //Get title:
55                 $title = $Private ? 'RSA PRIVATE KEY' : 'PUBLIC KEY';
56                 //Add wrapping:
57                 $result = "-----BEGIN {$title}-----\n";
58                 $result .= $body . "\n";
59                 $result .= "-----END {$title}-----\n";
60
61                 return $result;
62         }
63
64         /**
65          * @param string $Der der formatted string
66          * @return string
67          */
68         private static function DerToRsa($Der)
69         {
70                 //Encode:
71                 $Der = base64_encode($Der);
72                 //Split lines:
73                 $lines = str_split($Der, 64);
74                 $body = implode("\n", $lines);
75                 //Get title:
76                 $title = 'RSA PUBLIC KEY';
77                 //Add wrapping:
78                 $result = "-----BEGIN {$title}-----\n";
79                 $result .= $body . "\n";
80                 $result .= "-----END {$title}-----\n";
81
82                 return $result;
83         }
84
85         /**
86          * @param string $Modulus        modulo
87          * @param string $PublicExponent exponent
88          * @return string
89          */
90         private static function pkcs8Encode($Modulus, $PublicExponent)
91         {
92                 //Encode key sequence
93                 $modulus = new ASNValue(ASNValue::TAG_INTEGER);
94                 $modulus->SetIntBuffer($Modulus);
95                 $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
96                 $publicExponent->SetIntBuffer($PublicExponent);
97                 $keySequenceItems = [$modulus, $publicExponent];
98                 $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
99                 $keySequence->SetSequence($keySequenceItems);
100                 //Encode bit string
101                 $bitStringValue = $keySequence->Encode();
102                 $bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte
103                 $bitString = new ASNValue(ASNValue::TAG_BITSTRING);
104                 $bitString->Value = $bitStringValue;
105                 //Encode body
106                 $bodyValue = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00" . $bitString->Encode();
107                 $body = new ASNValue(ASNValue::TAG_SEQUENCE);
108                 $body->Value = $bodyValue;
109                 //Get DER encoded public key:
110                 $PublicDER = $body->Encode();
111                 return $PublicDER;
112         }
113
114         /**
115          * @param string $Modulus        modulo
116          * @param string $PublicExponent exponent
117          * @return string
118          */
119         private static function pkcs1Encode($Modulus, $PublicExponent)
120         {
121                 //Encode key sequence
122                 $modulus = new ASNValue(ASNValue::TAG_INTEGER);
123                 $modulus->SetIntBuffer($Modulus);
124                 $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
125                 $publicExponent->SetIntBuffer($PublicExponent);
126                 $keySequenceItems = [$modulus, $publicExponent];
127                 $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
128                 $keySequence->SetSequence($keySequenceItems);
129                 //Encode bit string
130                 $bitStringValue = $keySequence->Encode();
131                 return $bitStringValue;
132         }
133
134         /**
135          * @param string $m modulo
136          * @param string $e exponent
137          * @return string
138          */
139         public static function meToPem($m, $e)
140         {
141                 $der = self::pkcs8Encode($m, $e);
142                 $key = self::DerToPem($der, false);
143                 return $key;
144         }
145
146         /**
147          * @param string $key key
148          * @param string $m   modulo reference
149          * @param object $e   exponent reference
150          * @return void
151          */
152         private static function pubRsaToMe($key, &$m, &$e)
153         {
154                 $lines = explode("\n", $key);
155                 unset($lines[0]);
156                 unset($lines[count($lines)]);
157                 $x = base64_decode(implode('', $lines));
158
159                 $r = ASN_BASE::parseASNString($x);
160
161                 $m = base64url_decode($r[0]->asnData[0]->asnData);
162                 $e = base64url_decode($r[0]->asnData[1]->asnData);
163         }
164
165         /**
166          * @param string $key key
167          * @return string
168          */
169         public static function rsaToPem($key)
170         {
171                 self::pubRsaToMe($key, $m, $e);
172                 return self::meToPem($m, $e);
173         }
174
175         /**
176          * @param string $key key
177          * @return string
178          */
179         private static function pemToRsa($key)
180         {
181                 self::pemToMe($key, $m, $e);
182                 return self::meToRsa($m, $e);
183         }
184
185         /**
186          * @param string $key key
187          * @param string $m   modulo reference
188          * @param string $e   exponent reference
189          * @return void
190          */
191         public static function pemToMe($key, &$m, &$e)
192         {
193                 $lines = explode("\n", $key);
194                 unset($lines[0]);
195                 unset($lines[count($lines)]);
196                 $x = base64_decode(implode('', $lines));
197
198                 $r = ASN_BASE::parseASNString($x);
199
200                 $m = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[0]->asnData);
201                 $e = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[1]->asnData);
202         }
203
204         /**
205          * @param string $m modulo
206          * @param string $e exponent
207          * @return string
208          */
209         private static function meToRsa($m, $e)
210         {
211                 $der = self::pkcs1Encode($m, $e);
212                 $key = self::DerToRsa($der);
213                 return $key;
214         }
215
216         /**
217          * @param integer $bits number of bits
218          * @return mixed
219          */
220         public static function newKeypair($bits)
221         {
222                 $openssl_options = [
223                         'digest_alg'       => 'sha1',
224                         'private_key_bits' => $bits,
225                         'encrypt_key'      => false
226                 ];
227
228                 $conf = Config::get('system', 'openssl_conf_file');
229                 if ($conf) {
230                         $openssl_options['config'] = $conf;
231                 }
232                 $result = openssl_pkey_new($openssl_options);
233
234                 if (empty($result)) {
235                         logger('new_keypair: failed');
236                         return false;
237                 }
238
239                 // Get private key
240                 $response = ['prvkey' => '', 'pubkey' => ''];
241
242                 openssl_pkey_export($result, $response['prvkey']);
243
244                 // Get public key
245                 $pkey = openssl_pkey_get_details($result);
246                 $response['pubkey'] = $pkey["key"];
247
248                 return $response;
249         }
250
251         /**
252          * Encrypt a string with 'aes-256-cbc' cipher method.
253          * 
254          * @param string $data
255          * @param string $key   The key used for encryption.
256          * @param string $iv    A non-NULL Initialization Vector.
257          * 
258          * @return string|boolean Encrypted string or false on failure.
259          */
260         private static function encryptAES256CBC($data, $key, $iv)
261         {
262                 return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
263         }
264
265         /**
266          * Decrypt a string with 'aes-256-cbc' cipher method.
267          * 
268          * @param string $data
269          * @param string $key   The key used for decryption.
270          * @param string $iv    A non-NULL Initialization Vector.
271          * 
272          * @return string|boolean Decrypted string or false on failure.
273          */
274         private static function decryptAES256CBC($data, $key, $iv)
275         {
276                 return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
277         }
278
279         /**
280          * Encrypt a string with 'aes-256-ctr' cipher method.
281          * 
282          * @param string $data
283          * @param string $key   The key used for encryption.
284          * @param string $iv    A non-NULL Initialization Vector.
285          * 
286          * @return string|boolean Encrypted string or false on failure.
287          */
288         private static function encryptAES256CTR($data, $key, $iv)
289         {
290                 $key = substr($key, 0, 32);
291                 $iv = substr($iv, 0, 16);
292                 return openssl_encrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
293         }
294
295         /**
296          * Decrypt a string with 'aes-256-cbc' cipher method.
297          * 
298          * @param string $data
299          * @param string $key   The key used for decryption.
300          * @param string $iv    A non-NULL Initialization Vector.
301          * 
302          * @return string|boolean Decrypted string or false on failure.
303          */
304         private static function decryptAES256CTR($data, $key, $iv)
305         {
306                 $key = substr($key, 0, 32);
307                 $iv = substr($iv, 0, 16);
308                 return openssl_decrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
309         }
310
311         /**
312          * 
313          * @param string $data
314          * @param string $pubkey The public key.
315          * @param string $alg    The algorithm used for encryption.
316          * 
317          * @return array
318          */
319         public static function encapsulate($data, $pubkey, $alg = 'aes256cbc')
320         {
321                 if ($alg === 'aes256cbc') {
322                         return self::encapsulateAes($data, $pubkey);
323                 }
324                 return self::encapsulateOther($data, $pubkey, $alg);
325         }
326
327         /**
328          * 
329          * @param type $data
330          * @param type $pubkey The public key.
331          * @param type $alg    The algorithm used for encryption.
332          * 
333          * @return array
334          */
335         private static function encapsulateOther($data, $pubkey, $alg)
336         {
337                 if (!$pubkey) {
338                         logger('no key. data: '.$data);
339                 }
340                 $fn = 'encrypt' . strtoupper($alg);
341                 if (method_exists(__CLASS__, $fn)) {
342                         // A bit hesitant to use openssl_random_pseudo_bytes() as we know
343                         // it has been historically targeted by US agencies for 'weakening'.
344                         // It is still arguably better than trying to come up with an
345                         // alternative cryptographically secure random generator.
346                         // There is little point in using the optional second arg to flag the
347                         // assurance of security since it is meaningless if the source algorithms
348                         // have been compromised. Also none of this matters if RSA has been
349                         // compromised by state actors and evidence is mounting that this has
350                         // already happened.
351                         $result = ['encrypted' => true];
352                         $key = openssl_random_pseudo_bytes(256);
353                         $iv  = openssl_random_pseudo_bytes(256);
354                         $result['data'] = base64url_encode(self::$fn($data, $key, $iv), true);
355
356                         // log the offending call so we can track it down
357                         if (!openssl_public_encrypt($key, $k, $pubkey)) {
358                                 $x = debug_backtrace();
359                                 logger('RSA failed. ' . print_r($x[0], true));
360                         }
361
362                         $result['alg'] = $alg;
363                         $result['key'] = base64url_encode($k, true);
364                         openssl_public_encrypt($iv, $i, $pubkey);
365                         $result['iv'] = base64url_encode($i, true);
366
367                         return $result;
368                 } else {
369                         $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data];
370                         Addon::callHooks('other_encapsulate', $x);
371
372                         return $x['result'];
373                 }
374         }
375
376         /**
377          * 
378          * @param string $data
379          * @param string $pubkey
380          * 
381          * @return array
382          */
383         private static function encapsulateAes($data, $pubkey)
384         {
385                 if (!$pubkey) {
386                         logger('aes_encapsulate: no key. data: ' . $data);
387                 }
388
389                 $key = openssl_random_pseudo_bytes(32);
390                 $iv  = openssl_random_pseudo_bytes(16);
391                 $result = ['encrypted' => true];
392                 $result['data'] = base64url_encode(AES256CBC_encrypt($data, $key, $iv), true);
393
394                 // log the offending call so we can track it down
395                 if (!openssl_public_encrypt($key, $k, $pubkey)) {
396                         $x = debug_backtrace();
397                         logger('aes_encapsulate: RSA failed. ' . print_r($x[0], true));
398                 }
399
400                 $result['alg'] = 'aes256cbc';
401                 $result['key'] = base64url_encode($k, true);
402                 openssl_public_encrypt($iv, $i, $pubkey);
403                 $result['iv'] = base64url_encode($i, true);
404
405                 return $result;
406         }
407
408         /**
409          * 
410          * @param string $data
411          * @param string $prvkey  The private key used for decryption.
412          * 
413          * @return string|boolean The decrypted string or false on failure.
414          */
415         public static function unencapsulate($data, $prvkey)
416         {
417                 if (!$data) {
418                         return;
419                 }
420
421                 $alg = ((array_key_exists('alg', $data)) ? $data['alg'] : 'aes256cbc');
422                 if ($alg === 'aes256cbc') {
423                         return self::encapsulateAes($data, $prvkey);
424                 }
425                 return self::encapsulateOther($data, $prvkey, $alg);
426         }
427
428         /**
429          * 
430          * @param string $data
431          * @param string $prvkey  The private key used for decryption.
432          * @param string $alg
433          * 
434          * @return string|boolean The decrypted string or false on failure.
435          */
436         private static function unencapsulateOther($data, $prvkey, $alg)
437         {
438                 $fn = 'decrypt' . strtoupper($alg);
439
440                 if (method_exists(__CLASS__, $fn)) {
441                         openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey);
442                         openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey);
443
444                         return self::$fn(base64url_decode($data['data']), $k, $i);
445                 } else {
446                         $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data];
447                         Addon::callHooks('other_unencapsulate', $x);
448
449                         return $x['result'];
450                 }
451         }
452
453         /**
454          * 
455          * @param array  $data
456          * @param string $prvkey  The private key used for decryption.
457          * 
458          * @return string|boolean The decrypted string or false on failure.
459          */
460         private static function unencapsulateAes($data, $prvkey)
461         {
462                 openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey);
463                 openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey);
464
465                 return self::decryptAES256CBC(base64url_decode($data['data']), $k, $i);
466         }
467 }