@node-projects/excelforge 2.3.0 → 3.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/.github/FUNDING.yml +4 -0
- package/MISSING.md +326 -0
- package/README.md +484 -12
- package/dist/core/SharedStrings.js +6 -2
- package/dist/core/SharedStrings.js.map +1 -1
- package/dist/core/Workbook.d.ts +41 -1
- package/dist/core/Workbook.js +773 -57
- package/dist/core/Workbook.js.map +1 -1
- package/dist/core/WorkbookReader.d.ts +18 -4
- package/dist/core/WorkbookReader.js +1386 -20
- package/dist/core/WorkbookReader.js.map +1 -1
- package/dist/core/Worksheet.d.ts +130 -2
- package/dist/core/Worksheet.js +792 -66
- package/dist/core/Worksheet.js.map +1 -1
- package/dist/core/types.d.ts +287 -5
- package/dist/core/types.js +12 -1
- package/dist/core/types.js.map +1 -1
- package/dist/features/ChartBuilder.d.ts +9 -1
- package/dist/features/ChartBuilder.js +140 -14
- package/dist/features/ChartBuilder.js.map +1 -1
- package/dist/features/CsvModule.d.ts +11 -0
- package/dist/features/CsvModule.js +137 -0
- package/dist/features/CsvModule.js.map +1 -0
- package/dist/features/Encryption.d.ts +6 -0
- package/dist/features/Encryption.js +806 -0
- package/dist/features/Encryption.js.map +1 -0
- package/dist/features/FormControlBuilder.d.ts +6 -0
- package/dist/features/FormControlBuilder.js +135 -0
- package/dist/features/FormControlBuilder.js.map +1 -0
- package/dist/features/FormulaEngine.d.ts +22 -0
- package/dist/features/FormulaEngine.js +498 -0
- package/dist/features/FormulaEngine.js.map +1 -0
- package/dist/features/HtmlModule.d.ts +21 -0
- package/dist/features/HtmlModule.js +1417 -0
- package/dist/features/HtmlModule.js.map +1 -0
- package/dist/features/JsonModule.d.ts +10 -0
- package/dist/features/JsonModule.js +76 -0
- package/dist/features/JsonModule.js.map +1 -0
- package/dist/features/PivotTableBuilder.d.ts +7 -0
- package/dist/features/PivotTableBuilder.js +170 -0
- package/dist/features/PivotTableBuilder.js.map +1 -0
- package/dist/features/Signing.d.ts +12 -0
- package/dist/features/Signing.js +318 -0
- package/dist/features/Signing.js.map +1 -0
- package/dist/features/TableBuilder.js +2 -2
- package/dist/features/TableBuilder.js.map +1 -1
- package/dist/index-min.js +579 -144
- package/dist/index.d.ts +17 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/styles/StyleRegistry.d.ts +14 -0
- package/dist/styles/StyleRegistry.js +95 -30
- package/dist/styles/StyleRegistry.js.map +1 -1
- package/dist/utils/helpers.d.ts +4 -0
- package/dist/utils/helpers.js +64 -14
- package/dist/utils/helpers.js.map +1 -1
- package/dist/utils/zip.js +145 -73
- package/dist/utils/zip.js.map +1 -1
- package/dist/vba/VbaProject.d.ts +19 -0
- package/dist/vba/VbaProject.js +281 -0
- package/dist/vba/VbaProject.js.map +1 -0
- package/dist/vba/cfb.d.ts +7 -0
- package/dist/vba/cfb.js +352 -0
- package/dist/vba/cfb.js.map +1 -0
- package/dist/vba/ovba.d.ts +2 -0
- package/dist/vba/ovba.js +137 -0
- package/dist/vba/ovba.js.map +1 -0
- package/package.json +4 -3
- package/validator.cs +0 -155
- package/validatorEpplus.cs +0 -27
- package/validatorReadData.cs +0 -111
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
const CFB_SIG = new Uint8Array([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1]);
|
|
2
|
+
const ENDOFCHAIN = 0xFFFFFFFE;
|
|
3
|
+
const FREESECT = 0xFFFFFFFF;
|
|
4
|
+
const FATSECT = 0xFFFFFFFD;
|
|
5
|
+
const SECTOR_SZ = 512;
|
|
6
|
+
const MINI_SZ = 64;
|
|
7
|
+
const MINI_CUT = 0x1000;
|
|
8
|
+
const DIR_SZ = 128;
|
|
9
|
+
function setU16(buf, off, v) {
|
|
10
|
+
buf[off] = v & 0xFF;
|
|
11
|
+
buf[off + 1] = (v >> 8) & 0xFF;
|
|
12
|
+
}
|
|
13
|
+
function setU32(buf, off, v) {
|
|
14
|
+
buf[off] = v & 0xFF;
|
|
15
|
+
buf[off + 1] = (v >> 8) & 0xFF;
|
|
16
|
+
buf[off + 2] = (v >> 16) & 0xFF;
|
|
17
|
+
buf[off + 3] = (v >> 24) & 0xFF;
|
|
18
|
+
}
|
|
19
|
+
function u16(buf, off) {
|
|
20
|
+
return buf[off] | (buf[off + 1] << 8);
|
|
21
|
+
}
|
|
22
|
+
function u32(buf, off) {
|
|
23
|
+
return (buf[off] | (buf[off + 1] << 8) | (buf[off + 2] << 16) | (buf[off + 3] << 24)) >>> 0;
|
|
24
|
+
}
|
|
25
|
+
function encUtf16(name) {
|
|
26
|
+
const bytes = new Uint8Array(64);
|
|
27
|
+
const len = Math.min(name.length, 31);
|
|
28
|
+
for (let i = 0; i < len; i++) {
|
|
29
|
+
setU16(bytes, i * 2, name.charCodeAt(i));
|
|
30
|
+
}
|
|
31
|
+
setU16(bytes, len * 2, 0);
|
|
32
|
+
return { bytes, size: (len + 1) * 2 };
|
|
33
|
+
}
|
|
34
|
+
function buildCfb(entries) {
|
|
35
|
+
const dirCount = 1 + entries.length;
|
|
36
|
+
const allStreams = entries.map(e => e.data);
|
|
37
|
+
let miniData = new Uint8Array(0);
|
|
38
|
+
const miniBlocks = [];
|
|
39
|
+
const regularBlocks = [];
|
|
40
|
+
let miniOffset = 0;
|
|
41
|
+
for (const stream of allStreams) {
|
|
42
|
+
if (stream.length < MINI_CUT) {
|
|
43
|
+
const padLen = Math.ceil(stream.length / MINI_SZ) * MINI_SZ;
|
|
44
|
+
const newMini = new Uint8Array(miniData.length + padLen);
|
|
45
|
+
newMini.set(miniData);
|
|
46
|
+
newMini.set(stream, miniData.length);
|
|
47
|
+
miniBlocks.push({ offset: miniOffset, size: stream.length });
|
|
48
|
+
miniOffset += padLen;
|
|
49
|
+
miniData = newMini;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
miniBlocks.push({ offset: -1, size: stream.length });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const miniStreamSectors = Math.ceil(miniData.length / SECTOR_SZ);
|
|
56
|
+
const dirSectors = Math.ceil((dirCount * DIR_SZ) / SECTOR_SZ);
|
|
57
|
+
let nextSector = 1 + dirSectors + miniStreamSectors;
|
|
58
|
+
const regularStartSectors = [];
|
|
59
|
+
for (let i = 0; i < entries.length; i++) {
|
|
60
|
+
if (miniBlocks[i].offset === -1) {
|
|
61
|
+
regularStartSectors.push(nextSector);
|
|
62
|
+
const sects = Math.ceil(allStreams[i].length / SECTOR_SZ);
|
|
63
|
+
regularBlocks.push({ data: allStreams[i], startSector: nextSector });
|
|
64
|
+
nextSector += sects;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
regularStartSectors.push(-1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const totalSectors = nextSector;
|
|
71
|
+
const fatEntries = new Uint32Array(Math.max(totalSectors, 128));
|
|
72
|
+
fatEntries.fill(FREESECT);
|
|
73
|
+
fatEntries[0] = FATSECT;
|
|
74
|
+
for (let i = 1; i < 1 + dirSectors; i++) {
|
|
75
|
+
fatEntries[i] = (i < dirSectors) ? i + 1 : ENDOFCHAIN;
|
|
76
|
+
}
|
|
77
|
+
for (let i = 1 + dirSectors; i < 1 + dirSectors + miniStreamSectors; i++) {
|
|
78
|
+
fatEntries[i] = (i < dirSectors + miniStreamSectors) ? i + 1 : ENDOFCHAIN;
|
|
79
|
+
}
|
|
80
|
+
for (const rb of regularBlocks) {
|
|
81
|
+
const sects = Math.ceil(rb.data.length / SECTOR_SZ);
|
|
82
|
+
for (let j = 0; j < sects; j++) {
|
|
83
|
+
fatEntries[rb.startSector + j] = j < sects - 1 ? rb.startSector + j + 1 : ENDOFCHAIN;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const miniSectorCount = Math.ceil(miniData.length / MINI_SZ);
|
|
87
|
+
const miniFat = new Uint32Array(Math.max(miniSectorCount, 128));
|
|
88
|
+
miniFat.fill(FREESECT);
|
|
89
|
+
let mIdx = 0;
|
|
90
|
+
for (let i = 0; i < entries.length; i++) {
|
|
91
|
+
if (miniBlocks[i].offset !== -1) {
|
|
92
|
+
const msectors = Math.ceil(allStreams[i].length / MINI_SZ);
|
|
93
|
+
const startMiniSector = miniBlocks[i].offset / MINI_SZ;
|
|
94
|
+
for (let j = 0; j < msectors; j++) {
|
|
95
|
+
miniFat[startMiniSector + j] = j < msectors - 1 ? startMiniSector + j + 1 : ENDOFCHAIN;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const miniFatSectorCount = Math.ceil(miniFat.length * 4 / SECTOR_SZ);
|
|
100
|
+
const miniFatStartSector = nextSector;
|
|
101
|
+
for (let i = 0; i < miniFatSectorCount; i++) {
|
|
102
|
+
fatEntries[miniFatStartSector + i] = i < miniFatSectorCount - 1 ? miniFatStartSector + i + 1 : ENDOFCHAIN;
|
|
103
|
+
}
|
|
104
|
+
const actualTotalSectors = miniFatStartSector + miniFatSectorCount;
|
|
105
|
+
const fileSize = (1 + actualTotalSectors) * SECTOR_SZ;
|
|
106
|
+
const out = new Uint8Array(fileSize);
|
|
107
|
+
out.set(CFB_SIG, 0);
|
|
108
|
+
setU16(out, 0x18, 0x003E);
|
|
109
|
+
setU16(out, 0x1A, 0x0003);
|
|
110
|
+
setU16(out, 0x1C, 0xFFFE);
|
|
111
|
+
setU16(out, 0x1E, 9);
|
|
112
|
+
setU16(out, 0x20, 6);
|
|
113
|
+
setU32(out, 0x28, dirSectors);
|
|
114
|
+
setU32(out, 0x2C, 1);
|
|
115
|
+
setU32(out, 0x30, 1);
|
|
116
|
+
setU32(out, 0x38, MINI_CUT);
|
|
117
|
+
setU32(out, 0x3C, miniFatStartSector);
|
|
118
|
+
setU32(out, 0x40, miniFatSectorCount);
|
|
119
|
+
setU32(out, 0x44, ENDOFCHAIN);
|
|
120
|
+
setU32(out, 0x48, 0);
|
|
121
|
+
for (let i = 0; i < 109; i++)
|
|
122
|
+
setU32(out, 0x4C + i * 4, FREESECT);
|
|
123
|
+
setU32(out, 0x4C, 0);
|
|
124
|
+
const fatOff = SECTOR_SZ;
|
|
125
|
+
for (let i = 0; i < 128; i++) {
|
|
126
|
+
setU32(out, fatOff + i * 4, fatEntries[i]);
|
|
127
|
+
}
|
|
128
|
+
const dirOff = SECTOR_SZ + SECTOR_SZ;
|
|
129
|
+
const root = encUtf16('Root Entry');
|
|
130
|
+
out.set(root.bytes, dirOff);
|
|
131
|
+
setU16(out, dirOff + 0x40, root.size);
|
|
132
|
+
out[dirOff + 0x42] = 5;
|
|
133
|
+
out[dirOff + 0x43] = 1;
|
|
134
|
+
setU32(out, dirOff + 0x44, FREESECT);
|
|
135
|
+
setU32(out, dirOff + 0x48, FREESECT);
|
|
136
|
+
setU32(out, dirOff + 0x4C, entries.length > 0 ? 1 : FREESECT);
|
|
137
|
+
setU32(out, dirOff + 0x74, miniStreamSectors > 0 ? 1 + dirSectors : ENDOFCHAIN);
|
|
138
|
+
setU32(out, dirOff + 0x78, miniData.length);
|
|
139
|
+
for (let i = 0; i < entries.length; i++) {
|
|
140
|
+
const eOff = dirOff + (i + 1) * DIR_SZ;
|
|
141
|
+
const eName = encUtf16(entries[i].name);
|
|
142
|
+
out.set(eName.bytes, eOff);
|
|
143
|
+
setU16(out, eOff + 0x40, eName.size);
|
|
144
|
+
out[eOff + 0x42] = 2;
|
|
145
|
+
out[eOff + 0x43] = 1;
|
|
146
|
+
setU32(out, eOff + 0x44, FREESECT);
|
|
147
|
+
setU32(out, eOff + 0x48, i + 2 < entries.length + 1 ? i + 2 : FREESECT);
|
|
148
|
+
setU32(out, eOff + 0x4C, FREESECT);
|
|
149
|
+
if (miniBlocks[i].offset !== -1) {
|
|
150
|
+
setU32(out, eOff + 0x74, miniBlocks[i].offset / MINI_SZ);
|
|
151
|
+
setU32(out, eOff + 0x78, allStreams[i].length);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
setU32(out, eOff + 0x74, regularStartSectors[i]);
|
|
155
|
+
setU32(out, eOff + 0x78, allStreams[i].length);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const miniDataOff = SECTOR_SZ + SECTOR_SZ + dirSectors * SECTOR_SZ;
|
|
159
|
+
out.set(miniData.subarray(0, Math.min(miniData.length, miniStreamSectors * SECTOR_SZ)), miniDataOff);
|
|
160
|
+
for (const rb of regularBlocks) {
|
|
161
|
+
const off = SECTOR_SZ + rb.startSector * SECTOR_SZ;
|
|
162
|
+
out.set(rb.data, off);
|
|
163
|
+
}
|
|
164
|
+
const mfOff = SECTOR_SZ + miniFatStartSector * SECTOR_SZ;
|
|
165
|
+
for (let i = 0; i < miniFat.length && i * 4 < miniFatSectorCount * SECTOR_SZ; i++) {
|
|
166
|
+
setU32(out, mfOff + i * 4, miniFat[i]);
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
function readCfb(data) {
|
|
171
|
+
for (let i = 0; i < 8; i++) {
|
|
172
|
+
if (data[i] !== CFB_SIG[i])
|
|
173
|
+
throw new Error('Not a CFB file');
|
|
174
|
+
}
|
|
175
|
+
const sectorPow = u16(data, 0x1E);
|
|
176
|
+
const sectorSize = 1 << sectorPow;
|
|
177
|
+
const miniPow = u16(data, 0x20);
|
|
178
|
+
const miniSize = 1 << miniPow;
|
|
179
|
+
const miniCutoff = u32(data, 0x38);
|
|
180
|
+
const fatSectors = u32(data, 0x2C);
|
|
181
|
+
const dirStart = u32(data, 0x30);
|
|
182
|
+
const miniFatStart = u32(data, 0x3C);
|
|
183
|
+
const sectorOff = (s) => sectorSize + s * sectorSize;
|
|
184
|
+
const fatSectorList = [];
|
|
185
|
+
for (let i = 0; i < 109; i++) {
|
|
186
|
+
const s = u32(data, 0x4C + i * 4);
|
|
187
|
+
if (s === FREESECT || s === ENDOFCHAIN)
|
|
188
|
+
break;
|
|
189
|
+
fatSectorList.push(s);
|
|
190
|
+
}
|
|
191
|
+
const fat = [];
|
|
192
|
+
for (const s of fatSectorList) {
|
|
193
|
+
const off = sectorOff(s);
|
|
194
|
+
for (let i = 0; i < sectorSize / 4; i++) {
|
|
195
|
+
fat.push(u32(data, off + i * 4));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const followChain = (start) => {
|
|
199
|
+
const chain = [];
|
|
200
|
+
let s = start;
|
|
201
|
+
while (s !== ENDOFCHAIN && s !== FREESECT && chain.length < 10000) {
|
|
202
|
+
chain.push(s);
|
|
203
|
+
s = fat[s] ?? ENDOFCHAIN;
|
|
204
|
+
}
|
|
205
|
+
return chain;
|
|
206
|
+
};
|
|
207
|
+
const readStream = (start, size) => {
|
|
208
|
+
const chain = followChain(start);
|
|
209
|
+
const buf = new Uint8Array(size);
|
|
210
|
+
let pos = 0;
|
|
211
|
+
for (const s of chain) {
|
|
212
|
+
const off = sectorOff(s);
|
|
213
|
+
const len = Math.min(sectorSize, size - pos);
|
|
214
|
+
buf.set(data.subarray(off, off + len), pos);
|
|
215
|
+
pos += len;
|
|
216
|
+
}
|
|
217
|
+
return buf;
|
|
218
|
+
};
|
|
219
|
+
const dirChain = followChain(dirStart);
|
|
220
|
+
const dirData = new Uint8Array(dirChain.length * sectorSize);
|
|
221
|
+
dirChain.forEach((s, i) => dirData.set(data.subarray(sectorOff(s), sectorOff(s) + sectorSize), i * sectorSize));
|
|
222
|
+
const dirEntries = [];
|
|
223
|
+
for (let i = 0; i < dirData.length / DIR_SZ; i++) {
|
|
224
|
+
const off = i * DIR_SZ;
|
|
225
|
+
const nameLen = u16(dirData, off + 0x40);
|
|
226
|
+
if (nameLen === 0)
|
|
227
|
+
continue;
|
|
228
|
+
let name = '';
|
|
229
|
+
for (let j = 0; j < (nameLen - 2) / 2; j++) {
|
|
230
|
+
name += String.fromCharCode(u16(dirData, off + j * 2));
|
|
231
|
+
}
|
|
232
|
+
dirEntries.push({
|
|
233
|
+
name,
|
|
234
|
+
type: dirData[off + 0x42],
|
|
235
|
+
start: u32(dirData, off + 0x74),
|
|
236
|
+
size: u32(dirData, off + 0x78),
|
|
237
|
+
child: u32(dirData, off + 0x4C),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
const root = dirEntries[0];
|
|
241
|
+
let miniStreamData = new Uint8Array(0);
|
|
242
|
+
if (root && root.start !== ENDOFCHAIN) {
|
|
243
|
+
miniStreamData = new Uint8Array(readStream(root.start, root.size));
|
|
244
|
+
}
|
|
245
|
+
const miniFat = [];
|
|
246
|
+
if (miniFatStart !== ENDOFCHAIN) {
|
|
247
|
+
const mfChain = followChain(miniFatStart);
|
|
248
|
+
for (const s of mfChain) {
|
|
249
|
+
const off = sectorOff(s);
|
|
250
|
+
for (let i = 0; i < sectorSize / 4; i++) {
|
|
251
|
+
miniFat.push(u32(data, off + i * 4));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const readMiniStream = (start, size) => {
|
|
256
|
+
const buf = new Uint8Array(size);
|
|
257
|
+
let s = start, pos = 0;
|
|
258
|
+
while (s !== ENDOFCHAIN && pos < size) {
|
|
259
|
+
const off = s * miniSize;
|
|
260
|
+
const len = Math.min(miniSize, size - pos);
|
|
261
|
+
buf.set(miniStreamData.subarray(off, off + len), pos);
|
|
262
|
+
pos += len;
|
|
263
|
+
s = miniFat[s] ?? ENDOFCHAIN;
|
|
264
|
+
}
|
|
265
|
+
return buf;
|
|
266
|
+
};
|
|
267
|
+
const result = [];
|
|
268
|
+
for (let i = 1; i < dirEntries.length; i++) {
|
|
269
|
+
const e = dirEntries[i];
|
|
270
|
+
if (e.type !== 2)
|
|
271
|
+
continue;
|
|
272
|
+
let streamData;
|
|
273
|
+
if (e.size < miniCutoff) {
|
|
274
|
+
streamData = readMiniStream(e.start, e.size);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
streamData = readStream(e.start, e.size);
|
|
278
|
+
}
|
|
279
|
+
result.push({ name: e.name, data: streamData });
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
function getCrypto() {
|
|
284
|
+
if (typeof globalThis.crypto !== 'undefined')
|
|
285
|
+
return globalThis.crypto;
|
|
286
|
+
throw new Error('Web Crypto API not available. Requires Node.js 18+ or a modern browser.');
|
|
287
|
+
}
|
|
288
|
+
function randomBytes(n) {
|
|
289
|
+
return getCrypto().getRandomValues(new Uint8Array(n));
|
|
290
|
+
}
|
|
291
|
+
async function sha512(data) {
|
|
292
|
+
const buf = await getCrypto().subtle.digest('SHA-512', data);
|
|
293
|
+
return new Uint8Array(buf);
|
|
294
|
+
}
|
|
295
|
+
async function sha1(data) {
|
|
296
|
+
const buf = await getCrypto().subtle.digest('SHA-1', data);
|
|
297
|
+
return new Uint8Array(buf);
|
|
298
|
+
}
|
|
299
|
+
async function hmacSha512(key, data) {
|
|
300
|
+
const cryptoKey = await getCrypto().subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-512' }, false, ['sign']);
|
|
301
|
+
const sig = await getCrypto().subtle.sign('HMAC', cryptoKey, data);
|
|
302
|
+
return new Uint8Array(sig);
|
|
303
|
+
}
|
|
304
|
+
async function deriveKey(password, salt, spinCount, keyBits, blockKey) {
|
|
305
|
+
const pwBuf = new Uint8Array(password.length * 2);
|
|
306
|
+
for (let i = 0; i < password.length; i++) {
|
|
307
|
+
pwBuf[i * 2] = password.charCodeAt(i) & 0xFF;
|
|
308
|
+
pwBuf[i * 2 + 1] = (password.charCodeAt(i) >> 8) & 0xFF;
|
|
309
|
+
}
|
|
310
|
+
const h0Input = new Uint8Array(salt.length + pwBuf.length);
|
|
311
|
+
h0Input.set(salt);
|
|
312
|
+
h0Input.set(pwBuf, salt.length);
|
|
313
|
+
let hash = await sha512(h0Input);
|
|
314
|
+
for (let i = 0; i < spinCount; i++) {
|
|
315
|
+
const iterBuf = new Uint8Array(4 + hash.length);
|
|
316
|
+
iterBuf[0] = i & 0xFF;
|
|
317
|
+
iterBuf[1] = (i >> 8) & 0xFF;
|
|
318
|
+
iterBuf[2] = (i >> 16) & 0xFF;
|
|
319
|
+
iterBuf[3] = (i >> 24) & 0xFF;
|
|
320
|
+
iterBuf.set(hash, 4);
|
|
321
|
+
hash = await sha512(iterBuf);
|
|
322
|
+
}
|
|
323
|
+
const final = new Uint8Array(hash.length + blockKey.length);
|
|
324
|
+
final.set(hash);
|
|
325
|
+
final.set(blockKey, hash.length);
|
|
326
|
+
const derived = await sha512(final);
|
|
327
|
+
const keyLen = keyBits / 8;
|
|
328
|
+
if (derived.length >= keyLen)
|
|
329
|
+
return derived.subarray(0, keyLen);
|
|
330
|
+
const padded = new Uint8Array(keyLen);
|
|
331
|
+
padded.set(derived);
|
|
332
|
+
padded.fill(0x36, derived.length);
|
|
333
|
+
return padded;
|
|
334
|
+
}
|
|
335
|
+
async function aesCbcEncrypt(key, iv, data) {
|
|
336
|
+
const cryptoKey = await getCrypto().subtle.importKey('raw', key, { name: 'AES-CBC' }, false, ['encrypt']);
|
|
337
|
+
const buf = await getCrypto().subtle.encrypt({ name: 'AES-CBC', iv: iv }, cryptoKey, data);
|
|
338
|
+
return new Uint8Array(buf).subarray(0, data.length);
|
|
339
|
+
}
|
|
340
|
+
async function aesCbcDecrypt(key, iv, data) {
|
|
341
|
+
const subtle = getCrypto().subtle;
|
|
342
|
+
const lastBlock = data.subarray(data.length - 16);
|
|
343
|
+
const pkcs7 = new Uint8Array(16).fill(16);
|
|
344
|
+
const xored = new Uint8Array(16);
|
|
345
|
+
for (let i = 0; i < 16; i++)
|
|
346
|
+
xored[i] = pkcs7[i] ^ lastBlock[i];
|
|
347
|
+
const encKey = await subtle.importKey('raw', key, { name: 'AES-CBC' }, false, ['encrypt']);
|
|
348
|
+
const fakeEnc = await subtle.encrypt({ name: 'AES-CBC', iv: new Uint8Array(16) }, encKey, xored);
|
|
349
|
+
const fakeBlock = new Uint8Array(fakeEnc).subarray(0, 16);
|
|
350
|
+
const padded = concat(data, fakeBlock);
|
|
351
|
+
const decKey = await subtle.importKey('raw', key, { name: 'AES-CBC' }, false, ['decrypt']);
|
|
352
|
+
const buf = await subtle.decrypt({ name: 'AES-CBC', iv: iv }, decKey, padded);
|
|
353
|
+
return new Uint8Array(buf);
|
|
354
|
+
}
|
|
355
|
+
function padToBlockSize(data, blockSize) {
|
|
356
|
+
const rem = data.length % blockSize;
|
|
357
|
+
if (rem === 0)
|
|
358
|
+
return data;
|
|
359
|
+
const padded = new Uint8Array(data.length + (blockSize - rem));
|
|
360
|
+
padded.set(data);
|
|
361
|
+
return padded;
|
|
362
|
+
}
|
|
363
|
+
function concat(...arrays) {
|
|
364
|
+
let len = 0;
|
|
365
|
+
for (const a of arrays)
|
|
366
|
+
len += a.length;
|
|
367
|
+
const out = new Uint8Array(len);
|
|
368
|
+
let pos = 0;
|
|
369
|
+
for (const a of arrays) {
|
|
370
|
+
out.set(a, pos);
|
|
371
|
+
pos += a.length;
|
|
372
|
+
}
|
|
373
|
+
return out;
|
|
374
|
+
}
|
|
375
|
+
const BLOCK_KEY_VERIFIER_INPUT = new Uint8Array([0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79]);
|
|
376
|
+
const BLOCK_KEY_VERIFIER_VALUE = new Uint8Array([0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e]);
|
|
377
|
+
const BLOCK_KEY_ENCRYPTED_KEY = new Uint8Array([0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6]);
|
|
378
|
+
const BLOCK_KEY_DATA_INTEGRITY1 = new Uint8Array([0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6]);
|
|
379
|
+
const BLOCK_KEY_DATA_INTEGRITY2 = new Uint8Array([0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33]);
|
|
380
|
+
function buildEncryptionInfoXml(keySalt, encKeyValue, encVerifierInput, encVerifierHash, encKeyValueHmac, encKeyValueHmac2, passwordSalt, spinCount) {
|
|
381
|
+
const b64 = (buf) => {
|
|
382
|
+
let s = '';
|
|
383
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
384
|
+
for (let i = 0; i < buf.length; i += 3) {
|
|
385
|
+
const b0 = buf[i], b1 = buf[i + 1] ?? 0, b2 = buf[i + 2] ?? 0;
|
|
386
|
+
const n = (b0 << 16) | (b1 << 8) | b2;
|
|
387
|
+
s += chars[(n >> 18) & 63] + chars[(n >> 12) & 63];
|
|
388
|
+
s += i + 1 < buf.length ? chars[(n >> 6) & 63] : '=';
|
|
389
|
+
s += i + 2 < buf.length ? chars[n & 63] : '=';
|
|
390
|
+
}
|
|
391
|
+
return s;
|
|
392
|
+
};
|
|
393
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
394
|
+
<encryption xmlns="http://schemas.microsoft.com/office/2006/encryption"
|
|
395
|
+
xmlns:p="http://schemas.microsoft.com/office/2006/keyEncryptor/password">
|
|
396
|
+
<keyData saltSize="16" blockSize="16" keyBits="256" hashSize="64"
|
|
397
|
+
cipherAlgorithm="AES" cipherChaining="ChainingModeCBC"
|
|
398
|
+
hashAlgorithm="SHA512"
|
|
399
|
+
saltValue="${b64(keySalt)}"/>
|
|
400
|
+
<dataIntegrity encryptedHmacKey="${b64(encKeyValueHmac)}"
|
|
401
|
+
encryptedHmacValue="${b64(encKeyValueHmac2)}"/>
|
|
402
|
+
<keyEncryptors>
|
|
403
|
+
<keyEncryptor uri="http://schemas.microsoft.com/office/2006/keyEncryptor/password">
|
|
404
|
+
<p:encryptedKey spinCount="${spinCount}" saltSize="16" blockSize="16"
|
|
405
|
+
keyBits="256" hashSize="64"
|
|
406
|
+
cipherAlgorithm="AES" cipherChaining="ChainingModeCBC"
|
|
407
|
+
hashAlgorithm="SHA512"
|
|
408
|
+
saltValue="${b64(passwordSalt)}"
|
|
409
|
+
encryptedVerifierHashInput="${b64(encVerifierInput)}"
|
|
410
|
+
encryptedVerifierHashValue="${b64(encVerifierHash)}"
|
|
411
|
+
encryptedKeyValue="${b64(encKeyValue)}"/>
|
|
412
|
+
</keyEncryptor>
|
|
413
|
+
</keyEncryptors>
|
|
414
|
+
</encryption>`;
|
|
415
|
+
}
|
|
416
|
+
function b64Decode(s) {
|
|
417
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
418
|
+
const lookup = new Uint8Array(128);
|
|
419
|
+
for (let i = 0; i < chars.length; i++)
|
|
420
|
+
lookup[chars.charCodeAt(i)] = i;
|
|
421
|
+
const clean = s.replace(/[^A-Za-z0-9+/]/g, '');
|
|
422
|
+
const pad = s.endsWith('==') ? 2 : s.endsWith('=') ? 1 : 0;
|
|
423
|
+
const rawLen = clean.length + pad;
|
|
424
|
+
const len = (rawLen / 4) * 3 - pad;
|
|
425
|
+
const out = new Uint8Array(len);
|
|
426
|
+
let pos = 0;
|
|
427
|
+
for (let i = 0; i < clean.length; i += 4) {
|
|
428
|
+
const a = lookup[clean.charCodeAt(i)];
|
|
429
|
+
const b = lookup[clean.charCodeAt(i + 1)];
|
|
430
|
+
const c = lookup[clean.charCodeAt(i + 2)];
|
|
431
|
+
const d = lookup[clean.charCodeAt(i + 3)];
|
|
432
|
+
const n = (a << 18) | (b << 12) | (c << 6) | d;
|
|
433
|
+
if (pos < len)
|
|
434
|
+
out[pos++] = (n >> 16) & 0xFF;
|
|
435
|
+
if (pos < len)
|
|
436
|
+
out[pos++] = (n >> 8) & 0xFF;
|
|
437
|
+
if (pos < len)
|
|
438
|
+
out[pos++] = n & 0xFF;
|
|
439
|
+
}
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
function getAttr(xml, name) {
|
|
443
|
+
const re = new RegExp(`${name}="([^"]*)"`, 'i');
|
|
444
|
+
const m = xml.match(re);
|
|
445
|
+
return m ? m[1] : '';
|
|
446
|
+
}
|
|
447
|
+
function parseEncryptionInfo(xml) {
|
|
448
|
+
return {
|
|
449
|
+
keySalt: b64Decode(getAttr(xml.split('keyData')[1] ?? xml, 'saltValue')),
|
|
450
|
+
passwordSalt: b64Decode(getAttr(xml.split('encryptedKey')[1] ?? xml, 'saltValue')),
|
|
451
|
+
spinCount: parseInt(getAttr(xml, 'spinCount'), 10) || 100000,
|
|
452
|
+
keyBits: parseInt(getAttr(xml.split('encryptedKey')[1] ?? xml, 'keyBits'), 10) || 256,
|
|
453
|
+
encryptedVerifierInput: b64Decode(getAttr(xml, 'encryptedVerifierHashInput')),
|
|
454
|
+
encryptedVerifierHash: b64Decode(getAttr(xml, 'encryptedVerifierHashValue')),
|
|
455
|
+
encryptedKeyValue: b64Decode(getAttr(xml, 'encryptedKeyValue')),
|
|
456
|
+
encryptedHmacKey: b64Decode(getAttr(xml, 'encryptedHmacKey')),
|
|
457
|
+
encryptedHmacValue: b64Decode(getAttr(xml, 'encryptedHmacValue')),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function buildPrefixedUtf16(s) {
|
|
461
|
+
const byteLen = s.length * 2;
|
|
462
|
+
const padLen = (4 - (byteLen % 4)) % 4;
|
|
463
|
+
const buf = new Uint8Array(4 + byteLen + padLen);
|
|
464
|
+
setU32(buf, 0, byteLen);
|
|
465
|
+
for (let i = 0; i < s.length; i++)
|
|
466
|
+
setU16(buf, 4 + i * 2, s.charCodeAt(i));
|
|
467
|
+
return buf;
|
|
468
|
+
}
|
|
469
|
+
function buildDataSpacesVersion() {
|
|
470
|
+
const feature = 'Microsoft.Container.DataSpaces';
|
|
471
|
+
const strBytes = feature.length * 2;
|
|
472
|
+
const buf = new Uint8Array(4 + strBytes + 12);
|
|
473
|
+
setU32(buf, 0, strBytes);
|
|
474
|
+
for (let i = 0; i < feature.length; i++)
|
|
475
|
+
setU16(buf, 4 + i * 2, feature.charCodeAt(i));
|
|
476
|
+
const off = 4 + strBytes;
|
|
477
|
+
setU16(buf, off, 1);
|
|
478
|
+
setU16(buf, off + 2, 0);
|
|
479
|
+
setU16(buf, off + 4, 1);
|
|
480
|
+
setU16(buf, off + 6, 0);
|
|
481
|
+
setU16(buf, off + 8, 1);
|
|
482
|
+
setU16(buf, off + 10, 0);
|
|
483
|
+
return buf;
|
|
484
|
+
}
|
|
485
|
+
function buildDataSpaceMap() {
|
|
486
|
+
const refName = buildPrefixedUtf16('EncryptedPackage');
|
|
487
|
+
const dsName = buildPrefixedUtf16('StrongEncryptionDataSpace');
|
|
488
|
+
const entryLen = 4 + 4 + refName.length + dsName.length;
|
|
489
|
+
const buf = new Uint8Array(8 + 4 + entryLen);
|
|
490
|
+
setU32(buf, 0, 8);
|
|
491
|
+
setU32(buf, 4, 1);
|
|
492
|
+
setU32(buf, 8, entryLen);
|
|
493
|
+
let off = 12;
|
|
494
|
+
setU32(buf, off, 1);
|
|
495
|
+
off += 4;
|
|
496
|
+
setU32(buf, off, 0);
|
|
497
|
+
off += 4;
|
|
498
|
+
buf.set(refName, off);
|
|
499
|
+
off += refName.length;
|
|
500
|
+
buf.set(dsName, off);
|
|
501
|
+
return buf;
|
|
502
|
+
}
|
|
503
|
+
function buildStrongEncryptionDataSpace() {
|
|
504
|
+
const refName = buildPrefixedUtf16('StrongEncryptionTransform');
|
|
505
|
+
const buf = new Uint8Array(8 + refName.length);
|
|
506
|
+
setU32(buf, 0, 8);
|
|
507
|
+
setU32(buf, 4, 1);
|
|
508
|
+
buf.set(refName, 8);
|
|
509
|
+
return buf;
|
|
510
|
+
}
|
|
511
|
+
function buildPrimaryTransform() {
|
|
512
|
+
const id = buildPrefixedUtf16('{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}');
|
|
513
|
+
const name = buildPrefixedUtf16('Microsoft.Container.EncryptionTransform');
|
|
514
|
+
const contentLen = 4 + id.length + name.length + 12;
|
|
515
|
+
const buf = new Uint8Array(4 + contentLen);
|
|
516
|
+
setU32(buf, 0, contentLen);
|
|
517
|
+
let off = 4;
|
|
518
|
+
setU32(buf, off, 1);
|
|
519
|
+
off += 4;
|
|
520
|
+
buf.set(id, off);
|
|
521
|
+
off += id.length;
|
|
522
|
+
buf.set(name, off);
|
|
523
|
+
off += name.length;
|
|
524
|
+
setU16(buf, off, 1);
|
|
525
|
+
setU16(buf, off + 2, 0);
|
|
526
|
+
off += 4;
|
|
527
|
+
setU16(buf, off, 1);
|
|
528
|
+
setU16(buf, off + 2, 0);
|
|
529
|
+
off += 4;
|
|
530
|
+
setU16(buf, off, 1);
|
|
531
|
+
setU16(buf, off + 2, 0);
|
|
532
|
+
return buf;
|
|
533
|
+
}
|
|
534
|
+
function buildEncryptionCfb(encryptionInfo, encryptedPackage) {
|
|
535
|
+
const versionData = buildDataSpacesVersion();
|
|
536
|
+
const dsmData = buildDataSpaceMap();
|
|
537
|
+
const sedsData = buildStrongEncryptionDataSpace();
|
|
538
|
+
const primaryData = buildPrimaryTransform();
|
|
539
|
+
const dirs = [
|
|
540
|
+
{ name: 'Root Entry', type: 5, childId: 2, leftId: -1, rightId: -1, size: 0 },
|
|
541
|
+
{ name: '\x06DataSpaces', type: 1, childId: 5, leftId: -1, rightId: -1, size: 0 },
|
|
542
|
+
{ name: 'EncryptionInfo', type: 2, childId: -1, leftId: 1, rightId: 3, data: encryptionInfo, size: encryptionInfo.length },
|
|
543
|
+
{ name: 'EncryptedPackage', type: 2, childId: -1, leftId: -1, rightId: -1, data: encryptedPackage, size: encryptedPackage.length },
|
|
544
|
+
{ name: 'Version', type: 2, childId: -1, leftId: -1, rightId: -1, data: versionData, size: versionData.length },
|
|
545
|
+
{ name: 'DataSpaceMap', type: 2, childId: -1, leftId: 4, rightId: 6, data: dsmData, size: dsmData.length },
|
|
546
|
+
{ name: 'DataSpaceInfo', type: 1, childId: 8, leftId: -1, rightId: 7, size: 0 },
|
|
547
|
+
{ name: 'TransformInfo', type: 1, childId: 9, leftId: -1, rightId: -1, size: 0 },
|
|
548
|
+
{ name: 'StrongEncryptionDataSpace', type: 2, childId: -1, leftId: -1, rightId: -1, data: sedsData, size: sedsData.length },
|
|
549
|
+
{ name: 'StrongEncryptionTransform', type: 1, childId: 10, leftId: -1, rightId: -1, size: 0 },
|
|
550
|
+
{ name: '\x06Primary', type: 2, childId: -1, leftId: -1, rightId: -1, data: primaryData, size: primaryData.length },
|
|
551
|
+
];
|
|
552
|
+
let miniData = new Uint8Array(0);
|
|
553
|
+
let miniOffset = 0;
|
|
554
|
+
const streamInfo = [];
|
|
555
|
+
const regularStreams = [];
|
|
556
|
+
for (const d of dirs) {
|
|
557
|
+
if (!d.data) {
|
|
558
|
+
streamInfo.push({ inMini: false, miniStart: 0, regularStart: 0 });
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (d.data.length < MINI_CUT) {
|
|
562
|
+
const padLen = Math.ceil(d.data.length / MINI_SZ) * MINI_SZ;
|
|
563
|
+
const newMini = new Uint8Array(miniData.length + padLen);
|
|
564
|
+
newMini.set(miniData);
|
|
565
|
+
newMini.set(d.data, miniData.length);
|
|
566
|
+
streamInfo.push({ inMini: true, miniStart: miniOffset / MINI_SZ, regularStart: 0 });
|
|
567
|
+
miniOffset += padLen;
|
|
568
|
+
miniData = newMini;
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
streamInfo.push({ inMini: false, miniStart: 0, regularStart: 0 });
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
const dirCount = dirs.length;
|
|
575
|
+
const dirSectors = Math.ceil((dirCount * DIR_SZ) / SECTOR_SZ);
|
|
576
|
+
const miniStreamSectors = Math.ceil(miniData.length / SECTOR_SZ);
|
|
577
|
+
let nextSector = 1 + dirSectors + miniStreamSectors;
|
|
578
|
+
for (let i = 0; i < dirs.length; i++) {
|
|
579
|
+
if (dirs[i].data && !streamInfo[i].inMini) {
|
|
580
|
+
streamInfo[i].regularStart = nextSector;
|
|
581
|
+
regularStreams.push({ data: dirs[i].data, startSector: nextSector });
|
|
582
|
+
nextSector += Math.ceil(dirs[i].data.length / SECTOR_SZ);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const totalSectors = nextSector + 1;
|
|
586
|
+
const fatEntries = new Uint32Array(Math.max(totalSectors + 8, 128));
|
|
587
|
+
fatEntries.fill(FREESECT);
|
|
588
|
+
fatEntries[0] = FATSECT;
|
|
589
|
+
for (let i = 1; i < 1 + dirSectors; i++)
|
|
590
|
+
fatEntries[i] = i < dirSectors ? i + 1 : ENDOFCHAIN;
|
|
591
|
+
for (let i = 1 + dirSectors; i < 1 + dirSectors + miniStreamSectors; i++)
|
|
592
|
+
fatEntries[i] = i < dirSectors + miniStreamSectors ? i + 1 : ENDOFCHAIN;
|
|
593
|
+
for (const rb of regularStreams) {
|
|
594
|
+
const sects = Math.ceil(rb.data.length / SECTOR_SZ);
|
|
595
|
+
for (let j = 0; j < sects; j++)
|
|
596
|
+
fatEntries[rb.startSector + j] = j < sects - 1 ? rb.startSector + j + 1 : ENDOFCHAIN;
|
|
597
|
+
}
|
|
598
|
+
const miniSectorCount = Math.max(Math.ceil(miniData.length / MINI_SZ), 1);
|
|
599
|
+
const miniFat = new Uint32Array(Math.max(miniSectorCount, 128));
|
|
600
|
+
miniFat.fill(FREESECT);
|
|
601
|
+
for (let i = 0; i < dirs.length; i++) {
|
|
602
|
+
if (dirs[i].data && streamInfo[i].inMini) {
|
|
603
|
+
const msects = Math.ceil(dirs[i].data.length / MINI_SZ);
|
|
604
|
+
const start = streamInfo[i].miniStart;
|
|
605
|
+
for (let j = 0; j < msects; j++)
|
|
606
|
+
miniFat[start + j] = j < msects - 1 ? start + j + 1 : ENDOFCHAIN;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
const miniFatSector = nextSector;
|
|
610
|
+
fatEntries[miniFatSector] = ENDOFCHAIN;
|
|
611
|
+
const fileSize = (1 + miniFatSector + 1) * SECTOR_SZ;
|
|
612
|
+
const out = new Uint8Array(fileSize);
|
|
613
|
+
out.set(CFB_SIG, 0);
|
|
614
|
+
setU16(out, 0x18, 0x003E);
|
|
615
|
+
setU16(out, 0x1A, 0x0003);
|
|
616
|
+
setU16(out, 0x1C, 0xFFFE);
|
|
617
|
+
setU16(out, 0x1E, 9);
|
|
618
|
+
setU16(out, 0x20, 6);
|
|
619
|
+
setU32(out, 0x2C, 1);
|
|
620
|
+
setU32(out, 0x30, 1);
|
|
621
|
+
setU32(out, 0x38, MINI_CUT);
|
|
622
|
+
setU32(out, 0x3C, miniFatSector);
|
|
623
|
+
setU32(out, 0x40, 1);
|
|
624
|
+
setU32(out, 0x44, ENDOFCHAIN);
|
|
625
|
+
setU32(out, 0x48, 0);
|
|
626
|
+
for (let i = 0; i < 109; i++)
|
|
627
|
+
setU32(out, 0x4C + i * 4, FREESECT);
|
|
628
|
+
setU32(out, 0x4C, 0);
|
|
629
|
+
const fatOff = SECTOR_SZ;
|
|
630
|
+
for (let i = 0; i < 128; i++)
|
|
631
|
+
setU32(out, fatOff + i * 4, fatEntries[i]);
|
|
632
|
+
const dirOff = SECTOR_SZ * 2;
|
|
633
|
+
for (let i = 0; i < dirs.length; i++) {
|
|
634
|
+
const d = dirs[i];
|
|
635
|
+
const eOff = dirOff + i * DIR_SZ;
|
|
636
|
+
const eName = encUtf16(d.name);
|
|
637
|
+
out.set(eName.bytes, eOff);
|
|
638
|
+
setU16(out, eOff + 0x40, eName.size);
|
|
639
|
+
out[eOff + 0x42] = d.type;
|
|
640
|
+
out[eOff + 0x43] = 1;
|
|
641
|
+
setU32(out, eOff + 0x44, d.leftId >= 0 ? d.leftId : FREESECT);
|
|
642
|
+
setU32(out, eOff + 0x48, d.rightId >= 0 ? d.rightId : FREESECT);
|
|
643
|
+
setU32(out, eOff + 0x4C, d.childId >= 0 ? d.childId : FREESECT);
|
|
644
|
+
if (d.type === 5) {
|
|
645
|
+
setU32(out, eOff + 0x74, miniStreamSectors > 0 ? 1 + dirSectors : ENDOFCHAIN);
|
|
646
|
+
setU32(out, eOff + 0x78, miniData.length);
|
|
647
|
+
}
|
|
648
|
+
else if (d.data) {
|
|
649
|
+
if (streamInfo[i].inMini) {
|
|
650
|
+
setU32(out, eOff + 0x74, streamInfo[i].miniStart);
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
setU32(out, eOff + 0x74, streamInfo[i].regularStart);
|
|
654
|
+
}
|
|
655
|
+
setU32(out, eOff + 0x78, d.data.length);
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
setU32(out, eOff + 0x74, ENDOFCHAIN);
|
|
659
|
+
setU32(out, eOff + 0x78, 0);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const miniOff = SECTOR_SZ * (1 + 1 + dirSectors);
|
|
663
|
+
out.set(miniData, miniOff);
|
|
664
|
+
for (const rb of regularStreams)
|
|
665
|
+
out.set(rb.data, SECTOR_SZ * (1 + rb.startSector));
|
|
666
|
+
const mfOff = SECTOR_SZ * (1 + miniFatSector);
|
|
667
|
+
for (let i = 0; i < 128; i++)
|
|
668
|
+
setU32(out, mfOff + i * 4, miniFat[i]);
|
|
669
|
+
return out;
|
|
670
|
+
}
|
|
671
|
+
export async function encryptWorkbook(xlsxData, password, options) {
|
|
672
|
+
const spinCount = options?.spinCount ?? 100000;
|
|
673
|
+
const keySalt = randomBytes(16);
|
|
674
|
+
const passwordSalt = randomBytes(16);
|
|
675
|
+
const verifierInput = randomBytes(16);
|
|
676
|
+
const keyDerived = await deriveKey(password, passwordSalt, spinCount, 256, BLOCK_KEY_ENCRYPTED_KEY);
|
|
677
|
+
const dataKey = randomBytes(32);
|
|
678
|
+
const encKeyValue = await aesCbcEncrypt(keyDerived, passwordSalt, padToBlockSize(dataKey, 16));
|
|
679
|
+
const verifierKey = await deriveKey(password, passwordSalt, spinCount, 256, BLOCK_KEY_VERIFIER_INPUT);
|
|
680
|
+
const encVerifierInput = await aesCbcEncrypt(verifierKey, passwordSalt, padToBlockSize(verifierInput, 16));
|
|
681
|
+
const verifierHash = await sha512(verifierInput);
|
|
682
|
+
const verifierHashKey = await deriveKey(password, passwordSalt, spinCount, 256, BLOCK_KEY_VERIFIER_VALUE);
|
|
683
|
+
const encVerifierHash = await aesCbcEncrypt(verifierHashKey, passwordSalt, padToBlockSize(verifierHash, 16));
|
|
684
|
+
const packageData = xlsxData;
|
|
685
|
+
const segmentSize = 4096;
|
|
686
|
+
const encryptedSegments = [];
|
|
687
|
+
for (let offset = 0; offset < packageData.length; offset += segmentSize) {
|
|
688
|
+
const segment = packageData.subarray(offset, Math.min(offset + segmentSize, packageData.length));
|
|
689
|
+
const paddedSegment = padToBlockSize(segment, 16);
|
|
690
|
+
const segIdx = offset / segmentSize;
|
|
691
|
+
const segIdxBuf = new Uint8Array(4);
|
|
692
|
+
segIdxBuf[0] = segIdx & 0xFF;
|
|
693
|
+
segIdxBuf[1] = (segIdx >> 8) & 0xFF;
|
|
694
|
+
segIdxBuf[2] = (segIdx >> 16) & 0xFF;
|
|
695
|
+
segIdxBuf[3] = (segIdx >> 24) & 0xFF;
|
|
696
|
+
const segIvHash = await sha512(concat(keySalt, segIdxBuf));
|
|
697
|
+
const segIv = segIvHash.subarray(0, 16);
|
|
698
|
+
const encSegment = await aesCbcEncrypt(dataKey, segIv, paddedSegment);
|
|
699
|
+
encryptedSegments.push(encSegment);
|
|
700
|
+
}
|
|
701
|
+
const encryptedData = concat(...encryptedSegments);
|
|
702
|
+
const streamSizeHeader = new Uint8Array(8);
|
|
703
|
+
setU32(streamSizeHeader, 0, xlsxData.length);
|
|
704
|
+
const encryptedPackage = concat(streamSizeHeader, encryptedData);
|
|
705
|
+
const hmacKey = randomBytes(64);
|
|
706
|
+
const hmacKeyIv = (await sha512(concat(keySalt, BLOCK_KEY_DATA_INTEGRITY1))).subarray(0, 16);
|
|
707
|
+
const encHmacKey = await aesCbcEncrypt(dataKey, hmacKeyIv, padToBlockSize(hmacKey, 16));
|
|
708
|
+
const hmacValue = await hmacSha512(hmacKey, encryptedPackage);
|
|
709
|
+
const hmacValueIv = (await sha512(concat(keySalt, BLOCK_KEY_DATA_INTEGRITY2))).subarray(0, 16);
|
|
710
|
+
const encHmacValue = await aesCbcEncrypt(dataKey, hmacValueIv, padToBlockSize(hmacValue, 16));
|
|
711
|
+
const xmlStr = buildEncryptionInfoXml(keySalt, encKeyValue, encVerifierInput, encVerifierHash, encHmacKey, encHmacValue, passwordSalt, spinCount);
|
|
712
|
+
const xmlBytes = new TextEncoder().encode(xmlStr);
|
|
713
|
+
const infoHeader = new Uint8Array(8);
|
|
714
|
+
setU16(infoHeader, 0, 4);
|
|
715
|
+
setU16(infoHeader, 2, 4);
|
|
716
|
+
setU32(infoHeader, 4, 0x00040);
|
|
717
|
+
const encryptionInfo = concat(infoHeader, xmlBytes);
|
|
718
|
+
return buildEncryptionCfb(encryptionInfo, encryptedPackage);
|
|
719
|
+
}
|
|
720
|
+
export async function decryptWorkbook(encryptedData, password) {
|
|
721
|
+
const streams = readCfb(encryptedData);
|
|
722
|
+
const infoStream = streams.find(s => s.name === 'EncryptionInfo');
|
|
723
|
+
const pkgStream = streams.find(s => s.name === 'EncryptedPackage');
|
|
724
|
+
if (!infoStream || !pkgStream)
|
|
725
|
+
throw new Error('Not an encrypted Office file');
|
|
726
|
+
const xmlBytes = infoStream.data.subarray(8);
|
|
727
|
+
const xmlStr = new TextDecoder().decode(xmlBytes);
|
|
728
|
+
const params = parseEncryptionInfo(xmlStr);
|
|
729
|
+
const keyDerived = await deriveKey(password, params.passwordSalt, params.spinCount, params.keyBits, BLOCK_KEY_ENCRYPTED_KEY);
|
|
730
|
+
let dataKey;
|
|
731
|
+
try {
|
|
732
|
+
dataKey = await aesCbcDecrypt(keyDerived, params.passwordSalt, params.encryptedKeyValue);
|
|
733
|
+
dataKey = dataKey.subarray(0, params.keyBits / 8);
|
|
734
|
+
}
|
|
735
|
+
catch {
|
|
736
|
+
throw new Error('Incorrect password');
|
|
737
|
+
}
|
|
738
|
+
const verifierKey = await deriveKey(password, params.passwordSalt, params.spinCount, params.keyBits, BLOCK_KEY_VERIFIER_INPUT);
|
|
739
|
+
let verifierInput;
|
|
740
|
+
try {
|
|
741
|
+
verifierInput = await aesCbcDecrypt(verifierKey, params.passwordSalt, params.encryptedVerifierInput);
|
|
742
|
+
verifierInput = verifierInput.subarray(0, 16);
|
|
743
|
+
}
|
|
744
|
+
catch {
|
|
745
|
+
throw new Error('Incorrect password');
|
|
746
|
+
}
|
|
747
|
+
const verifierHashKey = await deriveKey(password, params.passwordSalt, params.spinCount, params.keyBits, BLOCK_KEY_VERIFIER_VALUE);
|
|
748
|
+
try {
|
|
749
|
+
const decVerifierHash = await aesCbcDecrypt(verifierHashKey, params.passwordSalt, params.encryptedVerifierHash);
|
|
750
|
+
const expectedHash = await sha512(verifierInput);
|
|
751
|
+
for (let i = 0; i < 64; i++) {
|
|
752
|
+
if (decVerifierHash[i] !== expectedHash[i])
|
|
753
|
+
throw new Error('Incorrect password');
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
catch (e) {
|
|
757
|
+
if (e.message === 'Incorrect password')
|
|
758
|
+
throw e;
|
|
759
|
+
throw new Error('Incorrect password');
|
|
760
|
+
}
|
|
761
|
+
const keySalt = params.keySalt;
|
|
762
|
+
const streamSize = u32(pkgStream.data, 0);
|
|
763
|
+
const encryptedPackage = pkgStream.data.subarray(8);
|
|
764
|
+
const segmentSize = 4096;
|
|
765
|
+
const decryptedSegments = [];
|
|
766
|
+
for (let offset = 0; offset < encryptedPackage.length; offset += segmentSize) {
|
|
767
|
+
let segEnd = offset + segmentSize;
|
|
768
|
+
if (segEnd > encryptedPackage.length)
|
|
769
|
+
segEnd = encryptedPackage.length;
|
|
770
|
+
const encSegLen = Math.ceil((segEnd - offset) / 16) * 16;
|
|
771
|
+
const encSegment = encryptedPackage.subarray(offset, offset + encSegLen);
|
|
772
|
+
const segIdx = offset / segmentSize;
|
|
773
|
+
const segIdxBuf = new Uint8Array(4);
|
|
774
|
+
segIdxBuf[0] = segIdx & 0xFF;
|
|
775
|
+
segIdxBuf[1] = (segIdx >> 8) & 0xFF;
|
|
776
|
+
segIdxBuf[2] = (segIdx >> 16) & 0xFF;
|
|
777
|
+
segIdxBuf[3] = (segIdx >> 24) & 0xFF;
|
|
778
|
+
const segIvHash = await sha512(concat(keySalt, segIdxBuf));
|
|
779
|
+
const segIv = segIvHash.subarray(0, 16);
|
|
780
|
+
try {
|
|
781
|
+
const decSegment = await aesCbcDecrypt(dataKey, segIv, encSegment);
|
|
782
|
+
decryptedSegments.push(decSegment);
|
|
783
|
+
}
|
|
784
|
+
catch {
|
|
785
|
+
throw new Error('Decryption failed — data may be corrupted');
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
const decryptedData = concat(...decryptedSegments);
|
|
789
|
+
return decryptedData.subarray(0, streamSize);
|
|
790
|
+
}
|
|
791
|
+
export function isEncrypted(data) {
|
|
792
|
+
if (data.length < 8)
|
|
793
|
+
return false;
|
|
794
|
+
for (let i = 0; i < 8; i++) {
|
|
795
|
+
if (data[i] !== CFB_SIG[i])
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
const streams = readCfb(data);
|
|
800
|
+
return streams.some(s => s.name === 'EncryptionInfo');
|
|
801
|
+
}
|
|
802
|
+
catch {
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
//# sourceMappingURL=Encryption.js.map
|