@dotrino/identity 0.101.0 → 0.102.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/package.json
CHANGED
package/vault/capabilities.js
CHANGED
|
@@ -210,7 +210,18 @@ export async function signDelegationWith (privateKey, iss, { sub, scope, iat, se
|
|
|
210
210
|
* Firma datos con la clave de DISPOSITIVO (formato byte-idéntico a `signData` del
|
|
211
211
|
* vault → lo que el dispositivo/bridge usa para firmar cada pin/acción).
|
|
212
212
|
*/
|
|
213
|
-
export async function signWithDevice ({ privateJwk, privateKey, publickey, data }) {
|
|
213
|
+
export async function signWithDevice ({ privateJwk, privateKey, publickey, data, sign }) {
|
|
214
|
+
// UNA LLAVE QUE NO VIVE AQUÍ (la del chip del teléfono, en la app nativa): se le pasa el
|
|
215
|
+
// texto canónico y devuelve la firma P1363 en base64. La privada no entra en este proceso
|
|
216
|
+
// en ningún momento. Con firmador externo es obligatorio decir de quién es la llave.
|
|
217
|
+
if (typeof sign === 'function') {
|
|
218
|
+
if (!publickey) throw new Error('signWithDevice: publickey is required with an external sign()')
|
|
219
|
+
const signature = await sign(canonicalStringify(data))
|
|
220
|
+
if (typeof signature !== 'string' || !signature) {
|
|
221
|
+
throw Object.assign(new Error('signWithDevice: the external sign() returned no signature'), { code: 'no-signature' })
|
|
222
|
+
}
|
|
223
|
+
return { signature, publickey }
|
|
224
|
+
}
|
|
214
225
|
// `privateKey` (CryptoKey, posiblemente NO extractable) tiene prioridad: firma
|
|
215
226
|
// sin tocar bytes de la privada. Con CryptoKey es obligatorio pasar `publickey`.
|
|
216
227
|
if (privateKey) {
|
package/vault/remote.js
CHANGED
|
@@ -75,7 +75,7 @@ export async function isAuthenticRevoke ({ body, signature, master, devicePubkey
|
|
|
75
75
|
async function identifyAsDevice (client, device, { cert = null, acta = null } = {}) {
|
|
76
76
|
if (!client.token) return
|
|
77
77
|
const data = { op: 'identify', publickey: device.publickey, token: client.token, ts: Date.now() }
|
|
78
|
-
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, privateKey: device.privateKey, publickey: device.publickey, data })
|
|
78
|
+
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, privateKey: device.privateKey, publickey: device.publickey, sign: device.sign, data })
|
|
79
79
|
// cert → el proxy enruta lo dirigido a la maestra; acta → lo dirigido a la PERSONA.
|
|
80
80
|
await client.identify({ data, signature, cert, acta })
|
|
81
81
|
}
|
|
@@ -128,7 +128,7 @@ async function askVault (client, qr) {
|
|
|
128
128
|
* @param {(c:{deviceId:string, code:string})=>void} [opts.onChallenge] Para mostrar el código a tipear en el PC.
|
|
129
129
|
* @param {string} [opts.label]
|
|
130
130
|
* @param {number} [opts.approveTimeoutMs] Espera de la aprobación humana (def 3 min).
|
|
131
|
-
* @returns {Promise<{device, cert, master:string, proxy:string, deviceId:string}>}
|
|
131
|
+
* @returns {Promise<{device, cert, master:string, proxy:string, deviceId:string, account:string}>}
|
|
132
132
|
*/
|
|
133
133
|
export async function enrollDevice ({ qr, device, onChallenge, label = '', continuity = null, encPub = null, approveTimeoutMs = 180000, intent = 'join', profileId = null, onAdopt = null } = {}) {
|
|
134
134
|
if (!qr?.sn || !(qr.iss || qr.conn)) throw new Error('invalid qr: missing vault or nonce')
|
|
@@ -169,7 +169,8 @@ export async function enrollDevice ({ qr, device, onChallenge, label = '', conti
|
|
|
169
169
|
...(adopting && profileId ? { profileId } : {}),
|
|
170
170
|
...(continuity ? { continuity } : {}), ...(encPub ? { encPub } : {})
|
|
171
171
|
}
|
|
172
|
-
|
|
172
|
+
// `dev.sign`: la llave vive fuera (el chip del teléfono) y solo se le pide la firma.
|
|
173
|
+
const { signature } = await signWithDevice({ privateJwk: dev.privateJwk, privateKey: dev.privateKey, publickey: dev.publickey, sign: dev.sign, data })
|
|
173
174
|
|
|
174
175
|
const enrolled = new Promise((resolve, reject) => {
|
|
175
176
|
let sellando = false
|
|
@@ -205,7 +206,7 @@ export async function enrollDevice ({ qr, device, onChallenge, label = '', conti
|
|
|
205
206
|
if (adopting) {
|
|
206
207
|
if (!res.acta) throw new Error('the vault did not return the adopted record')
|
|
207
208
|
if (res.acta.sealer !== qr.iss) throw new Error('the record is sealed by a vault other than the one you saw')
|
|
208
|
-
return { device: dev, cert: null, master: qr.iss, proxy: qr.proxy, deviceId, acta: res.acta, adopted: true }
|
|
209
|
+
return { device: dev, cert: null, master: qr.iss, proxy: qr.proxy, deviceId, acta: res.acta, adopted: true, account: qr.acct || '' }
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
// Validación estricta antes de guardar (cierra inyección de cert / sustitución de maestra).
|
|
@@ -224,7 +225,10 @@ export async function enrollDevice ({ qr, device, onChallenge, label = '', conti
|
|
|
224
225
|
// emparejaba con una segunda bóveda o con una que adoptó la cuenta (`checkVaultReply`).
|
|
225
226
|
const chk = await checkVaultReply({ acta: res.acta, cert: res.cert, vault: qr.iss, sub: dev.publickey, justSealed: true })
|
|
226
227
|
if (!chk.ok) throw new Error('the vault reply does not check out: ' + chk.reason)
|
|
227
|
-
|
|
228
|
+
// `account`: el nombre que la bóveda dio a la cuenta. Con la invitación corta no viene en
|
|
229
|
+
// el QR sino en el `hello`, así que solo lo sabe esto — y sin él, quien tiene varias
|
|
230
|
+
// cuentas en un aparato no sabe de cuál es cada cosa.
|
|
231
|
+
return { device: dev, cert: res.cert, master: qr.iss, proxy: qr.proxy, deviceId, acta: res.acta || null, account: qr.acct || '' }
|
|
228
232
|
} finally { try { client.close() } catch (_) {} }
|
|
229
233
|
}
|
|
230
234
|
|
|
@@ -272,14 +276,14 @@ function vaultError (p) {
|
|
|
272
276
|
}
|
|
273
277
|
|
|
274
278
|
async function vaultRpc ({ master, proxy, device, cert, acta = null, sendType, okType, data, onRevoked, timeoutMs = 15000 }) {
|
|
275
|
-
if (!master || !proxy || !(device?.privateJwk || device?.privateKey) || !cert) throw new Error('missing pairing data')
|
|
279
|
+
if (!master || !proxy || !(device?.privateJwk || device?.privateKey || typeof device?.sign === 'function') || !cert) throw new Error('missing pairing data')
|
|
276
280
|
const { WebSocketProxyClient } = await import('@dotrino/proxy-client')
|
|
277
281
|
const client = new WebSocketProxyClient({ url: proxy, enableWebRTC: false, autoReconnect: false })
|
|
278
282
|
await client.connect()
|
|
279
283
|
try {
|
|
280
284
|
try { await identifyAsDevice(client, device, { cert, acta }) } catch (_) { /* sin identify seguimos: solo perdemos la cola */ }
|
|
281
285
|
const signed = { ...data, publickey: device.publickey, ts: Date.now() }
|
|
282
|
-
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, privateKey: device.privateKey, publickey: device.publickey, data: signed })
|
|
286
|
+
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, privateKey: device.privateKey, publickey: device.publickey, sign: device.sign, data: signed })
|
|
283
287
|
const pending = new Promise((resolve, reject) => {
|
|
284
288
|
let graceTimer = null
|
|
285
289
|
const off = client.on('message', (_f, p) => {
|
|
@@ -325,7 +329,7 @@ async function vaultRpc ({ master, proxy, device, cert, acta = null, sendType, o
|
|
|
325
329
|
* nada, como cualquier otro mensaje sin firma.
|
|
326
330
|
*/
|
|
327
331
|
export async function checkMembership ({ master, proxy, device, onRevoked, timeoutMs = 12000 } = {}) {
|
|
328
|
-
if (!master || !proxy || !(device?.privateJwk || device?.privateKey)) throw new Error('missing device data')
|
|
332
|
+
if (!master || !proxy || !(device?.privateJwk || device?.privateKey || typeof device?.sign === 'function')) throw new Error('missing device data')
|
|
329
333
|
const { WebSocketProxyClient } = await import('@dotrino/proxy-client')
|
|
330
334
|
const client = new WebSocketProxyClient({ url: proxy, enableWebRTC: false, autoReconnect: false })
|
|
331
335
|
await client.connect()
|
|
@@ -334,7 +338,7 @@ export async function checkMembership ({ master, proxy, device, onRevoked, timeo
|
|
|
334
338
|
// estaba apagado, si todavía está dentro de las 24 h).
|
|
335
339
|
try { await identifyAsDevice(client, device) } catch (_) {}
|
|
336
340
|
const data = { op: 'check', publickey: device.publickey, ts: Date.now() }
|
|
337
|
-
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, privateKey: device.privateKey, publickey: device.publickey, data })
|
|
341
|
+
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, privateKey: device.privateKey, publickey: device.publickey, sign: device.sign, data })
|
|
338
342
|
const res = await new Promise((resolve) => {
|
|
339
343
|
let settled = false
|
|
340
344
|
const done = (v) => { if (!settled) { settled = true; cleanup(); resolve(v) } }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copia vendorizada de @dotrino/proxy-client@0.
|
|
1
|
+
Copia vendorizada de @dotrino/proxy-client@0.24.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.
|
|
@@ -437,6 +437,12 @@ export class WebSocketProxyClient {
|
|
|
437
437
|
* señalización WebRTC, presencia—: entregar eso mañana no es tarde, es
|
|
438
438
|
* incorrecto (reinicia negociaciones imposibles y muestra movimientos fuera de
|
|
439
439
|
* contexto). NO lo uses para mensajes de chat, que sí quieren esperar.
|
|
440
|
+
*
|
|
441
|
+
* `opts.quiet` hace lo contrario de despertar: el mensaje SE GUARDA en la cola igual,
|
|
442
|
+
* pero el proxio no toca el timbre push del destinatario. Úsalo para lo que puede
|
|
443
|
+
* esperar a que la otra punta abra por su cuenta —un aviso de que algo cambió—. Sin
|
|
444
|
+
* él, cada aviso hace sonar el teléfono, y un timbre que no trae nada que hacer
|
|
445
|
+
* enseña a ignorar el siguiente, que sí lo trae.
|
|
440
446
|
*/
|
|
441
447
|
/**
|
|
442
448
|
* Seal a payload towards a peer's encryption key and send it. This is what an app
|
|
@@ -708,6 +714,7 @@ export class WebSocketProxyClient {
|
|
|
708
714
|
message: typeof payload === 'string' ? payload : JSON.stringify(payload)
|
|
709
715
|
}
|
|
710
716
|
if (opts.ephemeral) msg.ephemeral = true
|
|
717
|
+
if (opts.quiet) msg.quiet = true
|
|
711
718
|
this._sendRaw(msg)
|
|
712
719
|
}
|
|
713
720
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copia vendorizada de @dotrino/vault@0.
|
|
1
|
+
Copia vendorizada de @dotrino/vault@0.72.0 (dotrino-vault/lib/src/{index,enroll,protocol,passwordLogins,loginClient,b64}.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
|
index.js importa ./enroll.js y ./protocol.js (relativos, van en esta misma copia),
|
|
4
4
|
@dotrino/identity/{capabilities,acta} (= ../../{capabilities,acta}.js) y
|