@dotrino/identity 0.12.0 → 0.13.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.js +1 -1
- package/src/node.js +1 -1
- package/vault/capabilities.js +22 -0
- package/vault/core.js +1 -1
- package/vault/remote.js +8 -5
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -426,4 +426,4 @@ export class Identity {
|
|
|
426
426
|
|
|
427
427
|
// Helpers de capacidad SIN clave maestra (lado dispositivo + verificación), reutilizables
|
|
428
428
|
// por apps/bridges sin cargar el iframe del vault.
|
|
429
|
-
export { makeDeviceKey, signWithDevice, verifyDelegation, verifyChain, pubkeyId, deriveSAS, verifyDeviceSig, MAX_DELEGATION_MS, DEFAULT_DELEGATION_MS } from '../vault/capabilities.js'
|
|
429
|
+
export { makeDeviceKey, signWithDevice, verifyDelegation, verifyChain, pubkeyId, deriveSAS, verifyDeviceSig, makePairingCode, commitCode, MAX_DELEGATION_MS, DEFAULT_DELEGATION_MS } from '../vault/capabilities.js'
|
package/src/node.js
CHANGED
|
@@ -186,4 +186,4 @@ export default Identity
|
|
|
186
186
|
|
|
187
187
|
// Helpers de capacidad SIN clave maestra (lado dispositivo + verificación), para que
|
|
188
188
|
// un bridge/bot Node pueda crear su clave, firmar acciones y verificar cadenas D←P.
|
|
189
|
-
export { makeDeviceKey, signWithDevice, verifyDelegation, verifyChain, pubkeyId, deriveSAS, verifyDeviceSig, MAX_DELEGATION_MS, DEFAULT_DELEGATION_MS } from '../vault/capabilities.js'
|
|
189
|
+
export { makeDeviceKey, signWithDevice, verifyDelegation, verifyChain, pubkeyId, deriveSAS, verifyDeviceSig, makePairingCode, commitCode, MAX_DELEGATION_MS, DEFAULT_DELEGATION_MS } from '../vault/capabilities.js'
|
package/vault/capabilities.js
CHANGED
|
@@ -77,6 +77,28 @@ export async function deriveSAS (master, dpub, sn) {
|
|
|
77
77
|
return String(n % 1000000).padStart(6, '0')
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Código de emparejamiento ALEATORIO de 6 dígitos. Lo genera el DISPOSITIVO y lo MUESTRA;
|
|
82
|
+
* el usuario lo tipea en el vault. El vault NO lo conoce: el dispositivo solo manda un
|
|
83
|
+
* COMPROMISO (`commitCode`), no el código → el vault lo aprende únicamente cuando vos se lo
|
|
84
|
+
* das, tipeándolo. Así, aprobar exige TENER el dispositivo (de ahí sale el código).
|
|
85
|
+
*/
|
|
86
|
+
export function makePairingCode () {
|
|
87
|
+
const b = crypto.getRandomValues(new Uint8Array(4))
|
|
88
|
+
const n = (((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]) >>> 0)
|
|
89
|
+
return String(n % 1000000).padStart(6, '0')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Compromiso del código: `SHA-256(code ‖ dpub ‖ sn)` en hex. Va en el ENROLL (no el código).
|
|
94
|
+
* Liga el código a ESTE dispositivo y sesión (no reusable para otro). El vault lo guarda y,
|
|
95
|
+
* cuando tipeás el código, recomputa y compara → verifica posesión sin conocer el código antes.
|
|
96
|
+
*/
|
|
97
|
+
export async function commitCode ({ code, dpub, sn }) {
|
|
98
|
+
const h = await crypto.subtle.digest('SHA-256', enc(canonicalStringify({ code: String(code), sub: dpub, sn })))
|
|
99
|
+
return [...new Uint8Array(h)].map((x) => x.toString(16).padStart(2, '0')).join('')
|
|
100
|
+
}
|
|
101
|
+
|
|
80
102
|
/** Cuerpo canónico del certificado (lo que se firma): el cert SIN la firma. */
|
|
81
103
|
export function delegationBody (cert) {
|
|
82
104
|
return { v: cert.v, iss: cert.iss, sub: cert.sub, scope: cert.scope, iat: cert.iat, exp: cert.exp, nonce: cert.nonce }
|
package/vault/core.js
CHANGED
|
@@ -519,7 +519,7 @@ export async function createIdentityCore ({ kv, peers, makeSync = null }) {
|
|
|
519
519
|
// TU identidad (P) desde la maestra M → una sola identidad (signData/identify/cert = P).
|
|
520
520
|
let device
|
|
521
521
|
try { const k = JSON.parse(kv.getItem(KEY_STORAGE)); device = { publickey: JSON.stringify(k.publicJwk), privateJwk: k.privateJwk } } catch (_) { device = undefined }
|
|
522
|
-
const res = await remoteEnroll({ qr, device, onChallenge: (c) => emitVault({ phase: 'challenge', deviceId: c.deviceId,
|
|
522
|
+
const res = await remoteEnroll({ qr, device, onChallenge: (c) => emitVault({ phase: 'challenge', deviceId: c.deviceId, code: c.code }) })
|
|
523
523
|
kv.setItem(VAULT_DEVICE_STORAGE, JSON.stringify(res.device))
|
|
524
524
|
kv.setItem(VAULT_CERT_STORAGE, JSON.stringify({ cert: res.cert, master: res.master, proxy: res.proxy, deviceId: res.deviceId, pairedAt: Date.now() }))
|
|
525
525
|
emitVault({ phase: 'paired', deviceId: res.deviceId, master: res.master })
|
package/vault/remote.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* No reimplementa cripto: usa `@dotrino/identity/capabilities`. Transporte:
|
|
13
13
|
* `@dotrino/proxy-client` (importado perezosamente; solo se carga al emparejar).
|
|
14
14
|
*/
|
|
15
|
-
import { makeDeviceKey, signWithDevice, verifyDelegation,
|
|
15
|
+
import { makeDeviceKey, signWithDevice, verifyDelegation, makePairingCode, commitCode, pubkeyId } from './capabilities.js'
|
|
16
16
|
|
|
17
17
|
const MSG = {
|
|
18
18
|
ENROLL: 'vault.enroll',
|
|
@@ -24,7 +24,7 @@ const MSG = {
|
|
|
24
24
|
/**
|
|
25
25
|
* @param {Object} opts
|
|
26
26
|
* @param {{v:number, iss:string, proxy:string, token:string, sn:string}} opts.qr QR v2 del vault.
|
|
27
|
-
* @param {(c:{deviceId:string,
|
|
27
|
+
* @param {(c:{deviceId:string, code:string})=>void} [opts.onChallenge] Para mostrar el código a tipear en el PC.
|
|
28
28
|
* @param {string} [opts.label]
|
|
29
29
|
* @param {number} [opts.approveTimeoutMs] Espera de la aprobación humana (def 3 min).
|
|
30
30
|
* @returns {Promise<{device, cert, master:string, proxy:string, deviceId:string}>}
|
|
@@ -40,14 +40,17 @@ export async function enrollDevice ({ qr, device, onChallenge, label = '', appro
|
|
|
40
40
|
// identify/cert son la misma P).
|
|
41
41
|
const dev = device || await makeDeviceKey({ label })
|
|
42
42
|
const deviceId = (await pubkeyId(dev.publickey)).slice(0, 8).toUpperCase().replace(/(.{4})(.{4})/, '$1-$2')
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// El DISPOSITIVO genera el código y manda solo su COMPROMISO (no el código). El vault
|
|
44
|
+
// aprende el código únicamente cuando vos lo tipeás en el PC → aprobar exige tener el dispositivo.
|
|
45
|
+
const code = makePairingCode()
|
|
46
|
+
const commit = await commitCode({ code, dpub: dev.publickey, sn: qr.sn })
|
|
47
|
+
const data = { op: 'enroll', dpub: dev.publickey, token: qr.token, sn: qr.sn, commit, label, ts: Date.now() }
|
|
45
48
|
const { signature } = await signWithDevice({ privateJwk: dev.privateJwk, data })
|
|
46
49
|
|
|
47
50
|
const enrolled = new Promise((resolve, reject) => {
|
|
48
51
|
const off = client.on('message', (_from, p) => {
|
|
49
52
|
if (!p || typeof p !== 'object') return
|
|
50
|
-
if (p.type === MSG.ENROLL_CHALLENGE) { try { onChallenge?.({ deviceId,
|
|
53
|
+
if (p.type === MSG.ENROLL_CHALLENGE) { try { onChallenge?.({ deviceId, code }) } catch (_) {} }
|
|
51
54
|
else if (p.type === MSG.ENROLLED) { cleanup(); resolve(p) }
|
|
52
55
|
else if (p.type === MSG.ERROR) { cleanup(); reject(new Error(p.error)) }
|
|
53
56
|
})
|