keylock: password-based at-rest encryption

- Pre-boot password gate (main.js) with language selector; backend starts only after unlock.
- keylock.js: scrypt KEK + AES-256-GCM wrap/unwrap of the SSB secret and keyrings.
- crypto.js: keyrings encrypted/decrypted with the KEK.
- ssb_config.js: decrypted keys in memory (no plaintext secret on disk).
This commit is contained in:
s1to 2026-07-09 09:56:30 +02:00
parent ea5f5cbcc2
commit 851042e576
4 changed files with 294 additions and 5 deletions

148
main.js
View file

@ -67,9 +67,153 @@ console.log('[Oasis] Launching Oasis Social Network Utopia...');
console.log('[Oasis] Backend path:', backendPath);
console.log('[Oasis] HOME:', process.env.HOME);
try {
const http = require('http');
const keylock = require('./src/models/keylock');
const configPath = path.join(process.env.HOME, '.ssb');
let backendStarted = false;
function startBackend() {
if (backendStarted) return;
backendStarted = true;
try {
require(backendPath);
console.log('[Oasis] Backend started on port 3000');
} catch (error) {
} catch (error) {
console.error('[Oasis] Error starting backend:', error);
}
}
function unlockAndStart(keys) {
global.__OASIS_KEYS__ = keys;
for (const s of sockets) { try { s.destroy(); } catch (_) {} }
try { lockServer.close(() => startBackend()); } catch (_) { startBackend(); }
}
const MODE = keylock.isConfigured(configPath) ? 'unlock' : 'setup';
const LOCK_LANGS = ['en','es','fr','eu','de','it','pt','zh','ar','hi','ru'];
const LOCK_I18N = {
en: { name:"English", t_setup:"Create your password", t_unlock:"Unlock Oasis", s_setup:"It encrypts your identity and keys on this device. <b>If you forget it, you lose the account</b> (no recovery).", s_unlock:"Enter your password to decrypt your keys and start Oasis.", ph:"Password", ph2:"Repeat password", b_setup:"Create and enter", b_unlock:"Enter", e_min:"Minimum 6 characters", e_mism:"Passwords do not match", e_wrong:"Incorrect password", e_pre:"Error: ", m_unlock:"Unlocking…" },
es: { name:"Español", t_setup:"Crea tu contraseña", t_unlock:"Desbloquea Oasis", s_setup:"Cifrará tu identidad y tus claves en este dispositivo. <b>Si la olvidas, pierdes la cuenta</b> (no hay recuperación).", s_unlock:"Introduce tu contraseña para descifrar tus claves y arrancar Oasis.", ph:"Contraseña", ph2:"Repite la contraseña", b_setup:"Crear y entrar", b_unlock:"Entrar", e_min:"Mínimo 6 caracteres", e_mism:"Las contraseñas no coinciden", e_wrong:"Contraseña incorrecta", e_pre:"Error: ", m_unlock:"Desbloqueando…" },
fr: { name:"Français", t_setup:"Créez votre mot de passe", t_unlock:"Déverrouiller Oasis", s_setup:"Il chiffre votre identité et vos clés sur cet appareil. <b>Si vous l'oubliez, vous perdez le compte</b> (aucune récupération).", s_unlock:"Saisissez votre mot de passe pour déchiffrer vos clés et démarrer Oasis.", ph:"Mot de passe", ph2:"Répétez le mot de passe", b_setup:"Créer et entrer", b_unlock:"Entrer", e_min:"6 caractères minimum", e_mism:"Les mots de passe ne correspondent pas", e_wrong:"Mot de passe incorrect", e_pre:"Erreur : ", m_unlock:"Déverrouillage…" },
eu: { name:"Euskara", t_setup:"Sortu zure pasahitza", t_unlock:"Desblokeatu Oasis", s_setup:"Zure identitatea eta gakoak gailu honetan zifratzen ditu. <b>Ahazten baduzu, kontua galtzen duzu</b> (ez dago berreskuratzerik).", s_unlock:"Sartu zure pasahitza gakoak deszifratu eta Oasis abiarazteko.", ph:"Pasahitza", ph2:"Errepikatu pasahitza", b_setup:"Sortu eta sartu", b_unlock:"Sartu", e_min:"Gutxienez 6 karaktere", e_mism:"Pasahitzak ez datoz bat", e_wrong:"Pasahitz okerra", e_pre:"Errorea: ", m_unlock:"Desblokeatzen…" },
de: { name:"Deutsch", t_setup:"Erstelle dein Passwort", t_unlock:"Oasis entsperren", s_setup:"Es verschlüsselt deine Identität und Schlüssel auf diesem Gerät. <b>Wenn du es vergisst, verlierst du das Konto</b> (keine Wiederherstellung).", s_unlock:"Gib dein Passwort ein, um deine Schlüssel zu entschlüsseln und Oasis zu starten.", ph:"Passwort", ph2:"Passwort wiederholen", b_setup:"Erstellen und eintreten", b_unlock:"Eintreten", e_min:"Mindestens 6 Zeichen", e_mism:"Passwörter stimmen nicht überein", e_wrong:"Falsches Passwort", e_pre:"Fehler: ", m_unlock:"Entsperren…" },
it: { name:"Italiano", t_setup:"Crea la tua password", t_unlock:"Sblocca Oasis", s_setup:"Cifra la tua identità e le tue chiavi su questo dispositivo. <b>Se la dimentichi, perdi l'account</b> (nessun recupero).", s_unlock:"Inserisci la password per decifrare le chiavi e avviare Oasis.", ph:"Password", ph2:"Ripeti la password", b_setup:"Crea ed entra", b_unlock:"Entra", e_min:"Minimo 6 caratteri", e_mism:"Le password non coincidono", e_wrong:"Password errata", e_pre:"Errore: ", m_unlock:"Sblocco…" },
pt: { name:"Português", t_setup:"Crie a sua palavra-passe", t_unlock:"Desbloquear o Oasis", s_setup:"Cifra a sua identidade e chaves neste dispositivo. <b>Se a esquecer, perde a conta</b> (sem recuperação).", s_unlock:"Introduza a sua palavra-passe para decifrar as chaves e iniciar o Oasis.", ph:"Palavra-passe", ph2:"Repita a palavra-passe", b_setup:"Criar e entrar", b_unlock:"Entrar", e_min:"Mínimo 6 caracteres", e_mism:"As palavras-passe não coincidem", e_wrong:"Palavra-passe incorreta", e_pre:"Erro: ", m_unlock:"A desbloquear…" },
zh: { name:"中文", t_setup:"创建你的密码", t_unlock:"解锁 Oasis", s_setup:"它会在此设备上加密你的身份和密钥。<b>如果忘记,你将失去账户</b>(无法恢复)。", s_unlock:"输入密码以解密你的密钥并启动 Oasis。", ph:"密码", ph2:"重复密码", b_setup:"创建并进入", b_unlock:"进入", e_min:"至少 6 个字符", e_mism:"密码不一致", e_wrong:"密码错误", e_pre:"错误:", m_unlock:"正在解锁…" },
ar: { name:"العربية", t_setup:"أنشئ كلمة المرور", t_unlock:"فتح Oasis", s_setup:"يشفّر هويتك ومفاتيحك على هذا الجهاز. <b>إذا نسيتها، تفقد الحساب</b> (لا استعادة).", s_unlock:"أدخل كلمة المرور لفك تشفير مفاتيحك وتشغيل Oasis.", ph:"كلمة المرور", ph2:"أعد كلمة المرور", b_setup:"إنشاء ودخول", b_unlock:"دخول", e_min:"6 أحرف على الأقل", e_mism:"كلمتا المرور غير متطابقتين", e_wrong:"كلمة مرور غير صحيحة", e_pre:"خطأ: ", m_unlock:"جارٍ الفتح…" },
hi: { name:"हिन्दी", t_setup:"अपना पासवर्ड बनाएं", t_unlock:"Oasis अनलॉक करें", s_setup:"यह इस डिवाइस पर आपकी पहचान और कुंजियाँ एन्क्रिप्ट करता है। <b>भूल गए तो खाता खो देंगे</b> (कोई रिकवरी नहीं)।", s_unlock:"अपनी कुंजियाँ डिक्रिप्ट करने और Oasis शुरू करने के लिए पासवर्ड दर्ज करें।", ph:"पासवर्ड", ph2:"पासवर्ड दोहराएँ", b_setup:"बनाएँ और प्रवेश करें", b_unlock:"प्रवेश करें", e_min:"कम से कम 6 अक्षर", e_mism:"पासवर्ड मेल नहीं खाते", e_wrong:"गलत पासवर्ड", e_pre:"त्रुटि: ", m_unlock:"अनलॉक हो रहा है…" },
ru: { name:"Русский", t_setup:"Создайте пароль", t_unlock:"Разблокировать Oasis", s_setup:"Он шифрует вашу личность и ключи на этом устройстве. <b>Если забудете, потеряете аккаунт</b> (без восстановления).", s_unlock:"Введите пароль, чтобы расшифровать ключи и запустить Oasis.", ph:"Пароль", ph2:"Повторите пароль", b_setup:"Создать и войти", b_unlock:"Войти", e_min:"Минимум 6 символов", e_mism:"Пароли не совпадают", e_wrong:"Неверный пароль", e_pre:"Ошибка: ", m_unlock:"Разблокировка…" },
};
function lockPage(mode) {
const confirmField = mode === 'setup'
? '<input id="c" type="password" inputmode="text" autocomplete="new-password" />'
: '';
return '<!doctype html><!-- OASIS_LOCK_MARKER --><html lang="en"><head><meta charset="utf-8">'
+ '<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">'
+ '<title>Oasis</title><style>'
+ '*{box-sizing:border-box}body{margin:0;background:#121212;color:#eee;font-family:system-ui,sans-serif;'
+ 'min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}'
+ '.card{width:100%;max-width:360px;text-align:center}h1{color:#FFD700;font-size:22px;margin:0 0 8px}'
+ 'p{color:#aaa;font-size:14px;line-height:1.4;margin:0 0 20px}'
+ 'input{width:100%;padding:14px;margin:8px 0;border-radius:10px;border:1px solid #333;'
+ 'background:#1c1c1c;color:#eee;font-size:16px}'
+ 'button[type=submit]{width:100%;padding:14px;margin-top:12px;border:0;border-radius:10px;background:#FFD700;'
+ 'color:#111;font-size:16px;font-weight:600}button[type=submit]:disabled{opacity:.5}'
+ '#err{color:#ff6b6b;font-size:13px;min-height:18px;margin-top:8px}#msg{color:#FFD700;font-size:14px}'
+ '#langbtn{position:fixed;top:calc(env(safe-area-inset-top,0px) + 12px);right:12px;width:42px;height:42px;'
+ 'border-radius:50%;border:1px solid #333;background:#1c1c1c;color:#FFD700;font-size:13px;font-weight:700;'
+ 'padding:0;z-index:10;cursor:pointer;display:flex;align-items:center;justify-content:center}'
+ '.langmenu{position:fixed;top:calc(env(safe-area-inset-top,0px) + 60px);right:12px;background:#1c1c1c;'
+ 'border:1px solid #333;border-radius:12px;padding:6px;z-index:11;max-height:70vh;overflow:auto;min-width:150px;'
+ 'box-shadow:0 6px 20px rgba(0,0,0,.5)}'
+ '.langmenu button{display:block;width:100%;text-align:left;background:transparent;color:#eee;border:0;'
+ 'border-radius:8px;padding:11px 12px;font-size:15px;margin:0;font-weight:400;cursor:pointer}'
+ '.langmenu button.cur{color:#FFD700;font-weight:700;background:#262626}'
+ '[dir=rtl] #langbtn,[dir=rtl] .langmenu{right:auto;left:12px}[dir=rtl] .langmenu button{text-align:right}'
+ '</style></head><body>'
+ '<button id="langbtn" type="button" aria-label="Language">EN</button>'
+ '<div id="langmenu" class="langmenu" hidden></div>'
+ '<div class="card"><h1 id="title"></h1><p id="sub"></p>'
+ '<form id="f"><input id="p" type="password" inputmode="text" autocomplete="' + (mode === 'setup' ? 'new-password' : 'current-password') + '" autofocus />'
+ confirmField
+ '<button type="submit" id="btn"></button>'
+ '<div id="err"></div><div id="msg"></div></form></div>'
+ '<script>'
+ 'var MODE="' + mode + '";var I18N=' + JSON.stringify(LOCK_I18N) + ';var LANGS=' + JSON.stringify(LOCK_LANGS) + ';'
+ 'var $=function(id){return document.getElementById(id);};'
+ 'function getLang(){try{var s=localStorage.getItem("oasisLockLang");if(s&&I18N[s])return s;}catch(e){}return "en";}'
+ 'var LANG=getLang();'
+ 'function setCookie(l){try{document.cookie="language="+l+";path=/;max-age=31536000";}catch(e){}}'
+ 'try{if(localStorage.getItem("oasisLockLang"))setCookie(LANG);}catch(e){}'
+ 'function apply(){var t=I18N[LANG]||I18N.en;'
+ 'document.documentElement.lang=LANG;document.documentElement.dir=(LANG==="ar")?"rtl":"ltr";'
+ '$("title").innerHTML=(MODE==="setup")?t.t_setup:t.t_unlock;'
+ '$("sub").innerHTML=(MODE==="setup")?t.s_setup:t.s_unlock;'
+ '$("p").placeholder=t.ph;var c=$("c");if(c)c.placeholder=t.ph2;'
+ '$("btn").textContent=(MODE==="setup")?t.b_setup:t.b_unlock;'
+ '$("langbtn").textContent=LANG.toUpperCase();}'
+ 'function buildMenu(){var m=$("langmenu");m.innerHTML="";LANGS.forEach(function(l){'
+ 'var b=document.createElement("button");b.type="button";b.textContent=(I18N[l]&&I18N[l].name)||l;'
+ 'if(l===LANG)b.className="cur";b.onclick=function(){LANG=l;try{localStorage.setItem("oasisLockLang",l);}catch(e){}'
+ 'setCookie(l);apply();buildMenu();m.hidden=true;};m.appendChild(b);});}'
+ '$("langbtn").onclick=function(){var m=$("langmenu");m.hidden=!m.hidden;};'
+ 'document.addEventListener("click",function(e){var m=$("langmenu");if(!m.hidden&&e.target!==$("langbtn")&&!m.contains(e.target))m.hidden=true;});'
+ 'buildMenu();apply();'
+ 'var f=$("f"),err=$("err"),msg=$("msg");'
+ 'f.addEventListener("submit",async function(e){e.preventDefault();var t=I18N[LANG]||I18N.en;err.textContent="";'
+ 'var p=$("p").value,c=$("c");'
+ 'if(MODE==="setup"){if(p.length<6){err.textContent=t.e_min;return;}if(c&&c.value!==p){err.textContent=t.e_mism;return;}}'
+ 'var btn=$("btn");btn.disabled=true;'
+ 'try{var r=await fetch(MODE==="setup"?"/__keylock/setup":"/__keylock/unlock",{method:"POST",'
+ 'headers:{"Content-Type":"application/json"},body:JSON.stringify({password:p})});'
+ 'if(r.ok){msg.textContent=t.m_unlock;waitReload();}else{err.textContent=t.e_wrong;btn.disabled=false;}}'
+ 'catch(e2){err.textContent=t.e_pre+e2.message;btn.disabled=false;}});'
+ 'async function waitReload(){for(var i=0;i<120;i++){try{var r=await fetch("/?_k="+Date.now(),{cache:"no-store"});'
+ 'var t=await r.text();if(t.indexOf("OASIS_LOCK_MARKER")===-1){location.reload();return;}}catch(e){}'
+ 'await new Promise(function(s){setTimeout(s,500);});}location.reload();}'
+ '</script></body></html>';
}
function readBody(req) {
return new Promise((resolve) => {
let b = '';
req.on('data', (d) => { b += d; if (b.length > 1e5) req.destroy(); });
req.on('end', () => { try { resolve(JSON.parse(b || '{}')); } catch (_) { resolve({}); } });
req.on('error', () => resolve({}));
});
}
const sockets = new Set();
const lockServer = http.createServer(async (req, res) => {
try {
if (req.method === 'POST' && req.url === '/__keylock/setup') {
const { password } = await readBody(req);
if (!password || String(password).length < 6) { res.writeHead(400, { 'Connection': 'close' }); return res.end('{"ok":false}'); }
keylock.setPassword(configPath, password);
const keys = keylock.loadOrCreateSecret(configPath);
res.writeHead(200, { 'Content-Type': 'application/json', 'Connection': 'close' });
res.end('{"ok":true}');
return unlockAndStart(keys);
}
if (req.method === 'POST' && req.url === '/__keylock/unlock') {
const { password } = await readBody(req);
const kek = password ? keylock.verifyPassword(configPath, password) : null;
if (!kek) { res.writeHead(401, { 'Connection': 'close' }); return res.end('{"ok":false}'); }
const keys = keylock.loadOrCreateSecret(configPath);
res.writeHead(200, { 'Content-Type': 'application/json', 'Connection': 'close' });
res.end('{"ok":true}');
return unlockAndStart(keys);
}
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Connection': 'close' });
res.end(lockPage(MODE));
} catch (e) {
res.writeHead(500, { 'Connection': 'close' });
res.end('{"ok":false,"error":"' + String(e && e.message).replace(/"/g, '') + '"}');
}
});
lockServer.on('connection', (s) => { sockets.add(s); s.on('close', () => sockets.delete(s)); });
lockServer.listen(3000, '127.0.0.1', () => {
console.log('[Oasis] keylock gate escuchando en 127.0.0.1:3000 (modo: ' + MODE + ')');
});

View file

@ -1,6 +1,7 @@
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const keylock = require('./keylock');
const SENSITIVE_FIELDS = [
'title', 'description', 'location', 'price', 'salary', 'options', 'votes',
@ -45,7 +46,14 @@ module.exports = (configPath, namespace = 'tribes') => {
const loadKeyring = () => {
try {
keyring = JSON.parse(fs.readFileSync(keyringPath, 'utf8'));
const raw = fs.readFileSync(keyringPath);
if (keylock.isWrapped(raw)) {
if (!keylock.getKEK()) throw new Error('keyring cifrado pero keylock bloqueado');
keyring = JSON.parse(keylock.unwrap(raw).toString('utf8'));
} else {
keyring = JSON.parse(raw.toString('utf8'));
if (keylock.getKEK()) { try { saveKeyring(); } catch (_) {} }
}
try { fs.chmodSync(keyringPath, 0o600); } catch (_) {}
} catch (e) {
if (e.code !== 'ENOENT') throw e;
@ -55,8 +63,11 @@ module.exports = (configPath, namespace = 'tribes') => {
};
const saveKeyring = () => {
const json = JSON.stringify(keyring, null, 2);
const kek = keylock.getKEK();
const out = kek ? keylock.wrap(Buffer.from(json, 'utf8'), kek) : Buffer.from(json, 'utf8');
const tmp = keyringPath + '.tmp.' + process.pid + '.' + Date.now();
fs.writeFileSync(tmp, JSON.stringify(keyring, null, 2), { encoding: 'utf8', mode: 0o600 });
fs.writeFileSync(tmp, out, { mode: 0o600 });
fs.renameSync(tmp, keyringPath);
try { fs.chmodSync(keyringPath, 0o600); } catch (_) {}
};

129
src/models/keylock.js Normal file
View file

@ -0,0 +1,129 @@
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const ssbKeys = require('../server/node_modules/ssb-keys');
const MAGIC = Buffer.from('OAES1\n', 'utf8');
const KEYLEN = 32;
const DEFAULT_SCRYPT = { N: 16384, r: 8, p: 1 };
const VERIFIER_PLAINTEXT = Buffer.from('oasis-keylock-v1', 'utf8');
let KEK = null; // SOLO en memoria
const maxmemFor = (s) => Math.max(64 * 1024 * 1024, 256 * s.N * s.r);
const deriveKEK = (password, salt, scrypt = DEFAULT_SCRYPT) =>
crypto.scryptSync(Buffer.from(String(password), 'utf8'), salt, KEYLEN,
{ N: scrypt.N, r: scrypt.r, p: scrypt.p, maxmem: maxmemFor(scrypt) });
const isWrapped = (buf) =>
Buffer.isBuffer(buf) && buf.length >= MAGIC.length && buf.subarray(0, MAGIC.length).equals(MAGIC);
const wrap = (buf, kek = KEK) => {
if (!kek) throw new Error('keylock: locked (no KEK)');
const iv = crypto.randomBytes(12);
const c = crypto.createCipheriv('aes-256-gcm', kek, iv);
const ct = Buffer.concat([c.update(buf), c.final()]);
const tag = c.getAuthTag();
return Buffer.concat([MAGIC, iv, tag, ct]);
};
const unwrap = (blob, kek = KEK) => {
if (!kek) throw new Error('keylock: locked (no KEK)');
if (!isWrapped(blob)) throw new Error('keylock: not a wrapped blob');
let o = MAGIC.length;
const iv = blob.subarray(o, o += 12);
const tag = blob.subarray(o, o += 16);
const ct = blob.subarray(o);
const d = crypto.createDecipheriv('aes-256-gcm', kek, iv);
d.setAuthTag(tag);
return Buffer.concat([d.update(ct), d.final()]);
};
const lockPath = (configPath) => path.join(configPath, 'keylock.json');
const isConfigured = (configPath) => {
try { return fs.existsSync(lockPath(configPath)); } catch (_) { return false; }
};
const setPassword = (configPath, password) => {
const salt = crypto.randomBytes(16);
const scrypt = DEFAULT_SCRYPT;
const kek = deriveKEK(password, salt, scrypt);
const verifier = wrap(VERIFIER_PLAINTEXT, kek);
const data = {
v: 1, kdf: 'scrypt', N: scrypt.N, r: scrypt.r, p: scrypt.p,
salt: salt.toString('hex'), verifier: verifier.toString('base64'),
};
fs.mkdirSync(configPath, { recursive: true, mode: 0o700 });
const tmp = lockPath(configPath) + '.tmp';
fs.writeFileSync(tmp, JSON.stringify(data), { mode: 0o600 });
fs.renameSync(tmp, lockPath(configPath));
KEK = kek;
return kek;
};
const verifyPassword = (configPath, password) => {
let data;
try { data = JSON.parse(fs.readFileSync(lockPath(configPath), 'utf8')); } catch (_) { return null; }
const salt = Buffer.from(data.salt, 'hex');
const scrypt = { N: data.N || DEFAULT_SCRYPT.N, r: data.r || DEFAULT_SCRYPT.r, p: data.p || DEFAULT_SCRYPT.p };
const kek = deriveKEK(password, salt, scrypt);
try {
if (unwrap(Buffer.from(data.verifier, 'base64'), kek).equals(VERIFIER_PLAINTEXT)) {
KEK = kek;
return kek;
}
} catch (_) {}
return null;
};
const getKEK = () => KEK;
const setKEK = (k) => { KEK = k; };
const isUnlocked = () => KEK != null;
const lock = () => { try { if (KEK) KEK.fill(0); } catch (_) {} KEK = null; };
const secretEncPath = (configPath) => path.join(configPath, 'secret.enc');
const legacySecretPath = (configPath) => path.join(configPath, 'secret');
const shred = (p) => {
try { fs.writeFileSync(p, crypto.randomBytes(Math.max(1, fs.statSync(p).size))); } catch (_) {}
try { fs.unlinkSync(p); } catch (_) {}
};
const writeSecret = (configPath, keys) => {
const blob = wrap(Buffer.from(JSON.stringify(keys), 'utf8'));
fs.mkdirSync(configPath, { recursive: true, mode: 0o700 });
const tmp = secretEncPath(configPath) + '.tmp';
fs.writeFileSync(tmp, blob, { mode: 0o600 });
fs.renameSync(tmp, secretEncPath(configPath));
};
const loadOrCreateSecret = (configPath) => {
if (!KEK) throw new Error('keylock: locked');
fs.mkdirSync(configPath, { recursive: true, mode: 0o700 });
const encP = secretEncPath(configPath);
if (fs.existsSync(encP)) {
return JSON.parse(unwrap(fs.readFileSync(encP)).toString('utf8'));
}
const legP = legacySecretPath(configPath);
if (fs.existsSync(legP)) {
const keys = ssbKeys.loadSync(legP);
writeSecret(configPath, keys);
shred(legP);
return keys;
}
const keys = ssbKeys.generate('ed25519'); // primer arranque
writeSecret(configPath, keys);
return keys;
};
const hasIdentity = (configPath) =>
fs.existsSync(secretEncPath(configPath)) || fs.existsSync(legacySecretPath(configPath));
module.exports = {
MAGIC, deriveKEK, wrap, unwrap, isWrapped,
isConfigured, setPassword, verifyPassword,
getKEK, setKEK, isUnlocked, lock,
loadOrCreateSecret, writeSecret, hasIdentity,
};

View file

@ -11,9 +11,14 @@ const i = argv.indexOf('--');
const conf = argv.slice(i + 1);
const cliArgs = ~i ? argv.slice(0, i) : argv;
let config = Config('ssb', minimist(conf));
const override = minimist(conf);
if (global.__OASIS_KEYS__) override.keys = global.__OASIS_KEYS__;
let config = Config('ssb', override);
config = { ...config, ...configData };
if (global.__OASIS_KEYS__) config.keys = global.__OASIS_KEYS__;
const debug = process.argv.includes('--debug') || process.env.OASIS_DEBUG === '1' || process.env.OASIS_DEBUG === 'true';
if (debug) {
config.logging = { ...(config.logging || {}), level: 'debug' };