@dotrino/vaultd 0.162.1 → 0.162.3
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/deviceInfo.js +2 -4
- package/lib/src/sealed.js +7 -36
- package/lib/src/service.js +2 -2
- package/package.json +4 -4
- package/src/client.js +2 -2
- package/src/ctl.js +3 -5
- package/src/replica.js +2 -2
package/lib/src/deviceInfo.js
CHANGED
|
@@ -14,12 +14,10 @@
|
|
|
14
14
|
* Sirve cualquier enlace con la forma común `{ device: { publickey }, cert, iss, proxy }`
|
|
15
15
|
* (el de remote-agent y el de un servicio de dotrino-env).
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
17
|
+
import { keyLabel } from '@dotrino/identity/capabilities'
|
|
18
18
|
|
|
19
19
|
/** El id corto de una llave, como lo enseña la bóveda: `AB12-CD34`. */
|
|
20
|
-
export
|
|
21
|
-
return (await pubkeyId(publickey)).slice(0, 8).toUpperCase().replace(/(.{4})(.{4})/, '$1-$2')
|
|
22
|
-
}
|
|
20
|
+
export const shortId = keyLabel
|
|
23
21
|
|
|
24
22
|
/**
|
|
25
23
|
* La información del aparato a partir de su enlace.
|
package/lib/src/sealed.js
CHANGED
|
@@ -16,30 +16,13 @@
|
|
|
16
16
|
* P-256 + AES-GCM, llaves como JWK string.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
function b64 (buf) {
|
|
22
|
-
const bytes = new Uint8Array(buf)
|
|
23
|
-
let s = ''
|
|
24
|
-
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i])
|
|
25
|
-
return btoa(s)
|
|
26
|
-
}
|
|
19
|
+
import { wrapForMember, openWrap } from '@dotrino/identity/content'
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)
|
|
32
|
-
return out
|
|
33
|
-
}
|
|
21
|
+
// El sobre es el MISMO que el de las llaves de contenido de identity (ECDH P-256 efímero
|
|
22
|
+
// → AES-256-GCM, `epk` en JWK): aquí solo se le mete un JSON en vez de una CEK. Antes
|
|
23
|
+
// estaba escrito otra vez en este archivo, byte a byte igual.
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
const pub = await subtle.importKey(
|
|
37
|
-
'jwk', JSON.parse(publicJwkStr),
|
|
38
|
-
{ name: 'ECDH', namedCurve: 'P-256' }, false, []
|
|
39
|
-
)
|
|
40
|
-
const bits = await subtle.deriveBits({ name: 'ECDH', public: pub }, privateKey, 256)
|
|
41
|
-
return subtle.importKey('raw', bits, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt'])
|
|
42
|
-
}
|
|
25
|
+
const subtle = globalThis.crypto.subtle
|
|
43
26
|
|
|
44
27
|
/**
|
|
45
28
|
* Genera la llave ECDH EFÍMERA del solicitante (una por petición).
|
|
@@ -57,17 +40,7 @@ export async function makeEphemeralKey () {
|
|
|
57
40
|
* @returns {Promise<{epk:string, iv:string, ct:string}>}
|
|
58
41
|
*/
|
|
59
42
|
export async function seal ({ ek, payload }) {
|
|
60
|
-
|
|
61
|
-
const key = await deriveAesKey(eph.privateKey, ek)
|
|
62
|
-
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12))
|
|
63
|
-
const pt = new TextEncoder().encode(JSON.stringify(payload))
|
|
64
|
-
const ct = await subtle.encrypt({ name: 'AES-GCM', iv }, key, pt)
|
|
65
|
-
const epkJwk = await subtle.exportKey('jwk', eph.publicKey)
|
|
66
|
-
return {
|
|
67
|
-
epk: JSON.stringify({ kty: epkJwk.kty, crv: epkJwk.crv, x: epkJwk.x, y: epkJwk.y }),
|
|
68
|
-
iv: b64(iv),
|
|
69
|
-
ct: b64(ct)
|
|
70
|
-
}
|
|
43
|
+
return wrapForMember({ cek: JSON.stringify(payload), memberEncPub: ek })
|
|
71
44
|
}
|
|
72
45
|
|
|
73
46
|
/**
|
|
@@ -78,7 +51,5 @@ export async function openSealed ({ privateKey, enc }) {
|
|
|
78
51
|
if (!enc || typeof enc.epk !== 'string' || typeof enc.iv !== 'string' || typeof enc.ct !== 'string') {
|
|
79
52
|
throw new Error('invalid sealed envelope')
|
|
80
53
|
}
|
|
81
|
-
|
|
82
|
-
const pt = await subtle.decrypt({ name: 'AES-GCM', iv: fromB64(enc.iv) }, key, fromB64(enc.ct))
|
|
83
|
-
return JSON.parse(new TextDecoder().decode(pt))
|
|
54
|
+
return JSON.parse(await openWrap({ wrap: enc, myEncPrivateKey: privateKey }))
|
|
84
55
|
}
|
package/lib/src/service.js
CHANGED
|
@@ -23,7 +23,7 @@ import path from 'node:path'
|
|
|
23
23
|
import { createHash } from 'node:crypto'
|
|
24
24
|
import {
|
|
25
25
|
makeDeviceKey, makeDeviceEncKey, importDeviceEncKey, signWithDevice,
|
|
26
|
-
verifyDeviceSig, makePairingCode, commitCode, pubkeyId } from '@dotrino/identity/capabilities'
|
|
26
|
+
verifyDeviceSig, makePairingCode, commitCode, pubkeyId, keyLabel } from '@dotrino/identity/capabilities'
|
|
27
27
|
import { openWrap, wrapForMember, decryptWithCek } from '@dotrino/identity/content'
|
|
28
28
|
import { verifyActa, sealKeyAt, memberCan, checkVaultReply } from '@dotrino/identity/acta'
|
|
29
29
|
import { MSG, secretsScope, isValidSecretsNs, VAULT_PROTOCOL, VAULT_SPEAKS } from './protocol.js'
|
|
@@ -373,7 +373,7 @@ export async function enrollWithVault ({ qr, label = 'agent', expectedScope = nu
|
|
|
373
373
|
// aparato entra al acta pero no le llega ningún secreto, y no da error. Un replicador
|
|
374
374
|
// entra así A PROPÓSITO (ver `withEncKey`): reparte sobres cerrados y no abre ninguno.
|
|
375
375
|
const enc = withEncKey ? await makeDeviceEncKey() : null
|
|
376
|
-
const deviceId =
|
|
376
|
+
const deviceId = await keyLabel(device.publickey)
|
|
377
377
|
// Código de emparejamiento ALEATORIO: se muestra y NO se envía. La bóveda lo
|
|
378
378
|
// aprende solo cuando un humano lo tipea → aprobar exige TENER esta máquina.
|
|
379
379
|
const code = makePairingCode()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vaultd",
|
|
3
|
-
"version": "0.162.
|
|
3
|
+
"version": "0.162.3",
|
|
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": {
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@dotrino/compat": "0.4.0",
|
|
25
|
-
"@dotrino/identity": "0.
|
|
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.36.
|
|
28
|
+
"@dotrino/proxy-client": "0.36.2",
|
|
29
29
|
"@dotrino/roadmap": "0.4.30",
|
|
30
30
|
"@dotrino/store": "0.12.3",
|
|
31
31
|
"@dotrino/update": "0.9.0",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"!lib/src/types.d.ts"
|
|
52
52
|
],
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@dotrino/remote-agent": "0.
|
|
54
|
+
"@dotrino/remote-agent": "0.21.0",
|
|
55
55
|
"@types/node": "^22.0.0",
|
|
56
56
|
"typescript": "5.9.3"
|
|
57
57
|
},
|
package/src/client.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* estrictamente (firmado por la maestra que vio en el QR, y para SU clave) antes de
|
|
12
12
|
* guardarlo. La maestra nunca sale del vault.
|
|
13
13
|
*/
|
|
14
|
-
import { makeDeviceKey, makeDeviceEncKey, importDeviceEncKey, signWithDevice, verifyDeviceSig, makePairingCode, commitCode,
|
|
14
|
+
import { makeDeviceKey, makeDeviceEncKey, importDeviceEncKey, signWithDevice, verifyDeviceSig, makePairingCode, commitCode, keyLabel } from '@dotrino/identity/capabilities'
|
|
15
15
|
import { myContentKey, encryptWithCek, decryptWithKeyring } from '@dotrino/identity/content'
|
|
16
16
|
// Con qué se juzga lo que devuelve la bóveda (papel + acta): la regla del pilar, una sola.
|
|
17
17
|
import { checkVaultReply } from '@dotrino/identity/acta'
|
|
@@ -65,7 +65,7 @@ export async function enroll ({ qr, label = '', dir, onChallenge, approveTimeout
|
|
|
65
65
|
// La llave de CIFRADO del aparato: con su pública en el acta, la bóveda le envuelve la
|
|
66
66
|
// clave de contenido al admitirlo. Sin ella no podría usar el almacén, que solo entra cifrado.
|
|
67
67
|
const enc = await makeDeviceEncKey()
|
|
68
|
-
const myDeviceId =
|
|
68
|
+
const myDeviceId = await keyLabel(device.publickey)
|
|
69
69
|
// ESTE dispositivo genera el código y lo MUESTRA; solo viaja su COMPROMISO. El vault
|
|
70
70
|
// lo aprende cuando un humano lo tipea, y solo firma el cert si el compromiso coincide.
|
|
71
71
|
const code = makePairingCode()
|
package/src/ctl.js
CHANGED
|
@@ -26,7 +26,7 @@ import path from 'node:path'
|
|
|
26
26
|
import { execFileSync } from 'node:child_process'
|
|
27
27
|
import tty from 'node:tty'
|
|
28
28
|
import crypto from 'node:crypto'
|
|
29
|
-
import { pubkeyId } from '@dotrino/identity/capabilities'
|
|
29
|
+
import { pubkeyId, keyLabel } from '@dotrino/identity/capabilities'
|
|
30
30
|
import { dataDir } from './paths.js'
|
|
31
31
|
// EL CANAL LOCAL VA CIFRADO (ver `ipc.js`): por aquí salen la contraseña y los valores.
|
|
32
32
|
import { ipcRead, ipcWrite } from './ipc.js'
|
|
@@ -133,10 +133,8 @@ function requireDaemon () {
|
|
|
133
133
|
if (!alive(s.pid)) { console.error('El daemon no está corriendo. Arráncalo: %s', START_HINT); process.exit(1) }
|
|
134
134
|
return s
|
|
135
135
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return pubkeyId(sub).then((id) => id.slice(0, 8).toUpperCase().replace(/(.{4})(.{4})/, '$1-$2'))
|
|
139
|
-
}
|
|
136
|
+
// mismo formato que el daemon: AB12-CD34
|
|
137
|
+
const deviceIdOf = keyLabel
|
|
140
138
|
|
|
141
139
|
/** Lo que dijo un aprobador sobre sus avisos, para enseñarlo. No decide nada. */
|
|
142
140
|
function avisosTxt (notifiedAt) {
|
package/src/replica.js
CHANGED
|
@@ -24,7 +24,7 @@ import fs from 'node:fs'
|
|
|
24
24
|
import path from 'node:path'
|
|
25
25
|
import os from 'node:os'
|
|
26
26
|
import { WebSocketProxyClient } from '@dotrino/proxy-client'
|
|
27
|
-
import { signWithDevice, verifyDeviceSig, verifyChain, pubkeyId } from '@dotrino/identity/capabilities'
|
|
27
|
+
import { signWithDevice, verifyDeviceSig, verifyChain, pubkeyId, keyLabel } from '@dotrino/identity/capabilities'
|
|
28
28
|
import { verifyActa, memberCan, memberCanReadSecrets, sealKeyAt, sealersOf } from '@dotrino/identity/acta'
|
|
29
29
|
import { MSG, secretsScope, isValidSecretsNs } from '../lib/src/protocol.js'
|
|
30
30
|
import { FRESH_WINDOW_MS } from '../lib/src/enroll.js'
|
|
@@ -156,7 +156,7 @@ export async function runReplica ({ dir = replicaDir(), proxyUrl, log = console.
|
|
|
156
156
|
{ code: 'replica-not-enrolled' })
|
|
157
157
|
}
|
|
158
158
|
const url = proxyUrl || link.proxy || process.env.PROXY_URL || 'wss://proxy.dotrino.com'
|
|
159
|
-
const id =
|
|
159
|
+
const id = await keyLabel(link.device.publickey)
|
|
160
160
|
|
|
161
161
|
const firmar = (data) => signWithDevice({ privateJwk: link.device.privateJwk, data })
|
|
162
162
|
// CONTESTAR es por la CONEXIÓN (`send`) y no por la llave (`sendByPubkey`): `from` es el
|