@dotrino/identity 0.68.0 → 0.70.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 +2 -0
- package/src/node.js +2 -0
- package/vault/acta.js +30 -1
- package/vault/core.js +46 -5
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -278,6 +278,8 @@ export class Identity {
|
|
|
278
278
|
|
|
279
279
|
/** El acta vigente + si este dispositivo es el master + sus capacidades efectivas. */
|
|
280
280
|
async profileActa () { return this._call('profileActa') }
|
|
281
|
+
/** La cadena de selladores, para mandarla con una firma (ver `vault/acta.js`). */
|
|
282
|
+
async sealerChain () { return this._call('sealerChain') }
|
|
281
283
|
|
|
282
284
|
/** Miembros del perfil, ya con id legible y capacidades efectivas. */
|
|
283
285
|
async profileMembers () { return this._call('profileMembers') }
|
package/src/node.js
CHANGED
|
@@ -169,6 +169,8 @@ export class Identity {
|
|
|
169
169
|
listDelegations () { return this._h('listDelegations') }
|
|
170
170
|
// Acta de perfil (qué llaves son del perfil y qué puede cada una; ver acta-de-perfil.md)
|
|
171
171
|
profileActa () { return this._h('profileActa') }
|
|
172
|
+
/** La cadena de selladores, para mandarla con una firma (ver `vault/acta.js`). */
|
|
173
|
+
sealerChain () { return this._h('sealerChain') }
|
|
172
174
|
profileMembers () { return this._h('profileMembers') }
|
|
173
175
|
/**
|
|
174
176
|
* Quien SELLA SOBRES (la bóveda) registra aquí cómo estrenar su llave de sellado: se
|
package/vault/acta.js
CHANGED
|
@@ -358,6 +358,35 @@ export async function sealActa ({ acta, privateKey, privateJwk }) {
|
|
|
358
358
|
* aceptando a un sellador que ya retiraste. Es el problema de siempre y hace falta un
|
|
359
359
|
* oráculo, no más firmas.
|
|
360
360
|
*/
|
|
361
|
+
/**
|
|
362
|
+
* ¿DE QUIÉN ES ESTO? La pregunta que se hacen geo, reputación y cualquiera que reciba algo
|
|
363
|
+
* firmado de un desconocido. Es lo único que necesitan llamar.
|
|
364
|
+
*
|
|
365
|
+
* Devuelve el `profileId` —la identidad— o por qué no.
|
|
366
|
+
*
|
|
367
|
+
* EL CERTIFICADO NO HACE FALTA AQUÍ, y conviene decirlo porque es lo que uno espera:
|
|
368
|
+
* el acta ya dice quién puede firmar por la identidad, y la cadena prueba el acta. El
|
|
369
|
+
* certificado es del protocolo aparato↔bóveda (autenticar peticiones), no de atribuir
|
|
370
|
+
* contenido. Mezclarlos obligaba a que cada post arrastrara un papel que no aporta nada.
|
|
371
|
+
*
|
|
372
|
+
* Lo que SÍ hay que entender: esto dice «esta identidad firmó esto», no «esto es
|
|
373
|
+
* reciente». Si la cadena que te llega es vieja, sigue verificando — para eso está el
|
|
374
|
+
* registro público, que es otra capa.
|
|
375
|
+
*/
|
|
376
|
+
export async function verifySignedBy ({ data, signature, publickey, chain, expectedProfileId = null } = {}) {
|
|
377
|
+
if (!data || typeof signature !== 'string' || typeof publickey !== 'string') {
|
|
378
|
+
return { ok: false, reason: 'shape' }
|
|
379
|
+
}
|
|
380
|
+
const c = await verifySealerChain(chain, { expectedProfileId })
|
|
381
|
+
if (!c.ok) return { ok: false, reason: 'cadena:' + c.reason }
|
|
382
|
+
const cabeza = chain[chain.length - 1]
|
|
383
|
+
// Quien firmó tiene que ser miembro CON `sign` en el acta vigente de esa cadena. Si le
|
|
384
|
+
// quitaste el permiso, sus firmas dejan de contar en cuanto se ve un acta más nueva.
|
|
385
|
+
if (!memberCanSign(cabeza, publickey)) return { ok: false, reason: 'firmante-no-autorizado' }
|
|
386
|
+
if (!(await verifyDeviceSig({ publickey, data, signature }))) return { ok: false, reason: 'firma-invalida' }
|
|
387
|
+
return { ok: true, profileId: c.profileId, seq: c.seq, signer: publickey }
|
|
388
|
+
}
|
|
389
|
+
|
|
361
390
|
export async function verifySealerChain (chain, { expectedProfileId = null } = {}) {
|
|
362
391
|
if (!Array.isArray(chain) || !chain.length) return { ok: false, reason: 'cadena-vacia' }
|
|
363
392
|
const [raiz] = chain
|
|
@@ -909,7 +938,7 @@ export async function canAdopt ({ candidate, current }) {
|
|
|
909
938
|
}
|
|
910
939
|
|
|
911
940
|
export default {
|
|
912
|
-
ACTA_V, CAPS, CAP_SCOPE, genesisActa, actaBody, actaHash, memberId, checkShape, verifySealerChain,
|
|
941
|
+
ACTA_V, CAPS, CAP_SCOPE, genesisActa, actaBody, actaHash, memberId, checkShape, verifySealerChain, verifySignedBy,
|
|
913
942
|
sealActa, verifyActa, applyChanges, makeRenounce, verifyRenounce,
|
|
914
943
|
makeContinuity, verifyContinuity,
|
|
915
944
|
cardBody, makeProfileCard, verifyProfileCard, canAdoptCard, sealersOf, canSeal,
|
package/vault/core.js
CHANGED
|
@@ -563,8 +563,13 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
563
563
|
const sealerChain = () => {
|
|
564
564
|
const cur = loadActa()
|
|
565
565
|
if (!cur) return []
|
|
566
|
-
|
|
567
|
-
|
|
566
|
+
// La vigente cuenta como parte del material aunque todavía no esté en la historia
|
|
567
|
+
// (ahí entra al dejar de serlo). Sin esto, una cuenta recién creada devolvía [].
|
|
568
|
+
const porSeq = new Map([...loadHistory(), cur].map((a) => [a.seq, a]))
|
|
569
|
+
const eslabones = [...porSeq.values()].filter((a) => a.sealerChanged).sort((a, b) => a.seq - b.seq)
|
|
570
|
+
// Y la actual va al final si ella misma no es un eslabón: el verificador necesita ver
|
|
571
|
+
// la cabeza para saber en qué `seq` está y quién la selló.
|
|
572
|
+
return eslabones.length && eslabones[eslabones.length - 1].seq === cur.seq ? eslabones : [...eslabones, cur]
|
|
568
573
|
}
|
|
569
574
|
|
|
570
575
|
const loadRenounces = () => { try { return JSON.parse(kv.getItem(RENOUNCE_STORAGE) || '[]') || [] } catch (_) { return [] } }
|
|
@@ -627,7 +632,14 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
627
632
|
const { generation } = await Content.makeGeneration({ members: base.members, gen: 1 })
|
|
628
633
|
base.keyring = [generation]
|
|
629
634
|
} catch (e) { console.warn('[identity] could not create the content key:', e.message) }
|
|
630
|
-
|
|
635
|
+
const genesis = await seal(base)
|
|
636
|
+
saveActa(genesis)
|
|
637
|
+
// EL GÉNESIS VA A LA HISTORIA DESDE EL PRIMER DÍA. Es el ancla de la cadena de
|
|
638
|
+
// selladores —el único eslabón autofirmado por la llave que da nombre al perfil— y
|
|
639
|
+
// sin él nadie de fuera puede verificar nada de esta cuenta, nunca. Antes solo se
|
|
640
|
+
// guardaba lo que DEJABA de ser vigente, así que una cuenta que nunca cambió su acta
|
|
641
|
+
// no tenía génesis guardado y su cadena salía vacía.
|
|
642
|
+
pushHistory(genesis)
|
|
631
643
|
return loadActa()
|
|
632
644
|
}
|
|
633
645
|
|
|
@@ -1144,7 +1156,7 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
1144
1156
|
const LOCK_EXEMPT = new Set([
|
|
1145
1157
|
'profileLockStatus', 'unlockProfile', 'listProfiles', 'currentProfile',
|
|
1146
1158
|
'switchProfile', 'createProfile',
|
|
1147
|
-
'profileActa', 'profileMembers', 'myMembership', 'isMaster'
|
|
1159
|
+
'profileActa', 'profileMembers', 'myMembership', 'isMaster', 'sealerChain'
|
|
1148
1160
|
])
|
|
1149
1161
|
|
|
1150
1162
|
const handlers = {
|
|
@@ -1345,7 +1357,22 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
1345
1357
|
if (data == null) throw new Error('data required')
|
|
1346
1358
|
const local = async () => {
|
|
1347
1359
|
const bytes = new TextEncoder().encode(canonicalStringify(data))
|
|
1348
|
-
|
|
1360
|
+
const acta = loadActa()
|
|
1361
|
+
return {
|
|
1362
|
+
signature: await signBytes(keypair.privateKey, bytes),
|
|
1363
|
+
publickey: publickeyJwkStr,
|
|
1364
|
+
// A NOMBRE DE QUIÉN VA. `publickey` es la llave de ESTE aparato, y las apps la
|
|
1365
|
+
// venían guardando como si fuera la identidad: publicar desde el teléfono y
|
|
1366
|
+
// desde el PC quedaba a nombre de dos «personas» distintas, y la reputación se
|
|
1367
|
+
// repartía entre ellas en vez de acumularse.
|
|
1368
|
+
//
|
|
1369
|
+
// La identidad es el `profileId` —la llave del génesis, que no cambia nunca— y
|
|
1370
|
+
// la cadena es lo que prueba que este firmante le pertenece. Van juntos porque
|
|
1371
|
+
// por separado no sirven: el `profileId` solo es una afirmación, y la cadena sin
|
|
1372
|
+
// él no dice a quién atribuir.
|
|
1373
|
+
profileId: acta?.profileId || publickeyJwkStr,
|
|
1374
|
+
chain: sealerChain()
|
|
1375
|
+
}
|
|
1349
1376
|
}
|
|
1350
1377
|
const acta = loadActa()
|
|
1351
1378
|
const puedeFirmar = !acta || Acta.memberCan(acta, publickeyJwkStr, 'sign', loadRenounces())
|
|
@@ -1498,6 +1525,20 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
1498
1525
|
return { acta, isMaster: amMaster(), myCaps: Acta.effectiveCaps(acta, publickeyJwkStr, loadRenounces()) }
|
|
1499
1526
|
},
|
|
1500
1527
|
|
|
1528
|
+
/**
|
|
1529
|
+
* LA CADENA DE SELLADORES, para mandarla junto con una firma.
|
|
1530
|
+
*
|
|
1531
|
+
* Es lo que deja a un EXTRAÑO comprobar que quien firmó habla por esta identidad, sin
|
|
1532
|
+
* preguntarle a nadie: `[génesis, …cambios de sellador…, acta actual]`. No son todas
|
|
1533
|
+
* las actas —eso crecería con cada emparejamiento— sino solo los eslabones donde
|
|
1534
|
+
* cambió quién sella, que casi nunca pasa. Lo normal es longitud 1: el génesis.
|
|
1535
|
+
*
|
|
1536
|
+
* Una cuenta de una sola bóveda devuelve SIEMPRE eso y nada más, y no puede quedarse
|
|
1537
|
+
* obsoleta: su conjunto de selladores no puede cambiar, porque el único no puede
|
|
1538
|
+
* quitarse a sí mismo y no hay otro que se lo quite.
|
|
1539
|
+
*/
|
|
1540
|
+
async sealerChain () { return sealerChain() },
|
|
1541
|
+
|
|
1501
1542
|
/**
|
|
1502
1543
|
* Quien sella sobres de secretos (la bóveda) registra aquí cómo estrenar su LLAVE DE
|
|
1503
1544
|
* SELLADO: una función que crea el par, se guarda la privada y devuelve la pública. Se
|