X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FUtil%2FCrypto.php;h=d983202c0c1847f149fc49ffa1a983702d9a4711;hb=720a43461d67ab229de0aecfc5008f22cc4c1c54;hp=3426babe36e27aa2102bf6d36152f80a939903ed;hpb=d4a02dc31408210ab9f67ecac1948dc22b95b8da;p=friendica.git diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index 3426babe36..d983202c0c 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -1,18 +1,37 @@ . + * */ + namespace Friendica\Util; -use Friendica\Core\Addon; -use Friendica\Core\Config; +use Exception; +use Friendica\Core\Hook; use Friendica\Core\Logger; -use Friendica\Util\Strings; -use ASN_BASE; -use ASNValue; +use Friendica\Core\System; +use Friendica\DI; +use ParagonIE\ConstantTime\Base64UrlSafe; +use phpseclib\Crypt\RSA; +use phpseclib\Math\BigInteger; /** - * @brief Crypto class + * Crypto class */ class Crypto { @@ -25,6 +44,9 @@ class Crypto */ public static function rsaSign($data, $key, $alg = 'sha256') { + if (empty($key)) { + Logger::warning('Empty key parameter', ['callstack' => System::callstack()]); + } openssl_sign($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg)); return $sig; } @@ -38,101 +60,13 @@ class Crypto */ public static function rsaVerify($data, $sig, $key, $alg = 'sha256') { + if (empty($key)) { + Logger::warning('Empty key parameter', ['callstack' => System::callstack()]); + } return openssl_verify($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg)); } /** - * @param string $Der der formatted string - * @param string $Private key type optional, default false - * @return string - */ - private static function DerToPem($Der, $Private = false) - { - //Encode: - $Der = base64_encode($Der); - //Split lines: - $lines = str_split($Der, 65); - $body = implode("\n", $lines); - //Get title: - $title = $Private ? 'RSA PRIVATE KEY' : 'PUBLIC KEY'; - //Add wrapping: - $result = "-----BEGIN {$title}-----\n"; - $result .= $body . "\n"; - $result .= "-----END {$title}-----\n"; - - return $result; - } - - /** - * @param string $Der der formatted string - * @return string - */ - private static function DerToRsa($Der) - { - //Encode: - $Der = base64_encode($Der); - //Split lines: - $lines = str_split($Der, 64); - $body = implode("\n", $lines); - //Get title: - $title = 'RSA PUBLIC KEY'; - //Add wrapping: - $result = "-----BEGIN {$title}-----\n"; - $result .= $body . "\n"; - $result .= "-----END {$title}-----\n"; - - return $result; - } - - /** - * @param string $Modulus modulo - * @param string $PublicExponent exponent - * @return string - */ - private static function pkcs8Encode($Modulus, $PublicExponent) - { - //Encode key sequence - $modulus = new ASNValue(ASNValue::TAG_INTEGER); - $modulus->SetIntBuffer($Modulus); - $publicExponent = new ASNValue(ASNValue::TAG_INTEGER); - $publicExponent->SetIntBuffer($PublicExponent); - $keySequenceItems = [$modulus, $publicExponent]; - $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE); - $keySequence->SetSequence($keySequenceItems); - //Encode bit string - $bitStringValue = $keySequence->Encode(); - $bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte - $bitString = new ASNValue(ASNValue::TAG_BITSTRING); - $bitString->Value = $bitStringValue; - //Encode body - $bodyValue = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00" . $bitString->Encode(); - $body = new ASNValue(ASNValue::TAG_SEQUENCE); - $body->Value = $bodyValue; - //Get DER encoded public key: - $PublicDER = $body->Encode(); - return $PublicDER; - } - - /** - * @param string $Modulus modulo - * @param string $PublicExponent exponent - * @return string - */ - private static function pkcs1Encode($Modulus, $PublicExponent) - { - //Encode key sequence - $modulus = new ASNValue(ASNValue::TAG_INTEGER); - $modulus->SetIntBuffer($Modulus); - $publicExponent = new ASNValue(ASNValue::TAG_INTEGER); - $publicExponent->SetIntBuffer($PublicExponent); - $keySequenceItems = [$modulus, $publicExponent]; - $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE); - $keySequence->SetSequence($keySequenceItems); - //Encode bit string - $bitStringValue = $keySequence->Encode(); - return $bitStringValue; - } - /** * @param string $m modulo * @param string $e exponent @@ -140,84 +74,52 @@ class Crypto */ public static function meToPem($m, $e) { - $der = self::pkcs8Encode($m, $e); - $key = self::DerToPem($der, false); - return $key; - } - - /** - * @param string $key key - * @param string $m modulo reference - * @param object $e exponent reference - * @return void - */ - private static function pubRsaToMe($key, &$m, &$e) - { - $lines = explode("\n", $key); - unset($lines[0]); - unset($lines[count($lines)]); - $x = base64_decode(implode('', $lines)); - - $r = ASN_BASE::parseASNString($x); - - $m = Strings::base64UrlDecode($r[0]->asnData[0]->asnData); - $e = Strings::base64UrlDecode($r[0]->asnData[1]->asnData); + $rsa = new RSA(); + $rsa->loadKey([ + 'e' => new BigInteger($e, 256), + 'n' => new BigInteger($m, 256) + ]); + return $rsa->getPublicKey(); } /** - * @param string $key key - * @return string + * Transform RSA public keys to standard PEM output + * + * @param string $key A RSA public key + * + * @return string The PEM output of this key */ - public static function rsaToPem($key) + public static function rsaToPem(string $key) { - self::pubRsaToMe($key, $m, $e); - return self::meToPem($m, $e); - } + $rsa = new RSA(); + $rsa->setPublicKey($key); - /** - * @param string $key key - * @return string - */ - private static function pemToRsa($key) - { - self::pemToMe($key, $m, $e); - return self::meToRsa($m, $e); + return $rsa->getPublicKey(RSA::PUBLIC_FORMAT_PKCS8); } /** - * @param string $key key - * @param string $m modulo reference - * @param string $e exponent reference + * Extracts the modulo and exponent reference from a public PEM key + * + * @param string $key public PEM key + * @param string $modulus (ref) modulo reference + * @param string $exponent (ref) exponent reference + * * @return void */ - public static function pemToMe($key, &$m, &$e) + public static function pemToMe(string $key, &$modulus, &$exponent) { - $lines = explode("\n", $key); - unset($lines[0]); - unset($lines[count($lines)]); - $x = base64_decode(implode('', $lines)); - - $r = ASN_BASE::parseASNString($x); - - $m = Strings::base64UrlDecode($r[0]->asnData[1]->asnData[0]->asnData[0]->asnData); - $e = Strings::base64UrlDecode($r[0]->asnData[1]->asnData[0]->asnData[1]->asnData); - } + $rsa = new RSA(); + $rsa->loadKey($key); + $rsa->setPublicKey(); - /** - * @param string $m modulo - * @param string $e exponent - * @return string - */ - private static function meToRsa($m, $e) - { - $der = self::pkcs1Encode($m, $e); - $key = self::DerToRsa($der); - return $key; + $modulus = $rsa->modulus->toBytes(); + $exponent = $rsa->exponent->toBytes(); } /** * @param integer $bits number of bits * @return mixed + * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ public static function newKeypair($bits) { @@ -227,14 +129,14 @@ class Crypto 'encrypt_key' => false ]; - $conf = Config::get('system', 'openssl_conf_file'); + $conf = DI::config()->get('system', 'openssl_conf_file'); if ($conf) { $openssl_options['config'] = $conf; } $result = openssl_pkey_new($openssl_options); if (empty($result)) { - Logger::log('new_keypair: failed'); + Logger::notice('new_keypair: failed'); return false; } @@ -251,82 +153,91 @@ class Crypto } /** - * Encrypt a string with 'aes-256-cbc' cipher method. - * - * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * - * @param string $data - * @param string $key The key used for encryption. - * @param string $iv A non-NULL Initialization Vector. - * - * @return string|boolean Encrypted string or false on failure. + * Create a new elliptic curve key pair + * + * @return array with the elements "prvkey", "pubkey", "vapid-public" and "vapid-private" */ - private static function encryptAES256CBC($data, $key, $iv) + public static function newECKeypair() { - return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); - } + $openssl_options = [ + 'curve_name' => 'prime256v1', + 'private_key_type' => OPENSSL_KEYTYPE_EC + ]; - /** - * Decrypt a string with 'aes-256-cbc' cipher method. - * - * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * - * @param string $data - * @param string $key The key used for decryption. - * @param string $iv A non-NULL Initialization Vector. - * - * @return string|boolean Decrypted string or false on failure. - */ - private static function decryptAES256CBC($data, $key, $iv) - { - return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); + $conf = DI::config()->get('system', 'openssl_conf_file'); + if ($conf) { + $openssl_options['config'] = $conf; + } + $result = openssl_pkey_new($openssl_options); + + if (empty($result)) { + throw new Exception('Key creation failed'); + } + + $response = ['prvkey' => '', 'pubkey' => '']; + + // Get private key + openssl_pkey_export($result, $response['prvkey']); + + // Get public key + $pkey = openssl_pkey_get_details($result); + $response['pubkey'] = $pkey['key']; + + // Create VAPID keys + // @see https://github.com/web-push-libs/web-push-php/blob/256a18b2a2411469c94943725fb6eccb9681bd75/src/Utils.php#L60-L62 + $hexString = '04'; + $hexString .= str_pad(bin2hex($pkey['ec']['x']), 64, '0', STR_PAD_LEFT); + $hexString .= str_pad(bin2hex($pkey['ec']['y']), 64, '0', STR_PAD_LEFT); + $response['vapid-public'] = Base64UrlSafe::encode(hex2bin($hexString)); + + // @see https://github.com/web-push-libs/web-push-php/blob/256a18b2a2411469c94943725fb6eccb9681bd75/src/VAPID.php + $response['vapid-private'] = Base64UrlSafe::encode(hex2bin(str_pad(bin2hex($pkey['ec']['d']), 64, '0', STR_PAD_LEFT))); + + return $response; } /** - * Encrypt a string with 'aes-256-ctr' cipher method. - * + * Encrypt a string with 'aes-256-cbc' cipher method. + * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * + * * @param string $data * @param string $key The key used for encryption. * @param string $iv A non-NULL Initialization Vector. - * + * * @return string|boolean Encrypted string or false on failure. */ - private static function encryptAES256CTR($data, $key, $iv) + private static function encryptAES256CBC($data, $key, $iv) { - $key = substr($key, 0, 32); - $iv = substr($iv, 0, 16); - return openssl_encrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); + return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); } /** - * Decrypt a string with 'aes-256-ctr' cipher method. - * + * Decrypt a string with 'aes-256-cbc' cipher method. + * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * + * * @param string $data * @param string $key The key used for decryption. * @param string $iv A non-NULL Initialization Vector. - * + * * @return string|boolean Decrypted string or false on failure. */ - private static function decryptAES256CTR($data, $key, $iv) + private static function decryptAES256CBC($data, $key, $iv) { - $key = substr($key, 0, 32); - $iv = substr($iv, 0, 16); - return openssl_decrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); + return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); } /** - * + * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * + * * @param string $data * @param string $pubkey The public key. * @param string $alg The algorithm used for encryption. - * + * * @return array + * @throws \Exception */ public static function encapsulate($data, $pubkey, $alg = 'aes256cbc') { @@ -337,19 +248,20 @@ class Crypto } /** - * + * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * - * @param type $data - * @param type $pubkey The public key. - * @param type $alg The algorithm used for encryption. - * + * + * @param string $data + * @param string $pubkey The public key. + * @param string $alg The algorithm used for encryption. + * * @return array + * @throws \Exception */ private static function encapsulateOther($data, $pubkey, $alg) { if (!$pubkey) { - Logger::log('no key. data: '.$data); + Logger::notice('no key. data: '.$data); } $fn = 'encrypt' . strtoupper($alg); if (method_exists(__CLASS__, $fn)) { @@ -361,7 +273,7 @@ class Crypto // log the offending call so we can track it down if (!openssl_public_encrypt($key, $k, $pubkey)) { $x = debug_backtrace(); - Logger::log('RSA failed. ' . print_r($x[0], true)); + Logger::notice('RSA failed', ['trace' => $x[0]]); } $result['alg'] = $alg; @@ -372,25 +284,26 @@ class Crypto return $result; } else { $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data]; - Addon::callHooks('other_encapsulate', $x); + Hook::callAll('other_encapsulate', $x); return $x['result']; } } /** - * + * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * + * * @param string $data * @param string $pubkey - * + * * @return array + * @throws \Exception */ private static function encapsulateAes($data, $pubkey) { if (!$pubkey) { - Logger::log('aes_encapsulate: no key. data: ' . $data); + Logger::notice('aes_encapsulate: no key. data: ' . $data); } $key = random_bytes(32); @@ -401,7 +314,7 @@ class Crypto // log the offending call so we can track it down if (!openssl_public_encrypt($key, $k, $pubkey)) { $x = debug_backtrace(); - Logger::log('aes_encapsulate: RSA failed. ' . print_r($x[0], true)); + Logger::notice('aes_encapsulate: RSA failed.', ['data' => $x[0]]); } $result['alg'] = 'aes256cbc'; @@ -413,38 +326,41 @@ class Crypto } /** - * + * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * - * @param string $data - * @param string $prvkey The private key used for decryption. - * + * + * @param array $data ['iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data] + * @param string $prvkey The private key used for decryption. + * * @return string|boolean The decrypted string or false on failure. + * @throws \Exception */ - public static function unencapsulate($data, $prvkey) + public static function unencapsulate(array $data, $prvkey) { if (!$data) { return; } - $alg = ((array_key_exists('alg', $data)) ? $data['alg'] : 'aes256cbc'); + $alg = $data['alg'] ?? 'aes256cbc'; if ($alg === 'aes256cbc') { - return self::encapsulateAes($data, $prvkey); + return self::unencapsulateAes($data['data'], $prvkey); } - return self::encapsulateOther($data, $prvkey, $alg); + + return self::unencapsulateOther($data, $prvkey, $alg); } /** - * + * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * - * @param string $data - * @param string $prvkey The private key used for decryption. + * + * @param array $data + * @param string $prvkey The private key used for decryption. * @param string $alg - * + * * @return string|boolean The decrypted string or false on failure. + * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - private static function unencapsulateOther($data, $prvkey, $alg) + private static function unencapsulateOther(array $data, $prvkey, $alg) { $fn = 'decrypt' . strtoupper($alg); @@ -455,20 +371,21 @@ class Crypto return self::$fn(Strings::base64UrlDecode($data['data']), $k, $i); } else { $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data]; - Addon::callHooks('other_unencapsulate', $x); + Hook::callAll('other_unencapsulate', $x); return $x['result']; } } /** - * + * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * + * * @param array $data - * @param string $prvkey The private key used for decryption. - * + * @param string $prvkey The private key used for decryption. + * * @return string|boolean The decrypted string or false on failure. + * @throws \Exception */ private static function unencapsulateAes($data, $prvkey) {