Skip to content

Fleet signing certificates: what to do when the 13 CSRs come back #591

Description

@rubenvdlinde

Thirteen certificate signing requests are open at nextcloud/app-certificate-requests. This issue is what to do when they come back, so the knowledge does not live only in one session's scrollback.

Why these were needed

Every one of these apps was renamed. Nextcloud verifies that a signing certificate's CN equals the app id (lib/private/IntegrityCheck/Checker.php, Certificate is not valid for required scope). A certificate issued for openbuild cannot sign an app whose id is now buildiq.

🔴 The trap: signing does NOT check the CN. Only verification does. writeAppSignature() has no CN guard, so a mismatched certificate produces a release that goes green and an app that fails occ integrity:check-app on the administrator's side. Six of our apps are in exactly that state right now: integriq, filinq, dossiq, stackiq, larpinq and thematiq all release successfully while signing with their pre-rename certificates.

The other seven fail loudly at "Prepare signing credentials", which is the honest half of the same defect.

The requests

PR App Was Old store entry
#1197 learniq scholiq published, removal requested
#1198 decidiq decidesk published, removal requested
#1199 buildiq openbuild published, removal requested
#1200 keepiq doriath published, removal requested
#1201 humaniq hrmq never published
#1202 versioniq app_versions published, removal requested
#1203 planninq planix never published
#1204 integriq openconnector published, removal requested
#1205 filinq docudesk published, removal requested
#1206 dossiq procest published, removal requested
#1207 stackiq softwarecatalog published, removal requested
#1208 larpinq larpingapp published, removal requested
#1209 thematiq nldesign published, removal requested

(PR numbers are in nextcloud/app-certificate-requests.)

When a request is merged

Nextcloud commits <app>/<app>.crt next to the CSR. Per app:

  1. Fetch the certificate

    gh api repos/nextcloud/app-certificate-requests/contents/<app>/<app>.crt \
      --jq .content | base64 -d > <app>.crt
    
  2. Verify the CN before using it. This is the whole point of the exercise.

    openssl x509 -in <app>.crt -noout -subject     # must read CN = <app>
    
  3. Verify it matches our private key. The keys live in the password vault, generated 2026-08-27, never committed anywhere.

    openssl x509 -in <app>.crt -noout -modulus | md5sum
    openssl rsa  -in <app>.key -noout -modulus | md5sum   # must be identical
    
  4. Set both repository secrets on ConductionNL/<app>, Settings, Secrets and variables, Actions:

    • NEXTCLOUD_SIGNING_CERT = the whole .crt, including the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- lines
    • NEXTCLOUD_SIGNING_KEY = the matching .key from the vault

    ⚠️ Four repos (learniq, decidiq, buildiq, keepiq) already hold a CERT set on 2026-08-20 that fails the PEM-header check, so it was pasted without its header and footer. That is the failure mode to avoid.

  5. Re-run the Release workflow on development and confirm "Prepare signing credentials" passes.

  6. Verify the signature actually verifies. A green release is not proof, see the trap above.

    occ integrity:check-app <app>
    
  7. Remove the superseded App Store entry for the old id, for the eleven marked "published" above.

Definition of done

  • All 13 certificates issued and stored in the vault next to their keys
  • All 13 repos have both secrets set, pasted whole
  • All 13 Release workflows green on development
  • occ integrity:check-app passes for all 13 on a real instance
  • The 11 superseded App Store entries are gone
  • mydash removed from the store: launchpad already has its own correct certificate (CN = launchpad), so only the stale store entry remains

Reference

  • Private keys: password vault, 13 entries, generated 2026-08-27, 4096-bit RSA, CN = <new app id>
  • The check that catches a bad paste lives in .github/workflows/release.yml, step "Prepare signing credentials"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions