Nextcloud 34 → Cairn OCM share refused: Digest header does not match received body #454
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Cordy/Cairn#454
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptom
Dogfood (2026-09-09): NC 34.0.3.2 (nextcloud.c0rdyceps.ch) sharing to
nikola-test@files-bao.c0rdyceps.ch→ NC UI "Failed to create share." NC log: "could not find nikola-test@…, maybe the server is currently unreachable".Evidence chain
GET /.well-known/ocm 200→POST /ocm/shares 401 (bytes_in=544, bytes_out=64)→ NC falls back to/ocs-provider/+/ocs/v2.php/cloud/shares(both 401, we do not serve legacy OCS) → NC reports recipient not found.ocm: request is unsigned…→ NC's request was NOT refused as unsigned; it carried a Signature.signature does not verifybody → our digest computation and the transit path are correct for well-formed requests.ocm: content digest does not match the request body(13-byte JSON overhead + 51-char message; verified against the 60/66-byte probes). → NC signed the request but its Digest header does not match the body bytes we received.NC source findings (stable34, reviewed)
OCMDiscoveryService::requestRemoteOcmEndpointjson_encodes once, signs that exact string (SignatureManager::signOutgoingRequestIClientPayload→OutgoingSignedRequest, digestSHA-256=+ b64 — format identical to ours), passes the same string as Guzzlebody. On paper consistent.OC\Http\Client\Client::buildRequestOptionsforces HTTP/2 (RequestOptions::VERSION => '2.0',CURL_HTTP_VERSION_2TLS) — the transport differs from the naive picture; suspect list includes h2 framing interactions and NC hashing vs sending divergence.core.enforce_signed_ocm_requestis set (default off). → NC↔NC federation works even if NC's own digests are broken — the monoculture masks it. We are the strict receiver that noticed.Plan
Digest/Content-Digestheader value, our computed value, received body byte count, and theContent-Lengthheader. No body content leaks — hashes and lengths only. NC records our response body in its exception, so both admins see the same facts. TDD.ocm-interop.ymlwith a regression case once root cause is known.House rules: karpathy-guidelines (surgical), Superpowers TDD, one-shot on a branch.
Step 1 shipped: v0.6.143 (PR #456, red witnessed on runner) live on both dogfoods. Digest refusals now name received header, computed digest, body bytes, Content-Length. Next: Nikola re-attempts the NC → files-bao share; the refusal in the pod log / NC exception gives the real values for root-causing.
Root cause captured (v0.6.144 refusal log, 2026-09-09 21:22:50Z):
Body arrived intact (545 = 545). Nextcloud signs OCM requests with SHA-512, not SHA-256. Confirmed in stable34
OC\OCM\OCMSignatoryManager::getOptions():'algorithm' => SignatureAlgorithm::RSA_SHA512, 'digestAlgorithm' => DigestAlgorithm::SHA512— overriding their own SHA-256 defaults and the documented example inSignatureManager. NC↔NC never notices because NC receivers accept broken/mismatched signatures unlessenforce_signed_ocm_requestis set (monoculture masking, as suspected).Three defects on our side fall out of the faithful reproduction:
SHA-256=.crypto.SHA256, ignoring thealgorithm="…"parameter.legacyBasebuilds a fixed list(request-target) content-length date digest, ignoring the sender'sheaders="…"declaration — NC signs… digest host, and over HTTP/2 (NC 34 forces h2) the host must be reconstructed from the request authority (req.Host), not the header map.Fix (v0.6.145, TDD — red test is a byte-faithful NC-34-style signed request): honour declared digest algorithm (SHA-256/SHA-512, unsupported refused by name), honour declared signature algorithm (sha256/sha512), rebuild the base from the declared header list with
hostfrom the authority. Our own SignLegacy dialect unchanged and still verifies via the fallback list.Fix shipped: v0.6.145 (PR #458, red witnessed with a byte-faithful NC-34 signed request, full suite green), live on both dogfoods. Verifier honours declared digest algorithm (SHA-256/SHA-512), declared signature algorithm (sha256/sha512), and rebuilds the signing base from the sender's
headerslist withhostfrom the request authority (HTTP/2-safe). Awaiting the live NC → files-bao share for end-to-end confirmation; then: interop-workflow regression case + handbook note on the NC SHA-512 quirk + close.End-to-end confirmed live (2026-09-10): NC 34 → files-bao share created without error, landed in Federated → Requests, accepted, now lives under the nextcloud.c0rdyceps.ch federation folder. First successful Nextcloud → Cairn OCM share. Remaining before close: interop-workflow regression case for the SHA-512 dialect + handbook note on the NC quirk (folding into the next release).
Closing: fix confirmed end-to-end live (first NC → Cairn share accepted and browsable);
TestVerifyNextcloudSHA512Signatureis the permanent byte-faithful regression in the suite; handbook interop note on the NC SHA-512 dialect shipped in v0.6.146. Full arc: v0.6.143 evidence-bearing refusals → v0.6.144 server-side refusal logging → capture → v0.6.145 SHA-512 + declared-header-list verification.