@dotrino/identity 0.35.0 → 0.36.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/remote.js +40 -3
package/package.json
CHANGED
package/vault/remote.js
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
import { makeDeviceKey, signWithDevice, verifyDelegation, verifyDeviceSig, makePairingCode, commitCode, pubkeyId } from './capabilities.js'
|
|
17
17
|
|
|
18
18
|
const MSG = {
|
|
19
|
+
HELLO: 'vault.hello',
|
|
20
|
+
HELLO_OK: 'vault.hello.ok',
|
|
19
21
|
ENROLL: 'vault.enroll',
|
|
20
22
|
ENROLL_CHALLENGE: 'vault.enroll.challenge',
|
|
21
23
|
ENROLLED: 'vault.enrolled',
|
|
@@ -56,6 +58,36 @@ async function identifyAsDevice (client, device, { cert = null, acta = null } =
|
|
|
56
58
|
await client.identify({ data, signature, cert, acta })
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* La respuesta al `hello` va firmada y con el `sn` DENTRO de lo firmado. Comprobarlo
|
|
64
|
+
* ata la respuesta a ESTA sesión: no vale la de otro emparejamiento ni la de otra
|
|
65
|
+
* bóveda. Ojo con lo que NO prueba: cualquiera puede firmar con una llave suya, así
|
|
66
|
+
* que esto no dice que sea TU bóveda — eso lo dice el código de 6 dígitos, que solo
|
|
67
|
+
* aprende la bóveda donde tú lo tecleas.
|
|
68
|
+
*/
|
|
69
|
+
async function verificarHola (p, sn) {
|
|
70
|
+
const b = p?.body
|
|
71
|
+
if (!b?.iss || b.sn !== sn) throw new Error('la bóveda contestó a otro emparejamiento')
|
|
72
|
+
if (!(await verifyDeviceSig({ publickey: b.iss, data: b, signature: p.signature }))) {
|
|
73
|
+
throw new Error('la respuesta de la bóveda no está bien firmada')
|
|
74
|
+
}
|
|
75
|
+
return b
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** «¿Quién eres?» del QR corto: devuelve `{ iss, proxy, acct, m }` de la bóveda. */
|
|
79
|
+
async function askVault (client, qr) {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const off = client.on('message', (_from, p) => {
|
|
82
|
+
if (p?.type === MSG.HELLO_OK) { fin(); verificarHola(p, qr.sn).then((b) => resolve({ iss: b.iss, proxy: b.proxy || qr.proxy, acct: b.acct || '', m: b.m || qr.m }), reject) }
|
|
83
|
+
else if (p?.type === MSG.ERROR) { fin(); reject(new Error(p.error)) }
|
|
84
|
+
})
|
|
85
|
+
const t = setTimeout(() => { fin(); reject(new Error('la bóveda no contestó: ese código pudo caducar')) }, 15000)
|
|
86
|
+
const fin = () => { off(); clearTimeout(t) }
|
|
87
|
+
try { client.send(qr.conn, { type: MSG.HELLO, sn: qr.sn }) } catch (e) { fin(); reject(e) }
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
59
91
|
/**
|
|
60
92
|
* @param {Object} opts
|
|
61
93
|
* @param {{v:number, iss:string, proxy:string, token:string, sn:string}} opts.qr QR v2 del vault.
|
|
@@ -65,11 +97,16 @@ async function identifyAsDevice (client, device, { cert = null, acta = null } =
|
|
|
65
97
|
* @returns {Promise<{device, cert, master:string, proxy:string, deviceId:string}>}
|
|
66
98
|
*/
|
|
67
99
|
export async function enrollDevice ({ qr, device, onChallenge, label = '', continuity = null, encPub = null, approveTimeoutMs = 180000, intent = 'join', profileId = null, onAdopt = null } = {}) {
|
|
68
|
-
if (!qr?.
|
|
100
|
+
if (!qr?.sn || !(qr.iss || qr.conn)) throw new Error('qr inválido: falta la bóveda o el nonce')
|
|
69
101
|
const { WebSocketProxyClient } = await import('@dotrino/proxy-client')
|
|
70
|
-
const client = new WebSocketProxyClient({ url: qr.proxy, enableWebRTC: false, autoReconnect: false })
|
|
102
|
+
const client = new WebSocketProxyClient({ url: qr.proxy || 'wss://proxy.dotrino.com', enableWebRTC: false, autoReconnect: false })
|
|
71
103
|
await client.connect()
|
|
72
104
|
try {
|
|
105
|
+
// QR CORTO: no trae la llave, solo la dirección de la bóveda en el proxy. Se le
|
|
106
|
+
// pregunta quién es, punto a punto, presentando el `sn`; solo contesta si esa sesión
|
|
107
|
+
// de emparejamiento sigue abierta. Lo que acredita la llave no es el QR: es la firma
|
|
108
|
+
// del certificado y el código de 6 dígitos, que solo aprende la bóveda donde lo tecleas.
|
|
109
|
+
if (!qr.iss) qr = { ...qr, ...(await askVault(client, qr)) }
|
|
73
110
|
// Por defecto genera una sub-clave nueva; pero el iframe pasa SU PROPIA llave de
|
|
74
111
|
// identidad (P) como `device` → el cert delega tu identidad y hay UNA sola (signData/
|
|
75
112
|
// identify/cert son la misma P).
|
|
@@ -94,7 +131,7 @@ export async function enrollDevice ({ qr, device, onChallenge, label = '', conti
|
|
|
94
131
|
const adoptar = intent === 'adopt'
|
|
95
132
|
if (adoptar && typeof onAdopt !== 'function') throw new Error('enrollDevice(adopt): falta onAdopt')
|
|
96
133
|
const data = {
|
|
97
|
-
op: 'enroll', dpub: dev.publickey, token: qr.token, sn: qr.sn, commit, label, ts: Date.now(), intent,
|
|
134
|
+
op: 'enroll', dpub: dev.publickey, token: qr.token || qr.sn, sn: qr.sn, commit, label, ts: Date.now(), intent,
|
|
98
135
|
...(adoptar && profileId ? { profileId } : {}),
|
|
99
136
|
...(continuity ? { continuity } : {}), ...(encPub ? { encPub } : {})
|
|
100
137
|
}
|