X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FUtil%2FCrypto.php;h=b3ae2d69b8442cad3c819f8cf2935e3f9d728889;hb=bdb5aa6bd92528da480baab99ca92a3768a89d0b;hp=b2fad997003bd352a43ebda268f376478966fd16;hpb=c845415a99ebc348103815a7b2c55b15c75cdd24;p=friendica.git diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index b2fad99700..b3ae2d69b8 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -1,15 +1,37 @@ . + * */ + namespace Friendica\Util; -use Friendica\Core\Config; -use ASN_BASE; -use ASNValue; +use Exception; +use Friendica\Core\Hook; +use Friendica\Core\Logger; +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 { @@ -22,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; } @@ -35,215 +60,359 @@ 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 + /** + * @param string $m modulo + * @param string $e exponent * @return string */ - private static function DerToPem($Der, $Private = false) + public static function meToPem($m, $e) { - //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; + $rsa = new RSA(); + $rsa->loadKey([ + 'e' => new BigInteger($e, 256), + 'n' => new BigInteger($m, 256) + ]); + return $rsa->getPublicKey(); } /** - * @param string $Der der formatted string - * @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 */ - private static function DerToRsa($Der) + public static function rsaToPem(string $key) { - //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"; + $rsa = new RSA(); + $rsa->setPublicKey($key); - return $result; + return $rsa->getPublicKey(RSA::PUBLIC_FORMAT_PKCS8); } /** - * @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 + * 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 */ - private static function pkcs1Encode($Modulus, $PublicExponent) + public static function pemToMe(string $key, &$modulus, &$exponent) { - //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; + $rsa = new RSA(); + $rsa->loadKey($key); + $rsa->setPublicKey(); + + $modulus = $rsa->modulus->toBytes(); + $exponent = $rsa->exponent->toBytes(); } /** - * @param string $m modulo - * @param string $e exponent - * @return string + * @param integer $bits number of bits + * @return mixed + * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public static function meToPem($m, $e) + public static function newKeypair($bits) { - $der = self::pkcs8Encode($m, $e); - $key = self::DerToPem($der, false); - return $key; + $openssl_options = [ + 'digest_alg' => 'sha1', + 'private_key_bits' => $bits, + 'encrypt_key' => false + ]; + + $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'); + return false; + } + + // Get private key + $response = ['prvkey' => '', 'pubkey' => '']; + + openssl_pkey_export($result, $response['prvkey']); + + // Get public key + $pkey = openssl_pkey_get_details($result); + $response['pubkey'] = $pkey["key"]; + + return $response; } /** - * @param string $key key - * @param string $m modulo reference - * @param object $e exponent reference - * @return void + * Create a new elliptic curve key pair + * + * @return array with the elements "prvkey", "pubkey", "vapid-public" and "vapid-private" */ - private static function pubRsaToMe($key, &$m, &$e) + public static function newECKeypair() { - $lines = explode("\n", $key); - unset($lines[0]); - unset($lines[count($lines)]); - $x = base64_decode(implode('', $lines)); + $openssl_options = [ + 'curve_name' => 'prime256v1', + 'private_key_type' => OPENSSL_KEYTYPE_EC + ]; + + $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)); - $r = ASN_BASE::parseASNString($x); + // @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))); - $m = base64url_decode($r[0]->asnData[0]->asnData); - $e = base64url_decode($r[0]->asnData[1]->asnData); + return $response; } /** - * @param string $key key - * @return string + * 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. */ - public static function rsaToPem($key) + private static function encryptAES256CBC($data, $key, $iv) { - self::pubRsaToMe($key, $m, $e); - return self::meToPem($m, $e); + return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); } /** - * @param string $key key - * @return string + * 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 pemToRsa($key) + private static function decryptAES256CBC($data, $key, $iv) { - self::pemToMe($key, $m, $e); - return self::meToRsa($m, $e); + return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); } /** - * @param string $key key - * @param string $m modulo reference - * @param string $e exponent reference - * @return void + * + * 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 pemToMe($key, &$m, &$e) + public static function encapsulate($data, $pubkey, $alg = 'aes256cbc') { - $lines = explode("\n", $key); - unset($lines[0]); - unset($lines[count($lines)]); - $x = base64_decode(implode('', $lines)); + if ($alg === 'aes256cbc') { + return self::encapsulateAes($data, $pubkey); + } + return self::encapsulateOther($data, $pubkey, $alg); + } - $r = ASN_BASE::parseASNString($x); + /** + * + * 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 + */ + private static function encapsulateOther($data, $pubkey, $alg) + { + if (!$pubkey) { + Logger::log('no key. data: '.$data); + } + $fn = 'encrypt' . strtoupper($alg); + if (method_exists(__CLASS__, $fn)) { + $result = ['encrypted' => true]; + $key = random_bytes(256); + $iv = random_bytes(256); + $result['data'] = Strings::base64UrlEncode(self::$fn($data, $key, $iv), true); + + // log the offending call so we can track it down + if (!openssl_public_encrypt($key, $k, $pubkey)) { + $x = debug_backtrace(); + Logger::notice('RSA failed', ['trace' => $x[0]]); + } - $m = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[0]->asnData); - $e = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[1]->asnData); + $result['alg'] = $alg; + $result['key'] = Strings::base64UrlEncode($k, true); + openssl_public_encrypt($iv, $i, $pubkey); + $result['iv'] = Strings::base64UrlEncode($i, true); + + return $result; + } else { + $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data]; + Hook::callAll('other_encapsulate', $x); + + return $x['result']; + } } /** - * @param string $m modulo - * @param string $e exponent - * @return string + * + * 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 meToRsa($m, $e) + private static function encapsulateAes($data, $pubkey) { - $der = self::pkcs1Encode($m, $e); - $key = self::DerToRsa($der); - return $key; + if (!$pubkey) { + Logger::log('aes_encapsulate: no key. data: ' . $data); + } + + $key = random_bytes(32); + $iv = random_bytes(16); + $result = ['encrypted' => true]; + $result['data'] = Strings::base64UrlEncode(self::encryptAES256CBC($data, $key, $iv), true); + + // 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)); + } + + $result['alg'] = 'aes256cbc'; + $result['key'] = Strings::base64UrlEncode($k, true); + openssl_public_encrypt($iv, $i, $pubkey); + $result['iv'] = Strings::base64UrlEncode($i, true); + + return $result; } /** - * @param integer $bits number of bits - * @return mixed + * + * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php + * + * @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 newKeypair($bits) + public static function unencapsulate(array $data, $prvkey) { - $openssl_options = [ - 'digest_alg' => 'sha1', - 'private_key_bits' => $bits, - 'encrypt_key' => false - ]; + if (!$data) { + return; + } - $conf = Config::get('system', 'openssl_conf_file'); - if ($conf) { - $openssl_options['config'] = $conf; + $alg = $data['alg'] ?? 'aes256cbc'; + if ($alg === 'aes256cbc') { + return self::unencapsulateAes($data['data'], $prvkey); } - $result = openssl_pkey_new($openssl_options); - if (empty($result)) { - logger('new_keypair: failed'); - return false; + return self::unencapsulateOther($data, $prvkey, $alg); + } + + /** + * + * 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 $alg + * + * @return string|boolean The decrypted string or false on failure. + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + private static function unencapsulateOther(array $data, $prvkey, $alg) + { + $fn = 'decrypt' . strtoupper($alg); + + if (method_exists(__CLASS__, $fn)) { + openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey); + openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey); + + return self::$fn(Strings::base64UrlDecode($data['data']), $k, $i); + } else { + $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data]; + Hook::callAll('other_unencapsulate', $x); + + return $x['result']; } + } - // Get private key - $response = ['prvkey' => '', 'pubkey' => '']; + /** + * + * 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. + * + * @return string|boolean The decrypted string or false on failure. + * @throws \Exception + */ + private static function unencapsulateAes($data, $prvkey) + { + openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey); + openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey); - openssl_pkey_export($result, $response['prvkey']); + return self::decryptAES256CBC(Strings::base64UrlDecode($data['data']), $k, $i); + } - // Get public key - $pkey = openssl_pkey_get_details($result); - $response['pubkey'] = $pkey["key"]; - return $response; + /** + * Creates cryptographic secure random digits + * + * @param string $digits The count of digits + * @return int The random Digits + * + * @throws \Exception In case 'random_int' isn't usable + */ + public static function randomDigits($digits) + { + $rn = ''; + + // generating cryptographically secure pseudo-random integers + for ($i = 0; $i < $digits; $i++) { + $rn .= random_int(0, 9); + } + + return $rn; } }