FR EN

Signature d'époque et délégation par clé d'accès

Deux couches, toutes deux additives et neutres, renforcent la reddition de comptes des racines d'époque sans jamais conditionner la vérification.

  1. Signature d'époque : chaque racine d'époque (journal d'accès et consentement par témoin) est signée, au moment du scellement, avec la clé DID Ed25519 opérationnelle enregistrée de l'entreprise.
  2. Délégation par clé d'accès racine : une fois, à la mise en place de l'espace de travail, l'entreprise enregistre une clé d'accès (WebAuthn) comme racine de son DID, puis signe une délégation autorisant cette clé de signature opérationnelle. L'entreprise signe rarement (mise en place, rotation), jamais à chaque époque.

Ce que ces couches sont, et ce qu'elles ne sont pas

La clé de signature opérationnelle est hébergée par Agreely : elle est générée côté serveur et son secret est scellé sous la clé de chiffrement du locataire, de sorte qu'Agreely peut la déballer et signer pour le compte de l'entreprise. Ces couches documentent que la racine porte l'attestation de l'entreprise et que l'entreprise a autorisé la clé de signature avec sa propre clé d'accès, engagée dans son DNS. Ce n'est pas de la non-répudiation, ce n'est pas « vous seul », et ce n'est pas à l'épreuve de toute falsification. La formule honnête est : « signé avec la clé DID enregistrée de votre entreprise » et « vous autorisez la clé de signature avec votre propre clé d'accès, engagée dans votre DNS, vérifiable et révocable ». C'est un renforcement de la reddition de comptes au sens de la Loi 25 (art. 3.1 et 3.2), non une garantie de conformité.

1. La signature d'époque

Une époque est une tranche périodique du journal d'accès (par entreprise) ou du journal de consentement par témoin (par site web), résumée par une racine de Merkle. La racine est ancrée sur la chaîne via AuditRootAnchored, de sorte qu'un auditeur peut confirmer que l'époque existait et n'a pas été réécrite après coup.

Au moment du scellement, la racine est aussi signée avec la clé DID opérationnelle enregistrée de l'entreprise (#kms-1, la même clé Ed25519 que companySig). Cela découple « pouvoir ancrer une racine sur la chaîne » (le relayeur d'Agreely) de « pouvoir produire l'attestation de l'entreprise sur cette racine » (la clé DID enregistrée de l'entreprise), un renforcement réel de défense en profondeur.

Le descripteur signé

La signature est prise sur un descripteur sans nombre (chaque valeur numérique voyage sous forme de chaîne) afin qu'il se canonicalise (JCS, RFC 8785) de manière reproductible. Un tiers reconstruit exactement les mêmes octets :

{
  "type": "agreely.auditEpoch.v1",
  "did": "did:web:agreely.ca:c:acme",
  "root": "0x<racine de Merkle de l'époque, 32 octets en hex>",
  "seqFrom": "1024",
  "seqTo": "2048",
  "chainId": "84532",
  "registry": "0x<adresse minuscule du contrat AgreelyRegistry>",
  "keyId": "0x<sha256 de la clé publique brute, le keyId sur la chaîne>",
  "didKeyId": "kms-1"
}
Champ Signification
type agreely.auditEpoch.v1 (journal d'accès) ou agreely.cookieEpoch.v1 (consentement par témoin).
did Le DID d'entreprise enregistré qui atteste (did:web:agreely.ca:c:<slug>).
root La racine de Merkle de l'époque, 32 octets, pliée en hex 0x minuscule.
seqFrom / seqTo La plage inclusive de séquences que l'époque couvre. Rejoue l'appartenance sans l'identifiant auto-incrémenté de la base.
chainId La chaîne d'ancrage (84532 = Base Sepolia).
registry L'adresse du contrat AgreelyRegistry (minuscule, hex 0x). Épingle la signature au bon contrat, car chainId seul ne lie pas le contrat (nous redéployons; deux registres peuvent partager un chainId).
keyId Le keyId sur la chaîne de la clé de signature = 0x + sha256(clé publique brute), le même bytes32 que registerCompanyDid publie. Il décrit quelle clé a signé et permet de raccorder la signature à la délégation.
didKeyId Le libellé local de la clé de signature (kms-1, ...), afin que la ligne locale et la clé sur la chaîne se résolvent toutes deux.

La signature elle-même est une signature Ed25519 détachée de 64 octets sur les octets JCS-canoniques de ce descripteur. Un vérificateur résout la clé publique depuis le #kms-1 du did.json de l'entreprise et contrôle la signature, sans faire confiance à Agreely.

Neutralité : une époque non signée reste pleinement valide

La signature d'époque est additive. Une époque sans signature (une entreprise sans clé provisionnable, ou une époque scellée avant cette couche) se vérifie exactement comme avant : sa propre intégrité (le rejeu de la chaîne de hachage plus l'ancrage sur la chaîne) est jugée à part et n'est pas touchée. Une signature absente ou invalide dégrade vers « non attesté par l'entreprise », jamais vers un échec d'intégrité.

2. La délégation par clé d'accès racine

La clé de signature opérationnelle est hébergée par Agreely. Pour qu'une entreprise puisse autoriser cette clé avec du matériel qu'elle contrôle elle-même, elle enregistre une clé d'accès racine et signe une délégation.

La racine : une clé d'accès WebAuthn

À la mise en place de l'espace de travail (dans la phase des fondations, après la vérification du domaine), l'entreprise enregistre une clé d'accès WebAuthn dédiée dont le userHandle est le DID de l'entreprise. La clé publique COSE brute est le matériel racine faisant autorité; elle est publique. Le rpId est figé sur l'hôte Agreely. Seuls ES256 (-7, P-256) et EdDSA (-8, Ed25519) sont acceptés.

Cette racine n'est jamais réutilisée pour l'authentification ou la connexion : elle sert uniquement à la délégation de capacité (capabilityDelegation). Un vérificateur rejette une assertion de la clé racine présentée pour tout autre cérémonial.

La délégation : une autorisation signée

L'entreprise signe ensuite, avec sa clé d'accès racine, une délégation : une autorisation sans nombre indiquant qu'une clé de signature opérationnelle précise peut sceller des époques en son nom, dans une fenêtre bornée. Le défi WebAuthn est le keccak256 de la délégation canonicalisée, de sorte que le sens de la signature vit entièrement dans le corps signé :

challenge = keccak256( JCS(corps de la délégation) )
{
  "type": "agreely.keyDelegation.v1",
  "companyDid": "did:web:agreely.ca:c:acme",
  "delegatee": { "keyId": "0x<sha256 de la clé opérationnelle brute>" },
  "rootKeyId": "0x<keccak de la clé COSE racine : quelle racine a signé>",
  "registry": "0x<adresse minuscule du contrat>",
  "chainId": "84532",
  "rpId": "agreely.ca",
  "nonce": "0x<32 octets aléatoires, usage unique>",
  "validFrom": "2026-07-20T15:00:00Z",
  "validUntil": "2027-07-20T15:00:00Z"
}

Une seule délégation couvre le journal d'accès et le consentement par témoin pour tous les sites de l'entreprise. L'entreprise signe rarement (mise en place, rotation), jamais à chaque époque; Agreely opère la clé opérationnelle déléguée au jour le jour. La fenêtre [validFrom, validUntil] est bornée (12 mois par défaut).

La racine est liée à l'entreprise par DEUX ancres

Une délégation qu'Agreely servirait dans un did.json qu'Agreely épingle ne prouverait rien à elle seule (risque de substitution de racine). La racine est donc liée à l'entreprise par deux ancres indépendantes :

  1. DNS-TXT root= (l'ancre décisive). L'entreprise étend son enregistrement durable _agreely.<domaine> avec un engagement root= égal à l'empreinte keccak de la clé COSE racine :

    Hôte :   _agreely.<domaine-d-entreprise>
    Valeur : agreely-did=did:web:agreely.ca:c:<slug>; key=<empreinte sha256 de la clé opérationnelle>; root=<0x + keccak de la clé COSE racine>
    

    La racine est aussi forte que le contrôle de l'entreprise sur sa zone DNS, la source de vérité déclarée du système. Sans cet engagement, la racine routerait uniquement par un did.json qu'un relayeur d'Agreely écrit.

  2. CID did.json sur la chaîne. Le did.json de l'entreprise (qui publie la racine sous #root-1) est épinglé sur IPFS et son CID est enregistré sur la chaîne via registerCompanyDid. Un vérificateur confirme que le did.json qu'il lit hache bien vers le CID sur la chaîne.

La racine n'est liée de façon vérifiable que lorsque les deux ancres la confirment : le #root-1 doit égaler le root= du DNS, et le did.json doit être celui que le CID sur la chaîne engage.

Révocable

L'autorisation est révocable. La révocation est réalisée comme une rotation de la clé opérationnelle sur la chaîne (rotateCompanyDid) : la clé révoquée sort de la fenêtre de clés sur la chaîne, de sorte que les époques qu'elle signe après le bloc de rotation ne se vérifient plus comme autorisées.

Caveat honnête sur la révocation

C'est le relayeur d'Agreely qui soumet la transaction de rotation. La révocation n'est donc pas garantie visible indépendamment du relayeur; ce qui est vrai, c'est que l'absence de la rotation attendue est détectable par l'entreprise, qui contrôle sa propre zone DNS et peut lire la chaîne directement.

Récupération, et la limite honnête de la garde

L'entreprise peut enregistrer plusieurs clés d'accès racines et une clé de récupération Ed25519 optionnelle (autorisée uniquement à faire tourner la racine, jamais à signer un consentement). L'ajout d'une deuxième racine ou d'une clé de récupération doit être signé par une racine active existante (une assertion par clé d'accès), jamais simplement authentifié par session. Une récupération par domaine doit engager la nouvelle empreinte racine dans le root= du DNS.

Agreely ne peut pas restaurer une racine perdue

Comme la racine est du matériel que l'entreprise contrôle, Agreely ne peut sincèrement pas la restaurer si elle est perdue et qu'aucune racine de secours ni clé de récupération n'existe. Enregistrez une deuxième racine ou une clé de récupération à la mise en place. Notez aussi le caveat de niveau ~AAL2 pour une clé d'accès synchronisée (une clé synchronisée par le fournisseur de plateforme n'est pas isolée au niveau matériel comme l'est une clé liée à un appareil).

3. Le modèle de confiance et la vérification hors ligne

La chaîne complète qu'un vérificateur contrôle, du bas vers le haut :

signature d'époque (clé opérationnelle #kms-1)
  -> la délégation autorise ce keyId opérationnel (signée par clé d'accès, dans la
     fenêtre, non révoquée)
  -> la délégation est signée par la clé d'accès RACINE de l'entreprise (assertion
     WebAuthn, #root-1)
  -> la racine est liée à l'entreprise par le `root=` du DNS ET le CID did.json sur
     la chaîne
  -> liaison de domaine (DNS-TXT `_agreely.<domaine>`)

Un auditeur vérifie sans faire confiance à Agreely

Le vérificateur hors ligne livré (verify-core.js / epoch-chain.js / webauthn.js, et le vérificateur hors ligne du SDK) rejoue toute la chaîne à partir d'un VerificationBundle téléchargé, en lisant Base et le DNS de l'entreprise directement :

  1. Signature d'époque : recomposer le descripteur, vérifier la signature Ed25519 par rapport au #kms-1 du did.json.
  2. Assertion de délégation : re-dériver le défi depuis le corps (keccak(JCS(corps)), jamais un défi fourni), puis vérifier l'assertion WebAuthn par rapport au #root-1. Contrôles durcis : origin = rpId, authData.rpIdHash = sha256(rpId), drapeau UV posé, type = webauthn.get, et s bas pour ES256.
  3. Couverture : la délégation autorise-t-elle ce keyId opérationnel, dans la fenêtre [validFrom, validUntil] résolue par le point (numéro de bloc, index de journal) de l'ancre, et liée au bon registry / chainId / did ?
  4. Liaison de la racine : le #root-1 égale-t-il le root= lu en direct dans la zone DNS de l'entreprise, et le did.json hache-t-il vers le CID sur la chaîne ?
  5. Fenêtre de clé sur la chaîne : la clé opérationnelle est-elle dans sa fenêtre de clés sur la chaîne au moment de l'ancrage (une clé sortie par rotation résout « non autorisée », c'est la révocation) ?

Le verdict « attesté par l'entreprise » (le vert) exige la chaîne entière : la signature d'époque, la signature de délégation, la couverture, la fenêtre effectivement résolue par rapport au temps d'ancrage, la liaison de la racine, et la clé non sortie par rotation.

Neutralité : le non-délégué reste vert

Une époque sans délégation, ou avec une délégation présente mais invalide, dégrade vers « non attesté par l'entreprise » (attested: false), jamais vers un échec d'intégrité. Les époques non signées et antérieures à la délégation restent vérifiées comme avant. La signature et la délégation sont additives : elles ne conditionnent jamais la vérification.

Ensuite

  • Registre sur la chaîne : AuditRootAnchored, registerCompanyDid / rotateCompanyDid et le CID did.json sur la chaîne.
  • Documents DID : les méthodes #root-1 (capabilityDelegation) et le bloc delegation dans le did.json de l'entreprise.
  • Frontières honnêtes : pourquoi ceci est de la reddition de comptes, non de la non-répudiation.