@dotrino/identity 0.90.0 → 0.91.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.d.ts +5 -0
- package/vault/core.js +17 -15
- package/vault/remote.js +1 -1
- package/vault/vendor/proxy-client/VERSION.txt +1 -1
- package/vault/vendor/proxy-client/client.js +107 -1
- package/vault/vendor/proxy-client/index.js +1 -1
- package/vault/vendor/proxy-client/sealing.js +79 -0
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -165,6 +165,11 @@ export class Identity {
|
|
|
165
165
|
vaultStatus (): Promise<any>
|
|
166
166
|
vaultUnpair (): Promise<any>
|
|
167
167
|
vaultSign (payload: any): Promise<{ signature: string; publickey: string }>
|
|
168
|
+
/**
|
|
169
|
+
* El almacén de hilos EN la bóveda, cifrado de punta a punta con la clave de contenido del
|
|
170
|
+
* perfil. Sin esa clave lanza `code: 'no-content-key'` en vez de mandarlo en claro (≥ 0.91.0);
|
|
171
|
+
* sin emparejar, `not-paired`; si la bóveda no contesta, `vault-no-reply`.
|
|
172
|
+
*/
|
|
168
173
|
vaultStore (method: string, args?: any): Promise<any>
|
|
169
174
|
listVaultDevices (): Promise<{ devices: any[]; revoked: any[] }>
|
|
170
175
|
/**
|
package/vault/core.js
CHANGED
|
@@ -2575,27 +2575,29 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
2575
2575
|
/**
|
|
2576
2576
|
* Store DELEGADO, CIFRADO de punta a punta. Los argumentos y el resultado viajan
|
|
2577
2577
|
* cifrados con la clave de contenido del perfil: el proxy transporta pero no ve nada
|
|
2578
|
-
* de lo que guardas.
|
|
2579
|
-
*
|
|
2578
|
+
* de lo que guardas.
|
|
2579
|
+
*
|
|
2580
|
+
* Sin la clave NO se manda nada. Antes iba en claro «como antes», y eso era dejar que
|
|
2581
|
+
* el proxio leyera el almacén entero justo en el aparato al que todavía no le habían
|
|
2582
|
+
* envuelto la clave. Ahora falla con `no-content-key`, que se arregla entrando al
|
|
2583
|
+
* perfil desde un aparato que ya la tenga — y se ve, en vez de viajar a la vista.
|
|
2584
|
+
* Por lo mismo, una respuesta sin cifrar tampoco se acepta.
|
|
2580
2585
|
*/
|
|
2581
2586
|
async vaultStore ({ method, args }) {
|
|
2582
2587
|
const v = loadVaultCert(); const device = loadVaultDevice()
|
|
2583
|
-
if (!v?.cert || !device) throw new Error('this device is not paired with a vault')
|
|
2588
|
+
if (!v?.cert || !device) throw Object.assign(new Error('this device is not paired with a vault'), { code: 'not-paired' })
|
|
2584
2589
|
maybeRenewVaultCert()
|
|
2585
|
-
const mine = await myCek()
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
payload = { method, enc: await Content.encryptWithCek({ cek: mine.cek, gen: mine.gen, plaintext: JSON.stringify(args ?? {}) }) }
|
|
2589
|
-
}
|
|
2590
|
+
const mine = await myCek()
|
|
2591
|
+
if (!mine) throw Object.assign(new Error('this device does not hold the profile content key yet'), { code: 'no-content-key' })
|
|
2592
|
+
const enc = await Content.encryptWithCek({ cek: mine.cek, gen: mine.gen, plaintext: JSON.stringify(args ?? {}) })
|
|
2590
2593
|
try {
|
|
2591
|
-
const res = await remoteStore({ master: v.master, proxy: v.proxy, device, cert: v.cert, method
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
return JSON.parse(await Content.decryptWithKeyring({
|
|
2595
|
-
envelope: res.__enc, keyring: loadActa()?.keyring, myPub: publickeyJwkStr, myEncPrivateKey: encKeypair.privateKey
|
|
2596
|
-
}))
|
|
2594
|
+
const res = await remoteStore({ master: v.master, proxy: v.proxy, device, cert: v.cert, method, enc, onRevoked: wipeVaultLink })
|
|
2595
|
+
if (!res || typeof res !== 'object' || !res.__enc) {
|
|
2596
|
+
throw Object.assign(new Error('the vault replied to the store without encrypting it'), { code: 'vault-reply-unsealed' })
|
|
2597
2597
|
}
|
|
2598
|
-
return
|
|
2598
|
+
return JSON.parse(await Content.decryptWithKeyring({
|
|
2599
|
+
envelope: res.__enc, keyring: loadActa()?.keyring, myPub: publickeyJwkStr, myEncPrivateKey: encKeypair.privateKey
|
|
2600
|
+
}))
|
|
2599
2601
|
} catch (e) { return handleVaultError(e) }
|
|
2600
2602
|
},
|
|
2601
2603
|
|
package/vault/remote.js
CHANGED
|
@@ -306,7 +306,7 @@ async function vaultRpc ({ master, proxy, device, cert, acta = null, sendType, o
|
|
|
306
306
|
cleanup(); reject(vaultError(p))
|
|
307
307
|
}
|
|
308
308
|
})
|
|
309
|
-
const t = setTimeout(() => { cleanup(); reject(new Error('the vault did not reply (is it running?)')) }, timeoutMs)
|
|
309
|
+
const t = setTimeout(() => { cleanup(); reject(Object.assign(new Error('the vault did not reply (is it running?)'), { code: 'vault-no-reply' })) }, timeoutMs)
|
|
310
310
|
const cleanup = () => { off(); clearTimeout(t); clearTimeout(graceTimer) }
|
|
311
311
|
})
|
|
312
312
|
client.sendByPubkey(master, { type: sendType, data: signed, signature, cert })
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copia vendorizada de @dotrino/proxy-client@0.
|
|
1
|
+
Copia vendorizada de @dotrino/proxy-client@0.22.0 (dotrino-proxy-client/src/{index,client,signature,canonical,sealing,encpub,webrtc}.js).
|
|
2
2
|
NO se edita a mano: la escribe `node vendor.mjs` y la vigila test/vendor-up-to-date.test.mjs.
|
|
3
3
|
sealing.js resuelve @dotrino/identity/content de forma PEREZOSA (= ../../content.js
|
|
4
4
|
por el import map): solo se carga si de verdad se sella algo.
|
|
@@ -13,6 +13,15 @@ import { WebRTCManager, RTC_TAG, DEFAULT_ICE_SERVERS, loadNodePeerConnection, re
|
|
|
13
13
|
* @param {string} code
|
|
14
14
|
* @returns {Error & { code: string }}
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* EL SALUDO: «este token es de esta identidad».
|
|
18
|
+
*
|
|
19
|
+
* Es una trama de CONTROL del transporte, como la señalización de WebRTC, y por eso va
|
|
20
|
+
* en claro y no la para `requireSealed`: lo único que lleva es una llave PÚBLICA que el
|
|
21
|
+
* proxio ya tiene atada a esta conexión desde `identify`. Nada del usuario viaja aquí.
|
|
22
|
+
*/
|
|
23
|
+
const HELLO_TAG = '__cc_hello__'
|
|
24
|
+
|
|
16
25
|
function errorCon (mensaje, code) {
|
|
17
26
|
const e = /** @type {Error & { code: string }} */ (new Error(mensaje))
|
|
18
27
|
e.code = code
|
|
@@ -33,6 +42,7 @@ function errorCon (mensaje, code) {
|
|
|
33
42
|
* - 'channel_joined' (channel, token) : new peer joined the channel
|
|
34
43
|
* - 'channel_left' (channel, token) : peer unpublished
|
|
35
44
|
* - 'peer_disconnected' (token, channel?) : peer dropped (with channel if it was published there)
|
|
45
|
+
* - 'peer_identity' (token, publickey) : that token said whose it is (helloTo)
|
|
36
46
|
* - 'reconnecting' (attempt, max)
|
|
37
47
|
* - 'reconnect_failed' (attempts)
|
|
38
48
|
*/
|
|
@@ -79,6 +89,23 @@ export class WebSocketProxyClient {
|
|
|
79
89
|
this._encPubs = new Map()
|
|
80
90
|
this._encPubInflight = new Map()
|
|
81
91
|
|
|
92
|
+
/**
|
|
93
|
+
* QUIÉN ESTÁ AL OTRO LADO DE CADA TOKEN (token → publickey), aprendido del saludo.
|
|
94
|
+
*
|
|
95
|
+
* Un token es una dirección del proxio y no dice de quién es. Hasta que alguien lo
|
|
96
|
+
* diga, a ese token no se le puede sellar nada: no se sabe a qué identidad, y por lo
|
|
97
|
+
* tanto tampoco a qué llave de cifrado. Es el hueco por el que una sala de
|
|
98
|
+
* desconocidos seguía hablando en claro aunque el sellado ya existiera.
|
|
99
|
+
*
|
|
100
|
+
* Se vacía al reconectar: los tokens se reparten por conexión y los de antes ya no
|
|
101
|
+
* son de nadie.
|
|
102
|
+
*/
|
|
103
|
+
this._tokenPubkeys = new Map()
|
|
104
|
+
this._helloSent = new Set()
|
|
105
|
+
|
|
106
|
+
/** Mi propia identidad en el cable, la que `identify` dejó atada a este token. */
|
|
107
|
+
this.myPublickey = null
|
|
108
|
+
|
|
82
109
|
/**
|
|
83
110
|
* QUIEN YA SABE LA RESPUESTA, QUE NO PREGUNTE. Los aparatos de un mismo dueño llevan su
|
|
84
111
|
* llave de cifrado escrita en el ACTA (`memberEncPub` de `@dotrino/identity`), firmada
|
|
@@ -451,8 +478,14 @@ export class WebSocketProxyClient {
|
|
|
451
478
|
async sendSealedTo (toTokens, payload, { peerPubkey, peerEncPub } = /** @type {any} */ ({})) {
|
|
452
479
|
const tokens = Array.isArray(toTokens) ? toTokens : [toTokens]
|
|
453
480
|
if (!peerEncPub) {
|
|
481
|
+
// Si la app no lo dice, lo dice el SALUDO: `helloTo` dejó apuntado de quién es
|
|
482
|
+
// cada token. Con más de uno no se adivina —cada identidad tiene su llave y un
|
|
483
|
+
// solo sobre solo lo abriría una—, así que ahí se exige decirlo.
|
|
484
|
+
if (!peerPubkey && tokens.length === 1) peerPubkey = this.pubkeyOfToken(tokens[0])
|
|
454
485
|
if (!peerPubkey) {
|
|
455
|
-
throw errorCon(
|
|
486
|
+
throw errorCon(
|
|
487
|
+
'sendSealedTo: nobody has said whose this token is — greet it (helloTo) or pass peerPubkey',
|
|
488
|
+
'no-peer-identity')
|
|
456
489
|
}
|
|
457
490
|
peerEncPub = await this.encPubOf(peerPubkey)
|
|
458
491
|
}
|
|
@@ -461,6 +494,65 @@ export class WebSocketProxyClient {
|
|
|
461
494
|
this.send(tokens, sobre)
|
|
462
495
|
}
|
|
463
496
|
|
|
497
|
+
// ---------- el saludo: de quién es este token ----------
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* SALUDAR: decirle a uno o varios tokens quién soy.
|
|
501
|
+
*
|
|
502
|
+
* Un token es una dirección del proxio y no dice de quién es, así que sin esto no hay
|
|
503
|
+
* a quién sellarle: una sala de desconocidos se queda muda o —lo que pasaba— hablando
|
|
504
|
+
* en claro. El saludo lleva SOLO mi llave pública, la misma que el proxio ya tiene
|
|
505
|
+
* atada a esta conexión desde `identify`; no hay nada del usuario dentro y por eso no
|
|
506
|
+
* necesita sobre.
|
|
507
|
+
*
|
|
508
|
+
* Quien lo recibe contesta el suyo una vez, así que basta con que salude UNA de las
|
|
509
|
+
* dos puntas y la app no tiene que coreografiar nada.
|
|
510
|
+
*
|
|
511
|
+
* **No autentica, y no hace falta que lo haga.** Mentir sobre la propia identidad solo
|
|
512
|
+
* consigue que te sellen a una llave que no puedes abrir: el embustero se queda sin
|
|
513
|
+
* leer, y nadie se queda suplantado. Quién firma de verdad lo dice el reto de la app,
|
|
514
|
+
* o el `from_publickey` que pone el proxio cuando se enruta por pubkey.
|
|
515
|
+
*/
|
|
516
|
+
helloTo (to) {
|
|
517
|
+
if (!this.myPublickey) {
|
|
518
|
+
throw errorCon('helloTo: identify first — a greeting with no identity says nothing', 'not-identified')
|
|
519
|
+
}
|
|
520
|
+
const tokens = Array.isArray(to) ? to : [to]
|
|
521
|
+
for (const t of tokens) {
|
|
522
|
+
if (!t || t === this.token) continue
|
|
523
|
+
this._helloSent.add(t)
|
|
524
|
+
this._proxySendOne(t, { t: HELLO_TAG, publickey: this.myPublickey })
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** De quién es este token, si alguien lo ha dicho. `null` es «todavía no lo sé». */
|
|
529
|
+
pubkeyOfToken (token) {
|
|
530
|
+
return this._tokenPubkeys.get(token) || null
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/** Olvidar un token (se fue, o se quiere volver a preguntar). Sin argumento, todos. */
|
|
534
|
+
forgetToken (token) {
|
|
535
|
+
if (token == null) { this._tokenPubkeys.clear(); this._helloSent.clear(); return }
|
|
536
|
+
this._tokenPubkeys.delete(token)
|
|
537
|
+
this._helloSent.delete(token)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
_onHello (from, msg) {
|
|
541
|
+
if (typeof msg.publickey !== 'string' || !msg.publickey) return
|
|
542
|
+
const antes = this._tokenPubkeys.get(from)
|
|
543
|
+
// UN TOKEN NO CAMBIA DE DUEÑO: la conexión ES la identidad, y el proxio no recicla
|
|
544
|
+
// tokens. Un segundo saludo con otra identidad es un intento de que le sellemos a
|
|
545
|
+
// otro; manda el primero y esto se dice en voz alta en vez de pisarlo.
|
|
546
|
+
if (antes && !samePubkey(antes, msg.publickey)) {
|
|
547
|
+
this._emit('error', { type: 'hello_conflict', from, code: 'hello-conflict' })
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
this._tokenPubkeys.set(from, msg.publickey)
|
|
551
|
+
// Contestar UNA vez: el saludo queda simétrico sin rebotar para siempre.
|
|
552
|
+
if (!this._helloSent.has(from) && this.myPublickey) this.helloTo(from)
|
|
553
|
+
this._emit('peer_identity', from, msg.publickey)
|
|
554
|
+
}
|
|
555
|
+
|
|
464
556
|
/** Sella con lo que haya: la bóveda de la app (`sealing`) o las primitivas del pilar. */
|
|
465
557
|
async _seal (payload, peerEncPub) {
|
|
466
558
|
if (!peerEncPub) throw errorCon('seal: missing peerEncPub', 'unsealed')
|
|
@@ -714,6 +806,10 @@ export class WebSocketProxyClient {
|
|
|
714
806
|
// escribirle a la PERSONA llega a cualquiera de sus dispositivos. Ver acta-de-perfil.md.
|
|
715
807
|
if (acta) msg.acta = acta
|
|
716
808
|
const done = this._request(msg, 'identified')
|
|
809
|
+
// Mi identidad en el cable, para poder decirla en el saludo sin que la app la
|
|
810
|
+
// repita. Se apunta al pedirlo y no al confirmarlo: si la identificación falla,
|
|
811
|
+
// el saludo tampoco sale (el proxio no reparte a una conexión sin identidad).
|
|
812
|
+
this.myPublickey = data.publickey
|
|
717
813
|
if (this._rtc && typeof sign === 'function' && data.publickey) {
|
|
718
814
|
done.then(() => this.enableTurn({ publicKey: data.publickey, sign })).catch(() => {})
|
|
719
815
|
}
|
|
@@ -1130,6 +1226,13 @@ export class WebSocketProxyClient {
|
|
|
1130
1226
|
this._rtc.handleIncoming(from, parsed)
|
|
1131
1227
|
break
|
|
1132
1228
|
}
|
|
1229
|
+
// El saludo se atiende AQUÍ, antes de `_deliver`: es del transporte y no sube a
|
|
1230
|
+
// la app, así que `requireSealed` no lo ve pasar ni tiene que hacerle una
|
|
1231
|
+
// excepción.
|
|
1232
|
+
if (parsed && parsed.t === HELLO_TAG) {
|
|
1233
|
+
this._onHello(from, parsed)
|
|
1234
|
+
break
|
|
1235
|
+
}
|
|
1133
1236
|
this._deliver(from, parsed ?? message, {
|
|
1134
1237
|
raw: message, timestamp, via: 'proxy',
|
|
1135
1238
|
fromPubkey: from_publickey || null,
|
|
@@ -1139,6 +1242,9 @@ export class WebSocketProxyClient {
|
|
|
1139
1242
|
break
|
|
1140
1243
|
}
|
|
1141
1244
|
case 'disconnected':
|
|
1245
|
+
// El token muere con la conexión y no se recicla: lo aprendido de él deja de
|
|
1246
|
+
// valer en el acto. Guardarlo «por si vuelve» sería sellarle a quien ya no está.
|
|
1247
|
+
this.forgetToken(data.token)
|
|
1142
1248
|
this._emit('peer_disconnected', data.token, data.channel || null)
|
|
1143
1249
|
if (this._rtc && data.token) this._rtc.closePeer(data.token)
|
|
1144
1250
|
this._resolvePending(data, 'token')
|
|
@@ -3,7 +3,7 @@ export { canonicalStringify } from './canonical.js'
|
|
|
3
3
|
export { getPublicKeyJwk, signData, verifyData, samePubkey, buildSignedChannel, setKeypairStore } from './signature.js'
|
|
4
4
|
export {
|
|
5
5
|
seal, open, isSealed, makeEncKeypair, importEncPrivate, exportEncPrivate,
|
|
6
|
-
setSealingPrimitives,
|
|
6
|
+
setSealingPrimitives, identitySealing,
|
|
7
7
|
} from './sealing.js'
|
|
8
8
|
export {
|
|
9
9
|
ENCPUB_V, ENCPUB_AUD, encPubBody, isEncPub,
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
const ECDH = { name: 'ECDH', namedCurve: 'P-256' }
|
|
19
|
+
|
|
20
|
+
/** Un error con `code`: quien llama decide por el código, nunca por la frase. */
|
|
21
|
+
function errorCon (mensaje, code) {
|
|
22
|
+
const e = /** @type {Error & { code: string }} */ (new Error(mensaje))
|
|
23
|
+
e.code = code
|
|
24
|
+
return e
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
const VERSION = 1
|
|
20
28
|
|
|
21
29
|
let primitives = null
|
|
@@ -75,3 +83,74 @@ export async function open (envelope, myEncPrivateKey) {
|
|
|
75
83
|
export function isSealed (msg) {
|
|
76
84
|
return !!msg && msg.v === VERSION && !!msg.sealed?.ct && !!msg.sealed?.epk
|
|
77
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* EL PUENTE DE LA BÓVEDA: sellar y abrir cuando la llave privada NO está aquí.
|
|
89
|
+
*
|
|
90
|
+
* En un aparato headless la privada de cifrado es suya y basta con `myEncPrivateKey`. En
|
|
91
|
+
* el navegador no: la privada vive dentro del iframe de la bóveda y no sale nunca, así
|
|
92
|
+
* que sellar y abrir se le delegan a `@dotrino/identity` (`encrypt` / `decrypt`), que es
|
|
93
|
+
* la MISMA cripto —ECDH P-256 efímero + AES-GCM— y no cripto nueva.
|
|
94
|
+
*
|
|
95
|
+
* Estaba escrito en el gestor de contraseñas, que fue la primera app que selló de verdad,
|
|
96
|
+
* y sube aquí porque son las dos puntas del MISMO sobre: si una cambia de forma, la otra
|
|
97
|
+
* deja de abrirlo, y ese fallo no hace ruido —la petición sale, al otro lado «no es para
|
|
98
|
+
* mí», y desde fuera se ve como que nadie contestó—. Con una sola pieza no hay dos formas.
|
|
99
|
+
*
|
|
100
|
+
* Dos DIALECTOS de identidad, porque no son el mismo objeto y los dos son correctos:
|
|
101
|
+
* · la clase `Identity` (la que habla con el iframe): `getEncryptionPubkey()` y
|
|
102
|
+
* `decrypt(remitente, miToken, sobre)` que devuelve `{ plaintext }`
|
|
103
|
+
* · el núcleo que corre dentro de un service worker: `encryptionPubkey()` y
|
|
104
|
+
* `decrypt(remitente, sobre)` que devuelve la cadena
|
|
105
|
+
*
|
|
106
|
+
* `app` es la MARCA del sobre: quien recibe lo que no es suyo lo descarta por aquí. Es
|
|
107
|
+
* estable por app y no se cambia a la ligera — cambiarla es dejar de abrir lo de la
|
|
108
|
+
* versión anterior.
|
|
109
|
+
*
|
|
110
|
+
* @param {any} identity cualquiera de los dos dialectos
|
|
111
|
+
* @param {{ app?: string }} [opts]
|
|
112
|
+
* @returns {{ seal:Function, open:Function, isSealed:Function }}
|
|
113
|
+
*/
|
|
114
|
+
export function identitySealing (identity, { app = 'dotrino' } = {}) {
|
|
115
|
+
// El dialecto se decide UNA vez, por lo que el objeto expone, y no por el resultado de
|
|
116
|
+
// cada llamada: así, si llega un tercero que no es ninguno de los dos, revienta aquí y
|
|
117
|
+
// con nombre, en vez de devolver sobres que nadie abre.
|
|
118
|
+
const iframe = typeof identity?.getEncryptionPubkey === 'function'
|
|
119
|
+
if (!iframe && typeof identity?.encryptionPubkey !== 'function') {
|
|
120
|
+
throw new Error('identitySealing: this identity exposes neither getEncryptionPubkey() nor encryptionPubkey()')
|
|
121
|
+
}
|
|
122
|
+
if (typeof identity?.encrypt !== 'function' || typeof identity?.decrypt !== 'function') {
|
|
123
|
+
throw new Error('identitySealing: this identity does not expose encrypt()/decrypt()')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const myEncPub = () => (iframe ? identity.getEncryptionPubkey() : identity.encryptionPubkey())
|
|
127
|
+
const openEnvelope = async (from, envelope) => {
|
|
128
|
+
const r = iframe
|
|
129
|
+
? await identity.decrypt(from, null, envelope)
|
|
130
|
+
: await identity.decrypt(from, envelope)
|
|
131
|
+
// Un dialecto devuelve `{ plaintext }` y el otro la cadena. Nada más se admite: un
|
|
132
|
+
// `?? ''` aquí sería un sobre vacío haciéndose pasar por un mensaje.
|
|
133
|
+
if (typeof r === 'string') return r
|
|
134
|
+
if (typeof r?.plaintext === 'string') return r.plaintext
|
|
135
|
+
throw new Error('identitySealing: decrypt returned neither a string nor { plaintext }')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
async seal (msg, peerEncPub) {
|
|
140
|
+
if (!peerEncPub) throw errorCon('no encryption key for the other side', 'unsealed')
|
|
141
|
+
// Destinatarios como OBJETOS: `encrypt` expande cada uno a todos los aparatos de
|
|
142
|
+
// esa persona, y una llave suelta se le cae sin envolver nada.
|
|
143
|
+
const sealed = await identity.encrypt([{ encryptionPubkey: peerEncPub }], JSON.stringify(msg))
|
|
144
|
+
// Y SE COMPRUEBA QUE ENVOLVIÓ A ALGUIEN. `encrypt` se salta en silencio al
|
|
145
|
+
// destinatario cuya llave no puede importar, y devuelve un sobre con el llavero
|
|
146
|
+
// VACÍO: cifrado de verdad, y que no abre nadie. Eso no es un sobre, es un mensaje
|
|
147
|
+
// perdido con cara de enviado.
|
|
148
|
+
if (!sealed || !sealed.wrap || Object.keys(sealed.wrap).length === 0) {
|
|
149
|
+
throw errorCon('identitySealing: the vault wrapped the message for nobody', 'unsealed')
|
|
150
|
+
}
|
|
151
|
+
return { app, sealed, from: await myEncPub() }
|
|
152
|
+
},
|
|
153
|
+
async open (env) { return JSON.parse(await openEnvelope(env.from, env.sealed)) },
|
|
154
|
+
isSealed: (m) => !!m && m.app === app && !!m.sealed,
|
|
155
|
+
}
|
|
156
|
+
}
|