From 11ef3895f5dbbb006a725c545a290b9fbbf3176a Mon Sep 17 00:00:00 2001 From: Philipp Date: Sat, 12 Sep 2020 11:57:37 +0200 Subject: [PATCH 1/4] Replace library/asn1.php with phpseclib --- composer.json | 3 +- composer.lock | 107 ++++++- library/asn1.php | 292 ------------------ src/Module/PublicRSAKey.php | 16 +- src/Protocol/Salmon.php | 4 +- src/Util/Crypto.php | 175 ++--------- tests/datasets/crypto/rsa/diaspora-public-pem | 6 + .../crypto/rsa/diaspora-public-rsa-base64 | 1 + tests/src/Util/CryptoTest.php | 48 ++- 9 files changed, 197 insertions(+), 455 deletions(-) delete mode 100644 library/asn1.php create mode 100644 tests/datasets/crypto/rsa/diaspora-public-pem create mode 100644 tests/datasets/crypto/rsa/diaspora-public-rsa-base64 diff --git a/composer.json b/composer.json index 2d6b46574d..fad962e414 100644 --- a/composer.json +++ b/composer.json @@ -63,7 +63,8 @@ "npm-asset/moment": "^2.24", "npm-asset/perfect-scrollbar": "0.6.16", "npm-asset/textcomplete": "^0.18.2", - "npm-asset/typeahead.js": "^0.11.1" + "npm-asset/typeahead.js": "^0.11.1", + "phpseclib/phpseclib": "^2.0" }, "repositories": [ { diff --git a/composer.lock b/composer.lock index 9f6f78d000..ebd434e39e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7d1fe40c28d815b56d0b5cb323860b26", + "content-hash": "ffe94190e166cebf80601fc3d6d26be0", "packages": [ { "name": "asika/simple-console", @@ -2474,6 +2474,111 @@ "homepage": "http://pear.php.net/package/Text_LanguageDetect", "time": "2020-05-17T12:19:40+00:00" }, + { + "name": "phpseclib/phpseclib", + "version": "2.0.29", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "497856a8d997f640b4a516062f84228a772a48a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/497856a8d997f640b4a516062f84228a772a48a8", + "reference": "497856a8d997f640b4a516062f84228a772a48a8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phing/phing": "~2.7", + "phpunit/phpunit": "^4.8.35|^5.7|^6.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2020-09-08T04:24:43+00:00" + }, { "name": "pragmarx/google2fa", "version": "v5.0.0", diff --git a/library/asn1.php b/library/asn1.php deleted file mode 100644 index cda96b6c8a..0000000000 --- a/library/asn1.php +++ /dev/null @@ -1,292 +0,0 @@ - 0x00, - 'ASN_APPLICATION' => 0x40, - 'ASN_CONTEXT' => 0x80, - 'ASN_PRIVATE' => 0xC0, - - 'ASN_PRIMITIVE' => 0x00, - 'ASN_CONSTRUCTOR' => 0x20, - - 'ASN_LONG_LEN' => 0x80, - 'ASN_EXTENSION_ID' => 0x1F, - 'ASN_BIT' => 0x80, - ); - - public static $ASN_TYPES = array( - 1 => 'ASN_BOOLEAN', - 2 => 'ASN_INTEGER', - 3 => 'ASN_BIT_STR', - 4 => 'ASN_OCTET_STR', - 5 => 'ASN_NULL', - 6 => 'ASN_OBJECT_ID', - 9 => 'ASN_REAL', - 10 => 'ASN_ENUMERATED', - 13 => 'ASN_RELATIVE_OID', - 48 => 'ASN_SEQUENCE', - 49 => 'ASN_SET', - 19 => 'ASN_PRINT_STR', - 22 => 'ASN_IA5_STR', - 23 => 'ASN_UTC_TIME', - 24 => 'ASN_GENERAL_TIME', - ); - - function __construct($v = false) - { - if (false !== $v) { - $this->asnData = $v; - if (is_array($this->asnData)) { - foreach ($this->asnData as $key => $value) { - if (is_object($value)) { - $this->asnData[$key]->setParent($this); - } - } - } else { - if (is_object($this->asnData)) { - $this->asnData->setParent($this); - } - } - } - } - - public function setParent($parent) - { - if (false !== $parent) { - $this->parent = $parent; - } - } - - /** - * This function will take the markers and types arrays and - * dynamically generate classes that extend this class for each one, - * and also define constants for them. - */ - public static function generateSubclasses() - { - define('ASN_BASE', 0); - foreach (self::$ASN_MARKERS as $name => $bit) - self::makeSubclass($name, $bit); - foreach (self::$ASN_TYPES as $bit => $name) - self::makeSubclass($name, $bit); - } - - /** - * Helper function for generateSubclasses() - */ - public static function makeSubclass($name, $bit) - { - define($name, $bit); - eval("class ".$name." extends ASN_BASE {}"); - } - - /** - * This function reset's the internal cursor used for value iteration. - */ - public function reset() - { - $this->cursor = 0; - } - - /** - * This function catches calls to get the value for the type, typeName, value, values, and data - * from the object. For type calls we just return the class name or the value of the constant that - * is named the same as the class. - */ - public function __get($name) - { - if ('type' == $name) { - // int flag of the data type - return constant(get_class($this)); - } elseif ('typeName' == $name) { - // name of the data type - return get_class($this); - } elseif ('value' == $name) { - // will always return one value and can be iterated over with: - // while ($v = $obj->value) { ... - // because $this->asnData["invalid key"] will return false - return is_array($this->asnData) ? $this->asnData[$this->cursor++] : $this->asnData; - } elseif ('values' == $name) { - // will always return an array - return is_array($this->asnData) ? $this->asnData : array($this->asnData); - } elseif ('data' == $name) { - // will always return the raw data - return $this->asnData; - } - } - - /** - * Parse an ASN.1 binary string. - * - * This function takes a binary ASN.1 string and parses it into it's respective - * pieces and returns it. It can optionally stop at any depth. - * - * @param string $string The binary ASN.1 String - * @param int $level The current parsing depth level - * @param int $maxLevel The max parsing depth level - * @return ASN_BASE The array representation of the ASN.1 data contained in $string - */ - public static function parseASNString($string=false, $level=1, $maxLevels=false){ - if (!class_exists('ASN_UNIVERSAL')) - self::generateSubclasses(); - if ($level>$maxLevels && $maxLevels) - return array(new ASN_BASE($string)); - $parsed = array(); - $endLength = strlen($string); - $bigLength = $length = $type = $dtype = $p = 0; - while ($p<$endLength){ - $type = ord($string[$p++]); - $dtype = ($type & 192) >> 6; - if ($type==0){ // if we are type 0, just continue - } else { - $length = ord($string[$p++]); - if (($length & ASN_LONG_LEN)==ASN_LONG_LEN){ - $tempLength = 0; - for ($x=0; $x<($length & (ASN_LONG_LEN-1)); $x++){ - $tempLength = @ord($string[$p++]) + ($tempLength * 256); - } - $length = $tempLength; - } - $data = substr($string, $p, intval($length)); - $parsed[] = self::parseASNData($type, $data, $level, $maxLevels); - $p = $p + $length; - } - } - return $parsed; - } - - /** - * Parse an ASN.1 field value. - * - * This function takes a binary ASN.1 value and parses it according to it's specified type - * - * @param int $type The type of data being provided - * @param string $data The raw binary data string - * @param int $level The current parsing depth - * @param int $maxLevels The max parsing depth - * @return mixed The data that was parsed from the raw binary data string - */ - public static function parseASNData($type, $data, $level, $maxLevels){ - $type = $type%50; // strip out context - switch ($type){ - default: - return new ASN_BASE($data); - case ASN_BOOLEAN: - return new ASN_BOOLEAN((bool)$data); - case ASN_INTEGER: - return new ASN_INTEGER(strtr(base64_encode($data),'+/','-_')); - case ASN_BIT_STR: - return new ASN_BIT_STR(self::parseASNString($data, $level+1, $maxLevels)); - case ASN_OCTET_STR: - return new ASN_OCTET_STR($data); - case ASN_NULL: - return new ASN_NULL(null); - case ASN_REAL: - return new ASN_REAL($data); - case ASN_ENUMERATED: - return new ASN_ENUMERATED(self::parseASNString($data, $level+1, $maxLevels)); - case ASN_RELATIVE_OID: // I don't really know how this works and don't have an example :-) - // so, lets just return it ... - return new ASN_RELATIVE_OID($data); - case ASN_SEQUENCE: - return new ASN_SEQUENCE(self::parseASNString($data, $level+1, $maxLevels)); - case ASN_SET: - return new ASN_SET(self::parseASNString($data, $level+1, $maxLevels)); - case ASN_PRINT_STR: - return new ASN_PRINT_STR($data); - case ASN_IA5_STR: - return new ASN_IA5_STR($data); - case ASN_UTC_TIME: - return new ASN_UTC_TIME($data); - case ASN_GENERAL_TIME: - return new ASN_GENERAL_TIME($data); - case ASN_OBJECT_ID: - return new ASN_OBJECT_ID(self::parseOID($data)); - } - } - - /** - * Parse an ASN.1 OID value. - * - * This takes the raw binary string that represents an OID value and parses it into its - * dot notation form. example - 1.2.840.113549.1.1.5 - * look up OID's here: http://www.oid-info.com/ - * (the multi-byte OID section can be done in a more efficient way, I will fix it later) - * - * @param string $data The raw binary data string - * @return string The OID contained in $data - */ - public static function parseOID($string){ - $ret = floor(ord($string[0])/40)."."; - $ret .= (ord($string[0]) % 40); - $build = array(); - $cs = 0; - - for ($i=1; $i127){ - $build[] = ord($string[$i])-ASN_BIT; - } elseif ($build){ - // do the build here for multibyte values - $build[] = ord($string[$i])-ASN_BIT; - // you know, it seems there should be a better way to do this... - $build = array_reverse($build); - $num = 0; - for ($x=0; $x> $x)) * $mult; - } else { - $value = ((($build[$x] & (ASN_BIT-1)) >> $x) ^ ($build[$x+1] << (7 - $x) & 255)) * $mult; - } - $num += $value; - } - $ret .= ".".$num; - $build = array(); // start over - } else { - $ret .= ".".$v; - $build = array(); - } - } - return $ret; - } - - public static function printASN($x, $indent=''){ - if (is_object($x)) { - echo $indent.$x->typeName."\n"; - if (ASN_NULL == $x->type) return; - if (is_array($x->data)) { - while ($d = $x->value) { - echo self::printASN($d, $indent.'. '); - } - $x->reset(); - } else { - echo self::printASN($x->data, $indent.'. '); - } - } elseif (is_array($x)) { - foreach ($x as $d) { - echo self::printASN($d, $indent); - } - } else { - if (preg_match('/[^[:print:]]/', $x)) // if we have non-printable characters that would - $x = base64_encode($x); // mess up the console, then print the base64 of them... - echo $indent.$x."\n"; - } - } - - -} - diff --git a/src/Module/PublicRSAKey.php b/src/Module/PublicRSAKey.php index 3d0423688f..7c46b63356 100644 --- a/src/Module/PublicRSAKey.php +++ b/src/Module/PublicRSAKey.php @@ -21,11 +21,13 @@ namespace Friendica\Module; -use ASN_BASE; use Friendica\BaseModule; use Friendica\DI; use Friendica\Model\User; use Friendica\Network\HTTPException\BadRequestException; +use Friendica\Util\Crypto; +use Friendica\Util\Strings; +use phpseclib\File\ASN1; /** * prints the public RSA key of a user @@ -49,18 +51,10 @@ class PublicRSAKey extends BaseModule throw new BadRequestException(); } - $lines = explode("\n", $user['spubkey']); - unset($lines[0]); - unset($lines[count($lines)]); - - $asnString = base64_decode(implode('', $lines)); - $asnBase = ASN_BASE::parseASNString($asnString); - - $m = $asnBase[0]->asnData[1]->asnData[0]->asnData[0]->asnData; - $e = $asnBase[0]->asnData[1]->asnData[0]->asnData[1]->asnData; + Crypto::pemToMe($user['spubkey'], $modulus, $exponent); header('Content-type: application/magic-public-key'); - echo 'RSA' . '.' . $m . '.' . $e; + echo 'RSA' . '.' . Strings::base64UrlEncode($modulus, true) . '.' . Strings::base64UrlEncode($exponent, true); exit(); } diff --git a/src/Protocol/Salmon.php b/src/Protocol/Salmon.php index 88c342a87c..169a4d0cbd 100644 --- a/src/Protocol/Salmon.php +++ b/src/Protocol/Salmon.php @@ -229,7 +229,7 @@ class Salmon */ public static function salmonKey($pubkey) { - Crypto::pemToMe($pubkey, $m, $e); - return 'RSA' . '.' . Strings::base64UrlEncode($m, true) . '.' . Strings::base64UrlEncode($e, true); + Crypto::pemToMe($pubkey, $modulus, $exponent); + return 'RSA' . '.' . Strings::base64UrlEncode($modulus, true) . '.' . Strings::base64UrlEncode($exponent, true); } } diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index 8adacf7104..ab669823b5 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -21,12 +21,12 @@ namespace Friendica\Util; -use ASN_BASE; use ASNValue; use Friendica\Core\Hook; use Friendica\Core\Logger; use Friendica\Core\System; use Friendica\DI; +use phpseclib\Crypt\RSA; /** * Crypto class @@ -86,27 +86,6 @@ class Crypto 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 @@ -136,26 +115,6 @@ class Crypto 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 @@ -169,79 +128,37 @@ class Crypto } /** - * @param string $key key - * @param string $m modulo reference - * @param object $e exponent reference + * 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(string $key) + { + $publicKey = new RSA(); + $publicKey->setPublicKey($key); + + return $publicKey->getPublicKey(RSA::PUBLIC_FORMAT_PKCS8); + } + + /** + * 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 - * @throws \Exception */ - private static function pubRsaToMe($key, &$m, &$e) + public static function pemToMe(string $key, string &$modulus, string &$exponent) { - $lines = explode("\n", $key); - unset($lines[0]); - unset($lines[count($lines)]); - $x = base64_decode(implode('', $lines)); + $publicKey = new RSA(); + $publicKey->loadKey($key); + $publicKey->setPublicKey(); - $r = ASN_BASE::parseASNString($x); - - $m = Strings::base64UrlDecode($r[0]->asnData[0]->asnData); - $e = Strings::base64UrlDecode($r[0]->asnData[1]->asnData); - } - - /** - * @param string $key key - * @return string - * @throws \Exception - */ - public static function rsaToPem($key) - { - self::pubRsaToMe($key, $m, $e); - return self::meToPem($m, $e); - } - - /** - * @param string $key key - * @return string - * @throws \Exception - */ - private static function pemToRsa($key) - { - self::pemToMe($key, $m, $e); - return self::meToRsa($m, $e); - } - - /** - * @param string $key key - * @param string $m modulo reference - * @param string $e exponent reference - * @return void - * @throws \Exception - */ - public static function pemToMe($key, &$m, &$e) - { - $lines = explode("\n", $key); - unset($lines[0]); - unset($lines[count($lines)]); - $x = base64_decode(implode('', $lines)); - - $r = ASN_BASE::parseASNString($x); - - if (isset($r[0])) { - $m = Strings::base64UrlDecode($r[0]->asnData[1]->asnData[0]->asnData[0]->asnData); - $e = Strings::base64UrlDecode($r[0]->asnData[1]->asnData[0]->asnData[1]->asnData); - } - } - - /** - * @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 = $publicKey->modulus->toBytes(); + $exponent = $publicKey->exponent->toBytes(); } /** @@ -312,42 +229,6 @@ class Crypto return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); } - /** - * Encrypt a string with 'aes-256-ctr' 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) - { - $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")); - } - - /** - * Decrypt a string with 'aes-256-ctr' 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) - { - $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")); - } - /** * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php diff --git a/tests/datasets/crypto/rsa/diaspora-public-pem b/tests/datasets/crypto/rsa/diaspora-public-pem new file mode 100644 index 0000000000..09dd1640d3 --- /dev/null +++ b/tests/datasets/crypto/rsa/diaspora-public-pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDReSjW7O4u4tK+UGKwogyw4Dok +j1Z4f70INc4CTlHk2sngzTa3uMzk1EU+9nYigqMfI1/DYoSCC0ZqikvZVGkrMJj6 +khM7orTasR4Av9Sn54rOQaM+raUC3JXd9AdkdXx1IBC71cAXVqIg/ERCrrUpxDxc +E6VXs4mFWpDHJ4q01QIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/tests/datasets/crypto/rsa/diaspora-public-rsa-base64 b/tests/datasets/crypto/rsa/diaspora-public-rsa-base64 new file mode 100644 index 0000000000..ba835a4711 --- /dev/null +++ b/tests/datasets/crypto/rsa/diaspora-public-rsa-base64 @@ -0,0 +1 @@ +LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tDQpNSUdKQW9HQkFORjVLTmJzN2k3aTByNVFZckNpRExEZ09pU1BWbmgvdlFnMXpnSk9VZVRheWVETk5yZTR6T1RVDQpSVDcyZGlLQ294OGpYOE5paElJTFJtcUtTOWxVYVNzd21QcVNFenVpdE5xeEhnQy8xS2ZuaXM1Qm96NnRwUUxjDQpsZDMwQjJSMWZIVWdFTHZWd0JkV29pRDhSRUt1dFNuRVBGd1RwVmV6aVlWYWtNY25pclRWQWdNQkFBRT0NCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0 \ No newline at end of file diff --git a/tests/src/Util/CryptoTest.php b/tests/src/Util/CryptoTest.php index 9f232210c9..51305337a9 100644 --- a/tests/src/Util/CryptoTest.php +++ b/tests/src/Util/CryptoTest.php @@ -21,6 +21,8 @@ */ namespace Friendica\Util; +use phpseclib\Crypt\RSA; +use phpseclib\Math\BigInteger; use PHPUnit\Framework\TestCase; class CryptoTest extends TestCase @@ -32,7 +34,7 @@ class CryptoTest extends TestCase private function assertRandomInt($min, $max) { global $phpMock; - $phpMock['random_int'] = function($mMin, $mMax) use ($min, $max) { + $phpMock['random_int'] = function ($mMin, $mMax) use ($min, $max) { $this->assertEquals($min, $mMin); $this->assertEquals($max, $mMax); return 1; @@ -51,6 +53,50 @@ class CryptoTest extends TestCase $this->assertEquals(8, strlen($test)); $this->assertEquals(11111111, $test); } + + public function dataRsa() + { + return [ + 'diaspora' => [ + 'key' => file_get_contents(__DIR__ . '/../../datasets/crypto/rsa/diaspora-public-rsa-base64'), + 'expected' => file_get_contents(__DIR__ . '/../../datasets/crypto/rsa/diaspora-public-pem'), + ], + ]; + } + + /** + * @dataProvider dataRsa + */ + public function testPubRsaToMe(string $key, string $expected) + { + $this->assertEquals($expected, Crypto::rsaToPem(base64_decode($key))); + } + + + public function datePem() + { + return [ + 'diaspora' => [ + 'key' => file_get_contents(__DIR__ . '/../../datasets/crypto/rsa/diaspora-public-pem'), + ], + ]; + } + + /** + * @dataProvider datePem + */ + public function testPemToMe(string $key) + { + Crypto::pemToMe($key, $m, $e); + + $expectedRSA = new RSA(); + $expectedRSA->loadKey([ + 'e' => new BigInteger($e, 256), + 'n' => new BigInteger($m, 256) + ]); + + $this->assertEquals($expectedRSA->getPublicKey(), $key); + } } /** From 90346f61bacf2c2e5867001d87f65e41ede9f651 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sat, 12 Sep 2020 20:54:37 +0200 Subject: [PATCH 2/4] Replace library/ASNValue.class.php with phpseclib functions --- library/ASNValue.class.php | 169 ---------------------------------- src/Util/Crypto.php | 63 ++----------- tests/src/Util/CryptoTest.php | 16 +++- 3 files changed, 22 insertions(+), 226 deletions(-) delete mode 100644 library/ASNValue.class.php diff --git a/library/ASNValue.class.php b/library/ASNValue.class.php deleted file mode 100644 index 7c17d10b40..0000000000 --- a/library/ASNValue.class.php +++ /dev/null @@ -1,169 +0,0 @@ -Tag = $Tag; - $this->Value = $Value; - } - - function Encode() - { - //Write type - $result = chr($this->Tag); - - //Write size - $size = strlen($this->Value); - if ($size < 127) { - //Write size as is - $result .= chr($size); - } - else { - //Prepare length sequence - $sizeBuf = self::IntToBin($size); - - //Write length sequence - $firstByte = 0x80 + strlen($sizeBuf); - $result .= chr($firstByte) . $sizeBuf; - } - - //Write value - $result .= $this->Value; - - return $result; - } - - function Decode(&$Buffer) - { - //Read type - $this->Tag = self::ReadByte($Buffer); - - //Read first byte - $firstByte = self::ReadByte($Buffer); - - if ($firstByte < 127) { - $size = $firstByte; - } - else if ($firstByte > 127) { - $sizeLen = $firstByte - 0x80; - //Read length sequence - $size = self::BinToInt(self::ReadBytes($Buffer, $sizeLen)); - } - else { - throw new Exception("Invalid ASN length value"); - } - - $this->Value = self::ReadBytes($Buffer, $size); - } - - protected static function ReadBytes(&$Buffer, $Length) - { - $result = substr($Buffer, 0, $Length); - $Buffer = substr($Buffer, $Length); - - return $result; - } - - protected static function ReadByte(&$Buffer) - { - return ord(self::ReadBytes($Buffer, 1)); - } - - protected static function BinToInt($Bin) - { - $len = strlen($Bin); - $result = 0; - for ($i=0; $i<$len; $i++) { - $curByte = self::ReadByte($Bin); - $result += $curByte << (($len-$i-1)*8); - } - - return $result; - } - - protected static function IntToBin($Int) - { - $result = ''; - do { - $curByte = $Int % 256; - $result .= chr($curByte); - - $Int = ($Int - $curByte) / 256; - } while ($Int > 0); - - $result = strrev($result); - - return $result; - } - - function SetIntBuffer($Value) - { - if (strlen($Value) > 1) { - $firstByte = ord($Value[0]); - if ($firstByte & 0x80) { //first bit set - $Value = chr(0x00) . $Value; - } - } - - $this->Value = $Value; - } - - function GetIntBuffer() - { - $result = $this->Value; - if (ord($result[0]) == 0x00) { - $result = substr($result, 1); - } - - return $result; - } - - function SetInt($Value) - { - $Value = self::IntToBin($Value); - - $this->SetIntBuffer($Value); - } - - function GetInt() - { - $result = $this->GetIntBuffer(); - $result = self::BinToInt($result); - - return $result; - } - - function SetSequence($Values) - { - $result = ''; - foreach ($Values as $item) { - $result .= $item->Encode(); - } - - $this->Value = $result; - } - - function GetSequence() - { - $result = array(); - $seq = $this->Value; - while (strlen($seq)) { - $val = new ASNValue(); - $val->Decode($seq); - $result[] = $val; - } - - return $result; - } -} diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index ab669823b5..0ff911ba79 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -21,12 +21,12 @@ namespace Friendica\Util; -use ASNValue; use Friendica\Core\Hook; use Friendica\Core\Logger; use Friendica\Core\System; use Friendica\DI; use phpseclib\Crypt\RSA; +use phpseclib\Math\BigInteger; /** * Crypto class @@ -65,56 +65,6 @@ class Crypto } /** - * @param string $Der der formatted string - * @param bool $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 $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 $m modulo * @param string $e exponent @@ -122,9 +72,12 @@ class Crypto */ public static function meToPem($m, $e) { - $der = self::pkcs8Encode($m, $e); - $key = self::DerToPem($der, false); - return $key; + $rsa = new RSA(); + $rsa->loadKey([ + 'e' => new BigInteger($e, 256), + 'n' => new BigInteger($m, 256) + ]); + return $rsa->getPublicKey(); } /** @@ -151,7 +104,7 @@ class Crypto * * @return void */ - public static function pemToMe(string $key, string &$modulus, string &$exponent) + public static function pemToMe(string $key, &$modulus, &$exponent) { $publicKey = new RSA(); $publicKey->loadKey($key); diff --git a/tests/src/Util/CryptoTest.php b/tests/src/Util/CryptoTest.php index 51305337a9..73b3d5372e 100644 --- a/tests/src/Util/CryptoTest.php +++ b/tests/src/Util/CryptoTest.php @@ -73,7 +73,7 @@ class CryptoTest extends TestCase } - public function datePem() + public function dataPEM() { return [ 'diaspora' => [ @@ -83,7 +83,7 @@ class CryptoTest extends TestCase } /** - * @dataProvider datePem + * @dataProvider dataPEM */ public function testPemToMe(string $key) { @@ -97,6 +97,18 @@ class CryptoTest extends TestCase $this->assertEquals($expectedRSA->getPublicKey(), $key); } + + /** + * @dataProvider dataPEM + */ + public function testMeToPem(string $key) + { + Crypto::pemToMe($key, $m, $e); + + $checkKey = Crypto::meToPem($m, $e); + + $this->assertEquals($key, $checkKey); + } } /** From 9d9489494e558572c4391160959f807d5480fd54 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sun, 13 Sep 2020 10:53:15 +0200 Subject: [PATCH 3/4] Format/name changes --- src/Util/Crypto.php | 37 ++++++++++++++++++----------------- tests/src/Util/CryptoTest.php | 9 +++++---- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index 0ff911ba79..55852d59ba 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -73,10 +73,11 @@ class Crypto public static function meToPem($m, $e) { $rsa = new RSA(); - $rsa->loadKey([ - 'e' => new BigInteger($e, 256), - 'n' => new BigInteger($m, 256) - ]); + $rsa->loadKey( + [ + 'e' => new BigInteger($e, 256), + 'n' => new BigInteger($m, 256) + ]); return $rsa->getPublicKey(); } @@ -89,10 +90,10 @@ class Crypto */ public static function rsaToPem(string $key) { - $publicKey = new RSA(); - $publicKey->setPublicKey($key); + $rsa = new RSA(); + $rsa->setPublicKey($key); - return $publicKey->getPublicKey(RSA::PUBLIC_FORMAT_PKCS8); + return $rsa->getPublicKey(RSA::PUBLIC_FORMAT_PKCS8); } /** @@ -106,12 +107,12 @@ class Crypto */ public static function pemToMe(string $key, &$modulus, &$exponent) { - $publicKey = new RSA(); - $publicKey->loadKey($key); - $publicKey->setPublicKey(); + $rsa = new RSA(); + $rsa->loadKey($key); + $rsa->setPublicKey(); - $modulus = $publicKey->modulus->toBytes(); - $exponent = $publicKey->exponent->toBytes(); + $modulus = $rsa->modulus->toBytes(); + $exponent = $rsa->exponent->toBytes(); } /** @@ -152,13 +153,13 @@ 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. */ private static function encryptAES256CBC($data, $key, $iv) @@ -168,13 +169,13 @@ class Crypto /** * 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) diff --git a/tests/src/Util/CryptoTest.php b/tests/src/Util/CryptoTest.php index 73b3d5372e..c6c17bdcc2 100644 --- a/tests/src/Util/CryptoTest.php +++ b/tests/src/Util/CryptoTest.php @@ -90,10 +90,11 @@ class CryptoTest extends TestCase Crypto::pemToMe($key, $m, $e); $expectedRSA = new RSA(); - $expectedRSA->loadKey([ - 'e' => new BigInteger($e, 256), - 'n' => new BigInteger($m, 256) - ]); + $expectedRSA->loadKey( + [ + 'e' => new BigInteger($e, 256), + 'n' => new BigInteger($m, 256) + ]); $this->assertEquals($expectedRSA->getPublicKey(), $key); } From 4db45aca066012fc725b2341436fde73b8027be3 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 14 Sep 2020 06:34:03 +0200 Subject: [PATCH 4/4] Fix indentation .. again ;-) --- src/Util/Crypto.php | 9 ++++----- tests/src/Util/CryptoTest.php | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index 55852d59ba..7b2c75a94a 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -73,11 +73,10 @@ class Crypto public static function meToPem($m, $e) { $rsa = new RSA(); - $rsa->loadKey( - [ - 'e' => new BigInteger($e, 256), - 'n' => new BigInteger($m, 256) - ]); + $rsa->loadKey([ + 'e' => new BigInteger($e, 256), + 'n' => new BigInteger($m, 256) + ]); return $rsa->getPublicKey(); } diff --git a/tests/src/Util/CryptoTest.php b/tests/src/Util/CryptoTest.php index c6c17bdcc2..6478c690ac 100644 --- a/tests/src/Util/CryptoTest.php +++ b/tests/src/Util/CryptoTest.php @@ -90,11 +90,10 @@ class CryptoTest extends TestCase Crypto::pemToMe($key, $m, $e); $expectedRSA = new RSA(); - $expectedRSA->loadKey( - [ - 'e' => new BigInteger($e, 256), - 'n' => new BigInteger($m, 256) - ]); + $expectedRSA->loadKey([ + 'e' => new BigInteger($e, 256), + 'n' => new BigInteger($m, 256) + ]); $this->assertEquals($expectedRSA->getPublicKey(), $key); }