@tuskydp/cli 0.3.0 → 0.4.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/CHANGELOG.md +19 -0
- package/dist/src/commands/account.d.ts.map +1 -1
- package/dist/src/commands/account.js +0 -1
- package/dist/src/commands/account.js.map +1 -1
- package/dist/src/commands/auth.d.ts.map +1 -1
- package/dist/src/commands/auth.js +8 -5
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/download.d.ts.map +1 -1
- package/dist/src/commands/download.js +2 -59
- package/dist/src/commands/download.js.map +1 -1
- package/dist/src/commands/export.d.ts +5 -26
- package/dist/src/commands/export.d.ts.map +1 -1
- package/dist/src/commands/export.js +6 -46
- package/dist/src/commands/export.js.map +1 -1
- package/dist/src/commands/files.js +2 -2
- package/dist/src/commands/files.js.map +1 -1
- package/dist/src/commands/mcp.d.ts.map +1 -1
- package/dist/src/commands/mcp.js +15 -9
- package/dist/src/commands/mcp.js.map +1 -1
- package/dist/src/commands/sui.d.ts +3 -0
- package/dist/src/commands/sui.d.ts.map +1 -0
- package/dist/src/commands/sui.js +64 -0
- package/dist/src/commands/sui.js.map +1 -0
- package/dist/src/commands/trash.js +1 -1
- package/dist/src/commands/trash.js.map +1 -1
- package/dist/src/commands/upload.d.ts.map +1 -1
- package/dist/src/commands/upload.js +3 -49
- package/dist/src/commands/upload.js.map +1 -1
- package/dist/src/commands/vault.d.ts.map +1 -1
- package/dist/src/commands/vault.js +2 -24
- package/dist/src/commands/vault.js.map +1 -1
- package/dist/src/config.d.ts +2 -2
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +2 -3
- package/dist/src/config.js.map +1 -1
- package/dist/src/index.js +2 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/resolve.d.ts.map +1 -1
- package/dist/src/lib/resolve.js +4 -5
- package/dist/src/lib/resolve.js.map +1 -1
- package/dist/src/mcp/context.d.ts +1 -9
- package/dist/src/mcp/context.d.ts.map +1 -1
- package/dist/src/mcp/context.js +1 -2
- package/dist/src/mcp/context.js.map +1 -1
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +0 -58
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/mcp/tools/account.d.ts.map +1 -1
- package/dist/src/mcp/tools/account.js +1 -3
- package/dist/src/mcp/tools/account.js.map +1 -1
- package/dist/src/mcp/tools/files.d.ts +2 -3
- package/dist/src/mcp/tools/files.d.ts.map +1 -1
- package/dist/src/mcp/tools/files.js +18 -56
- package/dist/src/mcp/tools/files.js.map +1 -1
- package/dist/src/mcp/tools/vaults.js +2 -2
- package/dist/src/mcp/tools/vaults.js.map +1 -1
- package/dist/src/tui/files-panel.d.ts +0 -1
- package/dist/src/tui/files-panel.d.ts.map +1 -1
- package/dist/src/tui/files-panel.js +1 -2
- package/dist/src/tui/files-panel.js.map +1 -1
- package/dist/src/tui/index.d.ts.map +1 -1
- package/dist/src/tui/index.js +7 -42
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/overview.d.ts.map +1 -1
- package/dist/src/tui/overview.js +2 -6
- package/dist/src/tui/overview.js.map +1 -1
- package/dist/src/tui/trash-screen.js +1 -1
- package/dist/src/tui/trash-screen.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/seal.test.ts +7 -54
- package/src/commands/account.ts +0 -1
- package/src/commands/auth.ts +7 -5
- package/src/commands/download.ts +2 -63
- package/src/commands/export.ts +7 -67
- package/src/commands/files.ts +2 -2
- package/src/commands/mcp.ts +16 -10
- package/src/commands/sui.ts +69 -0
- package/src/commands/trash.ts +1 -1
- package/src/commands/upload.ts +3 -59
- package/src/commands/vault.ts +2 -23
- package/src/config.ts +3 -4
- package/src/index.ts +2 -4
- package/src/lib/resolve.ts +3 -4
- package/src/mcp/context.ts +1 -11
- package/src/mcp/server.ts +0 -69
- package/src/mcp/tools/account.ts +1 -3
- package/src/mcp/tools/files.ts +19 -70
- package/src/mcp/tools/vaults.ts +3 -3
- package/src/tui/files-panel.ts +1 -3
- package/src/tui/index.ts +7 -51
- package/src/tui/overview.ts +2 -5
- package/src/tui/trash-screen.ts +1 -1
- package/dist/src/commands/decrypt.d.ts +0 -15
- package/dist/src/commands/decrypt.d.ts.map +0 -1
- package/dist/src/commands/decrypt.js +0 -256
- package/dist/src/commands/decrypt.js.map +0 -1
- package/dist/src/commands/encryption.d.ts +0 -3
- package/dist/src/commands/encryption.d.ts.map +0 -1
- package/dist/src/commands/encryption.js +0 -254
- package/dist/src/commands/encryption.js.map +0 -1
- package/dist/src/crypto.d.ts +0 -32
- package/dist/src/crypto.d.ts.map +0 -1
- package/dist/src/crypto.js +0 -121
- package/dist/src/crypto.js.map +0 -1
- package/dist/src/lib/keyring.d.ts +0 -4
- package/dist/src/lib/keyring.d.ts.map +0 -1
- package/dist/src/lib/keyring.js +0 -49
- package/dist/src/lib/keyring.js.map +0 -1
- package/src/__tests__/crypto.test.ts +0 -315
- package/src/commands/decrypt.ts +0 -309
- package/src/commands/encryption.ts +0 -305
- package/src/crypto.ts +0 -165
- package/src/lib/keyring.ts +0 -50
package/src/crypto.ts
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createHash,
|
|
3
|
-
createHmac,
|
|
4
|
-
randomBytes,
|
|
5
|
-
createCipheriv,
|
|
6
|
-
createDecipheriv,
|
|
7
|
-
pbkdf2Sync,
|
|
8
|
-
} from 'crypto';
|
|
9
|
-
|
|
10
|
-
export interface FileMeta {
|
|
11
|
-
/** Original filename */
|
|
12
|
-
n: string;
|
|
13
|
-
/** Original MIME type */
|
|
14
|
-
m: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const PBKDF2_ITERATIONS = 600_000;
|
|
18
|
-
const AES_ALGO = 'aes-256-gcm' as const;
|
|
19
|
-
const VERIFIER_CONTEXT = 'tuskydp-key-verifier-v1';
|
|
20
|
-
|
|
21
|
-
export function deriveMasterKey(passphrase: string, salt: Buffer): Buffer {
|
|
22
|
-
return pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, 32, 'sha256');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function computeVerifier(masterKey: Buffer): Buffer {
|
|
26
|
-
return createHmac('sha256', masterKey)
|
|
27
|
-
.update(VERIFIER_CONTEXT)
|
|
28
|
-
.digest();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function verifyPassphrase(masterKey: Buffer, expectedVerifier: Buffer): boolean {
|
|
32
|
-
const computed = computeVerifier(masterKey);
|
|
33
|
-
if (computed.length !== expectedVerifier.length) return false;
|
|
34
|
-
let diff = 0;
|
|
35
|
-
for (let i = 0; i < computed.length; i++) diff |= computed[i] ^ expectedVerifier[i];
|
|
36
|
-
return diff === 0;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function generateSalt(): Buffer {
|
|
40
|
-
return randomBytes(16);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function generateRecoveryKey(): Buffer {
|
|
44
|
-
return randomBytes(32);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function generateMasterKey(): Buffer {
|
|
48
|
-
return randomBytes(32);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function wrapMasterKey(masterKey: Buffer, recoveryKey: Buffer): Buffer {
|
|
52
|
-
const wrapIv = randomBytes(12);
|
|
53
|
-
const cipher = createCipheriv(AES_ALGO, recoveryKey, wrapIv);
|
|
54
|
-
const wrapped = Buffer.concat([cipher.update(masterKey), cipher.final()]);
|
|
55
|
-
const tag = cipher.getAuthTag();
|
|
56
|
-
return Buffer.concat([wrapIv, wrapped, tag]);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function unwrapMasterKey(wrappedData: Buffer, recoveryKey: Buffer): Buffer {
|
|
60
|
-
const wrapIv = wrappedData.subarray(0, 12);
|
|
61
|
-
const wrapped = wrappedData.subarray(12, wrappedData.length - 16);
|
|
62
|
-
const tag = wrappedData.subarray(wrappedData.length - 16);
|
|
63
|
-
const decipher = createDecipheriv(AES_ALGO, recoveryKey, wrapIv);
|
|
64
|
-
decipher.setAuthTag(tag);
|
|
65
|
-
return Buffer.concat([decipher.update(wrapped), decipher.final()]);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function encryptBuffer(plaintext: Buffer, masterKey: Buffer): {
|
|
69
|
-
ciphertext: Buffer;
|
|
70
|
-
wrappedKey: string;
|
|
71
|
-
iv: string;
|
|
72
|
-
plaintextChecksum: string;
|
|
73
|
-
} {
|
|
74
|
-
// Hash plaintext
|
|
75
|
-
const plaintextChecksum = createHash('sha256').update(plaintext).digest('hex');
|
|
76
|
-
|
|
77
|
-
// Generate random per-file key and IV
|
|
78
|
-
const fileKey = randomBytes(32);
|
|
79
|
-
const fileIv = randomBytes(12);
|
|
80
|
-
|
|
81
|
-
// Encrypt file
|
|
82
|
-
const cipher = createCipheriv(AES_ALGO, fileKey, fileIv);
|
|
83
|
-
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
84
|
-
const authTag = cipher.getAuthTag();
|
|
85
|
-
const ciphertext = Buffer.concat([encrypted, authTag]);
|
|
86
|
-
|
|
87
|
-
// Wrap file key with master key
|
|
88
|
-
const wrapIv = randomBytes(12);
|
|
89
|
-
const wrapCipher = createCipheriv(AES_ALGO, masterKey, wrapIv);
|
|
90
|
-
const wrappedKeyData = Buffer.concat([wrapCipher.update(fileKey), wrapCipher.final()]);
|
|
91
|
-
const wrapTag = wrapCipher.getAuthTag();
|
|
92
|
-
const wrappedKeyFull = Buffer.concat([wrapIv, wrappedKeyData, wrapTag]);
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
ciphertext,
|
|
96
|
-
wrappedKey: wrappedKeyFull.toString('base64'),
|
|
97
|
-
iv: fileIv.toString('base64'),
|
|
98
|
-
plaintextChecksum,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Encrypt filename + MIME type together using master key (AES-256-GCM).
|
|
104
|
-
* Format: base64([iv(12) | ciphertext | authTag(16)])
|
|
105
|
-
*/
|
|
106
|
-
export function encryptMetadata(name: string, mimeType: string, masterKey: Buffer): string {
|
|
107
|
-
const plaintext = Buffer.from(JSON.stringify({ n: name, m: mimeType }), 'utf8');
|
|
108
|
-
const iv = randomBytes(12);
|
|
109
|
-
const cipher = createCipheriv(AES_ALGO, masterKey, iv);
|
|
110
|
-
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
111
|
-
const tag = cipher.getAuthTag();
|
|
112
|
-
return Buffer.concat([iv, encrypted, tag]).toString('base64');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Decrypt an encryptedName blob produced by encryptMetadata.
|
|
117
|
-
* Returns the original { name, mimeType }.
|
|
118
|
-
*/
|
|
119
|
-
export function decryptMetadata(encryptedBase64: string, masterKey: Buffer): FileMeta {
|
|
120
|
-
const buf = Buffer.from(encryptedBase64, 'base64');
|
|
121
|
-
const iv = buf.subarray(0, 12);
|
|
122
|
-
const tag = buf.subarray(buf.length - 16);
|
|
123
|
-
const ciphertext = buf.subarray(12, buf.length - 16);
|
|
124
|
-
const decipher = createDecipheriv(AES_ALGO, masterKey, iv);
|
|
125
|
-
decipher.setAuthTag(tag);
|
|
126
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
127
|
-
return JSON.parse(plaintext.toString('utf8')) as FileMeta;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function decryptBuffer(
|
|
131
|
-
ciphertext: Buffer,
|
|
132
|
-
wrappedKeyBase64: string,
|
|
133
|
-
ivBase64: string,
|
|
134
|
-
masterKey: Buffer,
|
|
135
|
-
expectedChecksum?: string,
|
|
136
|
-
): Buffer {
|
|
137
|
-
// Unwrap file key
|
|
138
|
-
const wrappedKeyFull = Buffer.from(wrappedKeyBase64, 'base64');
|
|
139
|
-
const wrapIv = wrappedKeyFull.subarray(0, 12);
|
|
140
|
-
const wrappedKeyData = wrappedKeyFull.subarray(12, wrappedKeyFull.length - 16);
|
|
141
|
-
const wrapTag = wrappedKeyFull.subarray(wrappedKeyFull.length - 16);
|
|
142
|
-
|
|
143
|
-
const unwrapDecipher = createDecipheriv(AES_ALGO, masterKey, wrapIv);
|
|
144
|
-
unwrapDecipher.setAuthTag(wrapTag);
|
|
145
|
-
const fileKey = Buffer.concat([unwrapDecipher.update(wrappedKeyData), unwrapDecipher.final()]);
|
|
146
|
-
|
|
147
|
-
// Decrypt file
|
|
148
|
-
const fileIv = Buffer.from(ivBase64, 'base64');
|
|
149
|
-
const authTag = ciphertext.subarray(ciphertext.length - 16);
|
|
150
|
-
const encryptedData = ciphertext.subarray(0, ciphertext.length - 16);
|
|
151
|
-
|
|
152
|
-
const decipher = createDecipheriv(AES_ALGO, fileKey, fileIv);
|
|
153
|
-
decipher.setAuthTag(authTag);
|
|
154
|
-
const plaintext = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
|
155
|
-
|
|
156
|
-
// Verify integrity
|
|
157
|
-
if (expectedChecksum) {
|
|
158
|
-
const actualChecksum = createHash('sha256').update(plaintext).digest('hex');
|
|
159
|
-
if (actualChecksum !== expectedChecksum) {
|
|
160
|
-
throw new Error('Integrity check failed — file may be corrupted');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return plaintext;
|
|
165
|
-
}
|
package/src/lib/keyring.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
|
-
import { createCipheriv, createDecipheriv, randomBytes, createHash } from 'crypto';
|
|
5
|
-
|
|
6
|
-
const SESSION_DIR = join(homedir(), '.tusky');
|
|
7
|
-
const SESSION_FILE = join(SESSION_DIR, 'session.enc');
|
|
8
|
-
|
|
9
|
-
// Derive a machine-specific key from hostname + user
|
|
10
|
-
function getMachineKey(): Buffer {
|
|
11
|
-
const machineId = `${homedir()}-tusky-session-key`;
|
|
12
|
-
return createHash('sha256').update(machineId).digest();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function storeMasterKey(masterKey: Buffer): void {
|
|
16
|
-
mkdirSync(SESSION_DIR, { recursive: true, mode: 0o700 });
|
|
17
|
-
const key = getMachineKey();
|
|
18
|
-
const iv = randomBytes(12);
|
|
19
|
-
const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
20
|
-
const encrypted = Buffer.concat([cipher.update(masterKey), cipher.final()]);
|
|
21
|
-
const tag = cipher.getAuthTag();
|
|
22
|
-
const data = Buffer.concat([iv, tag, encrypted]);
|
|
23
|
-
writeFileSync(SESSION_FILE, data, { mode: 0o600 });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function loadMasterKey(): Buffer | null {
|
|
27
|
-
if (!existsSync(SESSION_FILE)) return null;
|
|
28
|
-
try {
|
|
29
|
-
const data = readFileSync(SESSION_FILE);
|
|
30
|
-
const key = getMachineKey();
|
|
31
|
-
const iv = data.subarray(0, 12);
|
|
32
|
-
const tag = data.subarray(12, 28);
|
|
33
|
-
const encrypted = data.subarray(28);
|
|
34
|
-
const decipher = createDecipheriv('aes-256-gcm', key, iv);
|
|
35
|
-
decipher.setAuthTag(tag);
|
|
36
|
-
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
37
|
-
} catch {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function clearSession(): void {
|
|
43
|
-
try {
|
|
44
|
-
if (existsSync(SESSION_FILE)) {
|
|
45
|
-
unlinkSync(SESSION_FILE);
|
|
46
|
-
}
|
|
47
|
-
} catch {
|
|
48
|
-
// Ignore
|
|
49
|
-
}
|
|
50
|
-
}
|