mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
feat(http-sig): occ commands to manage Ed25519 keys
ocm:keys:list list known keys with their slot and kid ocm:keys:stage generate a pending key, advertise via JWKS ocm:keys:activate promote pending -> active, demote previous active ocm:keys:retire delete the retiring key (kid stops resolving) Plus the autoloader regen covering the new classes from this branch. Signed-off-by: Micke Nordin <kano@sunet.se>
This commit is contained in:
parent
3b5107bc96
commit
166bc2c74b
7 changed files with 212 additions and 0 deletions
42
core/Command/OCM/ActivateKey.php
Normal file
42
core/Command/OCM/ActivateKey.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OC\Core\Command\OCM;
|
||||
|
||||
use OC\Core\Command\Base;
|
||||
use OC\OCM\OCMSignatoryManager;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ActivateKey extends Base {
|
||||
public function __construct(
|
||||
private readonly OCMSignatoryManager $signatoryManager,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function configure(): void {
|
||||
$this
|
||||
->setName('ocm:keys:activate')
|
||||
->setDescription('promote the staged Ed25519 key to active; the previous active key moves to retiring');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
try {
|
||||
$this->signatoryManager->activateStagedEd25519Key();
|
||||
} catch (\RuntimeException $e) {
|
||||
$output->writeln('<error>' . $e->getMessage() . '</error>');
|
||||
return 1;
|
||||
}
|
||||
$output->writeln('<info>Staged key promoted to active.</info>');
|
||||
$output->writeln('Run <info>occ ocm:keys:retire</info> once any in-flight signatures using the previous key have been verified.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
54
core/Command/OCM/ListKeys.php
Normal file
54
core/Command/OCM/ListKeys.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OC\Core\Command\OCM;
|
||||
|
||||
use OC\Core\Command\Base;
|
||||
use OC\OCM\OCMSignatoryManager;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ListKeys extends Base {
|
||||
public function __construct(
|
||||
private readonly OCMSignatoryManager $signatoryManager,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function configure(): void {
|
||||
$this
|
||||
->setName('ocm:keys:list')
|
||||
->setDescription('list Ed25519 keys used by OCM RFC 9421 HTTP Message Signatures');
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$keys = $this->signatoryManager->listEd25519Keys();
|
||||
$format = $input->getOption('output');
|
||||
if ($format === self::OUTPUT_FORMAT_JSON || $format === self::OUTPUT_FORMAT_JSON_PRETTY) {
|
||||
$output->writeln(json_encode($keys, $format === self::OUTPUT_FORMAT_JSON_PRETTY ? JSON_PRETTY_PRINT : 0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($keys === []) {
|
||||
$output->writeln('<comment>No Ed25519 keys yet; one will be generated on first OCM request.</comment>');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$table = new Table($output);
|
||||
$table->setHeaders(['Pool', 'Slot', 'Key ID']);
|
||||
foreach ($keys as $key) {
|
||||
$table->addRow([$key['poolId'], $key['slot'] ?? '-', $key['kid']]);
|
||||
}
|
||||
$table->render();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
41
core/Command/OCM/RetireKey.php
Normal file
41
core/Command/OCM/RetireKey.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OC\Core\Command\OCM;
|
||||
|
||||
use OC\Core\Command\Base;
|
||||
use OC\OCM\OCMSignatoryManager;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class RetireKey extends Base {
|
||||
public function __construct(
|
||||
private readonly OCMSignatoryManager $signatoryManager,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function configure(): void {
|
||||
$this
|
||||
->setName('ocm:keys:retire')
|
||||
->setDescription('delete the retiring Ed25519 key; signatures that referenced its kid can no longer be verified');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
try {
|
||||
$this->signatoryManager->retireEd25519Key();
|
||||
} catch (\RuntimeException $e) {
|
||||
$output->writeln('<error>' . $e->getMessage() . '</error>');
|
||||
return 1;
|
||||
}
|
||||
$output->writeln('<info>Retiring key deleted.</info>');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
42
core/Command/OCM/StageKey.php
Normal file
42
core/Command/OCM/StageKey.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OC\Core\Command\OCM;
|
||||
|
||||
use OC\Core\Command\Base;
|
||||
use OC\OCM\OCMSignatoryManager;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class StageKey extends Base {
|
||||
public function __construct(
|
||||
private readonly OCMSignatoryManager $signatoryManager,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function configure(): void {
|
||||
$this
|
||||
->setName('ocm:keys:stage')
|
||||
->setDescription('generate a new Ed25519 key and advertise it via JWKS without using it for signing yet');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
try {
|
||||
$signatory = $this->signatoryManager->stageEd25519Key();
|
||||
} catch (\RuntimeException $e) {
|
||||
$output->writeln('<error>' . $e->getMessage() . '</error>');
|
||||
return 1;
|
||||
}
|
||||
$output->writeln('Staged new Ed25519 key: <info>' . $signatory->getKeyId() . '</info>');
|
||||
$output->writeln('Wait for federated peers to refresh their JWKS cache before activating.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -74,6 +74,10 @@ use OC\Core\Command\Memcache\DistributedDelete;
|
|||
use OC\Core\Command\Memcache\DistributedGet;
|
||||
use OC\Core\Command\Memcache\DistributedSet;
|
||||
use OC\Core\Command\Memcache\RedisCommand;
|
||||
use OC\Core\Command\OCM\ActivateKey as OCMActivateKey;
|
||||
use OC\Core\Command\OCM\ListKeys as OCMListKeys;
|
||||
use OC\Core\Command\OCM\RetireKey as OCMRetireKey;
|
||||
use OC\Core\Command\OCM\StageKey as OCMStageKey;
|
||||
use OC\Core\Command\Preview\Generate;
|
||||
use OC\Core\Command\Preview\ResetRenderedTexts;
|
||||
use OC\Core\Command\Router\ListRoutes;
|
||||
|
|
@ -251,6 +255,11 @@ if ($config->getSystemValueBool('installed', false)) {
|
|||
$application->add(Server::get(SnowflakeDecodeId::class));
|
||||
$application->add(Server::get(Get::class));
|
||||
|
||||
$application->add(Server::get(OCMListKeys::class));
|
||||
$application->add(Server::get(OCMStageKey::class));
|
||||
$application->add(Server::get(OCMActivateKey::class));
|
||||
$application->add(Server::get(OCMRetireKey::class));
|
||||
|
||||
$application->add(Server::get(GetCommand::class));
|
||||
$application->add(Server::get(EnabledCommand::class));
|
||||
$application->add(Server::get(Command\TaskProcessing\ListCommand::class));
|
||||
|
|
|
|||
|
|
@ -1408,6 +1408,10 @@ return array(
|
|||
'OC\\Core\\Command\\Memcache\\DistributedGet' => $baseDir . '/core/Command/Memcache/DistributedGet.php',
|
||||
'OC\\Core\\Command\\Memcache\\DistributedSet' => $baseDir . '/core/Command/Memcache/DistributedSet.php',
|
||||
'OC\\Core\\Command\\Memcache\\RedisCommand' => $baseDir . '/core/Command/Memcache/RedisCommand.php',
|
||||
'OC\\Core\\Command\\OCM\\ActivateKey' => $baseDir . '/core/Command/OCM/ActivateKey.php',
|
||||
'OC\\Core\\Command\\OCM\\ListKeys' => $baseDir . '/core/Command/OCM/ListKeys.php',
|
||||
'OC\\Core\\Command\\OCM\\RetireKey' => $baseDir . '/core/Command/OCM/RetireKey.php',
|
||||
'OC\\Core\\Command\\OCM\\StageKey' => $baseDir . '/core/Command/OCM/StageKey.php',
|
||||
'OC\\Core\\Command\\Preview\\Cleanup' => $baseDir . '/core/Command/Preview/Cleanup.php',
|
||||
'OC\\Core\\Command\\Preview\\Generate' => $baseDir . '/core/Command/Preview/Generate.php',
|
||||
'OC\\Core\\Command\\Preview\\ResetRenderedTexts' => $baseDir . '/core/Command/Preview/ResetRenderedTexts.php',
|
||||
|
|
@ -1953,7 +1957,9 @@ return array(
|
|||
'OC\\OCM\\Model\\OCMResource' => $baseDir . '/lib/private/OCM/Model/OCMResource.php',
|
||||
'OC\\OCM\\OCMDiscoveryHandler' => $baseDir . '/lib/private/OCM/OCMDiscoveryHandler.php',
|
||||
'OC\\OCM\\OCMDiscoveryService' => $baseDir . '/lib/private/OCM/OCMDiscoveryService.php',
|
||||
'OC\\OCM\\OCMJwksHandler' => $baseDir . '/lib/private/OCM/OCMJwksHandler.php',
|
||||
'OC\\OCM\\OCMSignatoryManager' => $baseDir . '/lib/private/OCM/OCMSignatoryManager.php',
|
||||
'OC\\OCM\\Rfc9421SignatoryManager' => $baseDir . '/lib/private/OCM/Rfc9421SignatoryManager.php',
|
||||
'OC\\OCS\\ApiHelper' => $baseDir . '/lib/private/OCS/ApiHelper.php',
|
||||
'OC\\OCS\\CoreCapabilities' => $baseDir . '/lib/private/OCS/CoreCapabilities.php',
|
||||
'OC\\OCS\\DiscoveryService' => $baseDir . '/lib/private/OCS/DiscoveryService.php',
|
||||
|
|
@ -2157,7 +2163,13 @@ return array(
|
|||
'OC\\Security\\Signature\\Db\\SignatoryMapper' => $baseDir . '/lib/private/Security/Signature/Db/SignatoryMapper.php',
|
||||
'OC\\Security\\Signature\\Model\\IncomingSignedRequest' => $baseDir . '/lib/private/Security/Signature/Model/IncomingSignedRequest.php',
|
||||
'OC\\Security\\Signature\\Model\\OutgoingSignedRequest' => $baseDir . '/lib/private/Security/Signature/Model/OutgoingSignedRequest.php',
|
||||
'OC\\Security\\Signature\\Model\\Rfc9421IncomingSignedRequest' => $baseDir . '/lib/private/Security/Signature/Model/Rfc9421IncomingSignedRequest.php',
|
||||
'OC\\Security\\Signature\\Model\\Rfc9421OutgoingSignedRequest' => $baseDir . '/lib/private/Security/Signature/Model/Rfc9421OutgoingSignedRequest.php',
|
||||
'OC\\Security\\Signature\\Model\\SignedRequest' => $baseDir . '/lib/private/Security/Signature/Model/SignedRequest.php',
|
||||
'OC\\Security\\Signature\\Rfc9421\\Algorithm' => $baseDir . '/lib/private/Security/Signature/Rfc9421/Algorithm.php',
|
||||
'OC\\Security\\Signature\\Rfc9421\\ContentDigest' => $baseDir . '/lib/private/Security/Signature/Rfc9421/ContentDigest.php',
|
||||
'OC\\Security\\Signature\\Rfc9421\\IJwkResolvingSignatoryManager' => $baseDir . '/lib/private/Security/Signature/Rfc9421/IJwkResolvingSignatoryManager.php',
|
||||
'OC\\Security\\Signature\\Rfc9421\\SignatureBase' => $baseDir . '/lib/private/Security/Signature/Rfc9421/SignatureBase.php',
|
||||
'OC\\Security\\Signature\\SignatureManager' => $baseDir . '/lib/private/Security/Signature/SignatureManager.php',
|
||||
'OC\\Security\\TrustedDomainHelper' => $baseDir . '/lib/private/Security/TrustedDomainHelper.php',
|
||||
'OC\\Security\\VerificationToken\\CleanUpJob' => $baseDir . '/lib/private/Security/VerificationToken/CleanUpJob.php',
|
||||
|
|
|
|||
|
|
@ -1449,6 +1449,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC\\Core\\Command\\Memcache\\DistributedGet' => __DIR__ . '/../../..' . '/core/Command/Memcache/DistributedGet.php',
|
||||
'OC\\Core\\Command\\Memcache\\DistributedSet' => __DIR__ . '/../../..' . '/core/Command/Memcache/DistributedSet.php',
|
||||
'OC\\Core\\Command\\Memcache\\RedisCommand' => __DIR__ . '/../../..' . '/core/Command/Memcache/RedisCommand.php',
|
||||
'OC\\Core\\Command\\OCM\\ActivateKey' => __DIR__ . '/../../..' . '/core/Command/OCM/ActivateKey.php',
|
||||
'OC\\Core\\Command\\OCM\\ListKeys' => __DIR__ . '/../../..' . '/core/Command/OCM/ListKeys.php',
|
||||
'OC\\Core\\Command\\OCM\\RetireKey' => __DIR__ . '/../../..' . '/core/Command/OCM/RetireKey.php',
|
||||
'OC\\Core\\Command\\OCM\\StageKey' => __DIR__ . '/../../..' . '/core/Command/OCM/StageKey.php',
|
||||
'OC\\Core\\Command\\Preview\\Cleanup' => __DIR__ . '/../../..' . '/core/Command/Preview/Cleanup.php',
|
||||
'OC\\Core\\Command\\Preview\\Generate' => __DIR__ . '/../../..' . '/core/Command/Preview/Generate.php',
|
||||
'OC\\Core\\Command\\Preview\\ResetRenderedTexts' => __DIR__ . '/../../..' . '/core/Command/Preview/ResetRenderedTexts.php',
|
||||
|
|
@ -1994,7 +1998,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC\\OCM\\Model\\OCMResource' => __DIR__ . '/../../..' . '/lib/private/OCM/Model/OCMResource.php',
|
||||
'OC\\OCM\\OCMDiscoveryHandler' => __DIR__ . '/../../..' . '/lib/private/OCM/OCMDiscoveryHandler.php',
|
||||
'OC\\OCM\\OCMDiscoveryService' => __DIR__ . '/../../..' . '/lib/private/OCM/OCMDiscoveryService.php',
|
||||
'OC\\OCM\\OCMJwksHandler' => __DIR__ . '/../../..' . '/lib/private/OCM/OCMJwksHandler.php',
|
||||
'OC\\OCM\\OCMSignatoryManager' => __DIR__ . '/../../..' . '/lib/private/OCM/OCMSignatoryManager.php',
|
||||
'OC\\OCM\\Rfc9421SignatoryManager' => __DIR__ . '/../../..' . '/lib/private/OCM/Rfc9421SignatoryManager.php',
|
||||
'OC\\OCS\\ApiHelper' => __DIR__ . '/../../..' . '/lib/private/OCS/ApiHelper.php',
|
||||
'OC\\OCS\\CoreCapabilities' => __DIR__ . '/../../..' . '/lib/private/OCS/CoreCapabilities.php',
|
||||
'OC\\OCS\\DiscoveryService' => __DIR__ . '/../../..' . '/lib/private/OCS/DiscoveryService.php',
|
||||
|
|
@ -2198,7 +2204,13 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC\\Security\\Signature\\Db\\SignatoryMapper' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Db/SignatoryMapper.php',
|
||||
'OC\\Security\\Signature\\Model\\IncomingSignedRequest' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Model/IncomingSignedRequest.php',
|
||||
'OC\\Security\\Signature\\Model\\OutgoingSignedRequest' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Model/OutgoingSignedRequest.php',
|
||||
'OC\\Security\\Signature\\Model\\Rfc9421IncomingSignedRequest' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Model/Rfc9421IncomingSignedRequest.php',
|
||||
'OC\\Security\\Signature\\Model\\Rfc9421OutgoingSignedRequest' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Model/Rfc9421OutgoingSignedRequest.php',
|
||||
'OC\\Security\\Signature\\Model\\SignedRequest' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Model/SignedRequest.php',
|
||||
'OC\\Security\\Signature\\Rfc9421\\Algorithm' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Rfc9421/Algorithm.php',
|
||||
'OC\\Security\\Signature\\Rfc9421\\ContentDigest' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Rfc9421/ContentDigest.php',
|
||||
'OC\\Security\\Signature\\Rfc9421\\IJwkResolvingSignatoryManager' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Rfc9421/IJwkResolvingSignatoryManager.php',
|
||||
'OC\\Security\\Signature\\Rfc9421\\SignatureBase' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/Rfc9421/SignatureBase.php',
|
||||
'OC\\Security\\Signature\\SignatureManager' => __DIR__ . '/../../..' . '/lib/private/Security/Signature/SignatureManager.php',
|
||||
'OC\\Security\\TrustedDomainHelper' => __DIR__ . '/../../..' . '/lib/private/Security/TrustedDomainHelper.php',
|
||||
'OC\\Security\\VerificationToken\\CleanUpJob' => __DIR__ . '/../../..' . '/lib/private/Security/VerificationToken/CleanUpJob.php',
|
||||
|
|
|
|||
Loading…
Reference in a new issue