How to Sign ActivityPub Requests for Mastodon Federation Manually
🔍 WiseChecker

How to Sign ActivityPub Requests for Mastodon Federation Manually

Mastodon uses ActivityPub to communicate between instances. Every message sent to another server must be signed with your private key. Without a valid signature, the receiving server rejects the request as unauthenticated. This article explains the manual process of creating and attaching HTTP signatures to ActivityPub requests for Mastodon federation.

Key Takeaways: Manual ActivityPub Signature Workflow

  • RSA-SHA256 signature algorithm: Required by Mastodon to verify incoming federation requests.
  • HTTP Signature headers: Include (request-target), host, date, and digest values.
  • Key-ID in the Signature header: Must match the actor’s public key URL exactly.

ADVERTISEMENT

Why ActivityPub Requests Need Manual Signing

ActivityPub is a decentralized protocol. Each Mastodon instance is an independent server. When your server sends a message to another instance, the receiving server must confirm the sender is who they claim to be. The HTTP Signature standard provides this authentication.

The signature proves the request came from the actor associated with the public key. Without it, any server could impersonate a user or another instance. Mastodon rejects unsigned requests with a 401 or 403 HTTP status code.

Manual signing becomes necessary when you are building a custom ActivityPub client, testing federation logic, or debugging why your server’s messages are not accepted. Automated libraries like http-signatures or activitypub-core exist, but understanding the manual process helps you troubleshoot failures and implement the protocol correctly.

Steps to Sign ActivityPub Requests Manually

  1. Retrieve the actor’s private key
    Locate the RSA private key associated with the actor sending the request. Mastodon stores this key in the database. For a custom implementation, generate a key pair using OpenSSL: openssl genrsa -out private.pem 2048. Extract the public key in PEM format: openssl rsa -in private.pem -pubout -out public.pem.
  2. Prepare the HTTP Signature headers
    Mastodon requires these headers in the signature string: (request-target), host, date, and digest. The (request-target) header is the HTTP method and path in lowercase. For example: post /inbox. The host header is the domain of the receiving server. The date header is the current UTC timestamp in RFC 1123 format. The digest header contains the SHA-256 hash of the request body in base64.
  3. Create the signature string
    Join the header values in order, each on a new line with a colon and space between the header name and value. Example:
    (request-target): post /inbox
    host: remote-instance.social
    date: Thu, 15 Jun 2023 12:00:00 GMT
    digest: SHA-256=base64hashvalue
  4. Sign the string with RSA-SHA256
    Use the private key to sign the signature string. In OpenSSL: echo -n "signature string" | openssl dgst -sha256 -sign private.pem | openssl base64 -A. The output is the base64-encoded signature.
  5. Build the Signature header
    Construct the HTTP Signature header with these parameters:
    keyId="https://your-instance.social/users/username#main-key"
    algorithm="rsa-sha256"
    headers="(request-target) host date digest"
    signature="base64signature"
    Separate each parameter with a comma. The keyId must be the exact URL of the actor’s public key as exposed in the actor’s ActivityStreams JSON.
  6. Send the HTTP request
    Include the Signature header, Date header, Digest header, and the request body. Mastodon expects the body to be a JSON-LD document with a valid ActivityStreams type such as Create, Follow, or Announce.

ADVERTISEMENT

Common Mistakes When Signing ActivityPub Requests

Signature header order does not match the headers parameter

The headers parameter in the Signature header lists the header names in the exact order they appear in the signature string. If you include date before host in the list, the signature string must also list them in that same order. Mastodon verifies the signature by reconstructing the string using the declared order. A mismatch causes a signature verification failure.

Wrong keyId URL format

The keyId must point to the public key embedded in the actor’s JSON representation. A common error is using the actor’s profile URL without the #main-key fragment. Mastodon looks up the keyId URL and expects to find a publicKey object with a publicKeyPem field. Use the exact URL that the actor’s JSON exposes.

Date header too far from server time

Mastodon allows a maximum clock skew of 30 seconds between the Date header and the receiving server’s current time. If the clock on your server is off by more than 30 seconds, the request is rejected. Synchronize your server time using NTP. Alternatively, include the (created) header in the signature string to use a more flexible timestamp.

Item Using Automated Library Manual Signing
Implementation effort Minimal, library handles hashing and header construction Requires manual step-by-step construction of each header and signature
Debugging visibility Limited, library abstracts the signature process Full control over every header and parameter, easier to isolate failures
Error rate Lower, library follows the spec exactly Higher, one misplaced colon or wrong header order breaks the signature
Flexibility for custom headers Depends on library support for custom header lists Complete freedom to include any header in the signature string

Manual signing is best for testing, debugging, and learning the ActivityPub protocol. For production systems, use a well-tested library to reduce errors.

You can now construct and attach HTTP Signatures to ActivityPub requests for Mastodon federation. Start by testing with a simple Follow activity to a test instance. Verify the signature using Mastodon’s debug endpoint or by checking the server logs for 401 errors. For advanced debugging, use a tool like ngrok to inspect the exact headers your server sends and compare them to the expected format.

ADVERTISEMENT