@dotrino/identity 0.63.0 → 0.65.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/vault/acta.js +24 -1
- package/vault/capabilities.js +19 -0
- package/vault/remote.js +4 -2
package/package.json
CHANGED
package/vault/acta.js
CHANGED
|
@@ -551,6 +551,29 @@ export function effectiveCaps (acta, pub, extraRenounces = []) {
|
|
|
551
551
|
* ¿Puede este miembro leer el cajón de secretos `ns`? Solo si su CN es exactamente ése.
|
|
552
552
|
* Es la frontera que pediste: la llave del proxy no ve nada más que lo del proxy.
|
|
553
553
|
*/
|
|
554
|
+
/**
|
|
555
|
+
* ¿PUEDE ESTE MIEMBRO FIRMAR POR LA IDENTIDAD, Y QUÉ?
|
|
556
|
+
*
|
|
557
|
+
* Manda el acta, no el certificado (dueño, 2026-08-31). Y el matiz que hace falta decir en
|
|
558
|
+
* voz alta: `sign` **no** es «firma lo que quiera». Va enmascarado, igual que `secrets` lo
|
|
559
|
+
* va por el `cn` — *«puede firmar por identidad pero solo lo que permitan los permisos»*.
|
|
560
|
+
*
|
|
561
|
+
* Por qué existe con nombre propio en vez de un `memberCan(acta, pub, 'sign')` suelto: la
|
|
562
|
+
* regla se comprueba en DOS bóvedas (el daemon y la de navegador) y se comprobaba mal en
|
|
563
|
+
* las dos —miraban el papel—. Con un solo sitio, cuando el enmascarado crezca, crece una
|
|
564
|
+
* vez y las dos lo heredan.
|
|
565
|
+
*
|
|
566
|
+
* `ns` es opcional y es para lo que va atado a un cajón: un servicio (miembro con `cn`)
|
|
567
|
+
* solo firma dentro del suyo. Sin `ns`, se pregunta por la capacidad a secas.
|
|
568
|
+
*/
|
|
569
|
+
export function memberCanSign (acta, pub, ns = null) {
|
|
570
|
+
if (!memberCan(acta, pub, 'sign')) return false
|
|
571
|
+
if (ns == null) return true
|
|
572
|
+
const m = (acta?.members || []).find((x) => x.pub === pub)
|
|
573
|
+
// Un aparato tuyo (sin `cn`) firma como tú. Uno de servicio, solo lo de su cajón.
|
|
574
|
+
return !m?.cn || m.cn === ns
|
|
575
|
+
}
|
|
576
|
+
|
|
554
577
|
export function memberCanReadSecrets (acta, pub, ns) {
|
|
555
578
|
const m = (acta?.members || []).find((x) => x.pub === pub)
|
|
556
579
|
if (!m || !m.cn) return false
|
|
@@ -735,5 +758,5 @@ export default {
|
|
|
735
758
|
sealActa, verifyActa, applyChanges, makeRenounce, verifyRenounce,
|
|
736
759
|
makeContinuity, verifyContinuity,
|
|
737
760
|
cardBody, makeProfileCard, verifyProfileCard, canAdoptCard, sealersOf, canSeal,
|
|
738
|
-
effectiveCaps, memberCan, memberCanReadSecrets, memberScopes, isService, capScope, isValidCn, canAdopt
|
|
761
|
+
effectiveCaps, memberCan, memberCanSign, memberCanReadSecrets, memberScopes, isService, capScope, isValidCn, canAdopt
|
|
739
762
|
}
|
package/vault/capabilities.js
CHANGED
|
@@ -196,6 +196,25 @@ export async function signWithDevice ({ privateJwk, privateKey, publickey, data
|
|
|
196
196
|
* 5) `nonce` no revocado (`revoked`: fn(nonce)→bool, Set o mapa).
|
|
197
197
|
* @returns {{ok:boolean, reason?:string, iss?, sub?, scope?, iat?, exp?, nonce?}}
|
|
198
198
|
*/
|
|
199
|
+
/**
|
|
200
|
+
* MARGEN DE RELOJ ENTRE DOS APARATOS. Sin esto, emparejar es una lotería.
|
|
201
|
+
*
|
|
202
|
+
* La bóveda sella el certificado con SU reloj (`iat`) y el aparato lo valida con el suyo.
|
|
203
|
+
* Con tolerancia cero, un aparato que vaya un pelo por detrás rechaza un certificado
|
|
204
|
+
* perfectamente bueno con `not-yet-valid` — y el usuario no ve un problema de hora, ve un
|
|
205
|
+
* emparejamiento que no funciona. Pasó de verdad (2026-08-31): un teléfono **850 ms** por
|
|
206
|
+
* detrás, medido, no podía enrolarse en ninguna bóveda.
|
|
207
|
+
*
|
|
208
|
+
* Dos minutos es generoso para un reloj a la deriva y no significa nada frente a un
|
|
209
|
+
* certificado que vale 30 días: aceptarlo dos minutos antes de tiempo no le abre la puerta
|
|
210
|
+
* a nadie, porque lo que autoriza es la FIRMA, no la hora.
|
|
211
|
+
*
|
|
212
|
+
* Es para comparar aparatos DISTINTOS. Donde el mismo operador controla los dos relojes
|
|
213
|
+
* —un servicio validando en la máquina que emite— el estricto sigue siendo lo correcto, y
|
|
214
|
+
* por eso el default de `verifyDelegation` no cambia.
|
|
215
|
+
*/
|
|
216
|
+
export const PEER_SKEW_MS = 120_000
|
|
217
|
+
|
|
199
218
|
export async function verifyDelegation ({ cert, expectedScope, expectedSub, now = Date.now(), skewMs = 0, revoked } = {}) {
|
|
200
219
|
if (!cert || typeof cert !== 'object') return { ok: false, reason: 'no-cert' }
|
|
201
220
|
const { v, iss, sub, scope, iat, exp, nonce, sig } = cert
|
package/vault/remote.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* No reimplementa cripto: usa `@dotrino/identity/capabilities`. Transporte:
|
|
14
14
|
* `@dotrino/proxy-client` (importado perezosamente; solo se carga al emparejar).
|
|
15
15
|
*/
|
|
16
|
-
import { makeDeviceKey, signWithDevice, verifyDelegation, verifyDeviceSig, makePairingCode, commitCode, pubkeyId } from './capabilities.js'
|
|
16
|
+
import { makeDeviceKey, signWithDevice, verifyDelegation, verifyDeviceSig, makePairingCode, commitCode, pubkeyId, PEER_SKEW_MS } from './capabilities.js'
|
|
17
17
|
|
|
18
18
|
const MSG = {
|
|
19
19
|
HELLO: 'vault.hello',
|
|
@@ -208,7 +208,9 @@ export async function enrollDevice ({ qr, device, onChallenge, label = '', conti
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
// Validación estricta antes de guardar (cierra inyección de cert / sustitución de maestra).
|
|
211
|
-
|
|
211
|
+
// `PEER_SKEW_MS`: el cert lo acaba de sellar la bóveda con SU reloj y lo valida ESTE
|
|
212
|
+
// aparato con el suyo. Sin margen, ir 850 ms por detrás bastaba para no poder enrolarse.
|
|
213
|
+
const v = await verifyDelegation({ cert: res.cert, expectedSub: dev.publickey, skewMs: PEER_SKEW_MS })
|
|
212
214
|
if (!v.ok) throw new Error('invalid cert: ' + v.reason)
|
|
213
215
|
if (res.cert.iss !== qr.iss) throw new Error('cert signed by a master key different from the one you saw')
|
|
214
216
|
if (res.cert.sub !== dev.publickey) throw new Error('cert issued for a different device')
|