@aria-framework/secure-keystore
v0.4.0
Published
Aria App Framework — secure-keystore module. 256-bit key management (OS keychain via keytar, secure file fallback), AES-256-GCM field encryption, and a schema-driven SQLite-backed credential store.
Readme
@aria-framework/secure-keystore
Aria App Framework — secure-keystore module. Three pieces that together give an app encrypted-at-rest credentials with zero configuration on first run:
KeyManager— gets or creates a 256-bit encryption key. Tries the OS keychain first (Windows Credential Manager / macOS Keychain / Linux Secret Service viakeytar), falls back to a0600file. Fail-closed guard: once an install has used the keychain, a later keychain read failure throws rather than silently minting a divergent key (which would orphan encrypted data).EncryptionService— AES-256-GCM over strings. Output formativ:authTag:ciphertext(hex). Optional AAD.SecureStore— schema-driven credential store on SQLite (better-sqlite3). Each credential type is one single-row tablecredentials_<name>; fields markedencrypted: trueare transparently encrypted onsave()and decrypted onload(). Late-added schema fields areALTER TABLE-synced automatically.
Install
npm install @aria-framework/secure-keystore better-sqlite3
npm install keytar # optional — enables OS-keychain key storagebetter-sqlite3 is a peer dependency (your app owns the native build).
keytar is an optional peer: without it (or when its native library is
missing) KeyManager logs a warning and uses the file fallback.
Usage
const { KeyManager, EncryptionService, SecureStore } = require('@aria-framework/secure-keystore');
const km = new KeyManager({ serviceName: 'MyApp-Fields', keyDir: './data/keys', logger });
const key = await km.getKey(); // 64-char hex, created on first run
const enc = new EncryptionService(key);
const store = new SecureStore({
databasePath: './data/credentials.db',
encryptionKey: enc, // or the hex key string directly
logger,
credentials: {
smtp: {
fields: {
host: { type: 'text', required: true },
password: { type: 'text', required: true, encrypted: true }
}
}
}
});
store.save('smtp', { host: 'mail.example.com', password: 'hunter2' });
store.exists('smtp'); // true
store.load('smtp'); // { host: '...', password: 'hunter2' } (decrypted)
store.delete('smtp');
store.close();All names that touch persisted state are caller-supplied — serviceName
(keychain entry + key file name), keyDir, databasePath, and the credential
type names (table names). Migrating an app that previously vendored these
classes is therefore a require-path change only; existing key files, keychain
entries, and credentials.db files keep working unchanged.
Headless servers (the keytar/libsecret note)
On a headless Linux box keytar either fails to require() or fails at
runtime with libsecret-1.so.0: cannot open shared object file — there is no
desktop Secret Service to talk to. This is an expected, designed-for case:
KeyManager logs a warning and uses the file-backed key
(<keyDir>/<serviceName>.key, mode 0600). Don't install desktop keyring
packages to "fix" it; do put the key directory on an encrypted volume, and
check the boot log line reports storage: file.
Changelog
- 0.4.0 — three additions for SQLCipher-encrypted consumers (Acc101
migration). (1)
SecureStoreonOpen(db)option: runs right after the database file is opened, before any table access — the place forpragma key/kdf_iterwhen the app aliasesbetter-sqlite3to a cipher build (mirrors db-worker'sdatabase.inithook). (2)KeyManager.setKey(value): installs a specific 64-hex key (backup restore); same backend selection asgetKey, fail-loud on keychain write errors, writes the keychain marker so a restored key is fail-closed from its first boot. (3)KeyManagerfileOnlyoption: deliberately skips keytar (headless servers); no "keychain unavailable" warning since the fallback is chosen, not forced. Plus anpm testsmoke suite (fileOnly + store round-trips + onOpen order). - 0.3.0 — field names ending in
_encryptedare now rejected at construction: the suffix is the store's own column-naming convention, and a literal field with that name created ambiguous columns and flip-guard false positives. No real schema is affected. - 0.2.0 — two review-driven guards. (1) Encrypted-flag flip guard:
changing a field's
encryptedsetting now throws at init with a migrate-manually message (previously the value was silently stranded in the old column andload()returned null). (2) Honest Windows log: the file-fallback message no longer claims(0600)on win32 — POSIX modes are ignored there; it now says the file inherits directory ACLs and to restrict the directory. - 0.1.0 — first release. Extracted from Support101/Acc101
lib/secure-keystore/(previously copy-pasted between apps); API unchanged.
