Skip to content

feat(http-sig): Dual stack http-sig#60136

Open
mickenordin wants to merge 11 commits into
masterfrom
kano-dual-stack-rfc-9421-http-sig
Open

feat(http-sig): Dual stack http-sig#60136
mickenordin wants to merge 11 commits into
masterfrom
kano-dual-stack-rfc-9421-http-sig

Conversation

@mickenordin
Copy link
Copy Markdown
Contributor

@mickenordin mickenordin commented May 4, 2026

Nextcloud uses the old draft-cavage style http signatures, that was never adopted as an RFC by the IETF. This PR implements RFC 9421 style http signatures that is required for many of the new features of OCM. This implementation will allow both type of signatures to coexist as a dual stack implementation.

It also adds occ commands to rotate keys, by staging a new key, activating it, and remove the old one.

IMPORTANT: This should not be merged before: nextcloud/3rdparty#2413 since we need firebase/php-jwt and gapple/structured-field in this PR.

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

Checklist

@mickenordin mickenordin requested a review from a team as a code owner May 4, 2026 10:13
@mickenordin mickenordin requested review from ArtificialOwl, nfebe, salmart-dev and sorbaugh and removed request for a team May 4, 2026 10:13
@mickenordin mickenordin marked this pull request as draft May 4, 2026 10:13
@mickenordin mickenordin force-pushed the kano-dual-stack-rfc-9421-http-sig branch 6 times, most recently from d707a33 to 088dcbb Compare May 4, 2026 15:35
@mickenordin
Copy link
Copy Markdown
Contributor Author

I verified the flow end to end, using a mitm proxy, and old signatures are used talking to stock nextcloud and new style signatures used when talking to a build from this pr.

@mickenordin mickenordin force-pushed the kano-dual-stack-rfc-9421-http-sig branch 4 times, most recently from 16d70fc to b1cb1e4 Compare May 5, 2026 06:45
@mickenordin mickenordin marked this pull request as ready for review May 5, 2026 08:07
@mickenordin mickenordin added the 3. to review Waiting for reviews label May 5, 2026
@mickenordin mickenordin added this to the Nextcloud 34 milestone May 5, 2026
@mickenordin mickenordin force-pushed the kano-dual-stack-rfc-9421-http-sig branch from 482dc14 to e096272 Compare May 5, 2026 09:30
Copy link
Copy Markdown
Member

@CarlSchwan CarlSchwan left a comment

Choose a reason for hiding this comment

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

Make sense on paper, but there is too much crypto code which would be better to delegate to a third party library

Comment thread lib/private/Security/Jwks/Jwk.php Outdated
Comment thread lib/private/Security/Signature/Rfc9421/Algorithm.php
@mickenordin
Copy link
Copy Markdown
Contributor Author

Make sense on paper, but there is too much crypto code which would be better to delegate to a third party library

I will take a look at what I can replace with php-jwt. Also note that all the php libraries I was able to find that implements http-sig implemented draft-cavage signatures ~7 years ago, and has not been updated to implement the actual RFC9421, so we will need to implement the signature part ourselfs.

mickenordin added a commit to nextcloud/3rdparty that referenced this pull request May 5, 2026
The nextcloud/server PR nextcloud/server#60136
implements RFC9421 http-signatures. Rather than implementing some of the
JWK primitives our selfs, we can lean on firebase/php-jwt and that is
this commit adds that dependency here.

Signed-off-by: Micke Nordin <kano@sunet.se>
@mickenordin mickenordin force-pushed the kano-dual-stack-rfc-9421-http-sig branch 3 times, most recently from 7d35e62 to 55c7f16 Compare May 5, 2026 12:40
@mickenordin
Copy link
Copy Markdown
Contributor Author

@CarlSchwan I have whittled a way quite a bit now, and reused as much as I can from firebase/JWT, I think.

Another thing I could do is replace a lot of the structured fields parsing in lib/private/Security/Signature/Rfc9421/SfParser.php with https://github.com/gapple/structured-fields, but that would be at the cost of another 3rdparty vendoring. What do you think?

@mickenordin mickenordin force-pushed the kano-dual-stack-rfc-9421-http-sig branch 2 times, most recently from 4329323 to cc03919 Compare May 15, 2026 08:51
@mickenordin mickenordin requested review from a team and nickvergessen as code owners May 17, 2026 16:49
mickenordin added a commit to nextcloud/3rdparty that referenced this pull request May 17, 2026
The nextcloud/server PR nextcloud/server#60136
implements RFC9421 http-signatures. Rather than implementing some of the
JWK primitives our selfs, we can lean on firebase/php-jwt and that is
this commit adds that dependency here.

Signed-off-by: Micke Nordin <kano@sunet.se>
mickenordin added a commit to nextcloud/3rdparty that referenced this pull request May 17, 2026
The gapple/structured-fields package implements RFC 8941 (HTTP
Structured Field Values) and RFC 9651. nextcloud/server's RFC 9421
HTTP Message Signatures verifier and signer use it to drop the
hand-rolled Signature-Input / Signature dictionary parser, per review
feedback on nextcloud/server#60136.

Signed-off-by: Micke Nordin <kano@sunet.se>
@mickenordin mickenordin force-pushed the kano-dual-stack-rfc-9421-http-sig branch 9 times, most recently from d3e1886 to 6b2eaed Compare May 17, 2026 21:20
mickenordin and others added 11 commits May 18, 2026 12:25
…t + gapple/structured-fields)

Signed-off-by: Micke Nordin <kano@sunet.se>
Promote ext-sodium from recommended to required so RFC 9421 Ed25519
signing/verifying can rely on libsodium unconditionally. Add the
matching openssl + sodium psalm stubs.

Signed-off-by: Micke Nordin <kano@sunet.se>
Add the RFC 9421 (HTTP Message Signatures) sign/verify path alongside
the existing draft-cavage implementation:

- Algorithm: sodium for Ed25519, JWT::sign for RSA / ECDSA, ecdsaRawToDer
  for the ECDSA wire format. JWK parsing via JWK::parseKey.
- SignatureBase: RFC 9421 §2.5 base construction for the derived
  components OCM uses plus plain HTTP fields.
- ContentDigest: RFC 9530 helpers used as a covered component.
- Rfc9421IncomingSignedRequest / Rfc9421OutgoingSignedRequest:
  request models. Parsing of Signature-Input / Signature delegates
  to gapple\\StructuredFields\\Parser.
- IJwkResolvingSignatoryManager: capability bit signatory managers
  advertise to participate in RFC 9421 verification.
- OcmProfile: OCM-mandated dictionary label.
- SignatureManager: dispatch to RFC 9421 inbound when Signature-Input
  is present, outbound when rfc9421.format is set.

Plus tests for each primitive and a full round-trip across the model.

Signed-off-by: Micke Nordin <kano@sunet.se>
Add Manager::generateEd25519AppKey: persist a sodium-generated
Ed25519 keypair (raw 32-byte public, 64-byte secret) under the same
appdata layout the existing RSA path uses. Used by OCMSignatoryManager
for the slotted RFC 9421 signing keys.

Signed-off-by: Micke Nordin <kano@sunet.se>
OCM dual-stack integration of RFC 9421 alongside the existing cavage
publicKey path:

- OCMSignatoryManager: Ed25519 active/pending/retiring slot rotation
  backed by numbered pool appkeys, getRemoteKey for inbound JWK lookup
  with per-origin cache + cache-miss refetch, and getLocalEd25519Jwks
  for the JWKS endpoint.
- Rfc9421SignatoryManager: per-call wrapper that swaps in the Ed25519
  signatory and toggles `rfc9421.format`.
- OCMJwksHandler: serves /.well-known/jwks.json (RFC 7517) when signing
  is enabled.
- OCMDiscoveryService: advertises `http-sig` in capabilities when
  signing is enabled, and picks the signature scheme on outbound based
  on the remote's advertised capabilities.
- Application.php: register the JWKS well-known handler.

Signed-off-by: Micke Nordin <kano@sunet.se>
  ocm:keys:list      list known keys with their slot and kid
  ocm:keys:stage     generate a pending key, advertise via JWKS
  ocm:keys:activate  promote pending -> active, demote previous active
  ocm:keys:retire    delete the retiring key (kid stops resolving)

Plus the autoloader regen covering the new classes from this branch.

Signed-off-by: Micke Nordin <kano@sunet.se>
Use constants instead of 0/1

Also fix PHPDoc to use correct return values.

Co-authored-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: Micke Nordin <kano@sunet.se>
Throw when one of the headers are empty

Enumerate all the allowed algorithms in th NATIVE constant

Co-authored-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: Micke Nordin <kano@sunet.se>
This commit switches the default signature algorithm to
ecdsa-p256-sha256 instead of Ed25519. This allows us to make sodium
optional again, and we only pull it in to use it for verifying incomming
signatures. If sodium is not installed, we throw on Ed25519 signatures
instead. At least it is easy for most people to make their Nextcloud
install fully RFC compliant by installing sodium.

I also renamed all the Ed25519 function names to be more precis, using
Jwks for the JSON Web Keys, and RFC9421 for the http-signature code,
where it is needed to distinguish from draft-cavage signatures.

Signed-off-by: Micke Nordin <kano@sunet.se>
…eryService

Apps implementing OCM endpoints via OCMEndpointRequestEvent (e.g.
SUNET/nextcloud-ocm_request_share for request-share, nextcloud/contacts
for invite-accepted) need to apply the same identity check that the
built-in addShare and receiveNotification handlers apply, so it makes
sense to make it publicly accessible.

It also allows us to refactor RequestHandlerController::confirmSignedOrigin
to use the new public method and drop the confirmNotificationIdentity helper.

Signed-off-by: Micke Nordin <kano@sunet.se>
…t test

Two CI failures introduced by the test additions in this PR:

1. testEd25519VerifyAcceptedWhenSodiumLoaded calls setSignature() to inject
   an externally-produced Ed25519 signature (since Algorithm::sign() rejects
   Ed25519 by design). setSignature was declared protected, so the test
   couldn't call it from outside the class hierarchy. Make it public —
   SignedRequest lives in the OC\ private namespace, so this widens
   internal-only visibility, not the public API surface.

2. testParseKeyRejectsContradictoryAlg expected firebase/php-jwt's
   JWK::parseKey() to throw on a kty=OKP/crv=Ed25519/alg=ES256 key. The
   current firebase/php-jwt version does not validate that coherence at
   parse time, so the test now fails to see any throwable. The actual
   security check happens at Algorithm::verify() time and is covered by
   testVerifyEd25519KeyAgainstES256Alg right above it. Skip the parse-time
   test with a comment pointing at the verify-time coverage.

Signed-off-by: Micke Nordin <kano@sunet.se>
@github-actions
Copy link
Copy Markdown
Contributor

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

(If you believe you should not receive this message, you can add yourself to the blocklist.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants