@auxiora/vault 1.0.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/LICENSE +191 -0
- package/dist/cloud-vault.d.ts +42 -0
- package/dist/cloud-vault.d.ts.map +1 -0
- package/dist/cloud-vault.js +79 -0
- package/dist/cloud-vault.js.map +1 -0
- package/dist/crypto.d.ts +13 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +47 -0
- package/dist/crypto.js.map +1 -0
- package/dist/eject.d.ts +29 -0
- package/dist/eject.d.ts.map +1 -0
- package/dist/eject.js +45 -0
- package/dist/eject.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/key-management.d.ts +45 -0
- package/dist/key-management.d.ts.map +1 -0
- package/dist/key-management.js +67 -0
- package/dist/key-management.js.map +1 -0
- package/dist/storage.d.ts +14 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +51 -0
- package/dist/storage.js.map +1 -0
- package/dist/vault.d.ts +25 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +115 -0
- package/dist/vault.js.map +1 -0
- package/package.json +26 -0
- package/src/cloud-vault.ts +129 -0
- package/src/crypto.ts +63 -0
- package/src/eject.ts +55 -0
- package/src/index.ts +13 -0
- package/src/key-management.ts +86 -0
- package/src/storage.ts +66 -0
- package/src/vault.ts +151 -0
- package/tests/cloud-vault.test.ts +80 -0
- package/tests/eject.test.ts +64 -0
- package/tests/key-management.test.ts +76 -0
- package/tests/vault.test.ts +127 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
package/dist/storage.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { getVaultPath, isWindows } from '@auxiora/core';
|
|
4
|
+
export { getVaultPath };
|
|
5
|
+
export async function readVaultFile(customPath) {
|
|
6
|
+
const vaultPath = customPath || getVaultPath();
|
|
7
|
+
try {
|
|
8
|
+
const content = await fs.readFile(vaultPath, 'utf-8');
|
|
9
|
+
return JSON.parse(content);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (error.code === 'ENOENT') {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function writeVaultFile(vaultFile, customPath) {
|
|
19
|
+
const vaultPath = customPath || getVaultPath();
|
|
20
|
+
const vaultDir = path.dirname(vaultPath);
|
|
21
|
+
// Create parent directories if needed
|
|
22
|
+
await fs.mkdir(vaultDir, { recursive: true });
|
|
23
|
+
// Write the file
|
|
24
|
+
await fs.writeFile(vaultPath, JSON.stringify(vaultFile, null, 2), 'utf-8');
|
|
25
|
+
// Set permissions to 0600 on Unix
|
|
26
|
+
if (!isWindows()) {
|
|
27
|
+
await fs.chmod(vaultPath, 0o600);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function deleteVaultFile(customPath) {
|
|
31
|
+
const vaultPath = customPath || getVaultPath();
|
|
32
|
+
try {
|
|
33
|
+
await fs.unlink(vaultPath);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error.code !== 'ENOENT') {
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function vaultExists(customPath) {
|
|
42
|
+
const vaultPath = customPath || getVaultPath();
|
|
43
|
+
try {
|
|
44
|
+
await fs.access(vaultPath);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAUxD,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAmB;IACrD,MAAM,SAAS,GAAG,UAAU,IAAI,YAAY,EAAE,CAAC;IAE/C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAc,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,SAAoB,EAAE,UAAmB;IAC5E,MAAM,SAAS,GAAG,UAAU,IAAI,YAAY,EAAE,CAAC;IAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEzC,sCAAsC;IACtC,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE9C,iBAAiB;IACjB,MAAM,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAE3E,kCAAkC;IAClC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;QACjB,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAAmB;IACvD,MAAM,SAAS,GAAG,UAAU,IAAI,YAAY,EAAE,CAAC;IAE/C,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAAmB;IACnD,MAAM,SAAS,GAAG,UAAU,IAAI,YAAY,EAAE,CAAC;IAE/C,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/dist/vault.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare class VaultError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export interface VaultOptions {
|
|
5
|
+
path?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class Vault {
|
|
8
|
+
private key;
|
|
9
|
+
private salt;
|
|
10
|
+
private credentials;
|
|
11
|
+
private isUnlocked;
|
|
12
|
+
private vaultPath?;
|
|
13
|
+
constructor(options?: VaultOptions);
|
|
14
|
+
unlock(password: string): Promise<void>;
|
|
15
|
+
lock(): void;
|
|
16
|
+
private ensureUnlocked;
|
|
17
|
+
private save;
|
|
18
|
+
add(name: string, value: string): Promise<void>;
|
|
19
|
+
list(): string[];
|
|
20
|
+
remove(name: string): Promise<boolean>;
|
|
21
|
+
get(name: string): string | undefined;
|
|
22
|
+
has(name: string): boolean;
|
|
23
|
+
changePassword(newPassword: string): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=vault.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"AAcA,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,KAAK;IAChB,OAAO,CAAC,GAAG,CAAuB;IAClC,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAC,CAAS;gBAEf,OAAO,CAAC,EAAE,YAAY;IAI5B,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkC7C,IAAI,IAAI,IAAI;IAUZ,OAAO,CAAC,cAAc;YAMR,IAAI;IAkBZ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrD,IAAI,IAAI,MAAM,EAAE;IAKV,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAU5C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKrC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAKpB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAezD"}
|
package/dist/vault.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { deriveKey, generateSalt, encrypt, decrypt, zeroBuffer, } from './crypto.js';
|
|
2
|
+
import { readVaultFile, writeVaultFile } from './storage.js';
|
|
3
|
+
export class VaultError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'VaultError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class Vault {
|
|
10
|
+
key = null;
|
|
11
|
+
salt = null;
|
|
12
|
+
credentials = {};
|
|
13
|
+
isUnlocked = false;
|
|
14
|
+
vaultPath;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.vaultPath = options?.path;
|
|
17
|
+
}
|
|
18
|
+
async unlock(password) {
|
|
19
|
+
const vaultFile = await readVaultFile(this.vaultPath);
|
|
20
|
+
if (vaultFile === null) {
|
|
21
|
+
// New vault - create with this password
|
|
22
|
+
this.salt = generateSalt();
|
|
23
|
+
this.key = await deriveKey(password, this.salt);
|
|
24
|
+
this.credentials = {};
|
|
25
|
+
this.isUnlocked = true;
|
|
26
|
+
await this.save();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Existing vault - decrypt
|
|
30
|
+
this.salt = Buffer.from(vaultFile.salt, 'base64');
|
|
31
|
+
this.key = await deriveKey(password, this.salt);
|
|
32
|
+
const encryptedData = {
|
|
33
|
+
iv: Buffer.from(vaultFile.iv, 'base64'),
|
|
34
|
+
ciphertext: Buffer.from(vaultFile.data, 'base64'),
|
|
35
|
+
tag: Buffer.from(vaultFile.tag, 'base64'),
|
|
36
|
+
};
|
|
37
|
+
try {
|
|
38
|
+
const plaintext = decrypt(encryptedData, this.key);
|
|
39
|
+
const data = JSON.parse(plaintext.toString('utf-8'));
|
|
40
|
+
this.credentials = data.credentials;
|
|
41
|
+
this.isUnlocked = true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
this.lock();
|
|
45
|
+
throw new VaultError('Wrong password or corrupted vault');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
lock() {
|
|
49
|
+
if (this.key) {
|
|
50
|
+
zeroBuffer(this.key);
|
|
51
|
+
this.key = null;
|
|
52
|
+
}
|
|
53
|
+
this.salt = null;
|
|
54
|
+
this.credentials = {};
|
|
55
|
+
this.isUnlocked = false;
|
|
56
|
+
}
|
|
57
|
+
ensureUnlocked() {
|
|
58
|
+
if (!this.isUnlocked || !this.key || !this.salt) {
|
|
59
|
+
throw new VaultError('Vault is locked');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async save() {
|
|
63
|
+
this.ensureUnlocked();
|
|
64
|
+
const data = { credentials: this.credentials };
|
|
65
|
+
const plaintext = Buffer.from(JSON.stringify(data), 'utf-8');
|
|
66
|
+
const encrypted = encrypt(plaintext, this.key);
|
|
67
|
+
const vaultFile = {
|
|
68
|
+
version: 1,
|
|
69
|
+
salt: this.salt.toString('base64'),
|
|
70
|
+
iv: encrypted.iv.toString('base64'),
|
|
71
|
+
data: encrypted.ciphertext.toString('base64'),
|
|
72
|
+
tag: encrypted.tag.toString('base64'),
|
|
73
|
+
};
|
|
74
|
+
await writeVaultFile(vaultFile, this.vaultPath);
|
|
75
|
+
}
|
|
76
|
+
async add(name, value) {
|
|
77
|
+
this.ensureUnlocked();
|
|
78
|
+
this.credentials[name] = value;
|
|
79
|
+
await this.save();
|
|
80
|
+
}
|
|
81
|
+
list() {
|
|
82
|
+
this.ensureUnlocked();
|
|
83
|
+
return Object.keys(this.credentials);
|
|
84
|
+
}
|
|
85
|
+
async remove(name) {
|
|
86
|
+
this.ensureUnlocked();
|
|
87
|
+
if (!(name in this.credentials)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
delete this.credentials[name];
|
|
91
|
+
await this.save();
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
get(name) {
|
|
95
|
+
this.ensureUnlocked();
|
|
96
|
+
return this.credentials[name];
|
|
97
|
+
}
|
|
98
|
+
has(name) {
|
|
99
|
+
this.ensureUnlocked();
|
|
100
|
+
return name in this.credentials;
|
|
101
|
+
}
|
|
102
|
+
async changePassword(newPassword) {
|
|
103
|
+
this.ensureUnlocked();
|
|
104
|
+
// Zero the old key
|
|
105
|
+
if (this.key) {
|
|
106
|
+
zeroBuffer(this.key);
|
|
107
|
+
}
|
|
108
|
+
// Generate new salt and derive new key
|
|
109
|
+
this.salt = generateSalt();
|
|
110
|
+
this.key = await deriveKey(newPassword, this.salt);
|
|
111
|
+
// Re-save with new encryption
|
|
112
|
+
await this.save();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=vault.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault.js","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@auxiora/vault",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Encrypted credential vault with AES-256-GCM and Argon2id",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"argon2": "^0.44.0",
|
|
16
|
+
"@auxiora/core": "1.0.0"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.0.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"clean": "rm -rf dist",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
4
|
+
const IV_LENGTH = 12;
|
|
5
|
+
const AUTH_TAG_LENGTH = 16;
|
|
6
|
+
const KEY_LENGTH = 32;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Encrypted envelope containing a data key encrypted by the master key,
|
|
10
|
+
* and the data encrypted by the data key.
|
|
11
|
+
*/
|
|
12
|
+
export interface EncryptedEnvelope {
|
|
13
|
+
version: number;
|
|
14
|
+
/** Encrypted data key (base64) */
|
|
15
|
+
encryptedDataKey: string;
|
|
16
|
+
/** IV for data key encryption (base64) */
|
|
17
|
+
dataKeyIv: string;
|
|
18
|
+
/** Auth tag for data key encryption (base64) */
|
|
19
|
+
dataKeyTag: string;
|
|
20
|
+
/** Encrypted payload (base64) */
|
|
21
|
+
encryptedPayload: string;
|
|
22
|
+
/** IV for payload encryption (base64) */
|
|
23
|
+
payloadIv: string;
|
|
24
|
+
/** Auth tag for payload encryption (base64) */
|
|
25
|
+
payloadTag: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function encryptAes256Gcm(plaintext: Buffer, key: Buffer): { ciphertext: Buffer; iv: Buffer; tag: Buffer } {
|
|
29
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
30
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
31
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
32
|
+
const tag = cipher.getAuthTag();
|
|
33
|
+
return { ciphertext, iv, tag };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function decryptAes256Gcm(ciphertext: Buffer, key: Buffer, iv: Buffer, tag: Buffer): Buffer {
|
|
37
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
38
|
+
decipher.setAuthTag(tag);
|
|
39
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* CloudVault provides client-side AES-256-GCM encryption with envelope encryption.
|
|
44
|
+
* All data is encrypted before leaving the client, so the server never sees plaintext.
|
|
45
|
+
*/
|
|
46
|
+
export class CloudVault {
|
|
47
|
+
/**
|
|
48
|
+
* Encrypt data using envelope encryption.
|
|
49
|
+
* Generates a random data key, encrypts the payload with it,
|
|
50
|
+
* then encrypts the data key with the master key.
|
|
51
|
+
*/
|
|
52
|
+
static encrypt(plaintext: Buffer, masterKey: Buffer): EncryptedEnvelope {
|
|
53
|
+
// Generate a random data key
|
|
54
|
+
const dataKey = crypto.randomBytes(KEY_LENGTH);
|
|
55
|
+
|
|
56
|
+
// Encrypt the payload with the data key
|
|
57
|
+
const payload = encryptAes256Gcm(plaintext, dataKey);
|
|
58
|
+
|
|
59
|
+
// Encrypt the data key with the master key
|
|
60
|
+
const wrappedKey = encryptAes256Gcm(dataKey, masterKey);
|
|
61
|
+
|
|
62
|
+
// Zero the data key
|
|
63
|
+
dataKey.fill(0);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
version: 1,
|
|
67
|
+
encryptedDataKey: wrappedKey.ciphertext.toString('base64'),
|
|
68
|
+
dataKeyIv: wrappedKey.iv.toString('base64'),
|
|
69
|
+
dataKeyTag: wrappedKey.tag.toString('base64'),
|
|
70
|
+
encryptedPayload: payload.ciphertext.toString('base64'),
|
|
71
|
+
payloadIv: payload.iv.toString('base64'),
|
|
72
|
+
payloadTag: payload.tag.toString('base64'),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Decrypt an envelope-encrypted payload.
|
|
78
|
+
*/
|
|
79
|
+
static decrypt(envelope: EncryptedEnvelope, masterKey: Buffer): Buffer {
|
|
80
|
+
// Unwrap the data key
|
|
81
|
+
const dataKey = decryptAes256Gcm(
|
|
82
|
+
Buffer.from(envelope.encryptedDataKey, 'base64'),
|
|
83
|
+
masterKey,
|
|
84
|
+
Buffer.from(envelope.dataKeyIv, 'base64'),
|
|
85
|
+
Buffer.from(envelope.dataKeyTag, 'base64'),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Decrypt the payload
|
|
89
|
+
const plaintext = decryptAes256Gcm(
|
|
90
|
+
Buffer.from(envelope.encryptedPayload, 'base64'),
|
|
91
|
+
dataKey,
|
|
92
|
+
Buffer.from(envelope.payloadIv, 'base64'),
|
|
93
|
+
Buffer.from(envelope.payloadTag, 'base64'),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Zero the data key
|
|
97
|
+
dataKey.fill(0);
|
|
98
|
+
|
|
99
|
+
return plaintext;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Re-encrypt an envelope with a new master key (for key rotation).
|
|
104
|
+
* The data key is unwrapped with the old key and re-wrapped with the new key.
|
|
105
|
+
* The payload itself is not re-encrypted (only the key envelope changes).
|
|
106
|
+
*/
|
|
107
|
+
static reEncrypt(envelope: EncryptedEnvelope, oldMasterKey: Buffer, newMasterKey: Buffer): EncryptedEnvelope {
|
|
108
|
+
// Unwrap data key with old master key
|
|
109
|
+
const dataKey = decryptAes256Gcm(
|
|
110
|
+
Buffer.from(envelope.encryptedDataKey, 'base64'),
|
|
111
|
+
oldMasterKey,
|
|
112
|
+
Buffer.from(envelope.dataKeyIv, 'base64'),
|
|
113
|
+
Buffer.from(envelope.dataKeyTag, 'base64'),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Re-wrap data key with new master key
|
|
117
|
+
const wrappedKey = encryptAes256Gcm(dataKey, newMasterKey);
|
|
118
|
+
|
|
119
|
+
// Zero the data key
|
|
120
|
+
dataKey.fill(0);
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
...envelope,
|
|
124
|
+
encryptedDataKey: wrappedKey.ciphertext.toString('base64'),
|
|
125
|
+
dataKeyIv: wrappedKey.iv.toString('base64'),
|
|
126
|
+
dataKeyTag: wrappedKey.tag.toString('base64'),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
package/src/crypto.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as argon2 from 'argon2';
|
|
3
|
+
import { zeroBuffer } from '@auxiora/core';
|
|
4
|
+
|
|
5
|
+
const ARGON2_MEMORY_COST = 65536; // 64MB
|
|
6
|
+
const ARGON2_TIME_COST = 3;
|
|
7
|
+
const ARGON2_PARALLELISM = 1;
|
|
8
|
+
const KEY_LENGTH = 32;
|
|
9
|
+
const IV_LENGTH = 12;
|
|
10
|
+
const AUTH_TAG_LENGTH = 16;
|
|
11
|
+
|
|
12
|
+
export async function deriveKey(password: string, salt: Buffer): Promise<Buffer> {
|
|
13
|
+
const key = await argon2.hash(password, {
|
|
14
|
+
type: argon2.argon2id,
|
|
15
|
+
salt,
|
|
16
|
+
memoryCost: ARGON2_MEMORY_COST,
|
|
17
|
+
timeCost: ARGON2_TIME_COST,
|
|
18
|
+
parallelism: ARGON2_PARALLELISM,
|
|
19
|
+
hashLength: KEY_LENGTH,
|
|
20
|
+
raw: true,
|
|
21
|
+
});
|
|
22
|
+
return key;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function generateSalt(): Buffer {
|
|
26
|
+
return crypto.randomBytes(32);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function generateIv(): Buffer {
|
|
30
|
+
return crypto.randomBytes(IV_LENGTH);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface EncryptedData {
|
|
34
|
+
iv: Buffer;
|
|
35
|
+
ciphertext: Buffer;
|
|
36
|
+
tag: Buffer;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function encrypt(plaintext: Buffer, key: Buffer): EncryptedData {
|
|
40
|
+
const iv = generateIv();
|
|
41
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv, {
|
|
42
|
+
authTagLength: AUTH_TAG_LENGTH,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
46
|
+
const tag = cipher.getAuthTag();
|
|
47
|
+
|
|
48
|
+
return { iv, ciphertext, tag };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function decrypt(encryptedData: EncryptedData, key: Buffer): Buffer {
|
|
52
|
+
const { iv, ciphertext, tag } = encryptedData;
|
|
53
|
+
|
|
54
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv, {
|
|
55
|
+
authTagLength: AUTH_TAG_LENGTH,
|
|
56
|
+
});
|
|
57
|
+
decipher.setAuthTag(tag);
|
|
58
|
+
|
|
59
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
60
|
+
return plaintext;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { zeroBuffer };
|
package/src/eject.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export interface EjectableData {
|
|
5
|
+
version: number;
|
|
6
|
+
exportedAt: string;
|
|
7
|
+
credentials: Record<string, string>;
|
|
8
|
+
metadata?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* EjectManager provides data portability — export all tenant data
|
|
13
|
+
* in a portable, decrypted format that can be imported elsewhere.
|
|
14
|
+
*/
|
|
15
|
+
export class EjectManager {
|
|
16
|
+
/**
|
|
17
|
+
* Export all credentials to a portable JSON format.
|
|
18
|
+
*/
|
|
19
|
+
static exportData(credentials: Record<string, string>, metadata?: Record<string, unknown>): EjectableData {
|
|
20
|
+
return {
|
|
21
|
+
version: 1,
|
|
22
|
+
exportedAt: new Date().toISOString(),
|
|
23
|
+
credentials: { ...credentials },
|
|
24
|
+
metadata,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Save exported data to a file.
|
|
30
|
+
*/
|
|
31
|
+
static async saveToFile(data: EjectableData, filePath: string): Promise<void> {
|
|
32
|
+
const dir = path.dirname(filePath);
|
|
33
|
+
await fs.mkdir(dir, { recursive: true });
|
|
34
|
+
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Load exported data from a file.
|
|
39
|
+
*/
|
|
40
|
+
static async loadFromFile(filePath: string): Promise<EjectableData> {
|
|
41
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
42
|
+
const data = JSON.parse(content) as EjectableData;
|
|
43
|
+
if (!data.version || !data.credentials) {
|
|
44
|
+
throw new Error('Invalid eject file format');
|
|
45
|
+
}
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Import credentials from ejected data into a vault-compatible format.
|
|
51
|
+
*/
|
|
52
|
+
static getCredentials(data: EjectableData): Record<string, string> {
|
|
53
|
+
return { ...data.credentials };
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { Vault, VaultError, type VaultOptions } from './vault.js';
|
|
2
|
+
export {
|
|
3
|
+
deriveKey,
|
|
4
|
+
encrypt,
|
|
5
|
+
decrypt,
|
|
6
|
+
generateSalt,
|
|
7
|
+
generateIv,
|
|
8
|
+
zeroBuffer,
|
|
9
|
+
} from './crypto.js';
|
|
10
|
+
export { readVaultFile, writeVaultFile, deleteVaultFile, getVaultPath, vaultExists } from './storage.js';
|
|
11
|
+
export { CloudVault, type EncryptedEnvelope } from './cloud-vault.js';
|
|
12
|
+
export { KeyManager, type ExportedKey } from './key-management.js';
|
|
13
|
+
export { EjectManager, type EjectableData } from './eject.js';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const PBKDF2_ITERATIONS = 600000;
|
|
4
|
+
const KEY_LENGTH = 32;
|
|
5
|
+
const SALT_LENGTH = 32;
|
|
6
|
+
|
|
7
|
+
export interface ExportedKey {
|
|
8
|
+
version: number;
|
|
9
|
+
salt: string;
|
|
10
|
+
iterations: number;
|
|
11
|
+
keyHash: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* KeyManager handles key derivation, rotation, and import/export
|
|
16
|
+
* for client-side cloud vault encryption.
|
|
17
|
+
*/
|
|
18
|
+
export class KeyManager {
|
|
19
|
+
/**
|
|
20
|
+
* Derive a 256-bit key from a password using PBKDF2.
|
|
21
|
+
*/
|
|
22
|
+
static async deriveKey(password: string, salt?: Buffer): Promise<{ key: Buffer; salt: Buffer }> {
|
|
23
|
+
const keySalt = salt ?? crypto.randomBytes(SALT_LENGTH);
|
|
24
|
+
|
|
25
|
+
const key = await new Promise<Buffer>((resolve, reject) => {
|
|
26
|
+
crypto.pbkdf2(password, keySalt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512', (err, derivedKey) => {
|
|
27
|
+
if (err) reject(err);
|
|
28
|
+
else resolve(derivedKey);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return { key, salt: keySalt };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate a random 256-bit master key.
|
|
37
|
+
*/
|
|
38
|
+
static generateKey(): Buffer {
|
|
39
|
+
return crypto.randomBytes(KEY_LENGTH);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Rotate a key: derive a new key from a new password.
|
|
44
|
+
* Returns both old and new keys for re-encryption.
|
|
45
|
+
*/
|
|
46
|
+
static async rotateKey(
|
|
47
|
+
oldPassword: string,
|
|
48
|
+
oldSalt: Buffer,
|
|
49
|
+
newPassword: string,
|
|
50
|
+
): Promise<{ oldKey: Buffer; newKey: Buffer; newSalt: Buffer }> {
|
|
51
|
+
const { key: oldKey } = await KeyManager.deriveKey(oldPassword, oldSalt);
|
|
52
|
+
const { key: newKey, salt: newSalt } = await KeyManager.deriveKey(newPassword);
|
|
53
|
+
|
|
54
|
+
return { oldKey, newKey, newSalt };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Export a key's metadata for storage (does NOT export the key itself).
|
|
59
|
+
* Stores the salt and a hash of the derived key for verification.
|
|
60
|
+
*/
|
|
61
|
+
static exportKeyMetadata(salt: Buffer, key: Buffer): ExportedKey {
|
|
62
|
+
const keyHash = crypto.createHash('sha256').update(key).digest('hex');
|
|
63
|
+
return {
|
|
64
|
+
version: 1,
|
|
65
|
+
salt: salt.toString('base64'),
|
|
66
|
+
iterations: PBKDF2_ITERATIONS,
|
|
67
|
+
keyHash,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Import key metadata and verify a password against it.
|
|
73
|
+
*/
|
|
74
|
+
static async verifyPassword(password: string, exported: ExportedKey): Promise<{ valid: boolean; key: Buffer }> {
|
|
75
|
+
const salt = Buffer.from(exported.salt, 'base64');
|
|
76
|
+
const { key } = await KeyManager.deriveKey(password, salt);
|
|
77
|
+
const keyHash = crypto.createHash('sha256').update(key).digest('hex');
|
|
78
|
+
|
|
79
|
+
if (keyHash === exported.keyHash) {
|
|
80
|
+
return { valid: true, key };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
key.fill(0);
|
|
84
|
+
return { valid: false, key: Buffer.alloc(0) };
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/storage.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { getVaultPath, isWindows } from '@auxiora/core';
|
|
4
|
+
|
|
5
|
+
export interface VaultFile {
|
|
6
|
+
version: number;
|
|
7
|
+
salt: string;
|
|
8
|
+
iv: string;
|
|
9
|
+
data: string;
|
|
10
|
+
tag: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { getVaultPath };
|
|
14
|
+
|
|
15
|
+
export async function readVaultFile(customPath?: string): Promise<VaultFile | null> {
|
|
16
|
+
const vaultPath = customPath || getVaultPath();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const content = await fs.readFile(vaultPath, 'utf-8');
|
|
20
|
+
return JSON.parse(content) as VaultFile;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function writeVaultFile(vaultFile: VaultFile, customPath?: string): Promise<void> {
|
|
30
|
+
const vaultPath = customPath || getVaultPath();
|
|
31
|
+
const vaultDir = path.dirname(vaultPath);
|
|
32
|
+
|
|
33
|
+
// Create parent directories if needed
|
|
34
|
+
await fs.mkdir(vaultDir, { recursive: true });
|
|
35
|
+
|
|
36
|
+
// Write the file
|
|
37
|
+
await fs.writeFile(vaultPath, JSON.stringify(vaultFile, null, 2), 'utf-8');
|
|
38
|
+
|
|
39
|
+
// Set permissions to 0600 on Unix
|
|
40
|
+
if (!isWindows()) {
|
|
41
|
+
await fs.chmod(vaultPath, 0o600);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function deleteVaultFile(customPath?: string): Promise<void> {
|
|
46
|
+
const vaultPath = customPath || getVaultPath();
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await fs.unlink(vaultPath);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function vaultExists(customPath?: string): Promise<boolean> {
|
|
58
|
+
const vaultPath = customPath || getVaultPath();
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await fs.access(vaultPath);
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|