Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5dba7fa
feat: CanonicalManifest + golden import — G2 code-signing verifier (T…
DeepDiver1975 Jul 12, 2026
a94d861
feat: SignatureEnvelope v2/legacy parser (Task 2)
DeepDiver1975 Jul 12, 2026
826d177
test: exercise hostile hashes keys in SignatureEnvelope (Task 2 revie…
DeepDiver1975 Jul 12, 2026
b9d0ba2
feat: Task 3 — ManifestVerifier signature verification
DeepDiver1975 Jul 12, 2026
4e783ac
feat: AlgorithmAllowlist + VerifierConstants (Task 4)
DeepDiver1975 Jul 12, 2026
c12be43
feat: Task 5 — TrustStore + ChainValidator (security-critical chain v…
DeepDiver1975 Jul 12, 2026
0e5e267
feat: Task 6 — IntegrityDiffer, array-driven diff class
DeepDiver1975 Jul 12, 2026
2e82dcc
feat: Task 7 — AppIdResolver, app identity validation & CN matching
DeepDiver1975 Jul 12, 2026
61bc92a
feat: task 8 — CrlFetcher HTTPS-only fetcher with no redirects
DeepDiver1975 Jul 12, 2026
7d5de3b
feat: CrlValidator + CrlProvider (Task 9) — CRL validation & 3-step f…
DeepDiver1975 Jul 12, 2026
0a6840f
feat: Task 10 — Verifier orchestrator (Mode-1 pipeline)
DeepDiver1975 Jul 12, 2026
976d4c1
fix: isolate VerifierTest trust store to temp dir; drop test certs fr…
DeepDiver1975 Jul 12, 2026
de57b19
feat: Task 11 — G1 dual-trust transition (§8)
DeepDiver1975 Jul 12, 2026
5f306ce
fix: §8 case-2 warn applies only to expired (not not-yet-valid) G1 ce…
DeepDiver1975 Jul 12, 2026
a3971ff
feat: task 12 — Checker delegates to Verifier (§9 REASON codes, §8 wa…
DeepDiver1975 Jul 12, 2026
b992d6e
fix: repair CheckerTest delegation + restore signing/enforcement test…
DeepDiver1975 Jul 12, 2026
29409b5
fix: restore .htaccess marker-split coverage in CheckerTest (un-mask …
DeepDiver1975 Jul 12, 2026
09ac4f4
feat: Task 13 — DI wiring + trust-store migration
DeepDiver1975 Jul 12, 2026
e4924a8
fix: address whole-branch + security review (AIA SSRF, DI fail-open, …
DeepDiver1975 Jul 12, 2026
4af399a
fix: revert incorrect mimetypelist.js exclusion anchoring (FIX 5 regr…
DeepDiver1975 Jul 12, 2026
5f29c21
test: verify revoked intermediate serial triggers RevokedException (F…
DeepDiver1975 Jul 12, 2026
1eb983b
fix: defer HTTP client resolution in IntegrityCodeChecker until insta…
DeepDiver1975 Jul 13, 2026
a242fc9
style: prefix native function calls per ownCloud coding standard
DeepDiver1975 Jul 13, 2026
851f662
fix: use ReasonCodeException interface so Phan can narrow getReasonCode
DeepDiver1975 Jul 13, 2026
5368383
fix: track resources/codesigning/intermediates/ via .gitkeep
DeepDiver1975 Jul 13, 2026
77f8935
chore: use SPDX file headers for new IntegrityCheck verifier files
DeepDiver1975 Jul 13, 2026
9ed8faa
fix: restore global libxml entity loader after AppId XML parse
DeepDiver1975 Jul 13, 2026
53a5178
fix: address G2 verifier review — legacy-warn install gate + envelope…
DeepDiver1975 Jul 17, 2026
e783347
fix: preserve G1 legacy verification semantics (address self-review)
DeepDiver1975 Jul 17, 2026
0ff881c
feat: bundle G2 CA trust anchors + seed leaf CRL from ceremony
DeepDiver1975 Jul 20, 2026
cc83bb9
fix: point CRL_URL at owncloud.dev (github.io path 301s org-wide)
DeepDiver1975 Jul 20, 2026
621058f
docs: correct G2 trust-store README + add G1 sunset changelog
DeepDiver1975 Jul 21, 2026
1f58b0d
refactor: address G2 verifier review — naming, logging, DI, exception…
DeepDiver1975 Jul 21, 2026
26a4cc6
fix: use Util::WARN not undefined ILogger::WARN in CrlFetcher
DeepDiver1975 Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions changelog/unreleased/41680
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Change: G2 code-signing verifier and G1 signature sunset

We've replaced the app-signature verification with a new G2 code-signing
verifier that validates the full certificate chain against bundled trust
anchors, enforces an algorithm allowlist, and checks a fail-closed CRL for
revocation.

Existing G1-signed apps continue to verify during the transition. However,
all G1 signatures stop verifying on 2027-01-01 (the hardcoded sunset at
2026-12-31T23:59:59Z), regardless of individual certificate expiry. After
that cutoff, affected apps must be re-signed with a G2 certificate to install
or pass integrity verification. Administrators running third-party apps signed
under G1 should plan for re-signed releases ahead of that date.

https://github.com/owncloud/core/pull/41680
8 changes: 7 additions & 1 deletion core/Command/Integrity/CheckApp.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$path = \strval($input->getOption('path'));
$result = $this->checker->verifyAppSignature($appid, $path);
$this->writeArrayInOutputFormat($input, $output, $result);
if (\count($result)>0) {

// A valid but expired, pre-sunset legacy (G1) app verifies with the single
// 'LEGACY_ACCEPTED_WARN' marker (spec §8 "warn + allow"). The marker is printed
// above so the warning is visible, but it is a pass — do not fail the command.
$isLegacyWarnOnly = \array_key_exists('LEGACY_ACCEPTED_WARN', $result)
&& \count($result) === 1;
if (\count($result) > 0 && !$isLegacyWarnOnly) {
return 1;
}
return 0;
Expand Down
9 changes: 9 additions & 0 deletions lib/kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,15 @@ public static function init() {
\spl_autoload_register([self::$loader, 'load']);
$loaderEnd = \microtime(true);

// SECURITY: Disable phpseclib's automatic URL fetching process-wide.
// phpseclib's X509 will otherwise follow AIA caIssuers / CRL distribution
// point URLs found inside a certificate, which is an SSRF vector when
// validating attacker-supplied certificates (e.g. the IntegrityCheck
// code-signing verifier). ownCloud never relies on phpseclib fetching
// remote URLs, so this is disabled once here at boot rather than toggled
// per validation call — a single, greppable, non-hidden global default.
\phpseclib3\File\X509::disableURLFetch();

try {
self::initPaths();
} catch (\RuntimeException $e) {
Expand Down
7 changes: 6 additions & 1 deletion lib/private/Installer.php
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,12 @@ public static function checkAppsIntegrity($data, $extractDir, $path, $isShipped
$info['id'],
$extractDir
);
if ($integrityResult !== []) {
// A valid but expired, pre-sunset legacy (G1) app verifies with the
// single 'LEGACY_ACCEPTED_WARN' marker (spec §8 "warn + allow"). That is
// a pass-with-warning, not a failure, so it must not block the install.
$isLegacyWarnOnly = \array_key_exists('LEGACY_ACCEPTED_WARN', $integrityResult)
&& \count($integrityResult) === 1;
if ($integrityResult !== [] && !$isLegacyWarnOnly) {
$e = new \Exception(
$l->t(
'Signature could not get checked. Please contact the app developer and check your admin screen.'
Expand Down
Loading