Skip to content

Commit 33c8271

Browse files
committed
feat(signature): allow to provide custom signature
Signed-off-by: Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
1 parent 78ce5c7 commit 33c8271

4 files changed

Lines changed: 61 additions & 40 deletions

File tree

index.php

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -547,12 +547,12 @@ private function getUpdateServerResponse(): array {
547547
*
548548
* @throws \Exception
549549
*/
550-
public function downloadUpdate(string $url = ''): void {
550+
public function downloadUpdate(string $urlOverride = ''): void {
551551
$this->silentLog('[info] downloadUpdate()');
552552

553-
if ($url !== '') {
553+
if ($urlOverride !== '') {
554554
// If a URL is provided, use it directly
555-
$downloadURLs = [$url];
555+
$downloadURLs = [$urlOverride];
556556
} else {
557557
// Otherwise, get the download URLs from the update server
558558
$downloadURLs = $this->getDownloadURLs();
@@ -577,10 +577,10 @@ public function downloadUpdate(string $url = ''): void {
577577
}
578578
}
579579

580-
foreach ($downloadURLs as $url) {
580+
foreach ($downloadURLs as $urlOverride) {
581581
$this->previousProgress = 0;
582-
$saveLocation = $storageLocation . basename($url);
583-
if ($this->downloadArchive($url, $saveLocation)) {
582+
$saveLocation = $storageLocation . basename($urlOverride);
583+
if ($this->downloadArchive($urlOverride, $saveLocation)) {
584584
return;
585585
}
586586
}
@@ -758,25 +758,21 @@ private function getDownloadedFilePath(): string {
758758
*
759759
* @throws \Exception
760760
*/
761-
public function verifyIntegrity(string $urlOverride = ''): void {
761+
public function verifyIntegrity(string $urlOverride = '', string $signature = ''): void {
762762
$this->silentLog('[info] verifyIntegrity()');
763763

764764
if ($this->getCurrentReleaseChannel() === 'daily') {
765765
$this->silentLog('[info] current channel is "daily" which is not signed. Skipping verification.');
766766
return;
767767
}
768768

769-
if ($urlOverride !== '') {
770-
$this->silentLog('[info] custom download url provided, cannot verify signature');
771-
return;
772-
}
773-
774-
$response = $this->getUpdateServerResponse();
775-
if (empty($response['signature'])) {
776-
throw new \Exception('No signature specified for defined update');
777-
}
778-
if (!is_string($response['signature'])) {
779-
throw new \Exception('Signature specified for defined update should be a string');
769+
if ($signature === '') {
770+
if ($urlOverride !== '') {
771+
throw new \Exception(
772+
'Custom download url provided. You need to provide a signature with --signature or skip integrity check with --no-verify.'
773+
);
774+
}
775+
$signature = $this->getSignatureFromUpdater();
780776
}
781777

782778
$certificate = <<<EOF
@@ -811,7 +807,7 @@ public function verifyIntegrity(string $urlOverride = ''): void {
811807

812808
$validSignature = openssl_verify(
813809
file_get_contents($this->getDownloadedFilePath()),
814-
base64_decode($response['signature']),
810+
base64_decode($signature),
815811
$certificate,
816812
OPENSSL_ALGO_SHA512
817813
) === 1;
@@ -823,6 +819,19 @@ public function verifyIntegrity(string $urlOverride = ''): void {
823819
$this->silentLog('[info] end of verifyIntegrity()');
824820
}
825821

822+
private function getSignatureFromUpdater(): string {
823+
$response = $this->getUpdateServerResponse();
824+
if (empty($response['signature'])) {
825+
throw new \Exception('No signature specified for defined update');
826+
}
827+
828+
if (!is_string($response['signature'])) {
829+
throw new \Exception('Signature specified for defined update should be a string');
830+
}
831+
832+
return $response['signature'];
833+
}
834+
826835
/**
827836
* Gets the version as declared in $versionFile
828837
*

lib/UpdateCommand.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class UpdateCommand extends Command {
2323
protected bool $skipUpgrade = false;
2424
protected bool $skipIntegrityCheck = false;
2525
protected string $urlOverride = '';
26+
protected string $signature = '';
2627

2728
/** @var list<string> strings of text for stages of updater */
2829
protected array $checkTexts = [
@@ -49,7 +50,8 @@ protected function configure(): void {
4950
->addOption('no-backup', null, InputOption::VALUE_NONE, 'Skip backup of current Nextcloud version')
5051
->addOption('no-upgrade', null, InputOption::VALUE_NONE, "Don't automatically run occ upgrade")
5152
->addOption('url', null, InputOption::VALUE_OPTIONAL, 'The URL of the Nextcloud release to download')
52-
->addOption('no-verify', null, InputOption::VALUE_OPTIONAL, 'Skip integrity verification of the downloaded file');
53+
->addOption('no-verify', null, InputOption::VALUE_NONE, 'Skip integrity verification of the downloaded file')
54+
->addOption('signature', null, InputOption::VALUE_OPTIONAL, 'Base64 signature of the archive (use it in combination with --url option)');
5355
}
5456

5557
public static function getUpdaterVersion(): string {
@@ -66,6 +68,7 @@ protected function execute(InputInterface $input, OutputInterface $output) {
6668
$this->skipUpgrade = (bool)$input->getOption('no-upgrade');
6769
$this->skipIntegrityCheck = (bool)$input->getOption('no-verify');
6870
$this->urlOverride = (string)$input->getOption('url');
71+
$this->signature = (string)$input->getOption('signature');
6972

7073
$version = static::getUpdaterVersion();
7174
$output->writeln('Nextcloud Updater - version: ' . $version);
@@ -385,7 +388,7 @@ protected function executeStep(int $step): array {
385388
$this->updater->silentLog('[info] Skipping integrity check as requested');
386389
break;
387390
}
388-
$this->updater->verifyIntegrity($this->urlOverride);
391+
$this->updater->verifyIntegrity($this->urlOverride, $this->signature);
389392
break;
390393
case 6:
391394
$this->updater->extractDownload();

lib/Updater.php

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -509,12 +509,12 @@ private function getUpdateServerResponse(): array {
509509
*
510510
* @throws \Exception
511511
*/
512-
public function downloadUpdate(string $url = ''): void {
512+
public function downloadUpdate(string $urlOverride = ''): void {
513513
$this->silentLog('[info] downloadUpdate()');
514514

515-
if ($url !== '') {
515+
if ($urlOverride !== '') {
516516
// If a URL is provided, use it directly
517-
$downloadURLs = [$url];
517+
$downloadURLs = [$urlOverride];
518518
} else {
519519
// Otherwise, get the download URLs from the update server
520520
$downloadURLs = $this->getDownloadURLs();
@@ -539,10 +539,10 @@ public function downloadUpdate(string $url = ''): void {
539539
}
540540
}
541541

542-
foreach ($downloadURLs as $url) {
542+
foreach ($downloadURLs as $urlOverride) {
543543
$this->previousProgress = 0;
544-
$saveLocation = $storageLocation . basename($url);
545-
if ($this->downloadArchive($url, $saveLocation)) {
544+
$saveLocation = $storageLocation . basename($urlOverride);
545+
if ($this->downloadArchive($urlOverride, $saveLocation)) {
546546
return;
547547
}
548548
}
@@ -720,25 +720,21 @@ private function getDownloadedFilePath(): string {
720720
*
721721
* @throws \Exception
722722
*/
723-
public function verifyIntegrity(string $urlOverride = ''): void {
723+
public function verifyIntegrity(string $urlOverride = '', string $signature = ''): void {
724724
$this->silentLog('[info] verifyIntegrity()');
725725

726726
if ($this->getCurrentReleaseChannel() === 'daily') {
727727
$this->silentLog('[info] current channel is "daily" which is not signed. Skipping verification.');
728728
return;
729729
}
730730

731-
if ($urlOverride !== '') {
732-
$this->silentLog('[info] custom download url provided, cannot verify signature');
733-
return;
734-
}
735-
736-
$response = $this->getUpdateServerResponse();
737-
if (empty($response['signature'])) {
738-
throw new \Exception('No signature specified for defined update');
739-
}
740-
if (!is_string($response['signature'])) {
741-
throw new \Exception('Signature specified for defined update should be a string');
731+
if ($signature === '') {
732+
if ($urlOverride !== '') {
733+
throw new \Exception(
734+
'Custom download url provided. You need to provide a signature with --signature or skip integrity check with --no-verify.'
735+
);
736+
}
737+
$signature = $this->getSignatureFromUpdater();
742738
}
743739

744740
$certificate = <<<EOF
@@ -773,7 +769,7 @@ public function verifyIntegrity(string $urlOverride = ''): void {
773769

774770
$validSignature = openssl_verify(
775771
file_get_contents($this->getDownloadedFilePath()),
776-
base64_decode($response['signature']),
772+
base64_decode($signature),
777773
$certificate,
778774
OPENSSL_ALGO_SHA512
779775
) === 1;
@@ -785,6 +781,19 @@ public function verifyIntegrity(string $urlOverride = ''): void {
785781
$this->silentLog('[info] end of verifyIntegrity()');
786782
}
787783

784+
private function getSignatureFromUpdater(): string {
785+
$response = $this->getUpdateServerResponse();
786+
if (empty($response['signature'])) {
787+
throw new \Exception('No signature specified for defined update');
788+
}
789+
790+
if (!is_string($response['signature'])) {
791+
throw new \Exception('Signature specified for defined update should be a string');
792+
}
793+
794+
return $response['signature'];
795+
}
796+
788797
/**
789798
* Gets the version as declared in $versionFile
790799
*

updater.phar

550 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)