Skip to content

http-sig: Update http message signatures for OCM after spec review - #62543

Open
mickenordin wants to merge 6 commits into
masterfrom
kano-update-httpsig
Open

http-sig: Update http message signatures for OCM after spec review#62543
mickenordin wants to merge 6 commits into
masterfrom
kano-update-httpsig

Conversation

@mickenordin

@mickenordin mickenordin commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

This pr adjust the format of HTTP Message Signatures following the review of OCM that resulted in some modifications to OCM spec: cs3org/OCM-API#391

  1. Date is no longer signed, some intermediaries sometimes rewrite the date header, causing the signature to fail. Since we already sign created it is fine.
  2. We switch to using an ocm tag rather than a label. This is because a label is simply a key, where as the tag is signed.
  3. We add the jwksUri to the discovery, since RFC7517 does not in fact standardize .well-known/jwks.json. I left the actual endpoint there, but i can be moved in the future if we want, as long as the discovery is updated along with it.
  4. Key algorithm discovery is done via the alg parameter in the jwks.
  5. Require the keyid signature parameter.

I also updated annotations since, because the original httpsig PR was not merge before NC34 was released, so it needed some updates.

Checklist

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

Manual testing:

This request:

{
    "shareWith": "bob@https://bob.ocm.drive.test.sunet.se",
    "shareType": "user",
    "name": "2026-07-07T11:38:03,169846023+02:00.png",
    "resourceType": "file",
    "description": "",
    "providerId": "48",
    "owner": "alice@alice.ocm.drive.test.sunet.se",
    "ownerDisplayName": "alice",
    "sharedBy": "alice@alice.ocm.drive.test.sunet.se",
    "sharedByDisplayName": "alice",
    "sender": "alice@alice.ocm.drive.test.sunet.se",
    "senderDisplayName": "alice",
    "protocol": {
        "name": "webdav",
        "webdav": {
            "uri": "https://https://bob.ocm.drive.test.sunet.se/public.php/webdav/",
            "sharedSecret": "WB7i0NLgWFB6t2D65g7ATtW9QK1uwVRA",
            "permissions": [
                "{http://open-cloud-mesh.org/ns}share-permissions"
            ]
        }
    }
}

Has the headers:

POST https://bob.ocm.drive.test.sunet.se/index.php/ocm/shares HTTP/2.0
content-type: application/json
content-digest: sha-256=:ylqyzlJCuqc3YDhViVz61MWy6XGxYRDuSn4QfuMnc/Y=:
date: Sun, 26 Jul 2026 13:07:20 GMT
signature-input: ocm=("@method" "@target-uri" "content-digest" "content-length");created=1785071240;keyid="https://alice.ocm.drive.test.sunet.se/ocm#ecdsa-p256-sha256-1";tag="ocm"
signature: ocm=:H0JNrxoe6E61CKUtvbixpdzYdyr06sIpbS03H4ENtV7T5KblumxuZyvA5x4vSoMbcxugnX/8M2fiNKEmKeAAHg==:
user-agent: Nextcloud-Server-Crawler/35.0.0 dev
accept-encoding: br, gzip
content-length: 647

This is between two servers (alice and bob) that support the RFC9421 signatures.

Between a server that supports both, and a server with only draft-cavage support (alice and charlie) it looks like this for the body:

{
    "shareWith": "charlie@https://charlie.ocm.drive.test.sunet.se",
    "shareType": "user",
    "name": "welcome.txt",
    "resourceType": "file",
    "description": "",
    "providerId": "49",
    "owner": "alice@alice.ocm.drive.test.sunet.se",
    "ownerDisplayName": "alice",
    "sharedBy": "alice@alice.ocm.drive.test.sunet.se",
    "sharedByDisplayName": "alice",
    "sender": "alice@alice.ocm.drive.test.sunet.se",
    "senderDisplayName": "alice",
    "protocol": {
        "name": "webdav",
        "options": {
            "sharedSecret": "UzJ7vrrKNycDa2fDlyOlFGWopsRnEPr8",
            "permissions": "{http://open-cloud-mesh.org/ns}share-permissions"
        }
    }
}

headers:

POST https://charlie.ocm.drive.test.sunet.se/ocm/shares HTTP/2.0
content-type: application/json
date: Sun, 26 Jul 2026 13:14:04 GMT
digest: SHA-512=QMy9wsI/MzJJ15HouiFG0VvdgjTkYl3GUAZ0k6ZGyA6RzgdI2v6i+IFHeNRj9BDpfxl5r40MgVXE5Ha5DwmrsA==
signature: keyId="https://alice.ocm.drive.test.sunet.se/ocm#signature",algorithm="rsa-sha512",headers="(request-target) content-length date digest host",signature="u0QffxZWI6mtXEQUyGrjoE0SZDzNO3YFhKyKq46ewsSFqpAtrZzSqhJUHeNU4dVjAC09umFrvY74XN9dif14bmaMf6ps9lF5VweE+LVAk/FS7BmbqJ5nSSkxyc2ZHwcoDH6GbLJk7vg4PMSWxZpJvyk67vZ+RwH58GLi1n/1Vqcl8XyCrgjYXNer+zHS5Fzzy4gllM0QsLglKsKfv0pf/sfpUXkO9wa2aDo0tbDCLql4EuvGC7lyg8Fbn0kgpjrezimtuvzPeZ0BE/I2RGr6vPfebA8Y9fhAn6Fuzge8pviDsoC+2wQJUrMx3ZdDXMRTm3RvsOMUtpLzA0L6UYDYjg=="
user-agent: Nextcloud-Server-Crawler/35.0.0 dev
accept-encoding: br, gzip
content-length: 548

