@dotrino/vault 0.92.0 → 0.93.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/bin/dotrino-env.js +38 -38
- package/package.json +1 -1
- package/src/admin.js +2 -2
- package/src/atrest.js +4 -4
- package/src/browser.js +10 -10
- package/src/core/approvals.js +3 -2
- package/src/core/libVersion.js +2 -2
- package/src/core/sealer.js +2 -2
- package/src/core/sealers.js +15 -15
- package/src/core/secretsStore.js +66 -66
- package/src/core/subacta.js +8 -8
- package/src/core/threadStore.js +2 -2
- package/src/core/transport.js +31 -31
- package/src/core/vault.js +247 -214
- package/src/enroll.js +1 -1
- package/src/env.js +10 -10
- package/src/index.js +7 -7
- package/src/kek.js +6 -6
- package/src/localdesk.js +10 -10
- package/src/lock.js +18 -18
- package/src/loginClient.js +23 -23
- package/src/passwordLogins.js +8 -8
- package/src/proc.js +5 -5
- package/src/procContext.js +2 -2
- package/src/service.js +67 -43
- package/src/sshKeys.js +3 -3
- package/src/version.js +4 -4
package/src/service.js
CHANGED
|
@@ -174,8 +174,8 @@ async function freshClient (proxyUrl, connectTimeoutMs = 20000, master = null) {
|
|
|
174
174
|
// reventaría al negociar; en un navegador es nativo y es el camino directo que hay que
|
|
175
175
|
// preferir. Se mira si está, en vez de apagarlo a mano para siempre: el día que este
|
|
176
176
|
// código corra en un navegador —o que Node lo traiga— se enciende solo.
|
|
177
|
-
const
|
|
178
|
-
const client = new WebSocketProxyClient({ url: proxyUrl, enableWebRTC:
|
|
177
|
+
const hasWebRTC = typeof globalThis.RTCPeerConnection === 'function'
|
|
178
|
+
const client = new WebSocketProxyClient({ url: proxyUrl, enableWebRTC: hasWebRTC, autoReconnect: false })
|
|
179
179
|
// connect() del cliente solo se resuelve con 'connected' y solo rechaza en el
|
|
180
180
|
// evento 'error' de transporte: si el socket cierra LIMPIO antes de 'connected'
|
|
181
181
|
// (p.ej. el proxy banea la IP y envía close 1008), la promesa quedaría colgada
|
|
@@ -207,16 +207,40 @@ async function identifyAsService (client, device) {
|
|
|
207
207
|
/** Cuánto espera un agente a que alguien apruebe (el pedido vive 5 min en la bóveda). */
|
|
208
208
|
export const APPROVAL_TIMEOUT_MS = 5 * 60 * 1000 + 10 * 1000
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Espera la respuesta de la bóveda. Con `guard` (las peticiones de SECRETOS), un `vault.error`
|
|
212
|
+
* solo cuenta si viene firmado con la llave de sellado que nombra el acta y atado a ESTA
|
|
213
|
+
* petición (`re`, la huella de su firma): lo que va a la cuenta lo reciben todas sus bóvedas,
|
|
214
|
+
* y la que no puede servir contestaba con un error que se tomaba por la respuesta (accounts se
|
|
215
|
+
* quedaba sin secretos, 2026-10-18). Un error que no se verifica no corta nada; si al acabar el
|
|
216
|
+
* plazo nadie contestó otra cosa, se dice cuál fue, y que no se pudo comprobar.
|
|
217
|
+
*/
|
|
218
|
+
function waitForMsg (client, predicate, timeoutMs = 30000, guard = null) {
|
|
211
219
|
return new Promise((resolve, reject) => {
|
|
212
|
-
|
|
213
|
-
|
|
220
|
+
let unverified = null
|
|
221
|
+
const off = client.on('message', async (_from, payload) => {
|
|
222
|
+
if (!payload || typeof payload !== 'object' || !predicate(payload)) return
|
|
223
|
+
if (guard && payload.type === MSG.ERROR && !await isMyVaultError(payload, guard)) { unverified = payload; return }
|
|
224
|
+
cleanup(); resolve(payload)
|
|
214
225
|
})
|
|
215
|
-
const t = setTimeout(() => {
|
|
226
|
+
const t = setTimeout(() => {
|
|
227
|
+
cleanup()
|
|
228
|
+
if (unverified) reject(Object.assign(new Error(`no verifiable reply from the vault; an unverified one said: ${unverified.error}`), { code: 'unverified-error' }))
|
|
229
|
+
else reject(new Error('timeout waiting for the vault reply'))
|
|
230
|
+
}, timeoutMs)
|
|
216
231
|
const cleanup = () => { off(); clearTimeout(t) }
|
|
217
232
|
})
|
|
218
233
|
}
|
|
219
234
|
|
|
235
|
+
/** La huella con la que la bóveda ata un error a la petición que lo causó (`re`). */
|
|
236
|
+
export const requestRef = (signature) => createHash('sha256').update(String(signature || '')).digest('hex')
|
|
237
|
+
|
|
238
|
+
/** ¿Este error lo firmó una llave de sellado del acta, y es para esta petición? */
|
|
239
|
+
export async function isMyVaultError (p, { re, masterPubkey, knownSeq = null }) {
|
|
240
|
+
if (!p?.signed || p.signed.re !== re || p.signed.op !== 'secrets.error' || p.signed.error !== p.error) return false
|
|
241
|
+
try { await verifyResponder({ body: p.signed, seal: p.seal, acta: p.acta, masterPubkey, knownSeq }); return true } catch (_) { return false }
|
|
242
|
+
}
|
|
243
|
+
|
|
220
244
|
const identityFileOf = (dir) => path.join(dir, IDENTITY_FILE)
|
|
221
245
|
|
|
222
246
|
/**
|
|
@@ -521,7 +545,7 @@ export async function registerEncKey ({ dir, ns, proxyUrl, masterPubkey, device,
|
|
|
521
545
|
await identifyAsService(client, device)
|
|
522
546
|
const data = { op: 'enckey', ns, encPub, publickey: device.publickey, ts: Date.now() }
|
|
523
547
|
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, data })
|
|
524
|
-
const pending = waitForMsg(client, (p) => p.type === MSG.SECRETS_RESULT || p.type === MSG.ERROR, timeoutMs)
|
|
548
|
+
const pending = waitForMsg(client, (p) => p.type === MSG.SECRETS_RESULT || p.type === MSG.ERROR, timeoutMs, { re: requestRef(signature), masterPubkey, knownSeq: saved?.actaSeq ?? null })
|
|
525
549
|
client.sendByPubkey(masterPubkey, { type: MSG.SECRETS, data, signature, cert })
|
|
526
550
|
const res = await pending
|
|
527
551
|
if (res.type === MSG.ERROR) throw vaultError(res)
|
|
@@ -573,7 +597,7 @@ export async function storeVars ({ dir, ns, proxyUrl, masterPubkey, device, cert
|
|
|
573
597
|
const ask = async (data) => {
|
|
574
598
|
const signed = { ...data, ns, publickey: device.publickey, ts: Date.now() }
|
|
575
599
|
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, data: signed })
|
|
576
|
-
const pending = waitForMsg(client, (p) => p.type === MSG.SECRETS_RESULT || p.type === MSG.ERROR, timeoutMs)
|
|
600
|
+
const pending = waitForMsg(client, (p) => p.type === MSG.SECRETS_RESULT || p.type === MSG.ERROR, timeoutMs, { re: requestRef(signature), masterPubkey, knownSeq: saved?.actaSeq ?? null })
|
|
577
601
|
client.sendByPubkey(masterPubkey, { type: MSG.SECRETS, data: signed, signature, cert })
|
|
578
602
|
const res = await pending
|
|
579
603
|
if (res.type === MSG.ERROR) throw vaultError(res)
|
|
@@ -627,7 +651,7 @@ export async function fetchDigest ({ dir, ns, proxyUrl, masterPubkey, device, ce
|
|
|
627
651
|
await identifyAsService(client, device)
|
|
628
652
|
const data = { op: 'digest', ns, publickey: device.publickey, ts: Date.now() }
|
|
629
653
|
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, data })
|
|
630
|
-
const pending = waitForMsg(client, (p) => p.type === MSG.ERROR || (p.type === MSG.SECRETS_RESULT && p.body?.op === 'secrets.digest'), timeoutMs)
|
|
654
|
+
const pending = waitForMsg(client, (p) => p.type === MSG.ERROR || (p.type === MSG.SECRETS_RESULT && p.body?.op === 'secrets.digest'), timeoutMs, { re: requestRef(signature), masterPubkey, knownSeq: saved?.actaSeq ?? null })
|
|
631
655
|
client.sendByPubkey(masterPubkey, { type: MSG.SECRETS, data, signature, cert })
|
|
632
656
|
const res = await pending
|
|
633
657
|
if (res.type === MSG.ERROR) throw vaultError(res)
|
|
@@ -792,7 +816,7 @@ async function reportToVault (op, { product, version, from = null, dir, proxyUrl
|
|
|
792
816
|
await identifyAsService(client, device)
|
|
793
817
|
const data = { op, product, version, ...(typeof from === 'string' && from ? { from } : {}), publickey: device.publickey, ts: Date.now() }
|
|
794
818
|
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, data })
|
|
795
|
-
const pending = waitForMsg(client, (p) => p.type === MSG.ERROR || (p.type === MSG.SECRETS_RESULT && p.body?.op === op && p.body.product === product && p.body.version === version), timeoutMs)
|
|
819
|
+
const pending = waitForMsg(client, (p) => p.type === MSG.ERROR || (p.type === MSG.SECRETS_RESULT && p.body?.op === op && p.body.product === product && p.body.version === version), timeoutMs, { re: requestRef(signature), masterPubkey, knownSeq: saved?.actaSeq ?? null })
|
|
796
820
|
client.sendByPubkey(masterPubkey, { type: MSG.SECRETS, data, signature, cert })
|
|
797
821
|
const res = await pending.catch((e) => {
|
|
798
822
|
if (/timeout/.test(e.message)) throw Object.assign(new Error(`${op}: the vault did not answer`), { code: 'vault-no-reply' })
|
|
@@ -844,7 +868,7 @@ export async function fetchSecrets ({ dir, ns, proxyUrl, masterPubkey, device, c
|
|
|
844
868
|
* Eso pasó — renovar guardaba el papel nuevo y acto seguido apuntar el acta lo pisaba con
|
|
845
869
|
* el viejo, que ya estaba revocado. Al siguiente arranque: «unauthorized: revoked».
|
|
846
870
|
*/
|
|
847
|
-
const
|
|
871
|
+
const remember = (patch) => {
|
|
848
872
|
if (!saved) return
|
|
849
873
|
saved = { ...saved, ...patch }
|
|
850
874
|
if (dir) { try { writeServiceIdentity(dir, saved) } catch (_) {} }
|
|
@@ -858,14 +882,14 @@ export async function fetchSecrets ({ dir, ns, proxyUrl, masterPubkey, device, c
|
|
|
858
882
|
* ahí en adelante. Un pin que retrocede no es un pin — se llama `maxSeq` en el diseño
|
|
859
883
|
* justo por esto (`acta-de-perfil.md` §2.3).
|
|
860
884
|
*/
|
|
861
|
-
const
|
|
885
|
+
const recordActa = (acta) => {
|
|
862
886
|
if (typeof acta?.seq !== 'number') return
|
|
863
887
|
if (typeof saved?.actaSeq === 'number' && acta.seq <= saved.actaSeq) return
|
|
864
888
|
// Y QUIÉNES REPARTEN. Sale del acta, que llega firmada en cada respuesta, así que no
|
|
865
889
|
// hay una lista que mantener a mano ni un `.env` que se olvide: darle `replica` a un
|
|
866
890
|
// aparato basta para que sus servicios sepan preguntarle cuando la bóveda no esté.
|
|
867
891
|
const replicas = (acta.members || []).filter((m) => memberCan(acta, m.pub, 'replica')).map((m) => m.pub)
|
|
868
|
-
|
|
892
|
+
remember({ actaSeq: acta.seq, replicas })
|
|
869
893
|
}
|
|
870
894
|
|
|
871
895
|
// DOS MOTIVOS PARA PEDIR PAPEL NUEVO:
|
|
@@ -874,15 +898,15 @@ export async function fetchSecrets ({ dir, ns, proxyUrl, masterPubkey, device, c
|
|
|
874
898
|
// servicio. Sin esta rama la migración no terminaba nunca — el disparador comparaba
|
|
875
899
|
// `seq` contra `seq`, y un papel viejo no tiene ninguno, así que no saltaba jamás.
|
|
876
900
|
// · o el acta que hemos visto va por delante del papel: el dueño cambió permisos.
|
|
877
|
-
const
|
|
878
|
-
const
|
|
879
|
-
const
|
|
901
|
+
const legacy = !!cert && typeof cert.seq !== 'number'
|
|
902
|
+
const behind = typeof saved?.actaSeq === 'number' && typeof cert?.seq === 'number' && saved.actaSeq > cert.seq
|
|
903
|
+
const renew = legacy || behind
|
|
880
904
|
|
|
881
905
|
// RENOVAR YA NO ES UNA TAREA DE CALENDARIO. El papel no vence: lo único que obliga a
|
|
882
906
|
// pedir otro es que la bóveda diga que el nuestro se quedó atrás respecto del acta, y
|
|
883
907
|
// eso lo contesta ella cuando lo pide (`acta-vieja`). Aquí se intenta una vez y no se
|
|
884
908
|
// insiste: si falla, se sigue con el papel que hay — que probablemente valga.
|
|
885
|
-
if (
|
|
909
|
+
if (renew) {
|
|
886
910
|
try {
|
|
887
911
|
const data = { op: 'renew', publickey: device.publickey, ts: Date.now() }
|
|
888
912
|
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, data })
|
|
@@ -897,7 +921,7 @@ export async function fetchSecrets ({ dir, ns, proxyUrl, masterPubkey, device, c
|
|
|
897
921
|
(await checkVaultReply({ acta: res.acta, cert: res.cert, vault: masterPubkey, sub: device.publickey })).ok
|
|
898
922
|
if (ok) {
|
|
899
923
|
cert = res.cert
|
|
900
|
-
|
|
924
|
+
remember({ cert })
|
|
901
925
|
// QUIEN NO PASA `dir` GUARDA ÉL. Un servicio que lleva su identidad en su propio
|
|
902
926
|
// archivo (`link.json`) y nos pasa el enlace a mano no tiene `saved`, así que
|
|
903
927
|
// `recordar` no escribe nada: sin esto renovaba en cada arranque y volvía a
|
|
@@ -937,18 +961,18 @@ export async function fetchSecrets ({ dir, ns, proxyUrl, masterPubkey, device, c
|
|
|
937
961
|
// exactamente el caso que un replicador existe para cubrir.
|
|
938
962
|
//
|
|
939
963
|
// La bóveda va PRIMERO siempre: un replicador solo puede estar igual de al día o menos.
|
|
940
|
-
const
|
|
964
|
+
const targets = [masterPubkey, ...(saved?.replicas || []).filter((k) => k !== masterPubkey)]
|
|
941
965
|
// El plazo se reparte: no tiene sentido esperar el timeout entero a una bóveda apagada
|
|
942
966
|
// y quedarse sin tiempo para preguntarle a quien sí está.
|
|
943
|
-
const
|
|
967
|
+
const byTarget = Math.max(4000, Math.round(timeoutMs / targets.length))
|
|
944
968
|
let res = null
|
|
945
|
-
let
|
|
946
|
-
for (const
|
|
947
|
-
const pending = waitForMsg(client, (p) => p.type === MSG.SECRETS_RESULT || p.type === MSG.ERROR,
|
|
948
|
-
client.sendByPubkey(
|
|
949
|
-
try { res = await pending; break } catch (e) {
|
|
969
|
+
let last = null
|
|
970
|
+
for (const target of targets) {
|
|
971
|
+
const pending = waitForMsg(client, (p) => p.type === MSG.SECRETS_RESULT || p.type === MSG.ERROR, byTarget, { re: requestRef(signature), masterPubkey, knownSeq: saved?.actaSeq ?? null })
|
|
972
|
+
client.sendByPubkey(target, { type: MSG.SECRETS, data, signature, cert })
|
|
973
|
+
try { res = await pending; break } catch (e) { last = e }
|
|
950
974
|
}
|
|
951
|
-
if (!res) throw
|
|
975
|
+
if (!res) throw last || new Error('nobody answered: neither the vault nor any replica')
|
|
952
976
|
if (res.type === MSG.ERROR) throw vaultError(res)
|
|
953
977
|
|
|
954
978
|
// CAJÓN CON APROBACIÓN: la bóveda contesta «pendiente» (firmado) y la respuesta de
|
|
@@ -956,10 +980,10 @@ export async function fetchSecrets ({ dir, ns, proxyUrl, masterPubkey, device, c
|
|
|
956
980
|
// sigue identificada. Se espera lo que dura el pedido; denegado o vencido es error.
|
|
957
981
|
if (res.body?.op === 'secrets.pending' && res.body.ns === ns) {
|
|
958
982
|
await verifyResponder({ body: res.body, seal: res.seal, acta: res.acta, masterPubkey, knownSeq: saved?.actaSeq ?? null })
|
|
959
|
-
|
|
983
|
+
recordActa(res.acta)
|
|
960
984
|
try { onPending?.({ id: res.body.id, ns, exp: res.body.exp }) } catch (_) {}
|
|
961
985
|
const until = typeof res.body.exp === 'number' ? Math.max(5000, res.body.exp - Date.now() + 5000) : approvalTimeoutMs
|
|
962
|
-
res = await waitForMsg(client, (p) => (p.type === MSG.SECRETS_RESULT && p.body?.op === 'secrets.result') || p.type === MSG.ERROR, Math.min(until, approvalTimeoutMs))
|
|
986
|
+
res = await waitForMsg(client, (p) => (p.type === MSG.SECRETS_RESULT && p.body?.op === 'secrets.result') || p.type === MSG.ERROR, Math.min(until, approvalTimeoutMs), { re: requestRef(signature), masterPubkey, knownSeq: saved?.actaSeq ?? null })
|
|
963
987
|
.catch((e) => {
|
|
964
988
|
if (/timeout/.test(e.message)) throw Object.assign(new Error('approval: nobody approved the request in time'), { code: 'unanswered' })
|
|
965
989
|
throw e
|
|
@@ -981,7 +1005,7 @@ export async function fetchSecrets ({ dir, ns, proxyUrl, masterPubkey, device, c
|
|
|
981
1005
|
// dentro se usa hasta después de verificar.
|
|
982
1006
|
const payload = await openSealed({ privateKey: eph.privateKey, enc: body.enc })
|
|
983
1007
|
await verifyResponder({ body, seal: res.seal, acta: payload?.acta, masterPubkey, knownSeq: saved?.actaSeq ?? null })
|
|
984
|
-
|
|
1008
|
+
recordActa(payload?.acta)
|
|
985
1009
|
// La huella de lo que se acaba de recibir: con ella se compara después (`fetchDigest`)
|
|
986
1010
|
// sin volver a pedir claves. Ya comprobada: va dentro del cuerpo firmado.
|
|
987
1011
|
if (typeof body.digest === 'string') lastDigests.set(digestKey(ns, device.publickey), body.digest)
|
|
@@ -1117,10 +1141,10 @@ export async function makeSealCheck (acta, masterPubkey, log = console.log) {
|
|
|
1117
1141
|
log('[vault] ⚠ the record does not come from the master this agent knows: envelope provenance NOT checked')
|
|
1118
1142
|
return () => {}
|
|
1119
1143
|
}
|
|
1120
|
-
let
|
|
1144
|
+
let warned = false
|
|
1121
1145
|
return async (owner, key, gen, e, seal) => {
|
|
1122
1146
|
if (!seal?.sig) {
|
|
1123
|
-
if (!
|
|
1147
|
+
if (!warned) { warned = true; log('[vault] ⚠ some envelopes carry no signature (sealed before this vault could sign)') }
|
|
1124
1148
|
return
|
|
1125
1149
|
}
|
|
1126
1150
|
const pub = sealKeyAt(acta, seal.seq)
|
|
@@ -1142,23 +1166,23 @@ async function openSealedBundle (sealed, ident, acta = null, masterPubkey = null
|
|
|
1142
1166
|
// TODAS las envolturas de este aparato; se abren perezosamente, solo las que hagan
|
|
1143
1167
|
// falta. `sealed.ns`/`sealed.dev` (una sola, la vigente) siguen entrando: es el bundle
|
|
1144
1168
|
// de v4 y sirve para lo que ese vault selló.
|
|
1145
|
-
const
|
|
1146
|
-
const añade = (
|
|
1169
|
+
const byGen = { ns: new Map(), dev: new Map() }
|
|
1170
|
+
const añade = (which, info) => { if (info?.wrap) byGen[which].set(info.gen ?? 0, info.wrap) }
|
|
1147
1171
|
añade('ns', sealed.ns); añade('dev', sealed.dev)
|
|
1148
|
-
for (const
|
|
1172
|
+
for (const which of ['ns', 'dev']) for (const info of sealed.wraps?.[which] || []) añade(which, info)
|
|
1149
1173
|
|
|
1150
|
-
const
|
|
1151
|
-
const
|
|
1152
|
-
if (
|
|
1174
|
+
const opened = { ns: new Map(), dev: new Map() }
|
|
1175
|
+
const cekFrom = async (which, gen) => {
|
|
1176
|
+
if (opened[which].has(gen)) return opened[which].get(gen)
|
|
1153
1177
|
// Un bundle de v4 no traía `gen` en la envoltura: si solo hay una, es esa.
|
|
1154
|
-
const wrap =
|
|
1178
|
+
const wrap = byGen[which].get(gen) ?? (byGen[which].size === 1 ? [...byGen[which].values()][0] : null)
|
|
1155
1179
|
if (!wrap) return null
|
|
1156
1180
|
const cek = await openWrap({ wrap, myEncPrivateKey: mine })
|
|
1157
|
-
|
|
1181
|
+
opened[which].set(gen, cek)
|
|
1158
1182
|
return cek
|
|
1159
1183
|
}
|
|
1160
1184
|
|
|
1161
|
-
const
|
|
1185
|
+
const checkSignature = await makeSealCheck(acta, masterPubkey)
|
|
1162
1186
|
|
|
1163
1187
|
const out = {}
|
|
1164
1188
|
for (const [key, e] of Object.entries(sealed.entries || {})) {
|
|
@@ -1177,10 +1201,10 @@ async function openSealedBundle (sealed, ident, acta = null, masterPubkey = null
|
|
|
1177
1201
|
{ code: 'plaintext-var' })
|
|
1178
1202
|
}
|
|
1179
1203
|
// `owner` dice de qué cajón salió, y `gen` con qué llave de ese cajón se abre.
|
|
1180
|
-
const
|
|
1204
|
+
const which = String(e.owner || '').startsWith('dev:') ? 'dev' : 'ns'
|
|
1181
1205
|
const gen = e.gen ?? e.e?.gen ?? 0
|
|
1182
|
-
await
|
|
1183
|
-
const cek = await
|
|
1206
|
+
await checkSignature(e.owner, key, gen, e.e, e.seal)
|
|
1207
|
+
const cek = await cekFrom(which, gen)
|
|
1184
1208
|
if (!cek) {
|
|
1185
1209
|
// NO ES UN TROPIEZO: ES UN ESTADO QUE HAY QUE ARREGLAR EN LA BÓVEDA.
|
|
1186
1210
|
//
|
package/src/sshKeys.js
CHANGED
|
@@ -108,9 +108,9 @@ export function signSsh (k, data, flags = 0) {
|
|
|
108
108
|
if (k.type === 'ssh-rsa') {
|
|
109
109
|
// Sin flags es el `ssh-rsa` (SHA-1) histórico, que los servidores modernos rechazan;
|
|
110
110
|
// OpenSSH pide rsa-sha2-256/512 con las banderas del agente (RFC 8332).
|
|
111
|
-
const
|
|
112
|
-
const hash =
|
|
113
|
-
return Buffer.concat([sshString(Buffer.from(
|
|
111
|
+
const something = (flags & SSH_AGENT_RSA_SHA2_512) ? 'rsa-sha2-512' : (flags & SSH_AGENT_RSA_SHA2_256) ? 'rsa-sha2-256' : 'ssh-rsa'
|
|
112
|
+
const hash = something === 'rsa-sha2-512' ? 'sha512' : something === 'rsa-sha2-256' ? 'sha256' : 'sha1'
|
|
113
|
+
return Buffer.concat([sshString(Buffer.from(something)), sshString(nodeSign(hash, Buffer.from(data), k.privateKey))])
|
|
114
114
|
}
|
|
115
115
|
throw new Error('ssh: cannot sign with ' + k.type)
|
|
116
116
|
}
|
package/src/version.js
CHANGED
|
@@ -14,10 +14,10 @@ import fs from 'node:fs'
|
|
|
14
14
|
import { dirname, join } from 'node:path'
|
|
15
15
|
import { fileURLToPath } from 'node:url'
|
|
16
16
|
|
|
17
|
-
function
|
|
17
|
+
function read () {
|
|
18
18
|
try {
|
|
19
|
-
const
|
|
20
|
-
for (const p of [join(
|
|
19
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
20
|
+
for (const p of [join(here, '..', 'package.json'), join(here, '..', '..', 'package.json')]) {
|
|
21
21
|
try {
|
|
22
22
|
const v = JSON.parse(fs.readFileSync(p, 'utf8'))
|
|
23
23
|
if (v?.name === '@dotrino/vault' && typeof v.version === 'string') return v.version
|
|
@@ -27,4 +27,4 @@ function leer () {
|
|
|
27
27
|
return '0.0.0'
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export const PKG_VERSION =
|
|
30
|
+
export const PKG_VERSION = read()
|