WP Manifestindependent plugin directory
manifest / privacy / archiviotor

ArchivioTOR

Ed25519 post signing for WordPress. Keys never leave your browser. Ephemeral signing, Strict Containment Mode for Tor Browser Safest level, and onion-aware rate limiting. Built for journalists, activists, and anonymous publishers.

by ArchivioTOR Project · github.com/mountainviewprovisions/archiviotor · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/mountainviewprovisions/archiviotor/archive/refs/heads/main.zip

ArchivioTOR — Ed25519 Post Signing for WordPress

ArchivioTOR is a privacy-first cryptographic signing plugin for WordPress. Authors sign posts with Ed25519 keys generated and held entirely in the browser — the private key never touches the server, not even once. Signatures prove that post content has not been altered since signing and can be verified independently by any reader using standard tools, no WordPress required.

Designed for deployment on Tor hidden services: no external dependencies, bundled noble-ed25519 with integrity verification, Onion Mode rate limiting that works correctly when all clients arrive from 127.0.0.1, WordPress identity signal suppression (generator tag, version strings, pingback, author enumeration, REST user endpoint, oEmbed), and Strict Containment Mode for fully server-rendered badges at Tor Browser Safest level with JavaScript disabled. Ephemeral signing generates a one-time keypair per post and discards the private key immediately signatures prove integrity without linking posts to a persistent identity.

Contents

  1. What It Does
  2. What It Does Not Do
  3. Requirements
  4. Installation
  5. Quick Start
  6. Signing Posts
  7. Verifying Signatures
  8. Ephemeral Keys
  9. Offline Signing
  10. Tor Deployment
  11. Settings Reference
  12. Canonical Message Format
  13. REST API Reference
  14. Troubleshooting
  15. Uninstalling
  16. Security Notes
  17. Changelog
  18. License

What It Does

ArchivioTOR attaches a cryptographic proof to each WordPress post you sign. The proof lets any reader confirm two things independently, without special software and without contacting any external service:

  1. Integrity — the post content has not been altered since it was signed.
  2. Continuity — this post was signed by the same key holder as other posts sharing that public key.

Signing happens entirely in your browser. The server stores only the resulting public key and signature — never the private key, never intermediate values. The signature covers a deterministic canonical representation of the post: its ID, title, slug, stripped body text, and GMT publication date. Any change to any of those fields invalidates the signature and triggers a stale warning on the verification badge.


What It Does Not Do

Understanding the limits is as important as understanding the capabilities.

ArchivioTOR does not provide anonymity or network privacy. It is a content-integrity tool. It does not route traffic through Tor, does not hide who visits your site, and does not protect against traffic analysis. For network-level protection, deploy your site as a Tor hidden service — that is outside this plugin's scope.

ArchivioTOR does not protect against a fully compromised server. If an adversary controls this server, they can serve a manipulated canonical message and your browser will sign it without any visible indication that something is wrong. The standard signing workflow inherently trusts the server for message construction. See Offline Signing for how to remove that dependency for high-risk situations.

A signature does not prove when you published. It proves that the key holder signed that exact content at some point, not when. For cryptographic timestamping, submit a hash of your post to a public transparency log at publication time.

A signature does not prove your personal identity. It proves that whoever holds your private key approved that text. Identity depends on anchoring your public key fingerprint to independent out-of-band channels — a DNS TXT record, a Nostr profile, a Keybase proof, a second server you control.


Requirements

Component Minimum Recommended
WordPress 5.8 6.4+
PHP 7.4 8.1+
PHP ext-sodium Optional Recommended
Browser Chrome 67+ / Firefox 57+ Any modern browser
Tor Browser 7.5+ Latest stable

PHP ext-sodium is required only for server-side signature verification. Client-side verification (the default) works without it.


Installation

  1. Download the plugin .zip file.
  2. In WordPress admin go to Plugins → Add New Plugin → Upload Plugin.
  3. Upload the .zip, click Install Now, then Activate.
  4. Alternatively, unzip and upload the archiviotor/ folder to wp-content/plugins/ via FTP or SSH.
  5. Navigate to ArchivioTOR in the left sidebar.

On activation, Onion Mode and WordPress identity signal suppression are enabled automatically for new installations. See Settings Reference for what these do. Existing installations that have already saved settings are not affected.


