spd-lib 1.1.5 → 1.1.7
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/index.js +84 -13
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -11,11 +11,27 @@ class SPD {
|
|
|
11
11
|
this.keyPair; // Generate a key pair for encryption/decryption
|
|
12
12
|
this.userKey;
|
|
13
13
|
this.salt;
|
|
14
|
+
this.signingKeyPair;
|
|
14
15
|
this.init();
|
|
15
16
|
}
|
|
16
17
|
async init() {
|
|
17
18
|
await sodium.ready;
|
|
18
19
|
this.keyPair = sodium.crypto_box_keypair()
|
|
20
|
+
this.signingKeyPair = sodium.crypto_sign_keypair();
|
|
21
|
+
}
|
|
22
|
+
static async deriveSigningKeys(passcode, salt) {
|
|
23
|
+
if (!passcode || typeof passcode !== 'string' || passcode.length < 8 ||
|
|
24
|
+
!salt || !(salt instanceof Buffer) || salt.length !== 16) {
|
|
25
|
+
throw new Error('Invalid passcode or salt.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { pbk } = await SPD.derivePBK(passcode, salt);
|
|
29
|
+
await sodium.ready;
|
|
30
|
+
// Derive separate keys for signing to avoid key reuse
|
|
31
|
+
const signingSeed = pbk.slice(0, sodium.crypto_sign_SEEDBYTES);
|
|
32
|
+
const signingKeyPair = sodium.crypto_sign_seed_keypair(signingSeed);
|
|
33
|
+
|
|
34
|
+
return { signingKeyPair };
|
|
19
35
|
}
|
|
20
36
|
async setPassKey(passcode){
|
|
21
37
|
await sodium.ready;
|
|
@@ -24,6 +40,8 @@ class SPD {
|
|
|
24
40
|
const userKey = pqcKey.publicKey;
|
|
25
41
|
this.userKey = userKey;
|
|
26
42
|
this.salt = salt;
|
|
43
|
+
const derivedKeys = await SPD.deriveSigningKeys(passcode, salt);
|
|
44
|
+
this.signingKeyPair = derivedKeys.signingKeyPair;
|
|
27
45
|
}
|
|
28
46
|
|
|
29
47
|
async addData(name, data) {
|
|
@@ -36,7 +54,8 @@ this.salt = salt;
|
|
|
36
54
|
const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
|
|
37
55
|
const encryptedData = sodium.crypto_secretbox_easy(compressedData, nonce, this.userKey);
|
|
38
56
|
const hash = crypto.createHash('sha256').update(encryptedData).digest('hex');
|
|
39
|
-
|
|
57
|
+
const signature = sodium.crypto_sign_detached(encryptedData, this.signingKeyPair.privateKey);
|
|
58
|
+
this.data.push({ dataName: name, nonce: Array.from(nonce), data: Array.from(encryptedData),signature:Array.from(signature), hash, dataType: dmap[1] });
|
|
40
59
|
}
|
|
41
60
|
|
|
42
61
|
saveToFile(outputPath) {
|
|
@@ -44,7 +63,7 @@ this.salt = salt;
|
|
|
44
63
|
throw new Error('Invalid output path or salt.');
|
|
45
64
|
}
|
|
46
65
|
|
|
47
|
-
const spdData = JSON.stringify({ data: this.data, salt: Array.from(this.salt) });
|
|
66
|
+
const spdData = JSON.stringify({ data: this.data, salt: Array.from(this.salt),signingPublicKey:Array.from(this.signingKeyPair.publicKey) });
|
|
48
67
|
const compressedSpdData = zlib.deflateSync(spdData);
|
|
49
68
|
fs.writeFileSync(outputPath, compressedSpdData, { mode: 0o600 });
|
|
50
69
|
}
|
|
@@ -60,7 +79,7 @@ this.salt = salt;
|
|
|
60
79
|
await sodium.ready;
|
|
61
80
|
const compressedSpdData = fs.readFileSync(spdPath);
|
|
62
81
|
const spdData = zlib.inflateSync(compressedSpdData).toString('utf8');
|
|
63
|
-
const { data, salt } = JSON.parse(spdData);
|
|
82
|
+
const { data, salt, signingPublicKey } = JSON.parse(spdData);
|
|
64
83
|
|
|
65
84
|
const { pqcKey } = await new SPD().convertPasscodeToPQCKeySalted(passcode, new Uint8Array(salt));
|
|
66
85
|
const pbk = pqcKey.publicKey;
|
|
@@ -69,19 +88,34 @@ this.salt = salt;
|
|
|
69
88
|
spd.keyPair = {
|
|
70
89
|
publicKey: pbk.publicKey
|
|
71
90
|
};
|
|
91
|
+
|
|
92
|
+
spd.signingKeyPair = { publicKey: Uint8Array.from(signingPublicKey) };
|
|
72
93
|
spd.data = data.map(dat => ({
|
|
73
94
|
dataName: dat.dataName,
|
|
74
95
|
nonce: Buffer.from(dat.nonce),
|
|
75
96
|
data: Buffer.from(dat.data),
|
|
97
|
+
signature: Buffer.from(dat.signature),
|
|
76
98
|
hash: dat.hash,
|
|
77
99
|
dataType: dat.dataType
|
|
78
100
|
}));
|
|
79
101
|
spd.data.forEach(dat => {
|
|
80
|
-
const
|
|
102
|
+
const encryptedBuffer = Buffer.from(dat.data);
|
|
103
|
+
const signatureBuffer = Buffer.from(dat.signature);
|
|
104
|
+
const signingPublicKeyBuffer = Uint8Array.from(signingPublicKey);
|
|
105
|
+
|
|
106
|
+
const isValid = sodium.crypto_sign_verify_detached(signatureBuffer, encryptedBuffer, signingPublicKeyBuffer);
|
|
107
|
+
if (!isValid) {
|
|
108
|
+
rej(new Error(`Signature verification failed for ${dat.dataName}`));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const calculatedHash = crypto.createHash('sha256').update(encryptedBuffer).digest('hex');
|
|
81
113
|
if (calculatedHash !== dat.hash) {
|
|
82
114
|
rej(new Error(`Data integrity check failed for ${dat.dataName}`));
|
|
115
|
+
return;
|
|
83
116
|
}
|
|
84
117
|
});
|
|
118
|
+
|
|
85
119
|
res(spd);
|
|
86
120
|
}catch{
|
|
87
121
|
rej()
|
|
@@ -95,10 +129,34 @@ this.salt = salt;
|
|
|
95
129
|
await sodium.ready;
|
|
96
130
|
let extractedFiles = {};
|
|
97
131
|
this.data.forEach(async dat => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
132
|
+
try{
|
|
133
|
+
const calculatedHash = crypto.createHash('sha256').update(dat.data).digest('hex');
|
|
134
|
+
if (calculatedHash !== dat.hash) {
|
|
135
|
+
throw new Error(`Hash mismatch for ${dat.dataName}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Verify signature
|
|
139
|
+
const isValid = sodium.crypto_sign_verify_detached(
|
|
140
|
+
Buffer.from(dat.signature),
|
|
141
|
+
Buffer.from(dat.data),
|
|
142
|
+
this.signingKeyPair.publicKey
|
|
143
|
+
);
|
|
144
|
+
if (!isValid) {
|
|
145
|
+
throw new Error(`Signature verification failed for ${dat.dataName}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Decrypt data
|
|
149
|
+
const decryptedData = sodium.crypto_secretbox_open_easy(
|
|
150
|
+
Buffer.from(dat.data),
|
|
151
|
+
Buffer.from(dat.nonce),
|
|
152
|
+
this.userKey
|
|
153
|
+
);
|
|
154
|
+
const decompressedData = zlib.inflateSync(decryptedData);
|
|
155
|
+
const dt = decompressedData.toString('utf8');
|
|
156
|
+
extractedFiles[dat.dataName] = await this.CSTI(dt, dat.dataType);
|
|
157
|
+
}catch{
|
|
158
|
+
rej()
|
|
159
|
+
}
|
|
102
160
|
});
|
|
103
161
|
res(extractedFiles);
|
|
104
162
|
}catch{
|
|
@@ -141,14 +199,16 @@ this.salt = salt;
|
|
|
141
199
|
await sodium.ready;
|
|
142
200
|
const spdDataBuffer = Buffer.from(spdData, 'base64');
|
|
143
201
|
const spdData2 = zlib.inflateSync(spdDataBuffer).toString('utf8');
|
|
144
|
-
const { data, salt } = JSON.parse(spdData2);
|
|
202
|
+
const { data, salt, signingPublicKey } = JSON.parse(spdData2);
|
|
145
203
|
const { pqcKey } = await new SPD().convertPasscodeToPQCKeySalted(passcode, new Uint8Array(salt));
|
|
204
|
+
|
|
146
205
|
const pbk = pqcKey.publicKey;
|
|
147
206
|
const spd = new SPD();
|
|
148
207
|
spd.userKey = pbk;
|
|
149
208
|
spd.keyPair = {
|
|
150
209
|
publicKey: pbk.publicKey
|
|
151
210
|
};
|
|
211
|
+
spd.signingKeyPair = { publicKey: Uint8Array.from(signingPublicKey) };
|
|
152
212
|
spd.data = data.map(dat => ({
|
|
153
213
|
dataName: dat.dataName,
|
|
154
214
|
nonce: Buffer.from(dat.nonce),
|
|
@@ -157,10 +217,21 @@ this.salt = salt;
|
|
|
157
217
|
dataType: dat.dataType
|
|
158
218
|
}));
|
|
159
219
|
spd.data.forEach(dat => {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
220
|
+
const encryptedBuffer = Buffer.from(dat.data);
|
|
221
|
+
const signatureBuffer = Buffer.from(dat.signature);
|
|
222
|
+
const signingPublicKeyBuffer = Uint8Array.from(signingPublicKey);
|
|
223
|
+
|
|
224
|
+
const isValid = sodium.crypto_sign_verify_detached(signatureBuffer, encryptedBuffer, signingPublicKeyBuffer);
|
|
225
|
+
if (!isValid) {
|
|
226
|
+
rej(new Error(`Signature verification failed for ${dat.dataName}`));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const calculatedHash = crypto.createHash('sha256').update(encryptedBuffer).digest('hex');
|
|
231
|
+
if (calculatedHash !== dat.hash) {
|
|
232
|
+
rej(new Error(`Data integrity check failed for ${dat.dataName}`));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
164
235
|
});
|
|
165
236
|
res(spd);
|
|
166
237
|
}catch{
|