From 620e75b014ff81134e904ed503105b5a1bd404fa Mon Sep 17 00:00:00 2001 From: djmaze <> Date: Fri, 23 Oct 2026 17:24:28 +0200 Subject: [PATCH 1/3] Added curl_verify_peer option in application.ini, default is true --- .../v/0.0.0/app/libraries/RainLoop/Config/Application.php | 1 + snappymail/v/0.0.0/app/libraries/snappymail/repository.php | 2 ++ 2 files changed, 3 insertions(+) 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 0621cff84..f8db8862b 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 @@ -441,6 +441,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..c235b2c69 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/repository.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/repository.php @@ -12,6 +12,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. $oResponse = $oHTTP->doRequest('GET', static::BASE_URL . $path); @@ -37,6 +38,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); From f6e9ea600c27521c0d2432d40d35432bcd5e632c Mon Sep 17 00:00:00 2001 From: djmaze <> Date: Fri, 23 Oct 2026 18:28:28 +0200 Subject: [PATCH 2/3] Added example code to verify package signature --- .../app/libraries/snappymail/repository.php | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) 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 c235b2c69..07151ad78 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/repository.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/repository.php @@ -7,6 +7,9 @@ abstract class Repository // snappyMailRepo const BASE_URL = 'https://snappymail.eu/repository/v2/'; + const KEY = 'https://snappymail.eu/repository/releases.asc'; + const KEY_FINGERPRINT = '1016 E470 7914 5542 F8BA 1335 4820 8BA1 3290 F3EB'; + private static function get(string $path) : string { $oHTTP = HTTP\Request::factory(/*'socket' or 'curl'*/); @@ -264,7 +267,7 @@ abstract class Repository $sRealFile = ''; $bResult = false; - $sTmp = null; + $sTmp = $sTmpSignature = null; try { if ('plugin' === $sType) { $bReal = false; @@ -276,6 +279,39 @@ 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 + $sTmpSignature = static::download($aList[$sId]['file'] . '.asc'); + if (!$sTmpSignature) { + throw new \Exception('PGP signature is missing'); + } +/* + if ($aList[$sId]['pgp_sig'] !== \trim(\preg_replace('/-----(BEGIN|END) PGP SIGNATURE-----/', '', $sTmpSignature))) { + throw new \Exception('PGP signature invalid'); + } +*/ +/* + // TODO: verify signature + $gnupg = PGP\GnuPG::getInstance(''); + $sKey = ''; // static::KEY; + if (!$sKey) { + $sKey = PGP\Keyservers::get('4820 8BA1 3290 F3EB'); + if (!$sKey) { + $aKeys = PGP\Keyservers::index('releases@snappymail.eu'); + if ($aKeys) { + $sKey = PGP\Keyservers::get($aKeys[0]['keyid']); + } + } + } + if (!$sKey) { + throw new \Exception('PGP public key is missing'); + } + $gnupg->import($sKey); + // TODO: check KEY_FINGERPRINT ? + + // Verify signature + $gnupg->verify($sTmp, $sTmpSignature); +*/ } } From b5d587dc77bacff283f4b17f4b47087d3334c033 Mon Sep 17 00:00:00 2001 From: djmaze <> Date: Wed, 28 Oct 2026 16:52:54 +0100 Subject: [PATCH 3/3] Check PGP and x509 signatures, draft code --- build/nextcloud.php | 2 +- build/plugins.php | 18 ++++- .../app/libraries/snappymail/repository.php | 72 +++++++++++++----- .../snappymail/repository/releases.asc | 9 +++ .../snappymail/repository/releases.gpg | Bin 0 -> 240 bytes 5 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 snappymail/v/0.0.0/app/libraries/snappymail/repository/releases.asc create mode 100644 snappymail/v/0.0.0/app/libraries/snappymail/repository/releases.gpg 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 9cb4d9c1e..ce1addbad 100755 --- a/build/plugins.php +++ b/build/plugins.php @@ -104,6 +104,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).)*$/'); $tar->compress(Phar::GZ); +/* + $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(); +*/ unlink($tar_destination); rename("{$tar_destination}.gz", $tgz_destination); /* @@ -116,10 +124,18 @@ foreach (glob(ROOT_DIR . '/plugins/*', GLOB_NOSORT | GLOB_ONLYDIR) as $dir) { unlink($phar_destination); rename("{$phar_destination}.gz", $phar_destination); } + */ 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/snappymail/repository.php b/snappymail/v/0.0.0/app/libraries/snappymail/repository.php index 07151ad78..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,8 +7,12 @@ abstract class Repository // snappyMailRepo const BASE_URL = 'https://snappymail.eu/repository/v2/'; - const KEY = 'https://snappymail.eu/repository/releases.asc'; - const KEY_FINGERPRINT = '1016 E470 7914 5542 F8BA 1335 4820 8BA1 3290 F3EB'; + 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 { @@ -281,36 +285,70 @@ abstract class Repository $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('PGP signature is missing'); + throw new \Exception('Plugin PGP signature is missing'); } /* if ($aList[$sId]['pgp_sig'] !== \trim(\preg_replace('/-----(BEGIN|END) PGP SIGNATURE-----/', '', $sTmpSignature))) { - throw new \Exception('PGP signature invalid'); + throw new \Exception('Plugin PGP signature invalid'); } */ /* - // TODO: verify signature + // Like: gpg --no-default-keyring --keyring /path/to/pubkey.gpg --verify /path/to/file.txt.gpg $gnupg = PGP\GnuPG::getInstance(''); - $sKey = ''; // static::KEY; - if (!$sKey) { - $sKey = PGP\Keyservers::get('4820 8BA1 3290 F3EB'); + 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) { - $aKeys = PGP\Keyservers::index('releases@snappymail.eu'); - if ($aKeys) { - $sKey = PGP\Keyservers::get($aKeys[0]['keyid']); + $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'); + } } - if (!$sKey) { - throw new \Exception('PGP public key is missing'); + $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']}"); } - $gnupg->import($sKey); - // TODO: check KEY_FINGERPRINT ? - // Verify signature - $gnupg->verify($sTmp, $sTmpSignature); + // 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 0000000000000000000000000000000000000000..4f830265c639b8a7172b542e6e55c16125b940be GIT binary patch literal 240 zcmbPX%#y?_wMC3mn~jl$@s>M3BO|-RP2IJ7($;ecF!f!C+H{-C)x!DSZpP@KD=9mB zQ+HeJT)0I!I4`lFpwc%nGe^OuC^aWFu{gEZp%^Tf3l!8#Ew$^I!Xhlj#b6_*$RZ&2 zq@YqH)al1AVN(xC9XB*)Gq z-oU`cDFD;3*Ms4|^Ct~vjw=%uoMid6#U+PHtg=cZ|Ei39HP?rrCCMANbTcv>*kB~O hbmIbd2Cv!YC3=4zPc`fjy