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.
- 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.
- 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 :
-
DNS-TXT
root=(l'ancre décisive). L'entreprise étend son enregistrement durable_agreely.<domaine>avec un engagementroot=é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.
-
CID
did.jsonsur 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 viaregisterCompanyDid. 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 :
- Signature d'époque : recomposer le descripteur, vérifier la signature Ed25519
par rapport au
#kms-1du did.json. - 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, etsbas pour ES256. - 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 bonregistry/chainId/did? - Liaison de la racine : le
#root-1égale-t-il leroot=lu en direct dans la zone DNS de l'entreprise, et le did.json hache-t-il vers le CID sur la chaîne ? - 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/rotateCompanyDidet le CID did.json sur la chaîne. - Documents DID : les méthodes
#root-1(capabilityDelegation) et le blocdelegationdans le did.json de l'entreprise. - Frontières honnêtes : pourquoi ceci est de la reddition de comptes, non de la non-répudiation.