@dotrino/identity 0.73.0 → 0.74.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 +1 -1
- package/src/index.js +1 -1
- package/src/node.js +1 -1
- package/vault/capabilities.js +47 -26
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -693,4 +693,4 @@ export class Identity {
|
|
|
693
693
|
|
|
694
694
|
// Helpers de capacidad SIN clave maestra (lado dispositivo + verificación), reutilizables
|
|
695
695
|
// por apps/bridges sin cargar el iframe del vault.
|
|
696
|
-
export { makeDeviceKey, makeDeviceEncKey, importDeviceEncKey, signWithDevice, verifyDelegation, verifyChain, pubkeyId, deriveSAS, verifyDeviceSig, makePairingCode, commitCode, avatarSvg, avatarDataUri
|
|
696
|
+
export { makeDeviceKey, makeDeviceEncKey, importDeviceEncKey, signWithDevice, verifyDelegation, verifyChain, pubkeyId, deriveSAS, verifyDeviceSig, makePairingCode, commitCode, avatarSvg, avatarDataUri } from '../vault/capabilities.js'
|
package/src/node.js
CHANGED
|
@@ -302,4 +302,4 @@ export default Identity
|
|
|
302
302
|
|
|
303
303
|
// Helpers de capacidad SIN clave maestra (lado dispositivo + verificación), para que
|
|
304
304
|
// un bridge/bot Node pueda crear su clave, firmar acciones y verificar cadenas D←P.
|
|
305
|
-
export { makeDeviceKey, makeDeviceEncKey, importDeviceEncKey, signWithDevice, verifyDelegation, verifyChain, pubkeyId, deriveSAS, verifyDeviceSig, makePairingCode, commitCode, avatarSvg, avatarDataUri
|
|
305
|
+
export { makeDeviceKey, makeDeviceEncKey, importDeviceEncKey, signWithDevice, verifyDelegation, verifyChain, pubkeyId, deriveSAS, verifyDeviceSig, makePairingCode, commitCode, avatarSvg, avatarDataUri } from '../vault/capabilities.js'
|
package/vault/capabilities.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - El vault firma un CERTIFICADO: «la clave D puede `scope` para la identidad P,
|
|
11
11
|
* mientras el ACTA lo diga», con un `nonce` que es el mango de revocación.
|
|
12
12
|
* - El dispositivo firma cada acción con `D` y adjunta el cert. Cualquiera
|
|
13
|
-
* verifica la CADENA `D ← P` + scope +
|
|
13
|
+
* verifica la CADENA `D ← P` + scope + lo que diga el ACTA + revocación, offline.
|
|
14
14
|
*
|
|
15
15
|
* Garantía: robar el dispositivo solo permite lo del `scope` (p.ej. publicar
|
|
16
16
|
* ubicación) mientras el acta lo diga, y se puede revocar. La clave maestra queda intacta.
|
|
@@ -27,10 +27,27 @@ const ECDSA = { name: 'ECDSA', namedCurve: 'P-256' }
|
|
|
27
27
|
const ECDH = { name: 'ECDH', namedCurve: 'P-256' }
|
|
28
28
|
const SIGN = { name: 'ECDSA', hash: { name: 'SHA-256' } }
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
/**
|
|
31
|
+
* HASTA CUÁNDO SE ACEPTA UN PAPEL DEL MODELO VIEJO — **2026-10-01**, y se quita ese día.
|
|
32
|
+
*
|
|
33
|
+
* Los certificados de antes de 0.73 llevaban `exp` y no `seq`. Al pasar la bóveda al modelo
|
|
34
|
+
* del acta, TODOS los aparatos ya enrolados se quedaban fuera de golpe (`shape`) y sin forma
|
|
35
|
+
* de volver: el mostrador de renovación los rechazaba por lo mismo. Diez máquinas —los dos
|
|
36
|
+
* proxios, los nodes de contenido, el bot— habrían necesitado re-enrolarse a mano, con TTY
|
|
37
|
+
* en los dos lados.
|
|
38
|
+
*
|
|
39
|
+
* Así que se aceptan, con dos frenos y ninguna concesión de fondo:
|
|
40
|
+
* · hasta su PROPIO vencimiento, que sigue valiendo (el más lejano es 2026-09-30);
|
|
41
|
+
* · y en ningún caso pasado este tope.
|
|
42
|
+
* Además el mostrador los cruza con el acta igual que a los nuevos, así que un papel viejo
|
|
43
|
+
* no autoriza nada que el acta de hoy no autorice.
|
|
44
|
+
*
|
|
45
|
+
* Se apaga solo por partida doble: ninguna bóveda 0.73+ puede EMITIR uno de estos
|
|
46
|
+
* (`signDelegationWith` solo escribe `seq`), así que cuando el último caduque esta rama es
|
|
47
|
+
* inalcanzable. **Y aun así se borra en la fecha**, porque un repliegue sin fecha no es una
|
|
48
|
+
* migración: es un agujero (`CLAUDE.md`).
|
|
49
|
+
*/
|
|
50
|
+
export const LEGACY_CERTS_UNTIL = Date.parse('2026-10-01T00:00:00Z')
|
|
34
51
|
|
|
35
52
|
const enc = (s) => new TextEncoder().encode(s)
|
|
36
53
|
|
|
@@ -118,6 +135,13 @@ export { avatarSvg, avatarDataUri } from './avatar.js'
|
|
|
118
135
|
* ninguna renovación), y nadie tiene que abrir nada por calendario.
|
|
119
136
|
*/
|
|
120
137
|
export function delegationBody (cert) {
|
|
138
|
+
// LA FORMA DEL CUERPO ES LA DEL PAPEL, no la del código que lo lee. Un papel del modelo
|
|
139
|
+
// viejo se firmó sobre `{ …, exp, nonce }`; recomponerlo con `seq: undefined` da otra
|
|
140
|
+
// string canónica y la firma no cuadra NUNCA — que es como el repliegue de migración no
|
|
141
|
+
// servía de nada hasta que una prueba lo dijo.
|
|
142
|
+
if (typeof cert.seq !== 'number' && typeof cert.exp === 'number') {
|
|
143
|
+
return { v: cert.v, iss: cert.iss, sub: cert.sub, scope: cert.scope, iat: cert.iat, exp: cert.exp, nonce: cert.nonce }
|
|
144
|
+
}
|
|
121
145
|
return { v: cert.v, iss: cert.iss, sub: cert.sub, scope: cert.scope, iat: cert.iat, seq: cert.seq, nonce: cert.nonce }
|
|
122
146
|
}
|
|
123
147
|
|
|
@@ -208,30 +232,15 @@ export async function signWithDevice ({ privateJwk, privateKey, publickey, data
|
|
|
208
232
|
* 5) `nonce` no revocado (`revoked`: fn(nonce)→bool, Set o mapa).
|
|
209
233
|
* @returns {{ok:boolean, reason?:string, iss?, sub?, scope?, iat?, seq?, nonce?}}
|
|
210
234
|
*/
|
|
211
|
-
/**
|
|
212
|
-
* MARGEN DE RELOJ ENTRE DOS APARATOS. Sin esto, emparejar es una lotería.
|
|
213
|
-
*
|
|
214
|
-
* La bóveda sella el certificado con SU reloj (`iat`) y el aparato lo valida con el suyo.
|
|
215
|
-
* Con tolerancia cero, un aparato que vaya un pelo por detrás rechaza un certificado
|
|
216
|
-
* perfectamente bueno con `not-yet-valid` — y el usuario no ve un problema de hora, ve un
|
|
217
|
-
* emparejamiento que no funciona. Pasó de verdad (2026-08-31): un teléfono **850 ms** por
|
|
218
|
-
* detrás, medido, no podía enrolarse en ninguna bóveda.
|
|
219
|
-
*
|
|
220
|
-
* Dos minutos es generoso para un reloj a la deriva y no significa nada frente a un
|
|
221
|
-
* certificado que vale 30 días: aceptarlo dos minutos antes de tiempo no le abre la puerta
|
|
222
|
-
* a nadie, porque lo que autoriza es la FIRMA, no la hora.
|
|
223
|
-
*
|
|
224
|
-
* Es para comparar aparatos DISTINTOS. Donde el mismo operador controla los dos relojes
|
|
225
|
-
* —un servicio validando en la máquina que emite— el estricto sigue siendo lo correcto, y
|
|
226
|
-
* por eso el default de `verifyDelegation` no cambia.
|
|
227
|
-
*/
|
|
228
|
-
export const PEER_SKEW_MS = 120_000
|
|
229
235
|
|
|
230
236
|
export async function verifyDelegation ({ cert, expectedScope, expectedSub, actaSeq = null, sealers = null, revoked } = {}) {
|
|
231
237
|
if (!cert || typeof cert !== 'object') return { ok: false, reason: 'no-cert' }
|
|
232
238
|
const { v, iss, sub, scope, iat, seq, nonce, sig } = cert
|
|
233
239
|
if (v !== 1 || typeof iss !== 'string' || typeof sub !== 'string' || typeof sig !== 'string') return { ok: false, reason: 'shape' }
|
|
234
|
-
if (typeof iat !== 'number' ||
|
|
240
|
+
if (typeof iat !== 'number' || (typeof scope !== 'string' && !Array.isArray(scope))) return { ok: false, reason: 'shape' }
|
|
241
|
+
// ¿ES UN PAPEL DEL MODELO VIEJO? Llevaba `exp` y no `seq` (ver `LEGACY_CERTS_UNTIL`).
|
|
242
|
+
const legado = typeof seq !== 'number'
|
|
243
|
+
if (legado && typeof cert.exp !== 'number') return { ok: false, reason: 'shape' }
|
|
235
244
|
if (!(await rawVerify(iss, enc(canonicalStringify(delegationBody(cert))), sig))) return { ok: false, reason: 'bad-signature' }
|
|
236
245
|
// EL ACTA MANDA, Y NO HAY RELOJ (dueño, 2026-08-31). El papel no vence: dice «una
|
|
237
246
|
// selladora de este perfil, mirando el acta nº `seq`, avaló esta llave». Lo que puede
|
|
@@ -257,7 +266,19 @@ export async function verifyDelegation ({ cert, expectedScope, expectedSub, acta
|
|
|
257
266
|
// estaba apagado: vuelve, su papel ya no vale, y renovarlo exige una selladora ABIERTA.
|
|
258
267
|
// O sea que un cambio de acta te obligaría a abrir la bóveda para que tus aparatos
|
|
259
268
|
// volvieran — justo lo que se acaba de quitar de en medio.
|
|
260
|
-
if (seq > actaSeq) return { ok: false, reason: 'acta-vieja' }
|
|
269
|
+
if (!legado && seq > actaSeq) return { ok: false, reason: 'acta-vieja' }
|
|
270
|
+
// REPLIEGUE DE MIGRACIÓN, declarado y con fecha (ver `LEGACY_CERTS_UNTIL`). Un papel del
|
|
271
|
+
// modelo viejo se sigue aceptando, pero SOLO hasta su propio vencimiento: no se le regala
|
|
272
|
+
// ni un día. No hay nada que decidir sobre el acta que nombra, porque no nombra ninguna.
|
|
273
|
+
//
|
|
274
|
+
// Lo que NO se relaja, y es lo que hace que esto no sea un agujero: quien lo emitió tiene
|
|
275
|
+
// que poder sellar (abajo), y el mostrador cruza igual con el acta (`memberCanScope`). Un
|
|
276
|
+
// papel viejo no autoriza nada que el acta de hoy no autorice; lo único que compra es
|
|
277
|
+
// seguir hablando mientras su máquina se actualiza.
|
|
278
|
+
if (legado) {
|
|
279
|
+
if (Date.now() > cert.exp) return { ok: false, reason: 'expired' }
|
|
280
|
+
if (Date.now() > LEGACY_CERTS_UNTIL) return { ok: false, reason: 'legacy-cert-retirado' }
|
|
281
|
+
}
|
|
261
282
|
// Y QUIEN LO EMITIÓ TIENE QUE PODER SELLAR. No se compara contra «la maestra»: cualquiera
|
|
262
283
|
// que el acta nombre sellador emite papeles válidos — si no, la segunda bóveda podría
|
|
263
284
|
// invalidar todos los certificados al sellar y luego no poder dar los nuevos.
|
|
@@ -269,7 +290,7 @@ export async function verifyDelegation ({ cert, expectedScope, expectedSub, acta
|
|
|
269
290
|
: (revoked instanceof Set ? revoked.has(nonce) : !!revoked[nonce])
|
|
270
291
|
if (isRev) return { ok: false, reason: 'revoked' }
|
|
271
292
|
}
|
|
272
|
-
return { ok: true, iss, sub, scope, iat, seq, nonce }
|
|
293
|
+
return { ok: true, iss, sub, scope, iat, seq: legado ? null : seq, nonce, legacy: legado || undefined }
|
|
273
294
|
}
|
|
274
295
|
|
|
275
296
|
/**
|