@dotrino/vault 0.78.0 → 0.79.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/vault",
3
- "version": "0.78.0",
3
+ "version": "0.79.0",
4
4
  "description": "Usa ESTE dispositivo (navegador) como bóveda/CA del ecosistema Dotrino: atiende enrolamientos por el proxy y firma certificados de delegación a tus máquinas. Incluye el cliente de SERVICIO (Node): un proyecto se enrola una vez y jala sus credenciales del vault en vez del .env (`import '@dotrino/vault/config'`).",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/protocol.js CHANGED
@@ -65,7 +65,7 @@ export const MSG = Object.freeze({
65
65
  // empieza con el heap limpio.
66
66
  // Va FIRMADO por la maestra y el agente lo verifica contra su `iss` pineada: un
67
67
  // aviso de reinicio sin autenticar ES un ataque de denegación.
68
- SECRETS_CHANGED: 'vault.secrets.changed', // vault → servicio: { body:{op,ns,ts}, signature }
68
+ SECRETS_CHANGED: 'vault.secrets.changed', // vault → servicio: { body:{op,ns,ts}, seal, acta }
69
69
  // REPARTIR LA LLAVE DE UN CAJÓN A UN MIEMBRO NUEVO. Lo hace el SERVICIO y no la
70
70
  // bóveda, porque la bóveda no puede abrir la llave sin la frase y el servicio ya la
71
71
  // tiene: re-envolverla no le añade ningún poder. Ver `docs/secretos-sellados.md` §8.11.
@@ -74,7 +74,7 @@ export const MSG = Object.freeze({
74
74
  // el ACTA que viene dentro (también firmada) y saca de ahí la llave pública del
75
75
  // destinatario. Así, ni siquiera una bóveda comprometida puede hacerle envolver la
76
76
  // llave para alguien que la maestra no haya metido en su propio cajón.
77
- REWRAP: 'vault.rewrap', // vault → servicio: { body:{op,owner,gen,wrap,target,acta,ts}, signature }
77
+ REWRAP: 'vault.rewrap', // vault → servicio: { body:{op,owner,gen,wrap,target,acta,ts}, seal }
78
78
  REWRAP_OK: 'vault.rewrap.ok', // servicio → vault: { data:{op,owner,gen,target,wrap,ts}, signature, cert }
79
79
  // --- CONSOLA REMOTA (docs/consola-remota.md) — requiere cert `vault:admin` ---
80
80
  // Un solo mensaje con `data.op`: pending · pair · approve · reject · revoke · audit.
@@ -85,7 +85,7 @@ export const MSG = Object.freeze({
85
85
  // Aviso a TODOS los miembros de que el perfil cambió (alguien entró o salió). Es la
86
86
  // contrapartida de administrar a distancia: sin esto, un enrolamiento remoto sería
87
87
  // invisible para el resto de tus dispositivos.
88
- ADMIN_EVENT: 'vault.admin.event', // vault → todos: { body:{ev,deviceId,by,ts}, signature }
88
+ ADMIN_EVENT: 'vault.admin.event', // vault → todos: { body:{ev,deviceId,by,ts}, acta } (pedidos: + seal)
89
89
  // RÉPLICAS: la principal empuja lo que hay que servir (el acta y los sobres, que van
90
90
  // firmados de antes y no se pueden falsificar) y la réplica acusa hasta qué `seq` tiene.
91
91
  REPLICA_PUSH: 'vault.replica.push', // master → réplica: { body:{seq,acta,secrets,ts}, signature }
package/src/service.js CHANGED
@@ -1137,8 +1137,9 @@ export async function watchSecretsChanges ({
1137
1137
  * NO SE FÍA DE LO QUE LE MANDAN, y esto es lo que hace que sea seguro incluso si la
1138
1138
  * bóveda estuviera comprometida:
1139
1139
  *
1140
- * · la petición va firmada por la MAESTRA;
1141
- * · el acta viaja dentro y se comprueba aparte (también la firma la maestra);
1140
+ * · la petición la firma la llave de SELLADO que nombra el acta (`verifyResponder`), no la
1141
+ * maestra: esto tiene que funcionar con la bóveda cerrada;
1142
+ * · el acta viaja dentro y la selló la maestra que este agente conoce;
1142
1143
  * · **la llave pública del destinatario se saca del ACTA, nunca del mensaje** — si
1143
1144
  * se cogiera del mensaje, quien lo mandara podría hacer que este agente envolviera
1144
1145
  * la llave para una pública suya;
@@ -1150,9 +1151,9 @@ export async function watchSecretsChanges ({
1150
1151
  if (!body || body.op !== 'rewrap') return
1151
1152
  const mineOwners = [`ns:${ns}`, `dev:${saved.device.publickey}`]
1152
1153
  if (!mineOwners.includes(body.owner)) return log('[vault] rewrap for a drawer that is not mine: ignored')
1153
- if (!(await verifyDeviceSig({ publickey: master, data: body, signature: payload.signature }))) {
1154
- return log('[vault] rewrap request BADLY SIGNED: ignored')
1155
- }
1154
+ try {
1155
+ await verifyResponder({ body, seal: payload.seal, acta: body.acta, masterPubkey: master, knownSeq: readServiceIdentity(dir)?.actaSeq ?? null })
1156
+ } catch (e) { return log(`[vault] rewrap request BADLY SIGNED: ignored (${e.message})`) }
1156
1157
  const acta = body.acta
1157
1158
  if (!acta || acta.sealedBy !== master || !(await verifyActa({ acta })).ok) {
1158
1159
  return log('[vault] rewrap request without a valid record: ignored')
@@ -1192,11 +1193,15 @@ export async function watchSecretsChanges ({
1192
1193
  // que un aviso falso con fecha lejana no pueda dejar fuera a los de verdad.
1193
1194
  if (inFlight.has(body.ts)) return
1194
1195
  inFlight.add(body.ts)
1195
- let valid = false
1196
+ // FIRMADO COMO TODO LO QUE SIRVE LA BÓVEDA: la llave de sellado que nombra el acta, con
1197
+ // el acta al lado. Lo firmaba la maestra, y la bóveda vive CERRADA: el aviso reventaba
1198
+ // con «vault locked» antes de salir y el servicio no se enteraba de ningún cambio
1199
+ // (la maestra solo firma el acta y reenvuelve sobres, CLAUDE.md).
1200
+ let bad = null
1196
1201
  try {
1197
- valid = await verifyDeviceSig({ publickey: master, data: body, signature: payload.signature })
1198
- } finally { inFlight.delete(body.ts) }
1199
- if (!valid) return log('[vault] change notice BADLY SIGNED: ignored (not from your vault)')
1202
+ await verifyResponder({ body, seal: payload.seal, acta: payload.acta, masterPubkey: master, knownSeq: readServiceIdentity(dir)?.actaSeq ?? null })
1203
+ } catch (e) { bad = e } finally { inFlight.delete(body.ts) }
1204
+ if (bad) return log(`[vault] change notice BADLY SIGNED: ignored (${bad.message})`)
1200
1205
  if (body.ts <= lastTs) return
1201
1206
  lastTs = body.ts
1202
1207