@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/dist/src/crypto.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { createHash, createHmac, randomBytes, createCipheriv, createDecipheriv, pbkdf2Sync, } from 'crypto';
|
|
2
|
-
const PBKDF2_ITERATIONS = 600_000;
|
|
3
|
-
const AES_ALGO = 'aes-256-gcm';
|
|
4
|
-
const VERIFIER_CONTEXT = 'tuskydp-key-verifier-v1';
|
|
5
|
-
export function deriveMasterKey(passphrase, salt) {
|
|
6
|
-
return pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, 32, 'sha256');
|
|
7
|
-
}
|
|
8
|
-
export function computeVerifier(masterKey) {
|
|
9
|
-
return createHmac('sha256', masterKey)
|
|
10
|
-
.update(VERIFIER_CONTEXT)
|
|
11
|
-
.digest();
|
|
12
|
-
}
|
|
13
|
-
export function verifyPassphrase(masterKey, expectedVerifier) {
|
|
14
|
-
const computed = computeVerifier(masterKey);
|
|
15
|
-
if (computed.length !== expectedVerifier.length)
|
|
16
|
-
return false;
|
|
17
|
-
let diff = 0;
|
|
18
|
-
for (let i = 0; i < computed.length; i++)
|
|
19
|
-
diff |= computed[i] ^ expectedVerifier[i];
|
|
20
|
-
return diff === 0;
|
|
21
|
-
}
|
|
22
|
-
export function generateSalt() {
|
|
23
|
-
return randomBytes(16);
|
|
24
|
-
}
|
|
25
|
-
export function generateRecoveryKey() {
|
|
26
|
-
return randomBytes(32);
|
|
27
|
-
}
|
|
28
|
-
export function generateMasterKey() {
|
|
29
|
-
return randomBytes(32);
|
|
30
|
-
}
|
|
31
|
-
export function wrapMasterKey(masterKey, recoveryKey) {
|
|
32
|
-
const wrapIv = randomBytes(12);
|
|
33
|
-
const cipher = createCipheriv(AES_ALGO, recoveryKey, wrapIv);
|
|
34
|
-
const wrapped = Buffer.concat([cipher.update(masterKey), cipher.final()]);
|
|
35
|
-
const tag = cipher.getAuthTag();
|
|
36
|
-
return Buffer.concat([wrapIv, wrapped, tag]);
|
|
37
|
-
}
|
|
38
|
-
export function unwrapMasterKey(wrappedData, recoveryKey) {
|
|
39
|
-
const wrapIv = wrappedData.subarray(0, 12);
|
|
40
|
-
const wrapped = wrappedData.subarray(12, wrappedData.length - 16);
|
|
41
|
-
const tag = wrappedData.subarray(wrappedData.length - 16);
|
|
42
|
-
const decipher = createDecipheriv(AES_ALGO, recoveryKey, wrapIv);
|
|
43
|
-
decipher.setAuthTag(tag);
|
|
44
|
-
return Buffer.concat([decipher.update(wrapped), decipher.final()]);
|
|
45
|
-
}
|
|
46
|
-
export function encryptBuffer(plaintext, masterKey) {
|
|
47
|
-
// Hash plaintext
|
|
48
|
-
const plaintextChecksum = createHash('sha256').update(plaintext).digest('hex');
|
|
49
|
-
// Generate random per-file key and IV
|
|
50
|
-
const fileKey = randomBytes(32);
|
|
51
|
-
const fileIv = randomBytes(12);
|
|
52
|
-
// Encrypt file
|
|
53
|
-
const cipher = createCipheriv(AES_ALGO, fileKey, fileIv);
|
|
54
|
-
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
55
|
-
const authTag = cipher.getAuthTag();
|
|
56
|
-
const ciphertext = Buffer.concat([encrypted, authTag]);
|
|
57
|
-
// Wrap file key with master key
|
|
58
|
-
const wrapIv = randomBytes(12);
|
|
59
|
-
const wrapCipher = createCipheriv(AES_ALGO, masterKey, wrapIv);
|
|
60
|
-
const wrappedKeyData = Buffer.concat([wrapCipher.update(fileKey), wrapCipher.final()]);
|
|
61
|
-
const wrapTag = wrapCipher.getAuthTag();
|
|
62
|
-
const wrappedKeyFull = Buffer.concat([wrapIv, wrappedKeyData, wrapTag]);
|
|
63
|
-
return {
|
|
64
|
-
ciphertext,
|
|
65
|
-
wrappedKey: wrappedKeyFull.toString('base64'),
|
|
66
|
-
iv: fileIv.toString('base64'),
|
|
67
|
-
plaintextChecksum,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Encrypt filename + MIME type together using master key (AES-256-GCM).
|
|
72
|
-
* Format: base64([iv(12) | ciphertext | authTag(16)])
|
|
73
|
-
*/
|
|
74
|
-
export function encryptMetadata(name, mimeType, masterKey) {
|
|
75
|
-
const plaintext = Buffer.from(JSON.stringify({ n: name, m: mimeType }), 'utf8');
|
|
76
|
-
const iv = randomBytes(12);
|
|
77
|
-
const cipher = createCipheriv(AES_ALGO, masterKey, iv);
|
|
78
|
-
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
79
|
-
const tag = cipher.getAuthTag();
|
|
80
|
-
return Buffer.concat([iv, encrypted, tag]).toString('base64');
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Decrypt an encryptedName blob produced by encryptMetadata.
|
|
84
|
-
* Returns the original { name, mimeType }.
|
|
85
|
-
*/
|
|
86
|
-
export function decryptMetadata(encryptedBase64, masterKey) {
|
|
87
|
-
const buf = Buffer.from(encryptedBase64, 'base64');
|
|
88
|
-
const iv = buf.subarray(0, 12);
|
|
89
|
-
const tag = buf.subarray(buf.length - 16);
|
|
90
|
-
const ciphertext = buf.subarray(12, buf.length - 16);
|
|
91
|
-
const decipher = createDecipheriv(AES_ALGO, masterKey, iv);
|
|
92
|
-
decipher.setAuthTag(tag);
|
|
93
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
94
|
-
return JSON.parse(plaintext.toString('utf8'));
|
|
95
|
-
}
|
|
96
|
-
export function decryptBuffer(ciphertext, wrappedKeyBase64, ivBase64, masterKey, expectedChecksum) {
|
|
97
|
-
// Unwrap file key
|
|
98
|
-
const wrappedKeyFull = Buffer.from(wrappedKeyBase64, 'base64');
|
|
99
|
-
const wrapIv = wrappedKeyFull.subarray(0, 12);
|
|
100
|
-
const wrappedKeyData = wrappedKeyFull.subarray(12, wrappedKeyFull.length - 16);
|
|
101
|
-
const wrapTag = wrappedKeyFull.subarray(wrappedKeyFull.length - 16);
|
|
102
|
-
const unwrapDecipher = createDecipheriv(AES_ALGO, masterKey, wrapIv);
|
|
103
|
-
unwrapDecipher.setAuthTag(wrapTag);
|
|
104
|
-
const fileKey = Buffer.concat([unwrapDecipher.update(wrappedKeyData), unwrapDecipher.final()]);
|
|
105
|
-
// Decrypt file
|
|
106
|
-
const fileIv = Buffer.from(ivBase64, 'base64');
|
|
107
|
-
const authTag = ciphertext.subarray(ciphertext.length - 16);
|
|
108
|
-
const encryptedData = ciphertext.subarray(0, ciphertext.length - 16);
|
|
109
|
-
const decipher = createDecipheriv(AES_ALGO, fileKey, fileIv);
|
|
110
|
-
decipher.setAuthTag(authTag);
|
|
111
|
-
const plaintext = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
|
112
|
-
// Verify integrity
|
|
113
|
-
if (expectedChecksum) {
|
|
114
|
-
const actualChecksum = createHash('sha256').update(plaintext).digest('hex');
|
|
115
|
-
if (actualChecksum !== expectedChecksum) {
|
|
116
|
-
throw new Error('Integrity check failed — file may be corrupted');
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return plaintext;
|
|
120
|
-
}
|
|
121
|
-
//# sourceMappingURL=crypto.js.map
|
package/dist/src/crypto.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,UAAU,EACV,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,UAAU,GACX,MAAM,QAAQ,CAAC;AAShB,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAClC,MAAM,QAAQ,GAAG,aAAsB,CAAC;AACxC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAEnD,MAAM,UAAU,eAAe,CAAC,UAAkB,EAAE,IAAY;IAC9D,OAAO,UAAU,CAAC,UAAU,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,OAAO,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC;SACnC,MAAM,CAAC,gBAAgB,CAAC;SACxB,MAAM,EAAE,CAAC;AACd,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,gBAAwB;IAC1E,MAAM,QAAQ,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,QAAQ,CAAC,MAAM,KAAK,gBAAgB,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACpF,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,WAAmB;IAClE,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC1E,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAChC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,WAAmB,EAAE,WAAmB;IACtE,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IACjE,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACzB,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,SAAiB;IAMhE,iBAAiB;IACjB,MAAM,iBAAiB,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE/E,sCAAsC;IACtC,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAE/B,eAAe;IACf,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5E,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IAEvD,gCAAgC;IAChC,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC/B,MAAM,UAAU,GAAG,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAC/D,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACvF,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,EAAE,CAAC;IACxC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;IAExE,OAAO;QACL,UAAU;QACV,UAAU,EAAE,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC7C,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC7B,iBAAiB;KAClB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,QAAgB,EAAE,SAAiB;IAC/E,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;IAChF,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5E,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAChC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,eAAuB,EAAE,SAAiB;IACxE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;IACnD,MAAM,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/B,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;IAC3D,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACzB,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACjF,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAa,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,UAAkB,EAClB,gBAAwB,EACxB,QAAgB,EAChB,SAAiB,EACjB,gBAAyB;IAEzB,kBAAkB;IAClB,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9C,MAAM,cAAc,GAAG,cAAc,CAAC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAC/E,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAEpE,MAAM,cAAc,GAAG,gBAAgB,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IACrE,cAAc,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAE/F,eAAe;IACf,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAC5D,MAAM,aAAa,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAErE,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7D,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAEpF,mBAAmB;IACnB,IAAI,gBAAgB,EAAE,CAAC;QACrB,MAAM,cAAc,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,IAAI,cAAc,KAAK,gBAAgB,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"keyring.d.ts","sourceRoot":"","sources":["../../../src/lib/keyring.ts"],"names":[],"mappings":"AAcA,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAStD;AAED,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAc7C;AAED,wBAAgB,YAAY,IAAI,IAAI,CAQnC"}
|
package/dist/src/lib/keyring.js
DELETED
|
@@ -1,49 +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
|
-
const SESSION_DIR = join(homedir(), '.tusky');
|
|
6
|
-
const SESSION_FILE = join(SESSION_DIR, 'session.enc');
|
|
7
|
-
// Derive a machine-specific key from hostname + user
|
|
8
|
-
function getMachineKey() {
|
|
9
|
-
const machineId = `${homedir()}-tusky-session-key`;
|
|
10
|
-
return createHash('sha256').update(machineId).digest();
|
|
11
|
-
}
|
|
12
|
-
export function storeMasterKey(masterKey) {
|
|
13
|
-
mkdirSync(SESSION_DIR, { recursive: true, mode: 0o700 });
|
|
14
|
-
const key = getMachineKey();
|
|
15
|
-
const iv = randomBytes(12);
|
|
16
|
-
const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
17
|
-
const encrypted = Buffer.concat([cipher.update(masterKey), cipher.final()]);
|
|
18
|
-
const tag = cipher.getAuthTag();
|
|
19
|
-
const data = Buffer.concat([iv, tag, encrypted]);
|
|
20
|
-
writeFileSync(SESSION_FILE, data, { mode: 0o600 });
|
|
21
|
-
}
|
|
22
|
-
export function loadMasterKey() {
|
|
23
|
-
if (!existsSync(SESSION_FILE))
|
|
24
|
-
return null;
|
|
25
|
-
try {
|
|
26
|
-
const data = readFileSync(SESSION_FILE);
|
|
27
|
-
const key = getMachineKey();
|
|
28
|
-
const iv = data.subarray(0, 12);
|
|
29
|
-
const tag = data.subarray(12, 28);
|
|
30
|
-
const encrypted = data.subarray(28);
|
|
31
|
-
const decipher = createDecipheriv('aes-256-gcm', key, iv);
|
|
32
|
-
decipher.setAuthTag(tag);
|
|
33
|
-
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
export function clearSession() {
|
|
40
|
-
try {
|
|
41
|
-
if (existsSync(SESSION_FILE)) {
|
|
42
|
-
unlinkSync(SESSION_FILE);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
// Ignore
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=keyring.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"keyring.js","sourceRoot":"","sources":["../../../src/lib/keyring.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACpF,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEnF,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC9C,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;AAEtD,qDAAqD;AACrD,SAAS,aAAa;IACpB,MAAM,SAAS,GAAG,GAAG,OAAO,EAAE,oBAAoB,CAAC;IACnD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;IAC5B,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5E,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;IACjD,aAAa,CAAC,YAAY,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QAC1D,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAC7B,UAAU,CAAC,YAAY,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC"}
|
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
deriveMasterKey,
|
|
4
|
-
computeVerifier,
|
|
5
|
-
verifyPassphrase,
|
|
6
|
-
generateSalt,
|
|
7
|
-
generateMasterKey,
|
|
8
|
-
generateRecoveryKey,
|
|
9
|
-
wrapMasterKey,
|
|
10
|
-
unwrapMasterKey,
|
|
11
|
-
encryptBuffer,
|
|
12
|
-
decryptBuffer,
|
|
13
|
-
} from '../crypto.js';
|
|
14
|
-
|
|
15
|
-
describe('deriveMasterKey', () => {
|
|
16
|
-
it('produces deterministic 32-byte output', () => {
|
|
17
|
-
const salt = Buffer.from('a'.repeat(32), 'hex');
|
|
18
|
-
const key1 = deriveMasterKey('passphrase', salt);
|
|
19
|
-
const key2 = deriveMasterKey('passphrase', salt);
|
|
20
|
-
expect(key1).toEqual(key2);
|
|
21
|
-
expect(key1.length).toBe(32);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('different passphrase produces different key', () => {
|
|
25
|
-
const salt = Buffer.from('a'.repeat(32), 'hex');
|
|
26
|
-
const key1 = deriveMasterKey('pass1', salt);
|
|
27
|
-
const key2 = deriveMasterKey('pass2', salt);
|
|
28
|
-
expect(key1).not.toEqual(key2);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('different salt produces different key', () => {
|
|
32
|
-
const salt1 = Buffer.from('a'.repeat(32), 'hex');
|
|
33
|
-
const salt2 = Buffer.from('b'.repeat(32), 'hex');
|
|
34
|
-
const key1 = deriveMasterKey('pass', salt1);
|
|
35
|
-
const key2 = deriveMasterKey('pass', salt2);
|
|
36
|
-
expect(key1).not.toEqual(key2);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe('computeVerifier / verifyPassphrase', () => {
|
|
41
|
-
it('verifier matches correct key', () => {
|
|
42
|
-
const key = generateMasterKey();
|
|
43
|
-
const verifier = computeVerifier(key);
|
|
44
|
-
expect(verifyPassphrase(key, verifier)).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('verifier rejects wrong key', () => {
|
|
48
|
-
const key1 = generateMasterKey();
|
|
49
|
-
const key2 = generateMasterKey();
|
|
50
|
-
const verifier = computeVerifier(key1);
|
|
51
|
-
expect(verifyPassphrase(key2, verifier)).toBe(false);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('verifier is 32 bytes (SHA-256)', () => {
|
|
55
|
-
const key = generateMasterKey();
|
|
56
|
-
const verifier = computeVerifier(key);
|
|
57
|
-
expect(verifier.length).toBe(32);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('generateSalt / generateMasterKey / generateRecoveryKey', () => {
|
|
62
|
-
it('generateSalt returns 16 bytes', () => {
|
|
63
|
-
expect(generateSalt().length).toBe(16);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('generateMasterKey returns 32 bytes', () => {
|
|
67
|
-
expect(generateMasterKey().length).toBe(32);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('generateRecoveryKey returns 32 bytes', () => {
|
|
71
|
-
expect(generateRecoveryKey().length).toBe(32);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('generates different values each time', () => {
|
|
75
|
-
const a = generateSalt();
|
|
76
|
-
const b = generateSalt();
|
|
77
|
-
expect(a).not.toEqual(b);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('wrapMasterKey / unwrapMasterKey', () => {
|
|
82
|
-
it('round-trips successfully', () => {
|
|
83
|
-
const masterKey = generateMasterKey();
|
|
84
|
-
const recoveryKey = generateRecoveryKey();
|
|
85
|
-
const wrapped = wrapMasterKey(masterKey, recoveryKey);
|
|
86
|
-
const unwrapped = unwrapMasterKey(wrapped, recoveryKey);
|
|
87
|
-
expect(unwrapped).toEqual(masterKey);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('wrong recovery key throws', () => {
|
|
91
|
-
const masterKey = generateMasterKey();
|
|
92
|
-
const recoveryKey = generateRecoveryKey();
|
|
93
|
-
const wrongKey = generateRecoveryKey();
|
|
94
|
-
const wrapped = wrapMasterKey(masterKey, recoveryKey);
|
|
95
|
-
expect(() => unwrapMasterKey(wrapped, wrongKey)).toThrow();
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('encryptBuffer / decryptBuffer', () => {
|
|
100
|
-
it('round-trips successfully', () => {
|
|
101
|
-
const masterKey = generateMasterKey();
|
|
102
|
-
const plaintext = Buffer.from('Hello, TuskyDP!');
|
|
103
|
-
const { ciphertext, wrappedKey, iv, plaintextChecksum } = encryptBuffer(plaintext, masterKey);
|
|
104
|
-
const decrypted = decryptBuffer(ciphertext, wrappedKey, iv, masterKey, plaintextChecksum);
|
|
105
|
-
expect(decrypted).toEqual(plaintext);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('wrong master key throws', () => {
|
|
109
|
-
const masterKey = generateMasterKey();
|
|
110
|
-
const wrongKey = generateMasterKey();
|
|
111
|
-
const plaintext = Buffer.from('secret data');
|
|
112
|
-
const { ciphertext, wrappedKey, iv } = encryptBuffer(plaintext, masterKey);
|
|
113
|
-
expect(() => decryptBuffer(ciphertext, wrappedKey, iv, wrongKey)).toThrow();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('verifies checksum on corrupted data', () => {
|
|
117
|
-
const masterKey = generateMasterKey();
|
|
118
|
-
const plaintext = Buffer.from('important data');
|
|
119
|
-
const { ciphertext, wrappedKey, iv } = encryptBuffer(plaintext, masterKey);
|
|
120
|
-
// Pass a wrong checksum
|
|
121
|
-
expect(() => decryptBuffer(ciphertext, wrappedKey, iv, masterKey, 'wrong_checksum')).toThrow(
|
|
122
|
-
'Integrity check failed',
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('handles empty buffer', () => {
|
|
127
|
-
const masterKey = generateMasterKey();
|
|
128
|
-
const plaintext = Buffer.alloc(0);
|
|
129
|
-
const { ciphertext, wrappedKey, iv, plaintextChecksum } = encryptBuffer(plaintext, masterKey);
|
|
130
|
-
const decrypted = decryptBuffer(ciphertext, wrappedKey, iv, masterKey, plaintextChecksum);
|
|
131
|
-
expect(decrypted).toEqual(plaintext);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('handles large buffer', () => {
|
|
135
|
-
const masterKey = generateMasterKey();
|
|
136
|
-
const plaintext = Buffer.alloc(1024 * 1024, 0xab); // 1 MB
|
|
137
|
-
const { ciphertext, wrappedKey, iv, plaintextChecksum } = encryptBuffer(plaintext, masterKey);
|
|
138
|
-
const decrypted = decryptBuffer(ciphertext, wrappedKey, iv, masterKey, plaintextChecksum);
|
|
139
|
-
expect(decrypted).toEqual(plaintext);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('ciphertext is larger than plaintext (auth tag)', () => {
|
|
143
|
-
const masterKey = generateMasterKey();
|
|
144
|
-
const plaintext = Buffer.from('test');
|
|
145
|
-
const { ciphertext } = encryptBuffer(plaintext, masterKey);
|
|
146
|
-
expect(ciphertext.length).toBeGreaterThan(plaintext.length);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Cross-platform interop test vectors.
|
|
152
|
-
*
|
|
153
|
-
* These tests use hardcoded inputs and expected outputs generated from Node.js crypto.
|
|
154
|
-
* Any implementation (Rust, Python, Go, browser WebCrypto) of TuskyDP E2E encryption
|
|
155
|
-
* MUST produce identical outputs for the same inputs.
|
|
156
|
-
*
|
|
157
|
-
* Algorithms:
|
|
158
|
-
* - deriveMasterKey: PBKDF2-SHA256, 600,000 iterations, 32-byte output
|
|
159
|
-
* - computeVerifier: HMAC-SHA256 with context string "tuskydp-key-verifier-v1"
|
|
160
|
-
* - wrapMasterKey/unwrapMasterKey: AES-256-GCM, 12-byte IV, 16-byte auth tag
|
|
161
|
-
* Wire format: [IV (12) | ciphertext (32) | authTag (16)] = 60 bytes
|
|
162
|
-
* - encryptBuffer/decryptBuffer: AES-256-GCM per-file key + AES-256-GCM key wrapping
|
|
163
|
-
* Ciphertext format: [encrypted data | authTag (16)]
|
|
164
|
-
* WrappedKey format (base64): [wrapIV (12) | wrapped fileKey (32) | wrapAuthTag (16)]
|
|
165
|
-
*/
|
|
166
|
-
describe('cross-platform interop test vectors', () => {
|
|
167
|
-
// --- Test Vector Set 1 ---
|
|
168
|
-
const TV1 = {
|
|
169
|
-
passphrase: 'correct horse battery staple',
|
|
170
|
-
salt: '0102030405060708090a0b0c0d0e0f10',
|
|
171
|
-
derivedKey: '0008e69b89ffac1aa7bb1f44289ba65afaa711dd450f0aab6c322e4cd57bb216',
|
|
172
|
-
verifier: 'b15d5b00572798fd1f13667d790823b8756ba166c61cbdf99e2bbcfefa30baa6',
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// --- Test Vector Set 2 ---
|
|
176
|
-
const TV2 = {
|
|
177
|
-
passphrase: 'hunter2',
|
|
178
|
-
salt: 'deadbeefcafebabe1234567890abcdef',
|
|
179
|
-
derivedKey: '0992dbbb89cef243b310b48482d8c73ed8389ee29fb1f5a25c70552f5f01c6b8',
|
|
180
|
-
verifier: '5387d9046eafd061d4473e2b87d885a325630b3ae4f2cb11b94d3cf3cd81b35e',
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
// --- Key wrapping vector ---
|
|
184
|
-
const WRAP_VECTOR = {
|
|
185
|
-
masterKey: '0011223344556677889900aabbccddeeff0011223344556677889900aabbccdd',
|
|
186
|
-
recoveryKey: 'aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899',
|
|
187
|
-
// Pre-computed wrapped output (includes IV + ciphertext + authTag)
|
|
188
|
-
wrappedFull: 'AAECAwQFBgcICQoL5f88lTmbbm1t74wjfzEkZC6OBtte9PbLWIrK80jgH2CbiTyQ+3FYtoN2oaMj40Rg',
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
// --- File encryption vector ---
|
|
192
|
-
const ENCRYPT_VECTOR = {
|
|
193
|
-
plaintext: 'Hello, TuskyDP! This is a test vector for cross-platform interop.',
|
|
194
|
-
plaintextChecksum: 'dd543914f5d1af720f853daad21143e7b9376ea26c7c3e208390cafc223ea1f7',
|
|
195
|
-
masterKey: '0011223344556677889900aabbccddeeff0011223344556677889900aabbccdd',
|
|
196
|
-
// Pre-computed ciphertext (encrypted data + authTag)
|
|
197
|
-
ciphertext:
|
|
198
|
-
'B5MbJQBGzwGMbS9gj5V0xY2GoxzWGUx3ujkR2s9kRpmSIGKAt9pR8BE8VFc7X8Q0AcLukUQDy4R5MhryTj0rFWEGv4uS3WQzDizQDnYTe8uT',
|
|
199
|
-
// Pre-computed wrapped file key (wrapIV + wrapped + wrapAuthTag)
|
|
200
|
-
wrappedKey: 'FRQTEhEQEA8ODQwK5oDpgjZPzbX4cHXCntl3HkaI+jWLqmRv7cxPzoein4YMkzJn9PAt3Qf4RRMiP0Yl',
|
|
201
|
-
// File IV
|
|
202
|
-
iv: 'CgsMDQ4PEBESExQV',
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
describe('deriveMasterKey vectors', () => {
|
|
206
|
-
it('vector 1: "correct horse battery staple"', () => {
|
|
207
|
-
const salt = Buffer.from(TV1.salt, 'hex');
|
|
208
|
-
const key = deriveMasterKey(TV1.passphrase, salt);
|
|
209
|
-
expect(key.toString('hex')).toBe(TV1.derivedKey);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('vector 2: "hunter2"', () => {
|
|
213
|
-
const salt = Buffer.from(TV2.salt, 'hex');
|
|
214
|
-
const key = deriveMasterKey(TV2.passphrase, salt);
|
|
215
|
-
expect(key.toString('hex')).toBe(TV2.derivedKey);
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
describe('computeVerifier vectors', () => {
|
|
220
|
-
it('vector 1: verifier from derived key 1', () => {
|
|
221
|
-
const masterKey = Buffer.from(TV1.derivedKey, 'hex');
|
|
222
|
-
const verifier = computeVerifier(masterKey);
|
|
223
|
-
expect(verifier.toString('hex')).toBe(TV1.verifier);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('vector 2: verifier from derived key 2', () => {
|
|
227
|
-
const masterKey = Buffer.from(TV2.derivedKey, 'hex');
|
|
228
|
-
const verifier = computeVerifier(masterKey);
|
|
229
|
-
expect(verifier.toString('hex')).toBe(TV2.verifier);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('verifyPassphrase accepts correct verifier', () => {
|
|
233
|
-
const masterKey = Buffer.from(TV1.derivedKey, 'hex');
|
|
234
|
-
const verifier = Buffer.from(TV1.verifier, 'hex');
|
|
235
|
-
expect(verifyPassphrase(masterKey, verifier)).toBe(true);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('verifyPassphrase rejects wrong verifier', () => {
|
|
239
|
-
const masterKey = Buffer.from(TV1.derivedKey, 'hex');
|
|
240
|
-
const verifier = Buffer.from(TV2.verifier, 'hex');
|
|
241
|
-
expect(verifyPassphrase(masterKey, verifier)).toBe(false);
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
describe('unwrapMasterKey vector', () => {
|
|
246
|
-
it('unwraps pre-computed wrapped key to original master key', () => {
|
|
247
|
-
const recoveryKey = Buffer.from(WRAP_VECTOR.recoveryKey, 'hex');
|
|
248
|
-
const wrappedData = Buffer.from(WRAP_VECTOR.wrappedFull, 'base64');
|
|
249
|
-
const unwrapped = unwrapMasterKey(wrappedData, recoveryKey);
|
|
250
|
-
expect(unwrapped.toString('hex')).toBe(WRAP_VECTOR.masterKey);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it('wrapped data has correct wire format length (60 bytes)', () => {
|
|
254
|
-
const wrappedData = Buffer.from(WRAP_VECTOR.wrappedFull, 'base64');
|
|
255
|
-
// 12 (IV) + 32 (encrypted key) + 16 (auth tag) = 60
|
|
256
|
-
expect(wrappedData.length).toBe(60);
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
describe('decryptBuffer vector', () => {
|
|
261
|
-
it('decrypts pre-computed ciphertext to original plaintext', () => {
|
|
262
|
-
const masterKey = Buffer.from(ENCRYPT_VECTOR.masterKey, 'hex');
|
|
263
|
-
const ciphertext = Buffer.from(ENCRYPT_VECTOR.ciphertext, 'base64');
|
|
264
|
-
const decrypted = decryptBuffer(
|
|
265
|
-
ciphertext,
|
|
266
|
-
ENCRYPT_VECTOR.wrappedKey,
|
|
267
|
-
ENCRYPT_VECTOR.iv,
|
|
268
|
-
masterKey,
|
|
269
|
-
ENCRYPT_VECTOR.plaintextChecksum,
|
|
270
|
-
);
|
|
271
|
-
expect(decrypted.toString('utf8')).toBe(ENCRYPT_VECTOR.plaintext);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('decrypts without checksum verification', () => {
|
|
275
|
-
const masterKey = Buffer.from(ENCRYPT_VECTOR.masterKey, 'hex');
|
|
276
|
-
const ciphertext = Buffer.from(ENCRYPT_VECTOR.ciphertext, 'base64');
|
|
277
|
-
const decrypted = decryptBuffer(
|
|
278
|
-
ciphertext,
|
|
279
|
-
ENCRYPT_VECTOR.wrappedKey,
|
|
280
|
-
ENCRYPT_VECTOR.iv,
|
|
281
|
-
masterKey,
|
|
282
|
-
);
|
|
283
|
-
expect(decrypted.toString('utf8')).toBe(ENCRYPT_VECTOR.plaintext);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it('rejects tampered ciphertext', () => {
|
|
287
|
-
const masterKey = Buffer.from(ENCRYPT_VECTOR.masterKey, 'hex');
|
|
288
|
-
const ciphertext = Buffer.from(ENCRYPT_VECTOR.ciphertext, 'base64');
|
|
289
|
-
// Flip one byte in the encrypted data
|
|
290
|
-
ciphertext[0] ^= 0xff;
|
|
291
|
-
expect(() =>
|
|
292
|
-
decryptBuffer(ciphertext, ENCRYPT_VECTOR.wrappedKey, ENCRYPT_VECTOR.iv, masterKey),
|
|
293
|
-
).toThrow();
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('rejects tampered wrapped key', () => {
|
|
297
|
-
const masterKey = Buffer.from(ENCRYPT_VECTOR.masterKey, 'hex');
|
|
298
|
-
const ciphertext = Buffer.from(ENCRYPT_VECTOR.ciphertext, 'base64');
|
|
299
|
-
// Corrupt the wrapped key
|
|
300
|
-
const badWrappedKey = Buffer.from(ENCRYPT_VECTOR.wrappedKey, 'base64');
|
|
301
|
-
badWrappedKey[15] ^= 0xff;
|
|
302
|
-
expect(() =>
|
|
303
|
-
decryptBuffer(ciphertext, badWrappedKey.toString('base64'), ENCRYPT_VECTOR.iv, masterKey),
|
|
304
|
-
).toThrow();
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
it('plaintextChecksum matches SHA-256 of plaintext', () => {
|
|
308
|
-
const { createHash } = require('crypto');
|
|
309
|
-
const expectedChecksum = createHash('sha256')
|
|
310
|
-
.update(Buffer.from(ENCRYPT_VECTOR.plaintext, 'utf8'))
|
|
311
|
-
.digest('hex');
|
|
312
|
-
expect(expectedChecksum).toBe(ENCRYPT_VECTOR.plaintextChecksum);
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
});
|