And for charlie to bob:

{
    "shareWith": "bob@https://bob.ocm.drive.test.sunet.se",
    "shareType": "user",
    "name": "Readme.md",
    "resourceType": "file",
    "description": "",
    "providerId": "6",
    "owner": "charlie@charlie.ocm.drive.test.sunet.se",
    "ownerDisplayName": "charlie",
    "sharedBy": "charlie@charlie.ocm.drive.test.sunet.se",
    "sharedByDisplayName": "charlie",
    "sender": "charlie@charlie.ocm.drive.test.sunet.se",
    "senderDisplayName": "charlie",
    "protocol": {
        "name": "webdav",
        "options": {
            "sharedSecret": "FxnX4J6ZNMA3yUk",
            "permissions": "{http://open-cloud-mesh.org/ns}share-permissions"
        }
    }
}

with headers:

POST https://bob.ocm.drive.test.sunet.se/index.php/ocm/shares HTTP/2.0
content-type: application/json
date: Sun, 26 Jul 2026 13:17:21 GMT
digest: SHA-512=y5569iwDkc2FgiwPe/feC77ohzncy2NDpxcRAuZBjD2EETR7bH1l4EYtlCL7BxFiqE3q2gGVeuKaKWVqLVKTwQ==
signature: keyId="https://charlie.ocm.drive.test.sunet.se/ocm#signature",algorithm="rsa-sha512",headers="(request-target) content-length date digest host",signature="MgP+5oEV0+qAx1UiWWodQUSSkYDDyCgZJ+5EZPZOMtHj9c9TTR2xwbNuoWtzTf2TVq/HcQ9XZAS3TA6/ZF7DIhVG20/Em63UeTVNvUREojibxCRRB/IMBl5IUxve4swk7F1jnqUWINMi2MKd8igYdk1cESzLzQXC/GUL5CraYrKO1Ke8tG2MBzpjQWQ4o/53yjt93XKW+KC3dbx9LN5tq5U583z6RajwfdUt95uK0aRPDoJXRTU1satAryRRKmx0XYC7W/Aztpo6l6YEVAP0a33doEEDF6SpmwZHXJcoYM90AXcS/xxfY5YBudgUms2Alls8in0NXVFhn0EUR46a3A=="
user-agent: Nextcloud-Server-Crawler/33.0.3
accept-encoding: gzip
content-length: 538

Functionally the pr works, and is backwards compatible. It may break between servers running on an unpatched master and a patched master, but that is not a too big concern I think since the code it changes is not yet in any release.

@mickenordin
mickenordin requested review from a team and nickvergessen as code owners July 26, 2026 13:27
@mickenordin
mickenordin requested review from leftybournes, provokateurin and salmart-dev and removed request for a team July 26, 2026 13:27
@mickenordin mickenordin changed the title Kano update httpsig http-sig: Update http message signatures for OCM after spec review Jul 26, 2026
@mickenordin
mickenordin force-pushed the kano-update-httpsig branch from b8eb0c3 to 97e3ba5 Compare July 26, 2026 13:29
@mickenordin mickenordin added 3. to review Waiting for reviews 35-feedback labels Jul 26, 2026
@mickenordin mickenordin added this to the Nextcloud 35 milestone Jul 26, 2026
@mickenordin mickenordin added AI assisted community pull requests from community labels Jul 26, 2026
@Antreesy Antreesy closed this Jul 26, 2026
@Antreesy Antreesy reopened this Jul 26, 2026
@Antreesy

Copy link
Copy Markdown
Contributor

Sorry, tapped the wrong button

@nickvergessen

Copy link
Copy Markdown
Member

Sharing textfile0.txt failed, could not find group1@http://localhost:8180, maybe the server is currently unreachable or uses a self-signed certificate.

Seems this is breaking HTTP (without S) which can be used in real world, but is especially used in all tests (also in other apps outside of server)

@nickvergessen nickvergessen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sharing textfile0.txt failed, could not find group1@http://localhost:8180, maybe the server is currently unreachable or uses a self-signed certificate.

Seems this is breaking HTTP (without S) which can be used in real world, but is especially used in all tests (also in other apps outside of server)

@nickvergessen

Copy link
Copy Markdown
Member

@mickenordin

Copy link
Copy Markdown
Contributor Author

It looks like the same https problem as mentioned above. I will push another commit to fix that, and I think the agreement is to allow http for serving your keys if necessary.

@mickenordin
mickenordin force-pushed the kano-update-httpsig branch 2 times, most recently from 750e748 to f94f587 Compare July 28, 2026 14:43
@mickenordin