Quick Start

  1. Go to ArchivioTOR → Key Generator and click ⚡ Generate New Key Pair.
  2. Export a backup immediately: 📤 Export Key File → save the .json to an encrypted password manager or offline vault. If you skip this and clear browser data, the key is gone permanently.
  3. Go to ArchivioTOR → Sign Posts, load your key, select posts, click Sign Selected.
  4. Go to ArchivioTOR → Verify Posts and click 🔍 Verify (Client) to confirm signatures are valid.
  5. Publish your public key fingerprint in at least two independent places so readers can confirm it has not been substituted.

Signing Posts

Standard workflow

Go to ArchivioTOR → Sign Posts.

  1. Load your key: 🔄 Load from Storage if you saved it to this browser, or 📂 Import Key File to load from a .json export.
  2. Your active public key and fingerprint appear in the Active Key panel.
  3. Select posts by checkbox, or use Select all.
  4. Click Sign Selected, or click Sign on an individual row.

What happens:

  1. Your browser fetches the post's canonical message from the server.
  2. Ed25519 signing is performed locally using your private key.
  3. The browser sends only the public key and signature to the server.
  4. The server stores them in post meta (_archiviotor_signature, _archiviotor_pubkey).

Re-sign a post after editing its title, slug, or body content. The existing signature covers the content at signing time — any change invalidates it and triggers a stale warning.

Server-trust caveat

The standard workflow fetches the canonical message from this server before signing it. A compromised server could serve a manipulated message, and your signature would cover content you did not intend to approve with no visible indication. For situations where the server itself is inside your threat model, use Offline Signing instead.


Verifying Signatures

From the admin panel

Go to ArchivioTOR → Verify Posts and click 🔍 Verify (Client) on any row. The browser fetches the canonical message and verifies the signature locally — no private key required, no external network contact. This works over Tor and on .onion addresses.

If PHP ext-sodium is available on the server, a 🖥 Verify (Server) button also appears. Client-side verification is preferred when using Tor.

From the front end

When the signature badge is enabled in Settings, a badge appears on each signed post. Any reader — without an account or any special software — can click it to verify the signature in their own browser.

Independent verification

Because the canonical message format is documented and the signing library is standard Ed25519, any reader can verify a signature entirely independently using command-line tools. See Canonical Message Format.


Ephemeral Keys

An ephemeral key is a one-time key pair generated fresh for a single post and immediately discarded. The private key exists in browser memory only for the milliseconds needed to sign and is never saved anywhere.

Use ephemeral keys when:

  • You want to sign posts without linking them to a persistent author identity.
  • You are publishing anonymously or pseudonymously.
  • You want readers to be able to verify that content is unchanged, without establishing authorship continuity across posts.

How to use them:

  • Single post: click the Ephemeral button on any row in the Sign Posts table.
  • Bulk: check Ephemeral keys in the bulk-select bar before clicking Sign Selected.

The trade-off: Ephemeral signatures prove a post has not been altered. They do not prove authorship by a persistent identity, and two posts signed with different ephemeral keys cannot be linked to each other — this is intentional.


Offline Signing

The offline signing page removes the server from the signing process entirely. Instead of fetching the canonical message from the server, you supply the post field values yourself. The canonical message is constructed locally in your browser, signed there, and only the resulting signature is submitted to the server.

Use offline signing when:

  • The server itself is inside your threat model.
  • You are in a high-risk situation where a compromised server must not be able to influence what you sign.
  • You want to sign on an air-gapped machine and transfer only the signature.

Go to ArchivioTOR → Offline Signing.

Air-gap workflow:

  1. Copy keygen/keygen.html from the plugin folder to an offline machine.
  2. Open it in Tor Browser (or any browser) with no network connection.
  3. Generate your key pair, export the .json file.
  4. Transfer the .json to your online machine via encrypted USB.
  5. Import the key in Key Generator → 📂 Import Key File.
  6. Use Offline Signing to sign posts — the private key is never present in a networked browser.

Tor Deployment

ArchivioTOR is designed to be safe by default on .onion addresses.

No external dependencies. noble-ed25519.min.js is bundled with the plugin. No scripts, fonts, images, or analytics are fetched from external servers. The CDN fetch option is off by default.

Same-origin only. All AJAX and REST API calls go to your server's own origin. A .onion address works identically to a clearweb domain.

Onion Mode (on by default for new installs) switches the rate limiter from IP-based bucketing to per-session token bucketing. On a Tor hidden service every client arrives at PHP from 127.0.0.1, so IP-based rate limiting collapses all visitors into one bucket. Onion Mode issues each anonymous visitor a short-lived random cookie (SameSite=Strict, HttpOnly, Secure, host-scoped) that carries no identity. X-Forwarded-For is ignored entirely — Tor does not set it and trusting it would allow IP spoofing.

WordPress identity signal suppression (on by default for new installs) closes several WordPress-default information leaks:

Signal What it exposes Action
<meta name="generator"> WordPress version Removed from <head>
?ver=X.Y.Z on asset URLs WP core, plugin, and theme versions Stripped from all enqueued assets
X-Pingback header xmlrpc.php URL Removed from all responses
pingback.ping XML-RPC method Confirms xmlrpc.php; DDoS abuse vector Disabled
/?author=N redirect All registered usernames in sequence Blocked
/wp-json/wp/v2/users All registered users, names, slugs Removed (returns 404)
oEmbed <link> tags and Link: header Confirms WordPress; exposes REST API root Removed

Strict Containment Mode is available for deployments where readers use Tor Browser at the Safest security level (JavaScript disabled). When active, the signature badge is rendered entirely server-side with no JavaScript, the plugin's frontend assets are not enqueued, and all shortcode output passes through a link-stripper before being returned. Enable in Settings.

WebCrypto requirement. Signing uses window.crypto.subtle, which requires a secure context — HTTPS on clearweb or a .onion address over Tor. Plain HTTP admin access will not work.


Settings Reference

Setting Default Description
Signature badge on posts On Append a verification badge to signed posts on the front end
Server-side verification On Enable the PHP Sodium REST verification endpoint
Strict Containment Mode Off Fully server-rendered badges; no frontend JS; for Tor Browser Safest level
Onion Mode On Per-session rate limiting; X-Forwarded-For ignored
WordPress identity signal suppression On Suppresses version strings, pingback, author enumeration, REST user list, oEmbed
Manifest endpoint Off Enable the authenticated JSON manifest for archival pipelines
CDN library fetch Off Fetch noble-ed25519 from jsDelivr instead of the bundled copy

Settings marked On are enabled by default on fresh installations.

Manifest endpoint

The manifest endpoint (/wp-json/archiviotor/v1/manifest) returns a JSON document listing every signed post with its public key, canonical hash, signature prefix, and staleness status. It is intended for external archival and independent verification pipelines.

The endpoint always requires the Editor role or higher, regardless of the Settings toggle. It is never accessible to anonymous visitors. If you need a genuinely public manifest, reverse-proxy it behind your own access control layer.

When enabled, the manifest exposes post titles, slugs, publication dates, public keys, and modification history for every signed post in a single authenticated request. On a Tor deployment this creates a complete index of your published corpus. Disable it unless you have a specific archival need.


Canonical Message Format

The message signed for each post is a deterministic UTF-8 string:

archiviotor-v1
{post_id}
{post_title}
{post_slug}
{post_content_with_html_stripped}
{post_date_gmt}

Fields are separated by newline characters (\n). The format is versioned — future releases can introduce archiviotor-v2 without breaking existing signatures.

Retrieve the canonical message for any post:

GET /wp-json/archiviotor/v1/message/{post_id}

Independent command-line verification:

# Fetch the canonical message
curl -s https://your-site.onion/wp-json/archiviotor/v1/message/42 | jq -r '.message' > message.txt

# Fetch the stored public key and signature
curl -s https://your-site.onion/wp-json/archiviotor/v1/meta/42 | jq -r '.pubkey, .signature'

# Verify with Python (cryptography library)
python3 - <<'EOF'
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
import binascii

pubkey_hex = "YOUR_64_HEX_PUBKEY"
sig_hex    = "YOUR_128_HEX_SIGNATURE"
message    = open("message.txt", "rb").read()

key = Ed25519PublicKey.from_public_bytes(binascii.unhexlify(pubkey_hex))
key.verify(binascii.unhexlify(sig_hex), message)
print("Signature valid")
EOF

REST API Reference

Method Endpoint Auth Description
GET /wp-json/archiviotor/v1/message/{id} Public Canonical signable message for a post
GET /wp-json/archiviotor/v1/meta/{id} Public Signature, public key, ephemeral flag, staleness
POST /wp-json/archiviotor/v1/verify/{id} Public Server-side verification (requires ext-sodium)
POST /wp-json/archiviotor/v1/sign/{id} Editor+ Save a signature (REST alternative to AJAX)
GET /wp-json/archiviotor/v1/manifest Editor+ Signed-post manifest for archival pipelines
GET /wp-json/archiviotor/v1/sig-log/{id} Editor+ Signature audit log for a post

