@uagents/syncenv-cli 0.1.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/README.md +204 -0
- package/dist/chunk-F7ZZUTRW.js +403 -0
- package/dist/chunk-JBMZAAVP.js +176 -0
- package/dist/chunk-NV6H5OGL.js +218 -0
- package/dist/chunk-OVEYHV4C.js +333 -0
- package/dist/cookie-store-Z6DNTUGS.js +16 -0
- package/dist/crypto-X7MZU7DV.js +58 -0
- package/dist/index.js +2091 -0
- package/dist/interactive-GOIXZ6UH.js +6 -0
- package/dist/secure-storage-UEK3LD5L.js +35 -0
- package/package.json +53 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deleteConfig,
|
|
3
|
+
getConfig,
|
|
4
|
+
setConfig
|
|
5
|
+
} from "./chunk-OVEYHV4C.js";
|
|
6
|
+
|
|
7
|
+
// src/secure-storage.ts
|
|
8
|
+
var SERVICE_NAME = "syncenv-cli";
|
|
9
|
+
var ACCOUNT_KEK_PASSWORD = "kek-password";
|
|
10
|
+
var keytarModule = null;
|
|
11
|
+
async function getKeytar() {
|
|
12
|
+
if (keytarModule) return keytarModule;
|
|
13
|
+
try {
|
|
14
|
+
keytarModule = await import("keytar");
|
|
15
|
+
return keytarModule;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function saveKEKPassword(password) {
|
|
21
|
+
const keytar = await getKeytar();
|
|
22
|
+
if (!keytar) {
|
|
23
|
+
console.warn("Warning: Secure storage not available, using config file");
|
|
24
|
+
setConfig("kekPassword", password);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
await keytar.setPassword(SERVICE_NAME, ACCOUNT_KEK_PASSWORD, password);
|
|
29
|
+
} catch {
|
|
30
|
+
setConfig("kekPassword", password);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function getKEKPassword() {
|
|
34
|
+
const keytar = await getKeytar();
|
|
35
|
+
if (!keytar) {
|
|
36
|
+
return getConfig("kekPassword") || null;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return await keytar.getPassword(SERVICE_NAME, ACCOUNT_KEK_PASSWORD);
|
|
40
|
+
} catch {
|
|
41
|
+
return getConfig("kekPassword") || null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function deleteKEKPassword() {
|
|
45
|
+
const keytar = await getKeytar();
|
|
46
|
+
if (!keytar) return;
|
|
47
|
+
try {
|
|
48
|
+
await keytar.deletePassword(SERVICE_NAME, ACCOUNT_KEK_PASSWORD);
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
deleteConfig("kekPassword");
|
|
52
|
+
}
|
|
53
|
+
function saveEncryptedKEK(keys) {
|
|
54
|
+
setConfig("encryptedUserKek", keys.encryptedUserKek);
|
|
55
|
+
setConfig("kekIv", keys.kekIv);
|
|
56
|
+
setConfig("kekSalt", keys.kekSalt);
|
|
57
|
+
if (keys.version) {
|
|
58
|
+
setConfig("kekVersion", keys.version);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function getEncryptedKEK() {
|
|
62
|
+
const encryptedUserKek = getConfig("encryptedUserKek");
|
|
63
|
+
const kekIv = getConfig("kekIv");
|
|
64
|
+
const kekSalt = getConfig("kekSalt");
|
|
65
|
+
if (!encryptedUserKek || !kekIv || !kekSalt) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
encryptedUserKek,
|
|
70
|
+
kekIv,
|
|
71
|
+
kekSalt,
|
|
72
|
+
version: getConfig("kekVersion") || 1
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function deleteEncryptedKEK() {
|
|
76
|
+
deleteConfig("encryptedUserKek");
|
|
77
|
+
deleteConfig("kekIv");
|
|
78
|
+
deleteConfig("kekSalt");
|
|
79
|
+
deleteConfig("kekVersion");
|
|
80
|
+
}
|
|
81
|
+
var memoryKEK = null;
|
|
82
|
+
var kekExpiry = 0;
|
|
83
|
+
var KEK_TTL = 30 * 60 * 1e3;
|
|
84
|
+
function cacheKEKInMemory(kek) {
|
|
85
|
+
memoryKEK = kek;
|
|
86
|
+
kekExpiry = Date.now() + KEK_TTL;
|
|
87
|
+
}
|
|
88
|
+
function getCachedKEK() {
|
|
89
|
+
if (memoryKEK && Date.now() < kekExpiry) {
|
|
90
|
+
return memoryKEK;
|
|
91
|
+
}
|
|
92
|
+
memoryKEK = null;
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function clearCachedKEK() {
|
|
96
|
+
memoryKEK = null;
|
|
97
|
+
kekExpiry = 0;
|
|
98
|
+
}
|
|
99
|
+
function isKEKCached() {
|
|
100
|
+
return !!memoryKEK && Date.now() < kekExpiry;
|
|
101
|
+
}
|
|
102
|
+
function getKEKTimeRemaining() {
|
|
103
|
+
if (!memoryKEK) return 0;
|
|
104
|
+
return Math.max(0, kekExpiry - Date.now());
|
|
105
|
+
}
|
|
106
|
+
async function getUserKEK() {
|
|
107
|
+
const cached = getCachedKEK();
|
|
108
|
+
if (cached) return cached;
|
|
109
|
+
const password = await getKEKPassword();
|
|
110
|
+
const encryptedKeys = getEncryptedKEK();
|
|
111
|
+
if (password && encryptedKeys) {
|
|
112
|
+
try {
|
|
113
|
+
const { unlockUserKEK } = await import("./crypto-X7MZU7DV.js");
|
|
114
|
+
const kek = await unlockUserKEK(
|
|
115
|
+
password,
|
|
116
|
+
encryptedKeys.encryptedUserKek,
|
|
117
|
+
encryptedKeys.kekIv,
|
|
118
|
+
encryptedKeys.kekSalt
|
|
119
|
+
);
|
|
120
|
+
if (kek) {
|
|
121
|
+
cacheKEKInMemory(kek);
|
|
122
|
+
return kek;
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
await deleteKEKPassword();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
async function unlockAndStoreKEK(password, userKeys, remember = false) {
|
|
131
|
+
const { unlockUserKEK } = await import("./crypto-X7MZU7DV.js");
|
|
132
|
+
const kek = await unlockUserKEK(
|
|
133
|
+
password,
|
|
134
|
+
userKeys.encryptedUserKek,
|
|
135
|
+
userKeys.kekIv,
|
|
136
|
+
userKeys.kekSalt
|
|
137
|
+
);
|
|
138
|
+
if (!kek) {
|
|
139
|
+
throw new Error("Invalid password");
|
|
140
|
+
}
|
|
141
|
+
cacheKEKInMemory(kek);
|
|
142
|
+
saveEncryptedKEK(userKeys);
|
|
143
|
+
if (remember) {
|
|
144
|
+
await saveKEKPassword(password);
|
|
145
|
+
}
|
|
146
|
+
return kek;
|
|
147
|
+
}
|
|
148
|
+
async function lockKEK(removePassword = false) {
|
|
149
|
+
clearCachedKEK();
|
|
150
|
+
if (removePassword) {
|
|
151
|
+
await deleteKEKPassword();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function canAutoUnlock() {
|
|
155
|
+
const password = await getKEKPassword();
|
|
156
|
+
const encryptedKeys = getEncryptedKEK();
|
|
157
|
+
return !!password && !!encryptedKeys;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export {
|
|
161
|
+
saveKEKPassword,
|
|
162
|
+
getKEKPassword,
|
|
163
|
+
deleteKEKPassword,
|
|
164
|
+
saveEncryptedKEK,
|
|
165
|
+
getEncryptedKEK,
|
|
166
|
+
deleteEncryptedKEK,
|
|
167
|
+
cacheKEKInMemory,
|
|
168
|
+
getCachedKEK,
|
|
169
|
+
clearCachedKEK,
|
|
170
|
+
isKEKCached,
|
|
171
|
+
getKEKTimeRemaining,
|
|
172
|
+
getUserKEK,
|
|
173
|
+
unlockAndStoreKEK,
|
|
174
|
+
lockKEK,
|
|
175
|
+
canAutoUnlock
|
|
176
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// src/crypto/index.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
var scrypt = promisify(crypto.scrypt);
|
|
5
|
+
var KEY_LENGTH = 32;
|
|
6
|
+
var SALT_LENGTH = 16;
|
|
7
|
+
var IV_LENGTH = 12;
|
|
8
|
+
var ALGORITHM = "aes-256-gcm";
|
|
9
|
+
var PBKDF2_ITERATIONS = 1e5;
|
|
10
|
+
function base64ToBuffer(base64) {
|
|
11
|
+
return Buffer.from(base64, "base64");
|
|
12
|
+
}
|
|
13
|
+
function bufferToBase64(buffer) {
|
|
14
|
+
return buffer.toString("base64");
|
|
15
|
+
}
|
|
16
|
+
async function deriveKEK(password, salt) {
|
|
17
|
+
return crypto.pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
|
|
18
|
+
}
|
|
19
|
+
function generateUserKEK() {
|
|
20
|
+
return crypto.randomBytes(KEY_LENGTH);
|
|
21
|
+
}
|
|
22
|
+
function exportKEK(key) {
|
|
23
|
+
return bufferToBase64(key);
|
|
24
|
+
}
|
|
25
|
+
function importKEK(data) {
|
|
26
|
+
return base64ToBuffer(data);
|
|
27
|
+
}
|
|
28
|
+
function encryptUserKEK(userKek, passwordKek) {
|
|
29
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
30
|
+
const cipher = crypto.createCipheriv(ALGORITHM, passwordKek, iv);
|
|
31
|
+
const encrypted = Buffer.concat([cipher.update(userKek), cipher.final()]);
|
|
32
|
+
const tag = cipher.getAuthTag();
|
|
33
|
+
const combined = Buffer.concat([encrypted, tag]);
|
|
34
|
+
return {
|
|
35
|
+
encrypted: bufferToBase64(combined),
|
|
36
|
+
iv: bufferToBase64(iv)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function decryptUserKEK(encryptedBase64, ivBase64, passwordKek) {
|
|
40
|
+
const combined = base64ToBuffer(encryptedBase64);
|
|
41
|
+
const iv = base64ToBuffer(ivBase64);
|
|
42
|
+
const tag = combined.slice(-16);
|
|
43
|
+
const encrypted = combined.slice(0, -16);
|
|
44
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, passwordKek, iv);
|
|
45
|
+
decipher.setAuthTag(tag);
|
|
46
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
47
|
+
}
|
|
48
|
+
function generateDEK() {
|
|
49
|
+
return crypto.randomBytes(KEY_LENGTH);
|
|
50
|
+
}
|
|
51
|
+
function exportDEK(key) {
|
|
52
|
+
return bufferToBase64(key);
|
|
53
|
+
}
|
|
54
|
+
function importDEK(data) {
|
|
55
|
+
return base64ToBuffer(data);
|
|
56
|
+
}
|
|
57
|
+
function encryptDEK(dek, userKek) {
|
|
58
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
59
|
+
const cipher = crypto.createCipheriv(ALGORITHM, userKek, iv);
|
|
60
|
+
const encrypted = Buffer.concat([cipher.update(dek), cipher.final()]);
|
|
61
|
+
const tag = cipher.getAuthTag();
|
|
62
|
+
const combined = Buffer.concat([encrypted, tag]);
|
|
63
|
+
return {
|
|
64
|
+
encrypted: bufferToBase64(combined),
|
|
65
|
+
iv: bufferToBase64(iv)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function decryptDEK(encryptedBase64, ivBase64, userKek) {
|
|
69
|
+
const combined = base64ToBuffer(encryptedBase64);
|
|
70
|
+
const iv = base64ToBuffer(ivBase64);
|
|
71
|
+
const tag = combined.slice(-16);
|
|
72
|
+
const encrypted = combined.slice(0, -16);
|
|
73
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, userKek, iv);
|
|
74
|
+
decipher.setAuthTag(tag);
|
|
75
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
76
|
+
}
|
|
77
|
+
function encryptContent(content, dek) {
|
|
78
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
79
|
+
const cipher = crypto.createCipheriv(ALGORITHM, dek, iv);
|
|
80
|
+
const encrypted = Buffer.concat([cipher.update(content, "utf-8"), cipher.final()]);
|
|
81
|
+
const tag = cipher.getAuthTag();
|
|
82
|
+
const combined = Buffer.concat([encrypted, tag]);
|
|
83
|
+
return {
|
|
84
|
+
encrypted: bufferToBase64(combined),
|
|
85
|
+
iv: bufferToBase64(iv)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function decryptContent(encryptedBase64, ivBase64, dek) {
|
|
89
|
+
const combined = base64ToBuffer(encryptedBase64);
|
|
90
|
+
const iv = base64ToBuffer(ivBase64);
|
|
91
|
+
const tag = combined.slice(-16);
|
|
92
|
+
const encrypted = combined.slice(0, -16);
|
|
93
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, dek, iv);
|
|
94
|
+
decipher.setAuthTag(tag);
|
|
95
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf-8");
|
|
96
|
+
}
|
|
97
|
+
function generateContentHash(content) {
|
|
98
|
+
return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
|
|
99
|
+
}
|
|
100
|
+
async function setupUserKEK(password) {
|
|
101
|
+
const salt = crypto.randomBytes(SALT_LENGTH);
|
|
102
|
+
const kekSalt = bufferToBase64(salt);
|
|
103
|
+
const passwordKek = await deriveKEK(password, salt);
|
|
104
|
+
const userKek = generateUserKEK();
|
|
105
|
+
const { encrypted, iv } = encryptUserKEK(userKek, passwordKek);
|
|
106
|
+
return {
|
|
107
|
+
userKek,
|
|
108
|
+
encryptedUserKek: encrypted,
|
|
109
|
+
kekIv: iv,
|
|
110
|
+
kekSalt
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async function unlockUserKEK(password, encryptedUserKek, kekIv, kekSalt) {
|
|
114
|
+
try {
|
|
115
|
+
const salt = base64ToBuffer(kekSalt);
|
|
116
|
+
const passwordKek = await deriveKEK(password, salt);
|
|
117
|
+
const userKek = decryptUserKEK(encryptedUserKek, kekIv, passwordKek);
|
|
118
|
+
return userKek;
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error("Failed to unlock User KEK:", err);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function reencryptUserKEK(userKek, newPassword) {
|
|
125
|
+
const salt = crypto.randomBytes(SALT_LENGTH);
|
|
126
|
+
const kekSalt = bufferToBase64(salt);
|
|
127
|
+
const newPasswordKek = await deriveKEK(newPassword, salt);
|
|
128
|
+
const { encrypted, iv } = encryptUserKEK(userKek, newPasswordKek);
|
|
129
|
+
return {
|
|
130
|
+
encryptedUserKek: encrypted,
|
|
131
|
+
kekIv: iv,
|
|
132
|
+
kekSalt
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function generateSalt() {
|
|
136
|
+
return crypto.randomBytes(SALT_LENGTH);
|
|
137
|
+
}
|
|
138
|
+
async function deriveKeys(masterPassword, salt) {
|
|
139
|
+
const fullKey = await scrypt(masterPassword, salt, 64);
|
|
140
|
+
return {
|
|
141
|
+
kek: fullKey.slice(0, 32),
|
|
142
|
+
authKey: fullKey.slice(32, 64)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function generateDek() {
|
|
146
|
+
return generateDEK();
|
|
147
|
+
}
|
|
148
|
+
function encryptDek(dek, kek) {
|
|
149
|
+
const result = encryptDEK(dek, kek);
|
|
150
|
+
const combined = base64ToBuffer(result.encrypted);
|
|
151
|
+
const tag = combined.slice(-16);
|
|
152
|
+
const ciphertext = combined.slice(0, -16);
|
|
153
|
+
return {
|
|
154
|
+
ciphertext: bufferToBase64(ciphertext),
|
|
155
|
+
iv: result.iv,
|
|
156
|
+
tag: bufferToBase64(tag)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function decryptDek(encryptedDek, kek) {
|
|
160
|
+
const ciphertext = base64ToBuffer(encryptedDek.ciphertext);
|
|
161
|
+
const tag = base64ToBuffer(encryptedDek.tag);
|
|
162
|
+
const combined = bufferToBase64(Buffer.concat([ciphertext, tag]));
|
|
163
|
+
return decryptDEK(combined, encryptedDek.iv, kek);
|
|
164
|
+
}
|
|
165
|
+
function encryptEnvFile(content, dek) {
|
|
166
|
+
const data = typeof content === "string" ? content : content.toString("utf-8");
|
|
167
|
+
const contentHash = generateContentHash(data);
|
|
168
|
+
const encrypted = encryptContent(data, dek);
|
|
169
|
+
const combined = base64ToBuffer(encrypted.encrypted);
|
|
170
|
+
const tag = combined.slice(-16);
|
|
171
|
+
const ciphertext = combined.slice(0, -16);
|
|
172
|
+
return {
|
|
173
|
+
ciphertext: bufferToBase64(ciphertext),
|
|
174
|
+
iv: encrypted.iv,
|
|
175
|
+
tag: bufferToBase64(tag),
|
|
176
|
+
contentHash
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function decryptEnvFile(encryptedData, dek) {
|
|
180
|
+
const ciphertext = base64ToBuffer(encryptedData.ciphertext);
|
|
181
|
+
const tag = base64ToBuffer(encryptedData.tag);
|
|
182
|
+
const combined = bufferToBase64(Buffer.concat([ciphertext, tag]));
|
|
183
|
+
return decryptContent(combined, encryptedData.iv, dek);
|
|
184
|
+
}
|
|
185
|
+
function hashContent(content) {
|
|
186
|
+
const data = typeof content === "string" ? content : content.toString("utf-8");
|
|
187
|
+
return generateContentHash(data);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export {
|
|
191
|
+
base64ToBuffer,
|
|
192
|
+
bufferToBase64,
|
|
193
|
+
deriveKEK,
|
|
194
|
+
generateUserKEK,
|
|
195
|
+
exportKEK,
|
|
196
|
+
importKEK,
|
|
197
|
+
encryptUserKEK,
|
|
198
|
+
decryptUserKEK,
|
|
199
|
+
generateDEK,
|
|
200
|
+
exportDEK,
|
|
201
|
+
importDEK,
|
|
202
|
+
encryptDEK,
|
|
203
|
+
decryptDEK,
|
|
204
|
+
encryptContent,
|
|
205
|
+
decryptContent,
|
|
206
|
+
generateContentHash,
|
|
207
|
+
setupUserKEK,
|
|
208
|
+
unlockUserKEK,
|
|
209
|
+
reencryptUserKEK,
|
|
210
|
+
generateSalt,
|
|
211
|
+
deriveKeys,
|
|
212
|
+
generateDek,
|
|
213
|
+
encryptDek,
|
|
214
|
+
decryptDek,
|
|
215
|
+
encryptEnvFile,
|
|
216
|
+
decryptEnvFile,
|
|
217
|
+
hashContent
|
|
218
|
+
};
|