"paragonie/hidden-string": "^1.0",
"patrickschur/language-detection": "^5.0.0",
"pear/console_table": "^1.3",
- "phpseclib/phpseclib": "^2.0",
+ "phpseclib/phpseclib": "^3.0",
"pragmarx/google2fa": "^5.0",
"pragmarx/recovery": "^0.2",
"psr/container": "^1.0",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "e8626dc6957dff9cc783daad10cfc26f",
+ "content-hash": "2e082bac083ca61cc0c22f7055d690bf",
"packages": [
{
"name": "asika/simple-console",
},
{
"name": "phpseclib/phpseclib",
- "version": "2.0.38",
+ "version": "3.0.17",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
- "reference": "b03536539f43a4f9aa33c4f0b2f3a1c752088fcd"
+ "reference": "dbc2307d5c69aeb22db136c52e91130d7f2ca761"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/b03536539f43a4f9aa33c4f0b2f3a1c752088fcd",
- "reference": "b03536539f43a4f9aa33c4f0b2f3a1c752088fcd",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/dbc2307d5c69aeb22db136c52e91130d7f2ca761",
+ "reference": "dbc2307d5c69aeb22db136c52e91130d7f2ca761",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "paragonie/constant_time_encoding": "^1|^2",
+ "paragonie/random_compat": "^1.4|^2.0|^9.99.99",
+ "php": ">=5.6.1"
},
"require-dev": {
- "phing/phing": "~2.7",
- "phpunit/phpunit": "^4.8.35|^5.7|^6.0|^9.4",
- "squizlabs/php_codesniffer": "~2.0"
+ "phpunit/phpunit": "*"
},
"suggest": {
+ "ext-dom": "Install the DOM extension to load XML formatted public keys.",
"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.",
- "ext-xml": "Install the XML extension to load XML formatted public keys."
+ "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"type": "library",
"autoload": {
"phpseclib/bootstrap.php"
],
"psr-4": {
- "phpseclib\\": "phpseclib/"
+ "phpseclib3\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"type": "tidelift"
}
],
- "time": "2022-09-02T17:04:26+00:00"
+ "time": "2022-10-24T10:51:50+00:00"
},
{
"name": "pragmarx/google2fa",
throw new HTTPException\BadRequestException();
}
- $key_info = explode('.', $key);
+ $this->logger->info('Key details', ['info' => $key]);
- $m = Strings::base64UrlDecode($key_info[1]);
- $e = Strings::base64UrlDecode($key_info[2]);
-
- $this->logger->info('Key details', ['info' => $key_info]);
-
- $pubkey = Crypto::meToPem($m, $e);
+ $pubkey = SalmonProtocol::magicKeyToPem($key);
// We should have everything we need now. Let's see if it verifies.
use Friendica\BaseModule;
use Friendica\Core\System;
-use Friendica\DI;
use Friendica\Model\User;
use Friendica\Network\HTTPException\BadRequestException;
-use Friendica\Util\Crypto;
-use Friendica\Util\Strings;
+use Friendica\Protocol\Salmon;
/**
* prints the public RSA key of a user
throw new BadRequestException();
}
- Crypto::pemToMe($user['spubkey'], $modulus, $exponent);
-
- $content = 'RSA' . '.' . Strings::base64UrlEncode($modulus, true) . '.' . Strings::base64UrlEncode($exponent, true);
- System::httpExit($content, Response::TYPE_BLANK, 'application/magic-public-key');
+ System::httpExit(
+ Salmon::salmonKey($user['spubkey']),
+ Response::TYPE_BLANK,
+ 'application/magic-public-key'
+ );
}
}
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Email;
use Friendica\Protocol\Feed;
+use Friendica\Protocol\Salmon;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
$pubkey = $curlResult->getBody();
}
- $key = explode('.', $pubkey);
+ try {
+ $data['pubkey'] = Salmon::magicKeyToPem($pubkey);
+ } catch (\Throwable $e) {
- if (sizeof($key) >= 3) {
- $m = Strings::base64UrlDecode($key[1]);
- $e = Strings::base64UrlDecode($key[2]);
- $data['pubkey'] = Crypto::meToPem($m, $e);
}
}
}
use Friendica\DI;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\Probe;
+use Friendica\Protocol\Salmon\Format\Magic;
use Friendica\Util\Crypto;
use Friendica\Util\Strings;
use Friendica\Util\XML;
+use phpseclib3\Crypt\PublicKeyLoader;
/**
* Salmon Protocol class
*/
public static function salmonKey(string $pubkey): string
{
- Crypto::pemToMe($pubkey, $modulus, $exponent);
- return 'RSA' . '.' . Strings::base64UrlEncode($modulus, true) . '.' . Strings::base64UrlEncode($exponent, true);
+ \phpseclib3\Crypt\RSA::addFileFormat(Magic::class);
+
+ return PublicKeyLoader::load($pubkey)->toString('Magic');
+ }
+
+ /**
+ * @param string $magic Magic key format starting with "RSA."
+ * @return string
+ */
+ public static function magicKeyToPem(string $magic): string
+ {
+ \phpseclib3\Crypt\RSA::addFileFormat(Magic::class);
+
+ return (string) PublicKeyLoader::load($magic);
}
}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Protocol\Salmon\Format;
+
+use Friendica\Util\Strings;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * This custom public RSA key format class is meant to be used with the \phpseclib3\Crypto\RSA::addFileFormat method.
+ *
+ * It handles Salmon's specific magic key string starting with "RSA." and which MIME type is application/magic-key or
+ * application/magic-public-key
+ *
+ * @see https://web.archive.org/web/20160506073138/http://salmon-protocol.googlecode.com:80/svn/trunk/draft-panzer-magicsig-01.html#anchor13
+ */
+class Magic
+{
+ public static function load($key, $password = ''): array
+ {
+ if (!is_string($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ $key_info = explode('.', $key);
+
+ if (count($key_info) !== 3) {
+ throw new \UnexpectedValueException('Key should have three components separated by periods');
+ }
+
+ if ($key_info[0] !== 'RSA') {
+ throw new \UnexpectedValueException('Key first component should be "RSA"');
+ }
+
+ if (preg_match('#[+/]#', $key_info[1])
+ || preg_match('#[+/]#', $key_info[1])
+ ) {
+ throw new \UnexpectedValueException('Wrong encoding, expecting Base64URLencoding');
+ }
+
+ $m = Strings::base64UrlDecode($key_info[1]);
+ $e = Strings::base64UrlDecode($key_info[2]);
+
+ if (!$m || !$e) {
+ throw new \UnexpectedValueException('Base64 decoding produced an error');
+ }
+
+ return [
+ 'modulus' => new BigInteger($m, 256),
+ 'publicExponent' => new BigInteger($e, 256),
+ 'isPublicKey' => true,
+ ];
+ }
+
+ public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []): string
+ {
+ return 'RSA.' . Strings::base64UrlEncode($n->toBytes(), true) . '.' . Strings::base64UrlEncode($e->toBytes(), true);
+ }
+}
namespace Friendica\Util;
-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;
+use phpseclib3\Crypt\PublicKeyLoader;
/**
* Crypto class
return openssl_verify($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
}
- /**
- /**
- * @param string $m modulo
- * @param string $e exponent
- * @return string
- */
- public static function meToPem($m, $e)
- {
- $rsa = new RSA();
- $rsa->loadKey([
- 'e' => new BigInteger($e, 256),
- 'n' => new BigInteger($m, 256)
- ]);
- return $rsa->getPublicKey();
- }
-
/**
* Transform RSA public keys to standard PEM output
*
*/
public static function rsaToPem(string $key)
{
- $rsa = new RSA();
- $rsa->setPublicKey($key);
-
- return $rsa->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
- */
- public static function pemToMe(string $key, &$modulus, &$exponent)
- {
- $rsa = new RSA();
- $rsa->loadKey($key);
- $rsa->setPublicKey();
-
- $modulus = $rsa->modulus->toBytes();
- $exponent = $rsa->exponent->toBytes();
+ return (string)PublicKeyLoader::load($key);
}
/**
return $response;
}
- /**
- * Create a new elliptic curve key pair
- *
- * @return array with the elements "prvkey", "pubkey", "vapid-public" and "vapid-private"
- */
- public static function newECKeypair()
- {
- $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));
-
- // @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-cbc' cipher method.
*
use Friendica\Content\ContactSelector;
use Friendica\Core\Logger;
+use ParagonIE\ConstantTime\Base64;
/**
* This class handles string functions
* @param string $s URL to encode
* @param boolean $strip_padding Optional. Default false
* @return string Encoded URL
+ * @see https://web.archive.org/web/20160506073138/http://salmon-protocol.googlecode.com:80/svn/trunk/draft-panzer-magicsig-01.html#params
*/
public static function base64UrlEncode(string $s, bool $strip_padding = false): string
{
- $s = strtr(base64_encode($s), '+/', '-_');
-
if ($strip_padding) {
- $s = str_replace('=', '', $s);
+ $s = Base64::encodeUnpadded($s);
+ } else {
+ $s = Base64::encode($s);
}
- return $s;
+ return strtr($s, '+/', '-_');
}
/**
* @param string $s URL to decode
* @return string Decoded URL
* @throws \Exception
+ * @see https://web.archive.org/web/20160506073138/http://salmon-protocol.googlecode.com:80/svn/trunk/draft-panzer-magicsig-01.html#params
*/
public static function base64UrlDecode(string $s): string
{
- /*
- * // Placeholder for new rev of salmon which strips base64 padding.
- * // PHP base64_decode handles the un-padded input without requiring this step
- * // Uncomment if you find you need it.
- *
- * $l = strlen($s);
- * if (!strpos($s,'=')) {
- * $m = $l % 4;
- * if ($m == 2)
- * $s .= '==';
- * if ($m == 3)
- * $s .= '=';
- * }
- *
- */
-
- return base64_decode(strtr($s, '-_', '+/'));
+ return Base64::decode(strtr($s, '-_', '+/'));
}
/**
return $text;
}
-}
+}
\ No newline at end of file
--- /dev/null
+RSA.tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8_9FDqBYUigBSFDcJCzG27FM-zuddwpgJB0vDuPKQnt59kKRsw.AQAB
\ No newline at end of file
--- /dev/null
+-----BEGIN PUBLIC KEY-----\r
+MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALb7KAWWy1L6lrPtHfAuYVUC4ywo48cm\r
+W9e0ZvP/RQ6gWFIoAUhQ3CQsxtuxTPs7nXcKYCQdLw7jykJ7efZCkbMCAwEAAQ==\r
+-----END PUBLIC KEY-----
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Protocol;
+
+use Friendica\Protocol\Salmon;
+
+class SalmonTest extends \PHPUnit\Framework\TestCase
+{
+ public function dataMagic(): array
+ {
+ return [
+ 'salmon' => [
+ 'magic' => file_get_contents(__DIR__ . '/../../datasets/crypto/rsa/salmon-public-magic'),
+ 'pem' => file_get_contents(__DIR__ . '/../../datasets/crypto/rsa/salmon-public-pem'),
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataMagic
+ *
+ * @param $magic
+ * @param $pem
+ * @return void
+ * @throws \Exception
+ */
+ public function testSalmonKey($magic, $pem)
+ {
+ $this->assertEquals($magic, Salmon::salmonKey($pem));
+ }
+
+ /**
+ * @dataProvider dataMagic
+ *
+ * @param $magic
+ * @param $pem
+ * @return void
+ */
+ public function testMagicKeyToPem($magic, $pem)
+ {
+ $this->assertEquals($pem, Salmon::magicKeyToPem($magic));
+ }
+
+ public function dataMagicFailure(): array
+ {
+ return [
+ 'empty string' => [
+ 'magic' => '',
+ ],
+ 'Missing algo' => [
+ 'magic' => 'tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8_9FDqBYUigBSFDcJCzG27FM-zuddwpgJB0vDuPKQnt59kKRsw.AQAB',
+ ],
+ 'Missing modulus' => [
+ 'magic' => 'RSA.AQAB',
+ ],
+ 'Missing exponent' => [
+ 'magic' => 'RSA.tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8_9FDqBYUigBSFDcJCzG27FM-zuddwpgJB0vDuPKQnt59kKRsw',
+ ],
+ 'Missing key parts' => [
+ 'magic' => 'RSA.',
+ ],
+ 'Too many parts' => [
+ 'magic' => 'RSA.tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8_9FDqBYUigBSFDcJCzG27FM-zuddwpgJB0vDuPKQnt59kKRsw.AQAB.AQAB',
+ ],
+ 'Wrong encoding' => [
+ 'magic' => 'RSA.tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8/9FDqBYUigBSFDcJCzG27FM+zuddwpgJB0vDuPKQnt59kKRsw.AQAB',
+ ],
+ 'Wrong algo' => [
+ 'magic' => 'ECDSA.tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8_9FDqBYUigBSFDcJCzG27FM-zuddwpgJB0vDuPKQnt59kKRsw.AQAB',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataMagicFailure
+ *
+ * @param $magic
+ * @return void
+ */
+ public function testMagicKeyToPemFailure($magic)
+ {
+ $this->expectException(\Throwable::class);
+
+ Salmon::magicKeyToPem($magic);
+ }
+}
self::assertEquals(11111111, $test);
}
- public function dataRsa()
+ public function dataRsa(): array
{
return [
'diaspora' => [
],
];
}
-
- /**
- * @dataProvider dataPEM
- */
- 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)
- ]);
-
- self::assertEquals($expectedRSA->getPublicKey(), $key);
- }
-
- /**
- * @dataProvider dataPEM
- */
- public function testMeToPem(string $key)
- {
- Crypto::pemToMe($key, $m, $e);
-
- $checkKey = Crypto::meToPem($m, $e);
-
- self::assertEquals($key, $checkKey);
- }
}
/**