Public endpoints are rate-limited at 60 requests per minute per client. On Tor deployments rate limiting uses per-session token bucketing rather than IP hashing.


Troubleshooting

"noble-ed25519 not loaded" on the Dashboard
Confirm that js/noble-ed25519.min.js exists in the plugin folder, that no security plugin or server rule blocks .js files from wp-content/plugins/, and that JavaScript is enabled.

Signature shows "INVALID" or "Stale" after editing
Expected behaviour. The signature covers the content at signing time. Any change to the title, slug, or body invalidates it. Re-sign the post.

"WebCrypto not available"
Signing requires a secure context — HTTPS on clearweb, or a .onion address over Tor. Plain HTTP will not work.

Server-side verify button is missing
PHP ext-sodium is not available. Client-side verification still works. Ask your host to enable the sodium extension, or use PHP 7.2+ where it is bundled.

Key disappeared after clearing browser data
Keys are stored in sessionStorage and are cleared when the tab or browser closes — this is intentional. Import your .json backup on the Key Generator page, or generate a new key pair and re-sign your posts.

Bulk signing stops partway through
Large batches may hit PHP execution time limits. Sign in batches of 20–30. Each signature is saved immediately, so completed posts retain their signatures.

Author archive pages behave unexpectedly after enabling signal suppression
Signal suppression blocks the /?author=N numeric enumeration redirect only. Direct navigation to /author/username/ is unaffected.

The manifest endpoint returns 404
The manifest is disabled by default. Enable it in Settings. Even when enabled, it requires the Editor role — anonymous requests always receive 404.


Uninstalling

Deactivating and deleting the plugin removes all plugin files. Signatures and public keys stored in post meta remain in the database. To remove them:

DELETE FROM wp_postmeta WHERE meta_key IN (
  '_archiviotor_signature',
  '_archiviotor_pubkey',
  '_archiviotor_ephemeral',
  '_archiviotor_canonical_hash'
);

Replace wp_ with your actual table prefix if different.


Security Notes

Protect your private key. Anyone who obtains it can sign arbitrary content as you. Never paste it into a browser on a machine you do not fully trust. Store your .json export in an encrypted password manager or offline vault. For high-risk situations, generate keys on an air-gapped machine using the standalone keygen/keygen.html file.

Publish your public key fingerprint independently. A signature is only as trustworthy as the channel through which you distribute your public key. If an adversary controls your server they can substitute a different public key and sign fraudulent content. Publish your fingerprint in a DNS TXT record, Nostr profile, GitHub Gist, or on a second server you control so readers have a way to detect substitution.

Revoke compromised keys promptly. If a private key is stolen or exposed, mark it as revoked in ArchivioTOR → Key Registry immediately. Revocation signals to readers that signatures from that key should not be trusted, and prompts re-signing with a new key.

Sign close to the moment of writing. Signing protects content from that point forward. If your server was compromised before you signed, the version you signed may already be the tampered version.

Security disclosures. Contact the maintainer privately before opening a public issue for any security vulnerability.


Changelog

1.4.6

  • Added dismissible server-trust warning on the Sign Posts page with a direct link to Offline Signing, so operators in high-risk situations find the right workflow without hunting for it.

1.4.5

  • Author enumeration via /?author=N blocked; REST /wp-json/wp/v2/users endpoints removed (returns 404, not 401); oEmbed discovery links, wp-embed.js, and REST Link: header removed. All under the existing WordPress identity signal suppression option.
  • Threat model page updated with a dedicated section and summary table rows for all three paths.

1.4.4

  • Rate-limit cookie domain attribute now set to the exact request host, preventing subdomain leakage on multisite installs.

1.4.3

  • site_url removed from manifest response — it served no verification purpose and would have linked an onion address to a clearweb origin if the endpoint were ever proxied publicly.

1.4.2

  • Onion Mode and WordPress identity signal suppression enabled by default on fresh installations.
  • Manifest endpoint now requires Editor role or higher regardless of the Settings toggle.

1.4.1

  • Initial public release.

License

ArchivioTOR is released under the GNU General Public License v2.0 or later.

noble-ed25519 is bundled under the MIT License.