Copy link
Copy Markdown
Contributor Author

@nickvergessen can you rerun the talk test?

@mickenordin
mickenordin force-pushed the kano-update-httpsig branch from f94f587 to 41b32a8 Compare July 28, 2026 16:39
@mickenordin
mickenordin force-pushed the kano-update-httpsig branch from 41b32a8 to c0b1d0b Compare July 28, 2026 16:49
@nickvergessen

Copy link
Copy Markdown
Member

Seems to be green now again, thanks for the adjustment

@mickenordin

Copy link
Copy Markdown
Contributor Author

I found a bigger problem, the old code gets the identity of the calling server from the key id, and the new http-sig code did the same, but the keyid can be anything, it is an opaque identifier and we cant assume that it is a valid url. So now I am resolving the servers origin from the share or the notification instead. I will let the test suite run again, and run it once more through my manual testing to see that it is ok.

@mickenordin
mickenordin force-pushed the kano-update-httpsig branch 6 times, most recently from 438674c to 6abbe1a Compare July 28, 2026 22:31
@mickenordin

Copy link
Copy Markdown
Contributor Author

I noticed that php -S does not seem to be able to properly serve .well-known in php 8.3, causing a test to fail. SInce we can now move the jwks to another url, I put it next to the token endpoint in cloud_federation_api instead.

@mickenordin
mickenordin force-pushed the kano-update-httpsig branch 3 times, most recently from 427fe7a to 901383c Compare July 28, 2026 23:11
@mickenordin

Copy link
Copy Markdown
Contributor Author

Everything works in my testing I think now

Implements the changes from cs3org/OCM-API#391 and closes the
remaining gaps with the HTTP Message Signatures section:

- discover peer JWK Sets via the new `jwksUri` discovery field
  instead of the fixed /.well-known/jwks.json path; advertise
  our own jwksUri and require it (https) from peers advertising
  the `http-sig` capability
- stop covering the Date header; freshness is anchored on the
  `created` signature parameter
- mark and select the OCM signature by its integrity-protected
  tag="ocm" parameter instead of the dictionary label
- require the JWK `alg` parameter and derive the signature
  algorithm from it, accepting fully-specified RFC 9864 names
- reject signed requests whose verification key cannot be
  resolved instead of treating them as unsigned
- derive the signer origin from the spec-canonical fqdn#id
  keyid form

Signed-off-by: Micke Nordin <kano@sunet.se>
The annotation was added as 34.0.0 in #60136, but stable34
branched before the PR merged, so the API first ships in 35.

Signed-off-by: Micke Nordin <kano@sunet.se>
The spec mandates https for jwksUri but allows an HTTP fallback
in testing setups. Advertise the scheme the instance actually
serves, and accept an http jwksUri only from a peer whose own
endpoint is http: integration tests and http intranets work
without configuration, while an https peer advertising an http
jwksUri is still rejected, since that downgrade would let a
path attacker swap the key material. An https jwksUri is always
accepted, also from http-only peers, and it may live on a
different host than the peer.

Signed-off-by: Micke Nordin <kano@sunet.se>
The JWKS signing keyId was persisted as a full URL, frozen at whichever
host provisioned the key first. An instance addressed by more than one
host (the two-port integration rig, or a port change behind a proxy) then
signed with a keyId whose host no longer matched its federated share
identity, so receivers rejected the request with an origin mismatch.

Persist only a stable opaque key id (ecdsa-p256-sha256-<n>) and rebuild
the full keyId per request from the current URL, in both the signing
signatory and the published JWK Set. The signature keyid and the JWKS kid
still match, since both are built from the same opaque id and the current
request host. Rotation still works: slots differ by the counter, and the
host is just fresh context.

Drops the resolveKidBase/canonicalKid hostname machinery and the
ocm_jwks_kid_base appconfig, which are no longer needed.

Assisted-by: ClaudeCode:glm-5.2

Signed-off-by: Micke Nordin <kano@sunet.se>
RFC 7517 §4.5 leaves the keyid structure unspecified, so the receiver
must not parse it. The signer origin now comes from the trusted OCM
share/sender identity; the JWK Set is resolved against that origin and
the keyid is matched opaquely to the JWKS kid. Aligns with the OCM
verification procedure and fixes the origin-mismatch / flaky-kid
failures in the two-port integration rig.

Reverts the per-request host-based kid back to a stable persisted kid,
drops keyid->host parsing, threads a sender-origin parameter through
verification, and resolves that origin in every OCM inbound entry point
(notifications, shares, token exchange, OCM requests) and the federation
rate limiter. Cavage is unchanged.

Assisted-by: ClaudeCode:glm-5.2

Signed-off-by: Micke Nordin <kano@sunet.se>
Move from .well-known/jwks.json to the TokenController

Signed-off-by: Micke Nordin <kano@sunet.se>
@mickenordin
mickenordin force-pushed the kano-update-httpsig branch from 901383c to 3ae67bd Compare July 29, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Waiting for reviews 35-feedback AI assisted community pull requests from community

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants