Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1a2144d
Add utils to handle Accept-Signature
2chanhaeng Mar 7, 2026
8ea783f
Add fulfillAcceptSignature
2chanhaeng Mar 9, 2026
52b8a95
Add `rfc9421` param and fix related logic
2chanhaeng Mar 11, 2026
2d5f4a0
Add `InboxChallengePolicy` interface and implement Accept-Signature h…
2chanhaeng Mar 11, 2026
07a23f5
Add docs about RFC 9421 §5
2chanhaeng Mar 11, 2026
2e415b4
Format
2chanhaeng Mar 11, 2026
c7d4fdd
Add tests for inbound
2chanhaeng Mar 14, 2026
cb8b43c
Add `doubleKnock()` loop prevention test
2chanhaeng Mar 14, 2026
490ed50
Fix comments
2chanhaeng Mar 14, 2026
f37c162
Fix `http.ts`
2chanhaeng Mar 14, 2026
cc1c36c
Add changes
2chanhaeng Mar 16, 2026
08823f3
Improve nonce verification logic and add test
2chanhaeng Mar 16, 2026
828c093
Remove `requestCreated` attribute
2chanhaeng Mar 16, 2026
3db2ddc
Retry challenge on `TypeError` in `doubleKnock`
2chanhaeng Mar 16, 2026
1788323
Filter `@status` components
2chanhaeng Mar 16, 2026
7810dd0
Fix nonce and challenge component issues in inbox handler
2chanhaeng Mar 17, 2026
7d91283
Fix minor in docs
2chanhaeng Mar 18, 2026
edcf2ed
Fix null check
2chanhaeng Mar 18, 2026
f2432e2
Lint markdown
2chanhaeng Mar 18, 2026
030f07b
Add PR
2chanhaeng Mar 19, 2026
1257ee8
Initialize `pendingNonceLabel` as `undefined`
2chanhaeng Mar 19, 2026
ab7dcdd
Add conditional check for `kv.cas` in `verifySignatureNonce` function
2chanhaeng Mar 19, 2026
8950cc0
Add `AcceptSignatureComponent` and fix related code
2chanhaeng Mar 19, 2026
5d4d93d
Add `expires` attr
2chanhaeng Mar 19, 2026
9243884
Remove not requested components
2chanhaeng Mar 19, 2026
d31f5d6
Refactor `derivedComponents`
2chanhaeng Mar 19, 2026
fe8a9e3
Fix `rfc9421` components
2chanhaeng Mar 19, 2026
69e5048
Fix `rfc9421` components
2chanhaeng Mar 19, 2026
15db464
Return non-negotiation failures from challenge retry directly
2chanhaeng Mar 19, 2026
38097bf
Fulfill all compatible Accept-Signature entries
2chanhaeng Mar 19, 2026
3dda5bf
Lint
2chanhaeng Mar 20, 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
12 changes: 12 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,22 @@ To be released.
caused a `500 Internal Server Error` when interoperating with servers like
GoToSocial that have authorized fetch enabled. [[#473], [#589]]

- Added RFC 9421 §5 `Accept-Signature` negotiation for both outbound and
inbound paths. On the outbound side, `doubleKnock()` now parses
`Accept-Signature` challenges from `401` responses and retries with a
compatible RFC 9421 signature before falling back to legacy spec-swap.
On the inbound side, a new `InboxChallengePolicy` option in
`FederationOptions` enables emitting `Accept-Signature` headers on
inbox `401` responses, with optional one-time nonce support for replay
protection. [[#583], [#584], [#626] by ChanHaeng Lee]

[#472]: https://github.com/fedify-dev/fedify/issues/472
[#473]: https://github.com/fedify-dev/fedify/issues/473
[#583]: https://github.com/fedify-dev/fedify/issues/583
[#584]: https://github.com/fedify-dev/fedify/issues/584
[#589]: https://github.com/fedify-dev/fedify/pull/589
[#611]: https://github.com/fedify-dev/fedify/pull/611
[#626]: https://github.com/fedify-dev/fedify/pull/626

### @fedify/vocab-runtime

Expand Down
42 changes: 42 additions & 0 deletions docs/manual/inbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,48 @@ why some activities are rejected, you can turn on [logging](./log.md) for
[Linked Data Signatures]: https://web.archive.org/web/20170923124140/https://w3c-dvcg.github.io/ld-signatures/
[FEP-8b32]: https://w3id.org/fep/8b32

### `Accept-Signature` challenges

*This API is available since Fedify 2.1.0.*

You can optionally enable [`Accept-Signature`] challenge emission on inbox
`401` responses by setting the `inboxChallengePolicy` option when creating
a `Federation`:

~~~~ typescript
import { createFederation } from "@fedify/fedify";

const federation = createFederation<void>({
// ... other options ...
inboxChallengePolicy: {
enabled: true,
// Optional: customize covered components (defaults shown below)
// components: ["@method", "@target-uri", "@authority", "content-digest"],
// Optional: require a one-time nonce for replay protection
// requestNonce: false,
// Optional: nonce TTL in seconds (default: 300)
// nonceTtlSeconds: 300,
},
});
~~~~

When enabled, if HTTP Signature verification fails, the `401` response will
include an `Accept-Signature` header telling the sender which components and
parameters to include in a new signature. Senders that support [RFC 9421 §5]
(including Fedify 2.1.0+) will automatically retry with the requested
parameters.

Note that actor/key mismatch `401` responses are *not* challenged, since
re-signing with different parameters does not resolve an impersonation issue.

When `requestNonce` is enabled, a cryptographically random nonce is included
in each challenge and must be echoed back in the retry signature. The nonce
is stored in the key-value store and consumed on use, providing replay
protection. Nonces expire after `nonceTtlSeconds` (default: 5 minutes).

[`Accept-Signature`]: https://www.rfc-editor.org/rfc/rfc9421#section-5.1
[RFC 9421 §5]: https://www.rfc-editor.org/rfc/rfc9421#section-5


Handling unverified activities
------------------------------
Expand Down
33 changes: 33 additions & 0 deletions docs/manual/send.md
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,39 @@ to the draft cavage version and remembers it for the next time.

[double-knocking]: https://swicg.github.io/activitypub-http-signature/#how-to-upgrade-supported-versions

### `Accept-Signature` negotiation

*This API is available since Fedify 2.1.0.*

In addition to double-knocking, Fedify supports the [`Accept-Signature`]
challenge-response negotiation defined in [RFC 9421 §5]. When a recipient
server responds with a `401` status and includes an `Accept-Signature` header,
Fedify automatically parses the challenge, validates it, and retries the
request with the requested signature parameters (e.g., specific covered
components, a nonce, or a tag).

Safety constraints prevent abuse:

- The requested algorithm (`alg`) must match the local private key's
algorithm; otherwise the challenge entry is skipped.
- The requested key identifier (`keyid`) must match the local key; otherwise
the challenge entry is skipped.
- Fedify's minimum covered component set (`@method`, `@target-uri`,
`@authority`) is always included, even if the challenge does not request
them.

If the challenge cannot be fulfilled (e.g., incompatible algorithm),
Fedify falls through to the existing double-knocking spec-swap fallback.
At most three signed request attempts are made to the final URL per delivery
attempt (redirects may add extra HTTP requests):

1. Initial signed request
2. Challenge-driven retry (if `Accept-Signature` is present)
3. Legacy spec-swap retry (if the challenge retry also fails)

[`Accept-Signature`]: https://www.rfc-editor.org/rfc/rfc9421#section-5.1
[RFC 9421 §5]: https://www.rfc-editor.org/rfc/rfc9421#section-5


Linked Data Signatures
----------------------
Expand Down
3 changes: 3 additions & 0 deletions examples/astro/deno.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"compilerOptions": {
"moduleResolution": "nodenext"
},
"imports": {
"@deno/astro-adapter": "npm:@deno/astro-adapter@^0.3.2"
},
Expand Down
48 changes: 48 additions & 0 deletions packages/fedify/src/federation/federation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,43 @@ export interface FederationBuilder<TContextData>
): Promise<Federation<TContextData>>;
}

/**
* Policy for emitting `Accept-Signature` challenges on inbox `401`
* responses, as defined in
* [RFC 9421 §5](https://www.rfc-editor.org/rfc/rfc9421#section-5).
* @since 2.1.0
*/
export interface InboxChallengePolicy {
/**
* Whether to emit `Accept-Signature` headers on `401` responses
* caused by HTTP Signature verification failures.
*/
enabled: boolean;

/**
* The covered component identifiers to request. Only request-applicable
* identifiers should be used (`@status` is automatically excluded).
* @default `["@method", "@target-uri", "@authority", "content-digest"]`
*/
components?: string[];

/**
* Whether to generate and require a one-time nonce for replay protection.
* When enabled, a cryptographically random nonce is included in each
* challenge and verified on subsequent requests. Requires a
* {@link KvStore}.
* @default `false`
*/
requestNonce?: boolean;

/**
* The time-to-live (in seconds) for stored nonces. After this period,
* nonces expire and are no longer accepted.
* @default `300` (5 minutes)
*/
nonceTtlSeconds?: number;
}

/**
* Options for creating a {@link Federation} object.
* @template TContextData The context data to pass to the {@link Context}.
Expand Down Expand Up @@ -931,6 +968,17 @@ export interface FederationOptions<TContextData> {
*/
firstKnock?: HttpMessageSignaturesSpec;

/**
* The policy for emitting `Accept-Signature` challenges on inbox `401`
* responses (RFC 9421 §5). When enabled, failed HTTP Signature
* verification responses will include an `Accept-Signature` header
* telling the sender which components and parameters to include.
*
* Disabled by default (no `Accept-Signature` header is emitted).
* @since 2.1.0
*/
inboxChallengePolicy?: InboxChallengePolicy;

/**
* The retry policy for sending activities to recipients' inboxes.
* By default, this uses an exponential backoff strategy with a maximum of
Expand Down
Loading
Loading