Changeset f63cbc2 in Klonkt for src/services/CircleService.js


Ignore:
Timestamp:
06/16/2026 06:33:38 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
4fee228
Parents:
1b4d5dd
Message:

Circles: protocol version enforced via signature binding (update enforcement)

KLONKT_PROTO is now part of the signed basis (signingInput:
"klonkt/proto/N\n"+body). An instance on a different proto cannot verify
the signed outbox and vice versa → staying up to date is cryptographically
enforced, not a patchable check. Both sides verify the proto
(actor.klonkt.proto + Klonkt-Proto header → 426); too-old/too-new sources
are excluded with status 'outdated' + a clear message. Couple security to
every proto bump. Proto starts at 2; roll out in lockstep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/CircleService.js

    r1b4d5dd rf63cbc2  
    66
    77import db from '../config/database.js';
    8 import { verifyBody } from './CircleFederation.js';
     8import { verifyBody, KLONKT_PROTO, MIN_PROTO } from './CircleFederation.js';
    99import { getTenancy } from './SettingsService.js';
    1010
     
    2525function baseOf(remoteUrl) {
    2626  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.
     32function 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 };
    2736}
    2837
     
    3645      signal: ac.signal,
    3746      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      },
    3952    });
    4053    if (!res.ok) throw new Error(`HTTP ${res.status}`);
     
    8497  if (originOf(actorId) !== originOf(actorUrl)) throw new Error('actor.id heeft andere origin dan de actor-URL');
    8598
     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
    86113  // TOFU: een sleutelwissel vereist expliciete herbevestiging (anti-hijack)
    87114  const existing = db.prepare('SELECT public_key FROM remote_actors WHERE id = ?').get(actorId);
     
    104131  const sigHeader = o.headers.get('klonkt-signature') || '';
    105132  const sig = (sigHeader.match(/ed25519=(.+)\s*$/) || [])[1];
    106   if (!sig || !verifyBody(o.text, sig, pubKey)) {
     133  if (!sig || !verifyBody(o.text, sig, pubKey, remoteProto)) {
    107134    throw new Error('outbox-handtekening ongeldig of ontbreekt');
    108135  }
Note: See TracChangeset for help on using the changeset viewer.