0017-fediverse-auto-resolve-public-key.md 5812 octets

RFC 0017 — Fediverse: auto-résolution du PEM public dans HttpSigVerifier

  • Auteur : EBO (rustwarden)
  • Date : 2026-05-10
  • Statut : Accepted (auto-revue mainteneur unique 2026-05-10, score 27/35)
  • Apps consommatrices concernées : toute app fediverse en production qui veut une vérification HTTP-Sig sans avoir à pré-résoudre la clé publique côté handler.
  • Plan associé : Phase 5 — production polish.
  • Dépendances : RFC 0003 (RemoteIdentityService), RFC 0010 (SignatureVerifier), RFC 0015 / 0016 (HTTP-Sig).

Summary

Brancher HttpSigVerifier (RFC 0016) sur RemoteIdentityService (RFC 0003) pour qu'il résolve lui-même la clé publique de l'acteur signataire à partir de ctx.actor_ap_id quand ctx.public_key_pem est vide. Élimine le contrat « le caller doit pré-résoudre la clé », réduit le boilerplate côté inbox handlers et ferme le dernier TODO documenté de RFC 0016.

Motivation

Aujourd'hui HttpSigVerifier::verify retourne Err(Validation("public key not resolved")) quand ctx.public_key_pem est vide — ce qui est toujours le cas depuis inbox::handle et auth_routes::auth_inbox car ceux-ci ne touchent pas à RemoteIdentityService. La vérification réelle est donc en pratique désactivée sans configuration manuelle des apps consommatrices.

Cette RFC rend le wiring transparent : par défaut le verifier résout via le service de cache.

Non-goals

  • Pas de cache supplémentaire — RemoteIdentityService a déjà son TTL.
  • Pas de fallback WebFinger ici — si l'acteur n'est pas dans le cache, le verifier échoue avec Err. Une RFC suivante ajoutera la résolution paresseuse via RemoteFetcher si nécessaire.
  • Pas de modification de l'API publique de HttpSigVerifier — new() reste sans argument ; on ajoute un constructeur with_resolver(db).

API proposée

pub struct HttpSigVerifier {
    db: Option<sea_orm::DatabaseConnection>,
}

impl HttpSigVerifier {
    /// Verifier non-resolving. Si `ctx.public_key_pem` est vide, le
    /// verifier rejette la requête (comportement RFC 0016 inchangé).
    pub fn new() -> Self;

    /// Verifier auto-resolving. Si `ctx.public_key_pem` est vide,
    /// le verifier appelle `RemoteIdentityService::find_by_ap_id` sur
    /// la connection fournie pour récupérer le PEM avant la vérif.
    pub fn with_resolver(db: sea_orm::DatabaseConnection) -> Self;
}

L'impl verify :

  1. Si ctx.public_key_pem non-vide → comportement RFC 0016.
  2. Sinon, si self.db.is_some() → lookup remote_identities par ap_id = ctx.actor_ap_id. PEM trouvé → vérification. Pas trouvé → Err(Validation("actor not in cache")).
  3. Sinon → Err(Validation("public key not resolved")) (comportement RFC 0016).

Analyse de compatibilité

  • Non-breaking : nouveau constructeur, ancien new() inchangé.
  • Pas de schéma DB.
  • Pas de modif de la signature verify.

Alternatives considérées

Alternative A (recommandée) — Constructeur séparé with_resolver(db)

Pros : explicite, test-friendly, pas d'effet de bord sur les callers existants.

Alternative B — Toujours résoudre, paramètre db: Option<…> dans new

Cons : casse l'API minimaliste actuelle ; aucun test ne s'exécute sans DB.

Rejeté.

Alternative C — Trait PublicKeyResolver injectable

Pros : flexibilité maximale.

Cons : sur-engineered pour un cas d'usage à 1 impl ; complexité immédiate sans bénéfice tangible.

Rejeté pour cette RFC — pourra être revisitée si plusieurs impls émergent.

Alternative D — Ne rien faire (caller pré-résout)

Cons : tous les inbox handlers doivent porter le boilerplate. Une faute de wiring = vérif désactivée silencieusement.

Rejeté.

Testing plan

5 tests :

  1. with_resolver_constructor_stores_db.
  2. verify_uses_provided_pem_when_present (régression du chemin RFC 0016).
  3. verify_resolves_pem_from_remote_identities_when_empty — DB-mocked, PEM stockée → succès round-trip.
  4. verify_rejects_when_actor_not_in_cache — DB-mocked, lookup retourne None → Err(Validation).
  5. verify_without_resolver_rejects_empty_pem (régression du chemin RFC 0016).

Migration guide

### Added

- `HttpSigVerifier::with_resolver(db)` — verifier qui résout
  automatiquement le PEM via `RemoteIdentityService`.

### Migration steps

Avant :
```rust
builder.with_signature_verifier(Arc::new(HttpSigVerifier::new()))
// + caller-side resolution

Après (recommandé) :

builder.with_signature_verifier(Arc::new(HttpSigVerifier::with_resolver(db.clone())))

HttpSigVerifier::new() reste disponible pour les apps qui veulent gérer la résolution elles-mêmes.


## Open questions

Toutes résolues à l'auto-revue :

- [x] **Cache miss** : `Err(Validation)` — pas de fetch paresseux dans cette RFC.
- [x] **Service vs trait** : on appelle `RemoteIdentityService` directement (pas un trait) car c'est le seul backend prévu et le service est déjà gated `experimental-fediverse`.

## Tracking issue

À ouvrir au moment de la PR.

---

## Score d'évaluation

| Critère | Note (1-5) | Commentaire |
|---|---|---|
| Généralité | 4 | Pattern réutilisable pour toute résolution lazy à venir. |
| Stabilité API | 5 | Additif pur. |
| Performance | 4 | Une lookup DB par requête entrante ; cache RFC 0003 limite. |
| Sécurité | 5 | Ferme le trou « verifier accepte tout silencieusement ». |
| Complexité d'implémentation | 4 | ~80 lignes (constructeur + branch + 5 tests). |
| Maintenance | 3 | Une dépendance directe `RemoteIdentityService`. |
| Documentation | 2 | Rustdoc EN, doctest. |
| **Total** | **27/35** | ≥ 25 → Accepted |