@dotrino/vaultd 0.162.3 → 0.163.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/lib/src/core/libVersion.js +1 -1
- package/lib/src/core/transport.js +21 -1
- package/lib/src/core/vault.js +9 -2
- package/lib/src/kek.js +1 -1
- package/lib/src/loginClient.js +10 -1
- package/lib/src/passwordLogins.js +25 -2
- package/package.json +2 -2
- package/src/ctl.js +5 -14
- package/src/vault.js +6 -2
|
@@ -36,7 +36,7 @@ export async function masterPubkeyOf (identity) {
|
|
|
36
36
|
* @param {string} [opts.url] URL del proxy (default wss://proxy.dotrino.com).
|
|
37
37
|
* @returns {Promise<{ client, token:string, identify():Promise<void> }>}
|
|
38
38
|
*/
|
|
39
|
-
export async function createTransport ({ identity, url = DEFAULT_PROXY, commKey = null, log = () => {}, makeClient = null, announce = null, recheckMs = 60_000, patienceMs = 5 * 60_000, reannounceMs = 10 * 60_000 }) {
|
|
39
|
+
export async function createTransport ({ identity, url = DEFAULT_PROXY, commKey = null, log = () => {}, makeClient = null, announce = null, wakeUrl = null, recheckMs = 60_000, patienceMs = 5 * 60_000, reannounceMs = 10 * 60_000 }) {
|
|
40
40
|
// Import dinámico DESPUÉS de instalar los globals que el paquete usa.
|
|
41
41
|
// `WebSocketProxyClient` (la clase) y NO el helper `getWebSocketProxyClient`:
|
|
42
42
|
// ese es un SINGLETON de proceso, y con multi-perfil el vault necesita una
|
|
@@ -182,6 +182,25 @@ export async function createTransport ({ identity, url = DEFAULT_PROXY, commKey
|
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
/**
|
|
186
|
+
* DESPIÉRTAME AQUÍ (una bóveda que no vive conectada, `lambda/`). Si la plataforma da una
|
|
187
|
+
* URL, se registra en el proxio con la llave de comunicación y el acta, para que llame a esa
|
|
188
|
+
* URL cuando llegue algo para la cuenta y no haya nadie suyo conectado. Va pegado a
|
|
189
|
+
* `identify` por lo mismo que el anuncio: el acta puede haber cambiado. Si falla, se dice:
|
|
190
|
+
* lo que se pierde es que la despierten, y eso no se puede callar.
|
|
191
|
+
*/
|
|
192
|
+
const registerWake = async () => {
|
|
193
|
+
if (!wakeUrl || !identificado) return
|
|
194
|
+
const comm = commKey?.pub?.() || null
|
|
195
|
+
if (!comm || !actaVista) { log('[vault] cannot register the wake URL: no communication key in the record yet'); return }
|
|
196
|
+
try {
|
|
197
|
+
await client.registerWake({ publicKey: comm, sign: (d) => commKey.sign(d), url: wakeUrl, acta: actaVista, channel: announce || undefined })
|
|
198
|
+
log('[vault] wake URL registered on the proxy')
|
|
199
|
+
} catch (e) {
|
|
200
|
+
log(`[vault] could not register the wake URL (${e.code || ''} ${e.message}) — the proxy will not wake this vault`)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
185
204
|
let reintento = null
|
|
186
205
|
let fallos = 0
|
|
187
206
|
const identifyWithRetry = async () => {
|
|
@@ -189,6 +208,7 @@ export async function createTransport ({ identity, url = DEFAULT_PROXY, commKey
|
|
|
189
208
|
try {
|
|
190
209
|
await identify()
|
|
191
210
|
await announceAccount()
|
|
211
|
+
await registerWake()
|
|
192
212
|
if (fallos) log(`[vault] identified on the proxy after ${fallos + 1} attempt(s)`)
|
|
193
213
|
fallos = 0
|
|
194
214
|
} catch (e) {
|
package/lib/src/core/vault.js
CHANGED
|
@@ -80,6 +80,8 @@ const MAX_REPLY_BYTES = 768 * 1024
|
|
|
80
80
|
* readProcContext(pid) lo que el sistema dice de un proceso; `null` si no se puede mirar
|
|
81
81
|
* openLocalDesk({ name, onMessage, channels, maxBytes, log }) el atajo en la misma máquina
|
|
82
82
|
* makeClient quién crea el cliente del proxio (opcional)
|
|
83
|
+
* wakeUrl la URL que despierta a esta bóveda si no vive conectada (opcional, `lambda/`)
|
|
84
|
+
* onActivity() llega un mensaje del proxio (opcional; la Lambda mide con esto su inactividad)
|
|
83
85
|
* noticeGroupMs cuánto se agrupan los avisos de cambio
|
|
84
86
|
* opaque el servidor de OPAQUE, si no vale el del paquete (opcional)
|
|
85
87
|
* servePasswords `false`: el almacén de contraseñas lo atiende quien monta la bóveda,
|
|
@@ -492,7 +494,7 @@ export async function startVault ({ platform, proxyUrl, log = console.log, onEnr
|
|
|
492
494
|
// ninguna llave y solo sabe la dirección `nombre@AB12-CD34-EF56`— pueda encontrar a quien
|
|
493
495
|
// la atiende. Lo construye la pieza compartida, porque quien lo busca es la extensión.
|
|
494
496
|
const { client, identify: reidentificar, isIdentified } = await createTransport({
|
|
495
|
-
identity, url: proxyUrl, commKey, log, announce: vaultChannel(fp), makeClient: platform.makeClient || null
|
|
497
|
+
identity, url: proxyUrl, commKey, log, announce: vaultChannel(fp), makeClient: platform.makeClient || null, wakeUrl: platform.wakeUrl || null
|
|
496
498
|
})
|
|
497
499
|
|
|
498
500
|
// El registro público de cadenas de selladores: deposita, si hay a dónde, los eslabones
|
|
@@ -2189,7 +2191,12 @@ export async function startVault ({ platform, proxyUrl, log = console.log, onEnr
|
|
|
2189
2191
|
reply(from, { type: MSG.INCIDENT_RESULT, id: pend.id, approvers, blocked: false })
|
|
2190
2192
|
}
|
|
2191
2193
|
|
|
2192
|
-
client.on('message', (from, payload) =>
|
|
2194
|
+
client.on('message', (from, payload) => {
|
|
2195
|
+
// Quien monta la bóveda puede querer saber que hay trabajo (la Lambda se apaga cuando
|
|
2196
|
+
// deja de haberlo). Es un aviso, no decide nada.
|
|
2197
|
+
try { platform.onActivity?.() } catch (_) {}
|
|
2198
|
+
onMessage(from, payload)
|
|
2199
|
+
})
|
|
2193
2200
|
|
|
2194
2201
|
// EL MOSTRADOR LOCAL, siempre que se pueda. Si no se puede abrir (un sistema sin sockets
|
|
2195
2202
|
// de archivo, un directorio de solo lectura) se dice y se sigue: los servicios usan el
|
package/lib/src/kek.js
CHANGED
|
@@ -85,7 +85,7 @@ export function writeConfig (dir, cfg) {
|
|
|
85
85
|
* es a propósito el más tonto posible — cada CLI de KMS tiene sus manías con los binarios
|
|
86
86
|
* y sus flags, y se resuelven en un envoltorio de tres líneas en vez de aquí dentro.
|
|
87
87
|
*/
|
|
88
|
-
function runStep (step, input, what) {
|
|
88
|
+
export function runStep (step, input, what) {
|
|
89
89
|
if (!step || typeof step.cmd !== 'string' || !step.cmd) {
|
|
90
90
|
throw new KekError('kek-config', `kek provider "command": missing ${what}.cmd`)
|
|
91
91
|
}
|
package/lib/src/loginClient.js
CHANGED
|
@@ -44,6 +44,8 @@ const err = (code, message, extra = {}) => Object.assign(new Error(message), { c
|
|
|
44
44
|
|
|
45
45
|
/** Cuánto se espera a que una bóveda conteste antes de probar con la siguiente. */
|
|
46
46
|
export const REPLY_TIMEOUT_MS = 15_000
|
|
47
|
+
/** Lo que se espera a una bóveda que se está despertando: arrancar en frío y conectarse. */
|
|
48
|
+
export const WAKE_WAIT_MS = 30_000
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
51
|
* Escucha UNA respuesta de un token concreto. El `off` se suelta siempre —también al
|
|
@@ -98,7 +100,14 @@ export async function loginWithPassword ({ transport, address, password, label =
|
|
|
98
100
|
if (typeof password !== 'string' || !password) throw err('no-password', 'loginWithPassword: password required')
|
|
99
101
|
const { user, code, address: dir } = parseLoginAddress(address)
|
|
100
102
|
|
|
101
|
-
|
|
103
|
+
let tokens = await transport.list(vaultChannel(code))
|
|
104
|
+
// UNA BÓVEDA QUE DUERME (la de una Lambda): el proxio la está despertando y lo dice. Se
|
|
105
|
+
// espera a que se anuncie en vez de contestar que esa cuenta no existe.
|
|
106
|
+
const hasta = Date.now() + WAKE_WAIT_MS
|
|
107
|
+
while (!tokens.length && tokens.waking && Date.now() < hasta) {
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
109
|
+
tokens = await transport.list(vaultChannel(code))
|
|
110
|
+
}
|
|
102
111
|
if (!tokens.length) {
|
|
103
112
|
throw err('no-vault', 'no vault is answering for that address right now: turn yours on, or check the address')
|
|
104
113
|
}
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
* binario, `lib/src/index.js` en la pestaña—. Lo único que no es puro es `registerLogin`, al
|
|
25
25
|
* final, porque hace falta la identidad que firma.
|
|
26
26
|
*/
|
|
27
|
-
import { server as opaquePorDefecto, suiteId as suitePorDefecto } from '@dotrino/opaque'
|
|
28
|
-
import { pubkeyId } from '@dotrino/identity/capabilities'
|
|
27
|
+
import { server as opaquePorDefecto, client as opaqueClientePorDefecto, suiteId as suitePorDefecto } from '@dotrino/opaque'
|
|
28
|
+
import { pubkeyId, makeDeviceKey, makeDeviceEncKey } from '@dotrino/identity/capabilities'
|
|
29
29
|
import { DEVICE_CAPS, capScope } from '@dotrino/identity/acta'
|
|
30
30
|
import { deviceIdOf } from './enroll.js'
|
|
31
31
|
import { bytesToB64url, b64urlToBytes } from './b64.js'
|
|
@@ -448,6 +448,29 @@ export function createLoginDesk ({ load, save, now = () => Date.now(), opaque =
|
|
|
448
448
|
* `caps` los PERMISOS del acta que lleva el aparato, cualquiera de `DEVICE_CAPS`;
|
|
449
449
|
* por defecto firmar, leer y guardar
|
|
450
450
|
*/
|
|
451
|
+
/**
|
|
452
|
+
* DAR DE ALTA UN USUARIO, DEL LADO DE QUIEN TIENE LA CONTRASEÑA: el registro OPAQUE se
|
|
453
|
+
* termina aquí y las llaves del aparato nacen aquí y salen ya cerradas. La bóveda solo ve
|
|
454
|
+
* `begin`/`finish`, que es su mitad; quien llama decide por dónde llegan (el canal local de
|
|
455
|
+
* la CLI, o la propia bóveda en el arranque de una Lambda).
|
|
456
|
+
*
|
|
457
|
+
* @param {object} opts
|
|
458
|
+
* `begin(req)` → `{ response }` (`register-begin` de la bóveda)
|
|
459
|
+
* `finish(req)` → lo que devuelva (`register-finish` de la bóveda)
|
|
460
|
+
*/
|
|
461
|
+
export async function createPasswordLogin ({ user, password, label, caps, begin, finish, opaque = opaqueClientePorDefecto } = {}) {
|
|
462
|
+
if (!isValidUser(user)) throw Object.assign(new Error('invalid user name'), { code: 'bad-user' })
|
|
463
|
+
if (typeof password !== 'string' || !password) throw Object.assign(new Error('a password is required'), { code: 'no-password' })
|
|
464
|
+
const start = opaque.registrationStart({ password })
|
|
465
|
+
const begun = await begin({ user, request: start.request })
|
|
466
|
+
// La bóveda contesta con su mitad; el registro se termina AQUÍ, con la contraseña.
|
|
467
|
+
const fin = opaque.registrationFinish({ state: start.state, response: begun.response, password })
|
|
468
|
+
const device = await makeDeviceKey({ label })
|
|
469
|
+
const enc = await makeDeviceEncKey()
|
|
470
|
+
const blob = await sealDeviceKeys(fin.exportKey, { sign: device.privateJwk, enc: enc.privateJwk })
|
|
471
|
+
return finish({ user, upload: fin.upload, pub: device.publickey, encPub: enc.publickey, label, blob, caps })
|
|
472
|
+
}
|
|
473
|
+
|
|
451
474
|
export async function registerLogin ({
|
|
452
475
|
identity, logins, user, upload, pub, encPub = null, label = '', blob,
|
|
453
476
|
caps = ['sign', 'read', 'store'], scope, replace = false
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vaultd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.163.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Certificador personal de Dotrino: daemon headless que custodia la clave maestra y delega capacidades a tus dispositivos por el proxy. Tu CA propia.",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@dotrino/identity": "0.120.0",
|
|
26
26
|
"@dotrino/opaque": "0.1.0",
|
|
27
27
|
"@dotrino/passmanager": "0.18.4",
|
|
28
|
-
"@dotrino/proxy-client": "0.
|
|
28
|
+
"@dotrino/proxy-client": "0.38.0",
|
|
29
29
|
"@dotrino/roadmap": "0.4.30",
|
|
30
30
|
"@dotrino/store": "0.12.3",
|
|
31
31
|
"@dotrino/update": "0.9.0",
|
package/src/ctl.js
CHANGED
|
@@ -1706,21 +1706,12 @@ async function cmdLogins (rest) {
|
|
|
1706
1706
|
if (a[0] === '+') caps.add(cap); else caps.delete(cap)
|
|
1707
1707
|
}
|
|
1708
1708
|
const label = nombre.join(' ').trim() || 'equipo prestado'
|
|
1709
|
-
const {
|
|
1710
|
-
const { makeDeviceKey, makeDeviceEncKey } = await import('@dotrino/identity/capabilities')
|
|
1711
|
-
const { sealDeviceKeys } = await import('../lib/src/passwordLogins.js')
|
|
1709
|
+
const { createPasswordLogin } = await import('../lib/src/passwordLogins.js')
|
|
1712
1710
|
const password = await askNewPassword(user)
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
const fin = opaqueClient.registrationFinish({ state: start.state, response: begun.response, password })
|
|
1718
|
-
// Las llaves del aparato NACEN en esta terminal y salen de aquí ya cerradas.
|
|
1719
|
-
const device = await makeDeviceKey({ label })
|
|
1720
|
-
const enc = await makeDeviceEncKey()
|
|
1721
|
-
const blob = await sealDeviceKeys(fin.exportKey, { sign: device.privateJwk, enc: enc.privateJwk })
|
|
1722
|
-
const r = await loginsRequest('register-finish', {
|
|
1723
|
-
user, upload: fin.upload, pub: device.publickey, encPub: enc.publickey, label, blob, caps: [...caps]
|
|
1711
|
+
const r = await createPasswordLogin({
|
|
1712
|
+
user, password, label, caps: [...caps],
|
|
1713
|
+
begin: (req) => loginsRequest('register-begin', req),
|
|
1714
|
+
finish: (req) => loginsRequest('register-finish', req)
|
|
1724
1715
|
})
|
|
1725
1716
|
const { loginAddress } = await import('../lib/src/passwordLogins.js')
|
|
1726
1717
|
console.log('\nListo. Se entra con esta dirección y esa contraseña:')
|
package/src/vault.js
CHANGED
|
@@ -7,8 +7,12 @@ import { startVault as startVaultCore } from '../lib/src/core/vault.js'
|
|
|
7
7
|
import { nodePlatform } from './platform.js'
|
|
8
8
|
import { dataDir } from './paths.js'
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* `platform` suma o cambia piezas de la del binario: la Lambda (`lambda/session.mjs`) pone
|
|
12
|
+
* la URL que la despierta y un cliente del proxio que le dice cuándo hay trabajo.
|
|
13
|
+
*/
|
|
14
|
+
export async function startVault ({ dir = dataDir(), platform = null, ...opts } = {}) {
|
|
15
|
+
return startVaultCore({ ...opts, platform: { ...nodePlatform(dir), ...(platform || {}) } })
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
export default { startVault }
|