@dotrino/identity 0.113.0 → 0.114.1
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/README.md +5 -0
- package/package.json +1 -1
- package/vault/acta.js +5 -2
- package/vault/assertion.js +6 -3
- package/vault/core.js +6 -2
package/README.md
CHANGED
|
@@ -179,6 +179,11 @@ const v = await verifySignedFor({ data: pin, signature, publickey, chain, audien
|
|
|
179
179
|
`aud` es obligatorio igual que en la prueba; `exp` es opcional, porque la caducidad de lo
|
|
180
180
|
publicado la lleva el servicio (el TTL del pin) y no el cuerpo.
|
|
181
181
|
|
|
182
|
+
Si quien firma es un **servicio** de la cuenta (un miembro con `cn`), pásale `ns` con su
|
|
183
|
+
cajón (0.114.0+): un servicio solo firma dentro del suyo, y sin `ns` no pasa nunca. Así el
|
|
184
|
+
directorio de nombres acepta lo que firma su aparato `names` sin aceptar lo de cualquier
|
|
185
|
+
otro servicio de la misma cuenta.
|
|
186
|
+
|
|
182
187
|
### Permiso por origen (0.86.0+)
|
|
183
188
|
|
|
184
189
|
Una prueba dice **quién eres** sin preguntar nada: quien llega al iframe ya pasó el filtro
|
package/package.json
CHANGED
package/vault/acta.js
CHANGED
|
@@ -622,7 +622,7 @@ export async function sealActa ({ acta, privateKey, privateJwk }) {
|
|
|
622
622
|
* @param {{ data?: any, signature?: string, publickey?: string, chain?: any[], expectedProfileId?: string|null }} [args]
|
|
623
623
|
*/
|
|
624
624
|
export async function verifySignedBy (args = {}) {
|
|
625
|
-
const { data, signature, publickey, chain, expectedProfileId = null } = args
|
|
625
|
+
const { data, signature, publickey, chain, expectedProfileId = null, ns = null } = args
|
|
626
626
|
if (!data || typeof signature !== 'string' || typeof publickey !== 'string') {
|
|
627
627
|
return { ok: false, reason: 'shape' }
|
|
628
628
|
}
|
|
@@ -631,7 +631,10 @@ export async function verifySignedBy (args = {}) {
|
|
|
631
631
|
const cabeza = chain[chain.length - 1]
|
|
632
632
|
// Quien firmó tiene que ser miembro CON `sign` en el acta vigente de esa cadena. Si le
|
|
633
633
|
// quitaste el permiso, sus firmas dejan de contar en cuanto se ve un acta más nueva.
|
|
634
|
-
|
|
634
|
+
// Un SERVICIO (miembro con `cn`) solo firma dentro de su cajón: quien verifica dice cuál
|
|
635
|
+
// con `ns` (el directorio de nombres acepta lo firmado por su aparato `names`, no por
|
|
636
|
+
// cualquier servicio de la cuenta). Sin `ns`, un servicio nunca pasa, como siempre.
|
|
637
|
+
if (!memberCanSign(cabeza, publickey, ns)) return { ok: false, reason: 'firmante-no-autorizado' }
|
|
635
638
|
if (!(await verifyDeviceSig({ publickey, data, signature }))) return { ok: false, reason: 'firma-invalida' }
|
|
636
639
|
return { ok: true, profileId: c.profileId, seq: c.seq, signer: publickey }
|
|
637
640
|
}
|
package/vault/assertion.js
CHANGED
|
@@ -191,10 +191,13 @@ export async function verifyAssertion (assertion, opts = {}) {
|
|
|
191
191
|
* quien la lleva es el servicio; si el cuerpo trae `exp`, se respeta.
|
|
192
192
|
*/
|
|
193
193
|
/**
|
|
194
|
-
*
|
|
194
|
+
* `ns`: el cajón del servicio que firma (su `cn` en el acta). Solo hace falta cuando lo
|
|
195
|
+
* firmado viene de un SERVICIO de la cuenta y no de un aparato de la persona.
|
|
196
|
+
*
|
|
197
|
+
* @param {{ data?: any, signature?: string, publickey?: string, chain?: any[], audience?: string, expectedProfileId?: string|null, ns?: string|null, now?: number, maxSkewMs?: number }} [args]
|
|
195
198
|
*/
|
|
196
199
|
export async function verifySignedFor (args = {}) {
|
|
197
|
-
const { data, signature, publickey, chain, audience, expectedProfileId = null, now = Date.now(), maxSkewMs = ASSERTION_MAX_SKEW_MS } = args
|
|
200
|
+
const { data, signature, publickey, chain, audience, expectedProfileId = null, ns = null, now = Date.now(), maxSkewMs = ASSERTION_MAX_SKEW_MS } = args
|
|
198
201
|
if (typeof audience !== 'string' || !audience.trim()) return { ok: false, reason: 'no-audience' }
|
|
199
202
|
if (!data || typeof data !== 'object') return { ok: false, reason: 'shape' }
|
|
200
203
|
if (typeof data.aud !== 'string' || !data.aud) return { ok: false, reason: 'sin-destinatario' }
|
|
@@ -207,7 +210,7 @@ export async function verifySignedFor (args = {}) {
|
|
|
207
210
|
if (!Number.isFinite(data.iat)) return { ok: false, reason: 'shape' }
|
|
208
211
|
if (data.iat > now + maxSkewMs) return { ok: false, reason: 'del-futuro' }
|
|
209
212
|
}
|
|
210
|
-
const v = await verifySignedBy({ data, signature, publickey, chain, expectedProfileId })
|
|
213
|
+
const v = await verifySignedBy({ data, signature, publickey, chain, expectedProfileId, ns })
|
|
211
214
|
if (!v.ok) return { ok: false, reason: 'firma:' + v.reason }
|
|
212
215
|
return { ok: true, profileId: v.profileId, signer: v.signer, seq: v.seq, aud: data.aud }
|
|
213
216
|
}
|
package/vault/core.js
CHANGED
|
@@ -64,8 +64,12 @@ const PILAR_VAULT = '@dotrino/vault'
|
|
|
64
64
|
|
|
65
65
|
export function canonicalStringify (v) {
|
|
66
66
|
if (v === null || typeof v !== 'object') return JSON.stringify(v)
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
// Un array con `undefined` va como `null`, igual que en JSON.stringify.
|
|
68
|
+
if (Array.isArray(v)) return '[' + v.map((x) => x === undefined ? 'null' : canonicalStringify(x)).join(',') + ']'
|
|
69
|
+
// Una clave con `undefined` NO existe, igual que en JSON.stringify: antes salía como
|
|
70
|
+
// `"k":undefined` (ni siquiera es JSON) y lo firmado en memoria no cuadraba con lo mismo
|
|
71
|
+
// leído del disco o de la red (el directorio de nombres, 2026-10-15).
|
|
72
|
+
const ks = Object.keys(v).filter((k) => v[k] !== undefined).sort()
|
|
69
73
|
return '{' + ks.map(k => JSON.stringify(k) + ':' + canonicalStringify(v[k])).join(',') + '}'
|
|
70
74
|
}
|
|
71
75
|
|