From 5fab6aafdb83ca0829f3c02845626e052de65936 Mon Sep 17 00:00:00 2001 From: Dan Garner Date: Thu, 6 Feb 2025 15:57:46 +0100 Subject: [PATCH] Player software: verify package (#2893) * Player software: verify chromeos package on upload. xibosignageltd/xibo-private#926 --- Dockerfile | 8 ++- Dockerfile.ci | 8 ++- Dockerfile.dev | 8 ++- composer.json | 3 +- .../var/www/.gnupg/private-keys-v1.d/.gitkeep | 0 docker/var/www/.gnupg/pubring.kbx | Bin 0 -> 2504 bytes docker/var/www/.gnupg/trustdb.gpg | Bin 0 -> 1240 bytes lib/Entity/PlayerVersion.php | 54 +++++++++++++++++- lib/Helper/Environment.php | 8 +++ lib/Service/ConfigService.php | 7 ++- 10 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 docker/var/www/.gnupg/private-keys-v1.d/.gitkeep create mode 100644 docker/var/www/.gnupg/pubring.kbx create mode 100644 docker/var/www/.gnupg/trustdb.gpg diff --git a/Dockerfile b/Dockerfile index e8fcef7e5a..c9794091e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -70,6 +70,7 @@ RUN LC_ALL=C.UTF-8 DEBIAN_FRONTEND=noninteractive apt update && apt upgrade -y & libapache2-mod-xsendfile \ netcat \ iputils-ping \ + gnupg \ php8.2 \ libapache2-mod-php8.2 \ php8.2-gd \ @@ -90,6 +91,7 @@ RUN LC_ALL=C.UTF-8 DEBIAN_FRONTEND=noninteractive apt update && apt upgrade -y & php8.2-phar \ php8.2-opcache \ php8.2-mongodb \ + php8.2-gnupg \ tzdata \ msmtp \ openssl \ @@ -173,7 +175,8 @@ ENV CMS_DEV_MODE=false \ MEMCACHED_PORT=11211 \ CMS_USAGE_REPORT=true \ XTR_ENABLED=true \ - GIT_COMMIT=$GIT_COMMIT + GIT_COMMIT=$GIT_COMMIT \ + GNUPGHOME=/var/www/.gnupg # Expose port 80 EXPOSE 80 @@ -228,7 +231,8 @@ RUN mkdir -p /var/www/cms/library/temp && \ /etc/periodic/15min/cms-db-backup && \ mkdir -p /run/apache2 && \ ln -sf /usr/bin/msmtp /usr/sbin/sendmail && \ - chmod 777 /tmp + chmod 777 /tmp && \ + chown -R www-data:www-data /var/www/.gnupg # Expose volume mount points VOLUME /var/www/cms/library diff --git a/Dockerfile.ci b/Dockerfile.ci index 3b3338ba04..b7f7a941c8 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -55,6 +55,7 @@ RUN LC_ALL=C.UTF-8 DEBIAN_FRONTEND=noninteractive apt update && apt upgrade -y & libapache2-mod-xsendfile \ netcat \ iputils-ping \ + gnupg \ php8.2 \ libapache2-mod-php8.2 \ php8.2-gd \ @@ -75,6 +76,7 @@ RUN LC_ALL=C.UTF-8 DEBIAN_FRONTEND=noninteractive apt update && apt upgrade -y & php8.2-phar \ php8.2-opcache \ php8.2-mongodb \ + php8.2-gnupg \ tzdata \ msmtp \ openssl \ @@ -98,7 +100,8 @@ ADD docker/ / # Adjust file permissions as appropriate RUN chmod +x /entrypoint.sh /usr/local/bin/httpd-foreground /usr/local/bin/wait-for-command.sh \ /etc/periodic/15min/cms-db-backup && \ - chmod 777 /tmp + chmod 777 /tmp && \ + chown -R www-data:www-data /var/www/.gnupg # Update the PHP.ini file RUN sed -i "s/error_reporting = .*$/error_reporting = E_ERROR | E_WARNING | E_PARSE/" /etc/php/8.2/apache2/php.ini && \ @@ -152,7 +155,8 @@ ENV CMS_DEV_MODE=true \ MEMCACHED_PORT=11211 \ CMS_USAGE_REPORT=false \ XTR_ENABLED=true \ - GIT_COMMIT=$GIT_COMMIT + GIT_COMMIT=$GIT_COMMIT \ + GNUPGHOME=/var/www/.gnupg # Expose port 80 EXPOSE 80 diff --git a/Dockerfile.dev b/Dockerfile.dev index 0b269c6038..0aafcc949c 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -22,6 +22,7 @@ RUN LC_ALL=C.UTF-8 DEBIAN_FRONTEND=noninteractive apt update && apt upgrade -y & libapache2-mod-xsendfile \ netcat \ iputils-ping \ + gnupg \ php8.2 \ libapache2-mod-php8.2 \ php8.2-gd \ @@ -42,6 +43,7 @@ RUN LC_ALL=C.UTF-8 DEBIAN_FRONTEND=noninteractive apt update && apt upgrade -y & php8.2-phar \ php8.2-opcache \ php8.2-mongodb \ + php8.2-gnupg \ tzdata \ msmtp \ openssl \ @@ -65,7 +67,8 @@ ADD docker/ / # Adjust file permissions as appropriate RUN chmod +x /entrypoint.sh /usr/local/bin/httpd-foreground /usr/local/bin/wait-for-command.sh \ /etc/periodic/15min/cms-db-backup && \ - chmod 777 /tmp + chmod 777 /tmp && \ + chown -R www-data:www-data /var/www/.gnupg # Update the PHP.ini file RUN sed -i "s/error_reporting = .*$/error_reporting = E_ERROR | E_WARNING | E_PARSE/" /etc/php/8.2/apache2/php.ini && \ @@ -121,7 +124,8 @@ ENV CMS_DEV_MODE=true \ MEMCACHED_PORT=11211 \ CMS_USAGE_REPORT=true \ XTR_ENABLED=false \ - GIT_COMMIT=$GIT_COMMIT + GIT_COMMIT=$GIT_COMMIT \ + GNUPGHOME=/var/www/.gnupg # Expose port 80 EXPOSE 80 diff --git a/composer.json b/composer.json index 10bb04242e..32aa70bd7e 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "ext-json": "1", "ext-soap": "1", "ext-zip": "1", - "ext-fileinfo": "*" + "ext-fileinfo": "1" } }, "minimum-stability": "dev", @@ -66,7 +66,6 @@ "james-heinrich/getid3": "^1.9", "onelogin/php-saml": "4.1.*", "infostars/picofeed": "dev-westphal/php8", - "xibosignage/xibo-xmr": "0.*", "tedivm/stash": "^v0.17.6", "phenx/php-font-lib": "^0.5.0", "symfony/event-dispatcher": "^4.1", diff --git a/docker/var/www/.gnupg/private-keys-v1.d/.gitkeep b/docker/var/www/.gnupg/private-keys-v1.d/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docker/var/www/.gnupg/pubring.kbx b/docker/var/www/.gnupg/pubring.kbx new file mode 100644 index 0000000000000000000000000000000000000000..5ff31183c3c27ef7e40d1e9d57b44b32e6eb0a08 GIT binary patch literal 2504 zcmai!c{J4PAIHBlmRXEtWXl#RYl?Jj%|%6)gb2|n3?mF9##+i2L)I*1EJKrJY`3yC zwrt((F=(>WSmHMJ-f~I4L@vI5whnc^7`*Pqz+v1T5_WD z;387-j>-}|Gj}GtV0nDMjXId^7YINA062GF>HN5Z0rnr~e{F6#yVeJ-OfWaQTh2}) zh!1!=fu;r8hNq+aJoQ9kNu3D<)noS(7gh82{FQ58aZMeMFtH9+_I%PZnODEb>L^Nv z46Nqx#uH-LD$V# zrjzqskapU?*2d{A4V=u(=!zoryx9^}T<$zxq_f*$J{;dCK|vVlI!I5=!=#v2O(TiofMw#c#?vDL zW%f!|Rcj6#LZmkKMn!wYK3v>sV*e4Qg0IGFbO~{+bR_B#XB(7kIdT006F`Ch(DVKD zxcRvK%p*{xbIsiuFJDkX2>gz-EOHm<(zSa*yQ!5#0dAcgYAEBm_o;DYsn z_T7Fz%_lK`A0!bh#>T+~gF?WZf^Zm=3(PJA zW&?pGL15u901o5(=Q|J#c)#nH%Lg;m*pK(}i95v7xqWyXs=7@|cM)Eib*IJj5?m(S z>0;*|H(Gh}d>Ov}Uc_ps^CP3yfI*dfS@7nsbCVZqxEuURKc#o#<{}@nvxGISG#eNW7m6E5 z#X+hMMT`YqYqCuUnEB-z5Ng-jpn2aCV>D#jEsg+mHAu1r=z)s%XO~nbMi~{GD%157 zOCU?+t;i_1nf8qPr6IHSg~RhB*>$I6$vokLhgGpxNEoK}#H$tb1(!-i9dVsBgu!`V zRQl|@HO8&w-_~0m`+2ni9s)}|G=2*GPf?sbQY-{Fsz7u^*hL!!w^uJ|F~3^L=oc?3 zizH#+Y=jc6-t7)^RZyeo)2u(iyfOg6FMo;{ zS8XL2QWwe}wjlxI5_>R7{bmeTWtEv5(go_K7caEaZ*3BEOe4)0>{))tXM|}%<;T@? zgW6XnCjtS9B&4FBhooCzKjI|jokLX9X@$1nEt(^Cc_Vwp;e^}u#EbRVi)ZSOzUob* z_)4^P*`5?WBLdPQN`B#fwz%TzEO!tc?!{mxnHY{*s* z_B+bra6sg1$LGAfX&0P>Ec~=%6_G};g_xHXx$-uuY9-Um)*O9Wtu&U~OA)g5K8{0R`JQC2V z`02ZcR(O`G&;eNYKK)W&L0KzN6I2mD3p%yooOenAZMHg){&Cg!3#%?}vn`2D{AR#9 zv7>CZnxe?0excER84+2WHS24RpKrzuO1B1{BB~aasZ@ud6uUtvd71BvCH0)vgwlXd zKK{;&e8oQ6N1Mz!(11B<0R$?d4+mTwCPSv*ZDJ45(!IazE&AZ8*J8cdLWzGE}WfbZd^6@6Gs0n_SC+eZ>C;8J2Ya literal 0 HcmV?d00001 diff --git a/docker/var/www/.gnupg/trustdb.gpg b/docker/var/www/.gnupg/trustdb.gpg new file mode 100644 index 0000000000000000000000000000000000000000..1f37dc06a2d728a5d779b8dd49380a40b703baa9 GIT binary patch literal 1240 zcmZQfFGy!*W@Ke#Vqi$0`+GVAcEHGmT^vJ{pgK8-7J}-aCXcGa2x73nXt<1q3n&Ce hS~}oi5O9nM$bM#5utype === 'chromeOS') { $this->getLog()->debug('add: handling chromeOS upload'); - // TODO: check the signature of the file to make sure it comes from a verified source. + $fullFileName = $libraryFolder . 'playersoftware/' . $this->fileName; + + // Check the signature of the file to make sure it comes from a verified source. + try { + $this->getLog()->debug('unpack: loading gnupg to verify the signature'); + + $gpg = new \gnupg(); + $gpg->seterrormode(\gnupg::ERROR_EXCEPTION); + $info = $gpg->verify( + file_get_contents($fullFileName), + false, + ); + + if ($info === false + || $info[0]['fingerprint'] !== '10415C506BE63E70BAF1D58BC1EF165A0F880F75' + || $info[0]['status'] !== 0 + || $info[0]['summary'] !== 0 + ) { + $this->getLog()->error('unpack: unable to verify GPG. file = ' . $this->fileName); + throw new GeneralException(); + } + + $this->getLog()->debug('unpack: signature verified'); + + // Signature verified, move the file, so we can decrypt it. + rename($fullFileName, $libraryFolder . 'playersoftware/' . $this->versionId . '.gpg'); + + $this->getLog()->debug('unpack: using the shell to decrypt the file'); + + // Go to the shell to decrypt it. + shell_exec('gpg --decrypt --output ' . $libraryFolder . 'playersoftware/' . $this->versionId + . ' ' . $libraryFolder . 'playersoftware/' . $this->versionId . '.gpg'); + + // Was this successful? + if (!file_exists($libraryFolder . 'playersoftware/' . $this->versionId)) { + throw new NotFoundException('Not found after decryption'); + } + + // Rename the GPG file back to its original name. + rename($libraryFolder . 'playersoftware/' . $this->versionId . '.gpg', $fullFileName); + } catch (\Exception $e) { + $this->getLog()->error('unpack: ' . $e->getMessage()); + throw new InvalidArgumentException(__('Package file unsupported or invalid')); + } $zip = new \ZipArchive(); - if (!$zip->open($libraryFolder . 'playersoftware/' . $this->fileName)) { + if (!$zip->open($libraryFolder . 'playersoftware/' . $this->versionId)) { throw new InvalidArgumentException(__('Unable to open ZIP')); } @@ -273,6 +318,9 @@ public function unpack(string $libraryFolder): static $manifest = Str::replace('assets/icons/192x192.png', $this->config->uri('img/192x192.png'), $manifest); file_put_contents($folder . '/manifest.json', $manifest); + + // Unlink our decrypted file + unlink($libraryFolder . 'playersoftware/' . $this->versionId); } return $this; diff --git a/lib/Helper/Environment.php b/lib/Helper/Environment.php index 55090011a5..84ca9303e7 100644 --- a/lib/Helper/Environment.php +++ b/lib/Helper/Environment.php @@ -319,6 +319,14 @@ public static function checkSimpleXml() return extension_loaded('simplexml'); } + /** + * @return bool + */ + public static function checkGnu() + { + return extension_loaded('gnupg'); + } + /** * @param $url * @return bool diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php index 4a05650872..aa69e1475a 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -1,6 +1,6 @@ testItem($rows, __('GNUPG'), + Environment::checkGnu(), + __('checkGnu is used to verify the integrity of Player Software versions uploaded to the CMS') + ); + $this->envTested = true; return $rows;