Changeset f63cbc2 in Klonkt
- Timestamp:
- 06/16/2026 06:33:38 AM (3 months ago)
- Branches:
- main
- Children:
- 4fee228
- Parents:
- 1b4d5dd
- Location:
- src
- Files:
-
- 3 edited
-
routes/federation.js (modified) (3 diffs)
-
services/CircleFederation.js (modified) (5 diffs)
-
services/CircleService.js (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/federation.js
r1b4d5dd rf63cbc2 8 8 9 9 import express from 'express'; 10 import { buildActor, buildOutbox, signBody } from '../services/CircleFederation.js';10 import { buildActor, buildOutbox, signBody, KLONKT_PROTO, MIN_PROTO } from '../services/CircleFederation.js'; 11 11 import { getTenancy } from '../services/SettingsService.js'; 12 12 … … 18 18 } 19 19 20 // De proto die de consument zegt te draaien (uit z'n request-header), of 0. 21 function consumerProto(req) { 22 return parseInt(req.get('Klonkt-Proto') || '0', 10) || 0; 23 } 24 20 25 router.get('/.klonkt/actor.json', (req, res) => { 21 26 // Cirkels = solo-naar-solo; hubs publiceren geen federatie-actor. 22 27 if (getTenancy() === 'hub') return res.status(404).type('text/plain').send('Niet beschikbaar in hub-modus'); 28 // De actor serveren we ALTIJD (ook aan oudere consumenten) zodat zij onze proto 29 // kunnen lezen en een nette "update vereist"-melding kunnen tonen. 23 30 const body = JSON.stringify(buildActor(baseUrl(req)), null, 2); 24 31 res.type('application/activity+json; charset=utf-8'); 32 res.set('Klonkt-Proto', String(KLONKT_PROTO)); 25 33 res.set('Cache-Control', 'public, max-age=300'); 26 34 res.send(body); … … 29 37 router.get('/.klonkt/outbox.json', (req, res) => { 30 38 if (getTenancy() === 'hub') return res.status(404).type('text/plain').send('Niet beschikbaar in hub-modus'); 39 res.set('Klonkt-Proto', String(KLONKT_PROTO)); 40 // Te-oude consument? Weiger met 426 Upgrade Required (de crypto-binding sluit 'm 41 // sowieso al uit; dit geeft een expliciet, leesbaar signaal). proto 0 = geen 42 // header (bv. een browser/curl) → toestaan, die verifieert toch niet. 43 const cp = consumerProto(req); 44 if (cp && cp < MIN_PROTO) { 45 return res.status(426).type('text/plain') 46 .send(`Upgrade Required: deze cirkel draait proto ${KLONKT_PROTO}; jouw Klonkt (proto ${cp}) is te oud.`); 47 } 31 48 const body = JSON.stringify(buildOutbox(baseUrl(req)), null, 2); 32 49 res.type('application/activity+json; charset=utf-8'); -
src/services/CircleFederation.js
r1b4d5dd rf63cbc2 15 15 import db from '../config/database.js'; 16 16 import { getSetting, setSetting } from './SettingsService.js'; 17 18 // ── Protocol-versie (federatie) ─────────────────────────────── 19 // KLONKT_PROTO zit IN de ondertekende grondslag (zie signingInput): een instance 20 // die niet op deze proto draait kan onze getekende outbox NIET verifiëren, en wij 21 // de hare niet. Bijblijven is dus geen beleefde check die je wegpatcht, maar 22 // cryptografisch afgedwongen — de enige manier om mee te doen is dezelfde proto 23 // draaien (= de update). Bump KLONKT_PROTO bij elke release die federatie/security 24 // raakt, en koppel een securityfix aan elke bump → outdated = buiten + onveilig. 25 // MIN_PROTO = de laagste proto waarmee we nog federeren. 26 export const KLONKT_PROTO = 2; 27 export const MIN_PROTO = 2; 28 29 function signingInput(proto, body) { 30 return `klonkt/proto/${proto}\n${body}`; 31 } 17 32 18 33 // ── Sleutelbeheer ───────────────────────────────────────────── … … 37 52 } 38 53 39 /** Tekent een exacte body-string met de instance-privésleutel(Ed25519). */40 export function signBody(rawString ) {54 /** Tekent een body-string, gebonden aan de protocol-versie (Ed25519). */ 55 export function signBody(rawString, proto = KLONKT_PROTO) { 41 56 const key = crypto.createPrivateKey(getKeys().priv); 42 return crypto.sign(null, Buffer.from( rawString, 'utf8'), key).toString('base64');57 return crypto.sign(null, Buffer.from(signingInput(proto, rawString), 'utf8'), key).toString('base64'); 43 58 } 44 59 45 /** Verifieert een body tegen een SPKI-DER-base64 publieke sleutel (voor sync/tests). */ 46 export function verifyBody(rawString, sigB64, pubDerB64) { 60 /** Verifieert een body tegen een SPKI-DER-base64 publieke sleutel, voor de gegeven 61 * proto. Een mismatch in proto = mismatch in grondslag = ongeldige handtekening. */ 62 export function verifyBody(rawString, sigB64, pubDerB64, proto = KLONKT_PROTO) { 47 63 try { 48 64 const key = crypto.createPublicKey({ 49 65 key: Buffer.from(pubDerB64, 'base64'), format: 'der', type: 'spki', 50 66 }); 51 return crypto.verify(null, Buffer.from( rawString, 'utf8'), key, Buffer.from(sigB64, 'base64'));67 return crypto.verify(null, Buffer.from(signingInput(proto, rawString), 'utf8'), key, Buffer.from(sigB64, 'base64')); 52 68 } catch { 53 69 return false; … … 101 117 publicKeyBase64: getPublicKeyB64(), 102 118 }, 103 klonkt: { version: 1, allowCircle: allowsCircle(site) },119 klonkt: { version: 1, proto: KLONKT_PROTO, allowCircle: allowsCircle(site) }, 104 120 }; 105 121 } … … 111 127 const empty = { 112 128 '@context': 'https://www.w3.org/ns/activitystreams', 113 type: 'OrderedCollection', id, totalItems: 0, orderedItems: [], 129 type: 'OrderedCollection', id, totalItems: 0, orderedItems: [], klonkt: { proto: KLONKT_PROTO }, 114 130 }; 115 131 if (!allowsCircle(site)) return empty; … … 148 164 '@context': 'https://www.w3.org/ns/activitystreams', 149 165 type: 'OrderedCollection', id, totalItems: orderedItems.length, orderedItems, 166 klonkt: { proto: KLONKT_PROTO }, 150 167 }; 151 168 } -
src/services/CircleService.js
r1b4d5dd rf63cbc2 6 6 7 7 import db from '../config/database.js'; 8 import { verifyBody } from './CircleFederation.js';8 import { verifyBody, KLONKT_PROTO, MIN_PROTO } from './CircleFederation.js'; 9 9 import { getTenancy } from './SettingsService.js'; 10 10 … … 25 25 function baseOf(remoteUrl) { 26 26 return String(remoteUrl).replace(/\/+$/, ''); 27 } 28 29 // Bron buiten de cirkel zetten met een leesbare reden (geen stille mislukking). 30 // Aparte status 'outdated' zodat de Beheer-UI er een nette "update vereist"- 31 // melding van kan maken i.p.v. een generieke fout. 32 function markOutdated(link, msg) { 33 db.prepare("UPDATE circle_links SET status='outdated', last_error=?, last_synced=CURRENT_TIMESTAMP WHERE id=?") 34 .run(String(msg).slice(0, 300), link.id); 35 return { ok: false, outdated: true, link: link.remote_url, error: msg }; 27 36 } 28 37 … … 36 45 signal: ac.signal, 37 46 redirect: 'follow', 38 headers: { Accept: 'application/activity+json, application/json' }, 47 headers: { 48 Accept: 'application/activity+json, application/json', 49 // Vertel de publisher onze proto → die kan ons met 426 weren als we te oud zijn. 50 'Klonkt-Proto': String(KLONKT_PROTO), 51 }, 39 52 }); 40 53 if (!res.ok) throw new Error(`HTTP ${res.status}`); … … 84 97 if (originOf(actorId) !== originOf(actorUrl)) throw new Error('actor.id heeft andere origin dan de actor-URL'); 85 98 99 // Protocol-versie-gate. De proto zit óók in de outbox-handtekening-grondslag, 100 // dus liegen in de (ongetekende) actor helpt niet: bij een echte mismatch faalt 101 // de verificatie verderop alsnog. Hier vooral voor een DUIDELIJKE melding + 102 // buitensluiten zonder stille mislukking. 103 const remoteProto = Number(actor.klonkt && actor.klonkt.proto) || 1; 104 if (remoteProto > KLONKT_PROTO) { 105 return markOutdated(link, 106 `Jouw Klonkt (proto ${KLONKT_PROTO}) is ouder dan ${base} (proto ${remoteProto}). Werk je eigen instance bij om te blijven federeren.`); 107 } 108 if (remoteProto < MIN_PROTO) { 109 return markOutdated(link, 110 `${base} draait een oudere Klonkt (proto ${remoteProto}; minimaal ${MIN_PROTO} vereist). Vraag ze te updaten.`); 111 } 112 86 113 // TOFU: een sleutelwissel vereist expliciete herbevestiging (anti-hijack) 87 114 const existing = db.prepare('SELECT public_key FROM remote_actors WHERE id = ?').get(actorId); … … 104 131 const sigHeader = o.headers.get('klonkt-signature') || ''; 105 132 const sig = (sigHeader.match(/ed25519=(.+)\s*$/) || [])[1]; 106 if (!sig || !verifyBody(o.text, sig, pubKey )) {133 if (!sig || !verifyBody(o.text, sig, pubKey, remoteProto)) { 107 134 throw new Error('outbox-handtekening ongeldig of ontbreekt'); 108 135 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)