diff --git a/build/nextcloud.php b/build/nextcloud.php index 9970cd0f5..a87a97cf6 100755 --- a/build/nextcloud.php +++ b/build/nextcloud.php @@ -1,7 +1,7 @@ version}-nextcloud.tar"; diff --git a/build/plugins.php b/build/plugins.php index 39e6ea933..d8787f540 100755 --- a/build/plugins.php +++ b/build/plugins.php @@ -126,6 +126,14 @@ foreach (glob(ROOT_DIR . '/plugins/*', GLOB_NOSORT | GLOB_ONLYDIR) as $dir) { // $tar = new PharData($tar_destination); $tar->buildFromDirectory('./plugins/', '/' . \preg_quote("./plugins/{$name}/", '/') . '((?!\.bak).)*$/'); if ($tar instanceof PharData) { +/* + $private = \openssl_get_privatekey(\file_get_contents('private.pem')); + $pkey = ''; + \openssl_pkey_export($private, $pkey); + $tar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + $tar->setSignatureAlgorithm(Phar::Phar::PGP, file_get_contents($private_key)); + $signature = $tar->getSignature(); +*/ $tar->compress(Phar::GZ); unlink($tar_destination); rename("{$tar_destination}.gz", $tgz_destination); @@ -144,8 +152,15 @@ foreach (glob(ROOT_DIR . '/plugins/*', GLOB_NOSORT | GLOB_ONLYDIR) as $dir) { } */ if (isset($options['sign'])) { - passthru('gpg --local-user 1016E47079145542F8BA133548208BA13290F3EB --armor --detach-sign '.escapeshellarg($tgz_destination), $return_var); + $arg = escapeshellarg($tgz_destination); + passthru('gpg --local-user 1016E47079145542F8BA133548208BA13290F3EB --armor --detach-sign '.$arg, $return_var); $manifest_item['pgp_sig'] = trim(preg_replace('/-----(BEGIN|END) PGP SIGNATURE-----/', '', file_get_contents($tgz_destination.'.asc'))); + + $cert_dir = $_SERVER['HOME'].'/.snappymail/certificates'; + $out = escapeshellarg($tgz_destination . '.sig'); + passthru("openssl dgst -sha256 -sign {$cert_dir}/sign_releases.key -out {$out} {$arg}", $return_var); +// passthru("openssl dgst -sha512 -sign {$cert_dir}/sign_releases.key -out {$out} {$arg}", $return_var); +// $manifest_item['ssl_sig'] = base64_encode(file_get_contents($tgz_destination.'.sig')); } ksort($manifest_item); $manifest[$name] = $manifest_item; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php index adc04b97d..9da0dba46 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -444,6 +444,7 @@ Enables caching in the system'), 'folders_spec_limit' => array(50), 'curl_proxy' => array(''), 'curl_proxy_auth' => array(''), + 'curl_verify_peer' => array(true), 'custom_login_link' => array(''), 'custom_logout_link' => array(''), 'http_client_ip_check_proxy' => array(false), diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/repository.php b/snappymail/v/0.0.0/app/libraries/snappymail/repository.php index 2a36360b0..d2ac31ecc 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/repository.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/repository.php @@ -7,11 +7,19 @@ abstract class Repository // snappyMailRepo const BASE_URL = 'https://snappymail.eu/repository/v2/'; + const KEY_ARMOR_URL = 'https://snappymail.eu/repository/releases.asc'; +// const KEY_RAW_URL = 'https://snappymail.eu/.well-known/openpgpkey/hu/i4cdqgcarfjdjnba6y4jnf498asg8c6p'; + // Long Key ID is the last 16 bytes of the fingerprint + const KEY_FINGERPRINT = '1016E47079145542F8BA133548208BA13290F3EB'; + + const X509_PUB = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAOtwedIGp7T+3q5Hx+Gf/MoUqU6Ac3AHoI6KWlNaJH2g=\n-----END PUBLIC KEY-----"; + private static function get(string $path) : string { $oHTTP = HTTP\Request::factory(/*'socket' or 'curl'*/); $oHTTP->proxy = \RainLoop\Api::Config()->Get('labs', 'curl_proxy', ''); $oHTTP->proxy_auth = \RainLoop\Api::Config()->Get('labs', 'curl_proxy_auth', ''); + $oHTTP->verify_peer = \RainLoop\Api::Config()->Get('labs', 'curl_verify_peer', true); $oHTTP->max_response_kb = 0; $oHTTP->timeout = 15; // timeout in seconds. $oResponse = $oHTTP->doRequest('GET', static::BASE_URL . $path); @@ -37,6 +45,7 @@ abstract class Repository $oHTTP = HTTP\Request::factory(/*'socket' or 'curl'*/); $oHTTP->proxy = \RainLoop\Api::Config()->Get('labs', 'curl_proxy', ''); $oHTTP->proxy_auth = \RainLoop\Api::Config()->Get('labs', 'curl_proxy_auth', ''); + $oHTTP->verify_peer = \RainLoop\Api::Config()->Get('labs', 'curl_verify_peer', true); $oHTTP->max_response_kb = 0; $oHTTP->timeout = 15; // timeout in seconds. $oHTTP->streamBodyTo($pDest); @@ -262,7 +271,7 @@ abstract class Repository $sRealFile = ''; $bResult = false; - $sTmp = null; + $sTmp = $sTmpSignature = null; try { if ('plugin' === $sType) { $bReal = false; @@ -274,6 +283,73 @@ abstract class Repository if (isset($aList[$sId]) && (!$sFile || $sFile === $aList[$sId]['file'])) { $sRealFile = $aList[$sId]['file']; $sTmp = static::download($aList[$sId]['file']); + + // https://github.com/the-djmaze/snappymail/security/advisories/GHSA-j9c4-gf64-rph2 + // Verify PGP signature + $sTmpSignature = static::download($aList[$sId]['file'] . '.asc'); + if (!$sTmpSignature) { + throw new \Exception('Plugin PGP signature is missing'); + } +/* + if ($aList[$sId]['pgp_sig'] !== \trim(\preg_replace('/-----(BEGIN|END) PGP SIGNATURE-----/', '', $sTmpSignature))) { + throw new \Exception('Plugin PGP signature invalid'); + } +*/ +/* + // Like: gpg --no-default-keyring --keyring /path/to/pubkey.gpg --verify /path/to/file.txt.gpg + $gnupg = PGP\GnuPG::getInstance(''); + if (!\count($gnupg->keyinfo(static::KEY_FINGERPRINT))) { + $sKey = null; + $file = __DIR__ . '/repository/releases.asc'; + if (\is_readable($file)) { + if ('bfa5df5ab2e782940f6b2eede9e62b5b7a44415b' !== \sha1_file($file)) { + throw new \Exception('Repository invalid public key'); + } + $sKey = \file_get_contents($file); + } + if (!$sKey) { + $sKey = PGP\Keyservers::get(static::KEY_FINGERPRINT); + if (!$sKey) { + $aKeys = PGP\Keyservers::index('releases@snappymail.eu'); + if ($aKeys) { + $sKey = PGP\Keyservers::get($aKeys[0]['keyid']); + } + } + } + if (!$sKey) { + throw new \Exception('Repository public key is missing'); + } + $gnupg->import($sKey); + if (!\count($gnupg->keyinfo(static::KEY_FINGERPRINT))) { + throw new \Exception('Repository public key not imported'); + } + } + $result = $gnupg->verifyFile($sTmp, $sTmpSignature); + if (!empty($result[0]['keyid'])) { + throw new \Exception("Plugin PGP signature wrong key: {$result[0]['keyid']}"); + } + if (empty($result[0]['fingerprint'])) { + throw new \Exception('Plugin PGP signature invalid'); + } + if (static::KEY_FINGERPRINT !== $result[0]['fingerprint']) { + throw new \Exception("Plugin PGP signature wrong key: {$result[0]['fingerprint']}"); + } + + // Verify x509 signature +/* + // openssl rsautl -verify -inkey my-pub.pem -in in.txt.rsa -pubin + $sTmpSignature = static::download($aList[$sId]['file'] . '.sig'); + if (!$sTmpSignature) { + throw new \Exception('Plugin x509 signature is missing'); + } + openssl_verify( + \file_get_contents($sTmp), + \file_get_contents($sTmpSignature), + static::X509_PUB, + \OPENSSL_ALGO_SHA256, + 0 + ): int|false +*/ } } diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/repository/releases.asc b/snappymail/v/0.0.0/app/libraries/snappymail/repository/releases.asc new file mode 100644 index 000000000..39afe03b7 --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/snappymail/repository/releases.asc @@ -0,0 +1,9 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEYg0atBYJKwYBBAHaRw8BAQdA2S2tvGavChACjtBastsKRThD3rsBW1LUZLmN +Zbs4uaG0I1NuYXBweU1haWwgPHJlbGVhc2VzQHNuYXBweW1haWwuZXU+iJQEExYK +ADwWIQQQFuRweRRVQvi6EzVIIIuhMpDz6wUCYg0atAIbAwULCQgHAgMiAgEGFQoJ +CAsCBBYCAwECHgcCF4AACgkQSCCLoTKQ8+u9SAD/Q/IoAwjUkKDJBPq0RGwCFnl6 +FG/VHB97CvBSpGOxtIsBAMCwMhWlsaBHAEqbzxiN+cdlMYwV23+SWLUJ/XMFgukE +=5ZwF +-----END PGP PUBLIC KEY BLOCK----- diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/repository/releases.gpg b/snappymail/v/0.0.0/app/libraries/snappymail/repository/releases.gpg new file mode 100644 index 000000000..4f830265c Binary files /dev/null and b/snappymail/v/0.0.0/app/libraries/snappymail/repository/releases.gpg differ