diff --git a/core/Command/OCM/ActivateKey.php b/core/Command/OCM/ActivateKey.php new file mode 100644 index 00000000000..090538e0024 --- /dev/null +++ b/core/Command/OCM/ActivateKey.php @@ -0,0 +1,42 @@ +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('' . $e->getMessage() . ''); + return 1; + } + $output->writeln('Staged key promoted to active.'); + $output->writeln('Run occ ocm:keys:retire once any in-flight signatures using the previous key have been verified.'); + return 0; + } +} diff --git a/core/Command/OCM/ListKeys.php b/core/Command/OCM/ListKeys.php new file mode 100644 index 00000000000..f73a4763111 --- /dev/null +++ b/core/Command/OCM/ListKeys.php @@ -0,0 +1,54 @@ +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('No Ed25519 keys yet; one will be generated on first OCM request.'); + 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; + } +} diff --git a/core/Command/OCM/RetireKey.php b/core/Command/OCM/RetireKey.php new file mode 100644 index 00000000000..58db976077c --- /dev/null +++ b/core/Command/OCM/RetireKey.php @@ -0,0 +1,41 @@ +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('' . $e->getMessage() . ''); + return 1; + } + $output->writeln('Retiring key deleted.'); + return 0; + } +} diff --git a/core/Command/OCM/StageKey.php b/core/Command/OCM/StageKey.php new file mode 100644 index 00000000000..75437f460bf --- /dev/null +++ b/core/Command/OCM/StageKey.php @@ -0,0 +1,42 @@ +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('' . $e->getMessage() . ''); + return 1; + } + $output->writeln('Staged new Ed25519 key: ' . $signatory->getKeyId() . ''); + $output->writeln('Wait for federated peers to refresh their JWKS cache before activating.'); + return 0; + } +} diff --git a/core/register_command.php b/core/register_command.php index d28c1633c62..856894b5c4c 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -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)); diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 9c7357ea6aa..54ec6a944df 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -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', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index d25c72171fa..2b90f11fa7d 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.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',