archive-codec 1.6.8 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -3
- package/dist/crypto/md5.cjs +221 -0
- package/dist/crypto/md5.d.cts +4 -0
- package/dist/crypto/md5.d.ts +4 -0
- package/dist/crypto/md5.js +220 -0
- package/dist/crypto/office-rc4.cjs +69 -0
- package/dist/crypto/office-rc4.d.cts +17 -0
- package/dist/crypto/office-rc4.d.ts +17 -0
- package/dist/crypto/office-rc4.js +64 -0
- package/dist/crypto/rc4.cjs +31 -0
- package/dist/crypto/rc4.d.cts +4 -0
- package/dist/crypto/rc4.d.ts +4 -0
- package/dist/crypto/rc4.js +30 -0
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/package.json +16 -19
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/ExaDev/documents.js/tree/main/packages/archive-codec) [](https://www.npmjs.com/package/archive-codec) [](https://www.npmjs.com/package/archive-codec) [](https://github.com/ExaDev/documents.js/actions)
|
|
4
4
|
|
|
5
|
-
> ZIP-in-ZIP recursive walking under depth and cumulative decompressed-size guards, classic OLE compound-file ([MS-CFB]) reading and writing,
|
|
5
|
+
> ZIP-in-ZIP recursive walking under depth and cumulative decompressed-size guards, classic OLE compound-file ([MS-CFB]) reading and writing, [MS-OLEPS] Property Set Stream reading and writing, and the RC4/MD5 primitives the legacy OLE-based binary formats share for their own password-to-open encryption — zero document-format knowledge, the archive and container utility package for the [documents.js family](../../README.md). Worker-isomorphic: the same code runs under Node and inside a Cloudflare Workers isolate.
|
|
6
6
|
|
|
7
7
|
Created for [documents.js#564](https://github.com/ExaDev/documents.js/issues/564): nothing in the ecosystem recursed into a nested archive. Most concretely, OOXML's embedded-object model — a docx/pptx carrying a genuinely separate ZIP blob at `word/embeddings/oleObject1.xlsx` — had no safe handling anywhere, and no package guarded against recursive-archive inputs at all (`byte-codec`'s 512 MiB per-stream inflate cap does not compose across recursion). A new sibling was chosen over extending `byte-codec` (whose charter is byte/image primitives, zero container-format knowledge) or doing it inline in `documents.js` (which would repeat the duplication `byte-codec`'s own extraction was meant to avoid). Its first family consumer is `ooxml.js`'s OLE embedded-object recovery — [documents.js#733](https://github.com/ExaDev/documents.js/issues/733) (pptx, `p:oleObj`) and [documents.js#734](https://github.com/ExaDev/documents.js/issues/734) (docx, `o:OLEObject`): an OLE payload part's bytes are checked through `isZipArchive` and, when they are a ZIP, decoded as a nested OOXML package behind this package's guarded walk — the bounded inflate that populates `document-schema.js`'s `ContentEmbeddedObject`/`ContentEmbeddedObjectBlock` (the same vocabulary odf.js embeds formula sub-documents through) with a genuinely recovered sub-document.
|
|
8
8
|
|
|
@@ -14,7 +14,9 @@ Created for [documents.js#564](https://github.com/ExaDev/documents.js/issues/564
|
|
|
14
14
|
|
|
15
15
|
[documents.js#815](https://github.com/ExaDev/documents.js/issues/815), [#816](https://github.com/ExaDev/documents.js/issues/816), and [#817](https://github.com/ExaDev/documents.js/issues/817) also each named the same remaining gap: `doc-codec`, `xls-codec`, and `ppt-codec` all hard-coded document metadata (title, author, dates) to an empty object, because that metadata lives in a genuinely different structure from the one each format's own reader already parses -- a [MS-OLEPS] Property Set Stream, conventionally stored as a "\x05SummaryInformation" stream beside `WordDocument`/`Workbook`/`PowerPoint Document` in the identical [MS-CFB] container all three already read through this package. `oleps/read` and `oleps/write` are the generic property-set codec (the stream header, the PropertySet packet's dictionary, and VT_I2/VT_I4/VT_LPSTR/VT_LPWSTR/VT_FILETIME typed values), and `oleps/summary-information` is the SummaryInformation-specific mapping on top of it -- the same two-layer split `cfb/read.ts` and `cfb/ole-package.ts` already establish for the OLE Package stream, container structure below, one named stream's own field layout above.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
[documents.js#1108](https://github.com/ExaDev/documents.js/issues/1108) added the RC4/MD5 primitives `xls-codec` needs to decrypt a legacy-encrypted `.xls` workbook: the [MS-OFFCRYPTO] 2.3.6.1 "RC4 encryption header" scheme every BIFF8 `FilePass` record protected this way shares with the same header shape in `.doc` and `.ppt`. RC4 and MD5 are trivial and completely unavailable from any platform crypto API this package can portably reach (WebCrypto has never offered either; `node:crypto` dropped RC4 from its default provider with OpenSSL 3), so hand-writing them here — once, shared — is the only option that keeps `xls-codec`, `doc-codec`, and `ppt-codec` (already this package's only consumers) from each carrying their own copy. The newer "RC4 CryptoAPI encryption header" scheme (2.3.5.1, a different header shape and derivation) and the older XOR obfuscation scheme are explicitly out of scope for now — tracked separately in [documents.js#922](https://github.com/ExaDev/documents.js/issues/922).
|
|
18
|
+
|
|
19
|
+
Scope: **ZIP containers** (read and write over [`fflate`](https://github.com/101arrowz/fflate), recursive walking of ZIP-in-ZIP entries), **classic OLE compound files** (bounded [MS-CFB] reading and conformant [MS-CFB] writing, plus OLE Package stream reading and writing), **[MS-OLEPS] Property Set Streams** (generic read/write of a single-property-set stream, plus SummaryInformation's own title/subject/author/keywords/comments/created/last-saved/last-printed fields), and the **RC4/MD5 primitives** the legacy OLE-based binary formats share for password-to-open encryption. **tar and gzip, DocumentSummaryInformation's extended and user-defined property sets, writing VT_LPSTR (ANSI-codepage) string properties, RC4 CryptoAPI encryption, and XOR obfuscation are explicitly out of scope.**
|
|
18
20
|
|
|
19
21
|
## Getting started
|
|
20
22
|
|
|
@@ -57,6 +59,9 @@ The smoke suite (`test/smoke.test.mjs`) is the guard on that advertisement: it l
|
|
|
57
59
|
| `oleps/write` | `writePropertySetStream` (generic [MS-OLEPS] property-set encoding), `PropertySetWriteError` — takes the `PropertySet` shape `oleps/read` returns |
|
|
58
60
|
| `oleps/summary-information` | `readSummaryInformation`, `writeSummaryInformationStream`, `SummaryInformationProperties`, `FMTID_SUMMARY_INFORMATION` |
|
|
59
61
|
| `oleps/layout-metadata` | `summaryInformationToLayoutMetadata`, `layoutMetadataToSummaryInformation`, `hasSummaryInformationFields` — the `SummaryInformationProperties` <-> `document-schema.js`'s `LayoutMetadata` mapping, shared by `doc-codec`/`xls-codec`/`ppt-codec` (see [Property sets](#property-sets)) |
|
|
62
|
+
| `crypto/md5` | `md5` — RFC 1321 MD5, hand-written (see [Legacy Office encryption](#legacy-office-encryption)) |
|
|
63
|
+
| `crypto/rc4` | `rc4` — the RC4 stream cipher, hand-written; symmetric, so also the decrypt operation |
|
|
64
|
+
| `crypto/office-rc4` | `deriveOfficeRc4BaseHash`, `deriveOfficeRc4BlockKey`, `decryptOfficeRc4`, `OFFICE_RC4_VERIFIER_LENGTH`, `OFFICE_RC4_BLOCK_SIZE` — [MS-OFFCRYPTO] 2.3.6.2's own key derivation and the block-keyed stream decryption built on it |
|
|
60
65
|
|
|
61
66
|
### Recursive walking
|
|
62
67
|
|
|
@@ -168,6 +173,21 @@ A property whose type this reader does not decode — an unsupported `PropertyTy
|
|
|
168
173
|
|
|
169
174
|
`oleps/layout-metadata`'s `summaryInformationToLayoutMetadata`/`layoutMetadataToSummaryInformation`/`hasSummaryInformationFields` map `SummaryInformationProperties` to and from `document-schema.js`'s own `LayoutMetadata` — the shared metadata shape every codec's `ContentDocument` carries, format-agnostic rather than specific to any one legacy binary format. `doc-codec`, `xls-codec`, and `ppt-codec` each import these directly rather than maintaining their own copy, wrapping `layoutMetadataToSummaryInformation` with their own `createdIso`/`modifiedIso` date validation so a malformed date is reported through that package's own error vocabulary rather than an opaque `RangeError` out of the FILETIME conversion.
|
|
170
175
|
|
|
176
|
+
### Legacy Office encryption
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { deriveOfficeRc4BaseHash, decryptOfficeRc4 } from "archive-codec";
|
|
180
|
+
|
|
181
|
+
// salt and streamOffset come from the caller's own FilePass-record reading
|
|
182
|
+
// (xls-codec's own [MS-XLS] 2.4.117 handling, not this package's concern).
|
|
183
|
+
const baseHash = deriveOfficeRc4BaseHash(password, salt);
|
|
184
|
+
const plaintext = decryptOfficeRc4(baseHash, streamOffset, ciphertext);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`deriveOfficeRc4BaseHash`/`deriveOfficeRc4BlockKey`/`decryptOfficeRc4` implement [MS-OFFCRYPTO] 2.3.6.1/2.3.6.2's RC4 encryption header scheme: a per-workbook base hash derived once from the password and the header's own salt, then a real 40-bit RC4 key re-derived from that base hash at every 1024-byte boundary of the underlying decrypted stream. Both the key length and the block-reset interval come from Apache POI's own `Biff8EncryptionKey`, a mature independent implementation, rather than the published spec's own more generic prose — see each constant's own doc comment for the discrepancy. `decryptOfficeRc4` takes the byte offset a chunk starts at within the whole encrypted stream (not within the chunk itself), so a stream can be decrypted in arbitrary pieces, not only from its own start, and still land on the correct per-block key throughout.
|
|
188
|
+
|
|
189
|
+
This is exactly the piece `xls-codec`'s `FilePass`-record reader needs and nothing more: locating the `FilePass` record, reading its own encryption-header bytes, and verifying the password against `EncryptedVerifier`/`EncryptedVerifierHash` are `xls-codec`'s own concern, not this package's — `archive-codec` carries only the format-agnostic cryptography, never a `.xls`-specific byte layout. `md5`/`rc4` are exported individually too, for `doc-codec`/`ppt-codec`'s own eventual integration against the identical header scheme.
|
|
190
|
+
|
|
171
191
|
### ZIP container
|
|
172
192
|
|
|
173
193
|
`zipPackage` takes an _ordered_ array of `[path, entry]` tuples, not a `Record`, so the caller controls the exact emission order deterministically (the property formats with a fixed-offset first entry — ODF's `mimetype` — depend on), and any entry can be written stored-uncompressed via `stored: true`. `unzipPackage` is the read side; the returned `Record` makes no ordering promise and collapses duplicate paths.
|
|
@@ -176,7 +196,7 @@ A property whose type this reader does not decode — an unsupported `PropertyTy
|
|
|
176
196
|
|
|
177
197
|
- Worker-isomorphic (see the [family-wide convention](../../README.md#conventions)): runtime `src/` must not import `node:*`, a bare Node builtin, or use the `Buffer` global — enforced by a `no-restricted-imports`/`no-restricted-globals` ESLint rule and exercised in CI by running the test suite inside an actual `workerd` isolate (`pnpm test:workers`). Test files under `src/**/*.test.ts` and `src/test-support/` are exempt and may use Node APIs for fixtures.
|
|
178
198
|
- Only `src/index.ts` may be named `index.*` — a custom ESLint rule (`local/no-non-barrel-index`) rejects any other module using an `index` basename, since that would be a hidden entry point the `exports` map in `package.json` doesn't advertise.
|
|
179
|
-
- Zero document-format knowledge: this package knows bytes and container structure — ZIP entries, compound-file sectors and directory entries, the OLE packaging wrapper, [MS-OLEPS] property identifiers and typed values — never that any entry or stream is a document, or that PID 2 means a title. It depends only on `fflate` — not on `byte-codec`, `ooxml.js`, or `odf.js` (whose ZIP wrappers it deliberately mirrors rather than imports, keeping their branding and release cadences decoupled).
|
|
199
|
+
- Zero document-format knowledge: this package knows bytes and container structure — ZIP entries, compound-file sectors and directory entries, the OLE packaging wrapper, [MS-OLEPS] property identifiers and typed values, and the one shared RC4/MD5 cryptography scheme the legacy OLE-based binary formats happen to encrypt themselves with — never that any entry or stream is a document, or that PID 2 means a title. It depends only on `fflate` — not on `byte-codec`, `ooxml.js`, or `odf.js` (whose ZIP wrappers it deliberately mirrors rather than imports, keeping their branding and release cadences decoupled).
|
|
180
200
|
|
|
181
201
|
## Install
|
|
182
202
|
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/crypto/md5.ts
|
|
3
|
+
const BLOCK_BYTES = 64;
|
|
4
|
+
const DIGEST_BYTES = 16;
|
|
5
|
+
const ROUNDS = 64;
|
|
6
|
+
const WORDS_PER_BLOCK = 16;
|
|
7
|
+
const T_VALUES = [
|
|
8
|
+
3614090360,
|
|
9
|
+
3905402710,
|
|
10
|
+
606105819,
|
|
11
|
+
3250441966,
|
|
12
|
+
4118548399,
|
|
13
|
+
1200080426,
|
|
14
|
+
2821735955,
|
|
15
|
+
4249261313,
|
|
16
|
+
1770035416,
|
|
17
|
+
2336552879,
|
|
18
|
+
4294925233,
|
|
19
|
+
2304563134,
|
|
20
|
+
1804603682,
|
|
21
|
+
4254626195,
|
|
22
|
+
2792965006,
|
|
23
|
+
1236535329,
|
|
24
|
+
4129170786,
|
|
25
|
+
3225465664,
|
|
26
|
+
643717713,
|
|
27
|
+
3921069994,
|
|
28
|
+
3593408605,
|
|
29
|
+
38016083,
|
|
30
|
+
3634488961,
|
|
31
|
+
3889429448,
|
|
32
|
+
568446438,
|
|
33
|
+
3275163606,
|
|
34
|
+
4107603335,
|
|
35
|
+
1163531501,
|
|
36
|
+
2850285829,
|
|
37
|
+
4243563512,
|
|
38
|
+
1735328473,
|
|
39
|
+
2368359562,
|
|
40
|
+
4294588738,
|
|
41
|
+
2272392833,
|
|
42
|
+
1839030562,
|
|
43
|
+
4259657740,
|
|
44
|
+
2763975236,
|
|
45
|
+
1272893353,
|
|
46
|
+
4139469664,
|
|
47
|
+
3200236656,
|
|
48
|
+
681279174,
|
|
49
|
+
3936430074,
|
|
50
|
+
3572445317,
|
|
51
|
+
76029189,
|
|
52
|
+
3654602809,
|
|
53
|
+
3873151461,
|
|
54
|
+
530742520,
|
|
55
|
+
3299628645,
|
|
56
|
+
4096336452,
|
|
57
|
+
1126891415,
|
|
58
|
+
2878612391,
|
|
59
|
+
4237533241,
|
|
60
|
+
1700485571,
|
|
61
|
+
2399980690,
|
|
62
|
+
4293915773,
|
|
63
|
+
2240044497,
|
|
64
|
+
1873313359,
|
|
65
|
+
4264355552,
|
|
66
|
+
2734768916,
|
|
67
|
+
1309151649,
|
|
68
|
+
4149444226,
|
|
69
|
+
3174756917,
|
|
70
|
+
718787259,
|
|
71
|
+
3951481745
|
|
72
|
+
];
|
|
73
|
+
const T = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(T_VALUES.length * 4));
|
|
74
|
+
T_VALUES.forEach((value, index) => {
|
|
75
|
+
T.setUint32(index * 4, value, true);
|
|
76
|
+
});
|
|
77
|
+
const SHIFT_VALUES = [
|
|
78
|
+
7,
|
|
79
|
+
12,
|
|
80
|
+
17,
|
|
81
|
+
22,
|
|
82
|
+
7,
|
|
83
|
+
12,
|
|
84
|
+
17,
|
|
85
|
+
22,
|
|
86
|
+
7,
|
|
87
|
+
12,
|
|
88
|
+
17,
|
|
89
|
+
22,
|
|
90
|
+
7,
|
|
91
|
+
12,
|
|
92
|
+
17,
|
|
93
|
+
22,
|
|
94
|
+
5,
|
|
95
|
+
9,
|
|
96
|
+
14,
|
|
97
|
+
20,
|
|
98
|
+
5,
|
|
99
|
+
9,
|
|
100
|
+
14,
|
|
101
|
+
20,
|
|
102
|
+
5,
|
|
103
|
+
9,
|
|
104
|
+
14,
|
|
105
|
+
20,
|
|
106
|
+
5,
|
|
107
|
+
9,
|
|
108
|
+
14,
|
|
109
|
+
20,
|
|
110
|
+
4,
|
|
111
|
+
11,
|
|
112
|
+
16,
|
|
113
|
+
23,
|
|
114
|
+
4,
|
|
115
|
+
11,
|
|
116
|
+
16,
|
|
117
|
+
23,
|
|
118
|
+
4,
|
|
119
|
+
11,
|
|
120
|
+
16,
|
|
121
|
+
23,
|
|
122
|
+
4,
|
|
123
|
+
11,
|
|
124
|
+
16,
|
|
125
|
+
23,
|
|
126
|
+
6,
|
|
127
|
+
10,
|
|
128
|
+
15,
|
|
129
|
+
21,
|
|
130
|
+
6,
|
|
131
|
+
10,
|
|
132
|
+
15,
|
|
133
|
+
21,
|
|
134
|
+
6,
|
|
135
|
+
10,
|
|
136
|
+
15,
|
|
137
|
+
21,
|
|
138
|
+
6,
|
|
139
|
+
10,
|
|
140
|
+
15,
|
|
141
|
+
21
|
|
142
|
+
];
|
|
143
|
+
const SHIFT = new DataView(new ArrayBuffer(SHIFT_VALUES.length));
|
|
144
|
+
SHIFT_VALUES.forEach((value, index) => {
|
|
145
|
+
SHIFT.setUint8(index, value);
|
|
146
|
+
});
|
|
147
|
+
const INITIAL_A = 1732584193;
|
|
148
|
+
const INITIAL_B = 4023233417;
|
|
149
|
+
const INITIAL_C = 2562383102;
|
|
150
|
+
const INITIAL_D = 271733878;
|
|
151
|
+
function rotl32(value, bits) {
|
|
152
|
+
return (value << bits | value >>> 32 - bits) >>> 0;
|
|
153
|
+
}
|
|
154
|
+
function padMessage(bytes) {
|
|
155
|
+
const paddedLength = (Math.floor((bytes.length + 8) / BLOCK_BYTES) + 1) * BLOCK_BYTES;
|
|
156
|
+
const padded = new Uint8Array(paddedLength);
|
|
157
|
+
padded.set(bytes);
|
|
158
|
+
padded[bytes.length] = 128;
|
|
159
|
+
const view = new DataView(padded.buffer);
|
|
160
|
+
const bitLength = bytes.length * 8;
|
|
161
|
+
let low = bitLength % 4294967296;
|
|
162
|
+
let high = Math.floor(bitLength / 4294967296);
|
|
163
|
+
for (let i = 0; i < 4; i++) {
|
|
164
|
+
view.setUint8(paddedLength - 8 + i, low & 255);
|
|
165
|
+
low = Math.floor(low / 256);
|
|
166
|
+
view.setUint8(paddedLength - 4 + i, high & 255);
|
|
167
|
+
high = Math.floor(high / 256);
|
|
168
|
+
}
|
|
169
|
+
return padded;
|
|
170
|
+
}
|
|
171
|
+
function md5(bytes) {
|
|
172
|
+
const padded = padMessage(bytes);
|
|
173
|
+
const paddedView = new DataView(padded.buffer);
|
|
174
|
+
let stateA = INITIAL_A;
|
|
175
|
+
let stateB = INITIAL_B;
|
|
176
|
+
let stateC = INITIAL_C;
|
|
177
|
+
let stateD = INITIAL_D;
|
|
178
|
+
const block = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(64));
|
|
179
|
+
for (let offset = 0; offset < padded.length; offset += BLOCK_BYTES) {
|
|
180
|
+
for (let i = 0; i < WORDS_PER_BLOCK; i++) block.setUint32(i * 4, paddedView.getUint32(offset + i * 4, true), true);
|
|
181
|
+
let a = stateA;
|
|
182
|
+
let b = stateB;
|
|
183
|
+
let c = stateC;
|
|
184
|
+
let d = stateD;
|
|
185
|
+
for (let i = 0; i < ROUNDS; i++) {
|
|
186
|
+
let f;
|
|
187
|
+
let g;
|
|
188
|
+
if (i < 16) {
|
|
189
|
+
f = b & c | ~b & d;
|
|
190
|
+
g = i;
|
|
191
|
+
} else if (i < 32) {
|
|
192
|
+
f = d & b | ~d & c;
|
|
193
|
+
g = (5 * i + 1) % WORDS_PER_BLOCK;
|
|
194
|
+
} else if (i < 48) {
|
|
195
|
+
f = b ^ c ^ d;
|
|
196
|
+
g = (3 * i + 5) % WORDS_PER_BLOCK;
|
|
197
|
+
} else {
|
|
198
|
+
f = c ^ (b | ~d);
|
|
199
|
+
g = 7 * i % WORDS_PER_BLOCK;
|
|
200
|
+
}
|
|
201
|
+
const rotated = rotl32(a + (f >>> 0) + T.getUint32(i * 4, true) + block.getUint32(g * 4, true) >>> 0, SHIFT.getUint8(i));
|
|
202
|
+
a = d;
|
|
203
|
+
d = c;
|
|
204
|
+
c = b;
|
|
205
|
+
b = b + rotated >>> 0;
|
|
206
|
+
}
|
|
207
|
+
stateA = stateA + a | 0;
|
|
208
|
+
stateB = stateB + b | 0;
|
|
209
|
+
stateC = stateC + c | 0;
|
|
210
|
+
stateD = stateD + d | 0;
|
|
211
|
+
}
|
|
212
|
+
const digest = new Uint8Array(DIGEST_BYTES);
|
|
213
|
+
const digestView = new DataView(digest.buffer);
|
|
214
|
+
digestView.setInt32(0, stateA, true);
|
|
215
|
+
digestView.setInt32(4, stateB, true);
|
|
216
|
+
digestView.setInt32(8, stateC, true);
|
|
217
|
+
digestView.setInt32(12, stateD, true);
|
|
218
|
+
return digest;
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
exports.md5 = md5;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
//#region src/crypto/md5.ts
|
|
2
|
+
const BLOCK_BYTES = 64;
|
|
3
|
+
const DIGEST_BYTES = 16;
|
|
4
|
+
const ROUNDS = 64;
|
|
5
|
+
const WORDS_PER_BLOCK = 16;
|
|
6
|
+
const T_VALUES = [
|
|
7
|
+
3614090360,
|
|
8
|
+
3905402710,
|
|
9
|
+
606105819,
|
|
10
|
+
3250441966,
|
|
11
|
+
4118548399,
|
|
12
|
+
1200080426,
|
|
13
|
+
2821735955,
|
|
14
|
+
4249261313,
|
|
15
|
+
1770035416,
|
|
16
|
+
2336552879,
|
|
17
|
+
4294925233,
|
|
18
|
+
2304563134,
|
|
19
|
+
1804603682,
|
|
20
|
+
4254626195,
|
|
21
|
+
2792965006,
|
|
22
|
+
1236535329,
|
|
23
|
+
4129170786,
|
|
24
|
+
3225465664,
|
|
25
|
+
643717713,
|
|
26
|
+
3921069994,
|
|
27
|
+
3593408605,
|
|
28
|
+
38016083,
|
|
29
|
+
3634488961,
|
|
30
|
+
3889429448,
|
|
31
|
+
568446438,
|
|
32
|
+
3275163606,
|
|
33
|
+
4107603335,
|
|
34
|
+
1163531501,
|
|
35
|
+
2850285829,
|
|
36
|
+
4243563512,
|
|
37
|
+
1735328473,
|
|
38
|
+
2368359562,
|
|
39
|
+
4294588738,
|
|
40
|
+
2272392833,
|
|
41
|
+
1839030562,
|
|
42
|
+
4259657740,
|
|
43
|
+
2763975236,
|
|
44
|
+
1272893353,
|
|
45
|
+
4139469664,
|
|
46
|
+
3200236656,
|
|
47
|
+
681279174,
|
|
48
|
+
3936430074,
|
|
49
|
+
3572445317,
|
|
50
|
+
76029189,
|
|
51
|
+
3654602809,
|
|
52
|
+
3873151461,
|
|
53
|
+
530742520,
|
|
54
|
+
3299628645,
|
|
55
|
+
4096336452,
|
|
56
|
+
1126891415,
|
|
57
|
+
2878612391,
|
|
58
|
+
4237533241,
|
|
59
|
+
1700485571,
|
|
60
|
+
2399980690,
|
|
61
|
+
4293915773,
|
|
62
|
+
2240044497,
|
|
63
|
+
1873313359,
|
|
64
|
+
4264355552,
|
|
65
|
+
2734768916,
|
|
66
|
+
1309151649,
|
|
67
|
+
4149444226,
|
|
68
|
+
3174756917,
|
|
69
|
+
718787259,
|
|
70
|
+
3951481745
|
|
71
|
+
];
|
|
72
|
+
const T = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(T_VALUES.length * 4));
|
|
73
|
+
T_VALUES.forEach((value, index) => {
|
|
74
|
+
T.setUint32(index * 4, value, true);
|
|
75
|
+
});
|
|
76
|
+
const SHIFT_VALUES = [
|
|
77
|
+
7,
|
|
78
|
+
12,
|
|
79
|
+
17,
|
|
80
|
+
22,
|
|
81
|
+
7,
|
|
82
|
+
12,
|
|
83
|
+
17,
|
|
84
|
+
22,
|
|
85
|
+
7,
|
|
86
|
+
12,
|
|
87
|
+
17,
|
|
88
|
+
22,
|
|
89
|
+
7,
|
|
90
|
+
12,
|
|
91
|
+
17,
|
|
92
|
+
22,
|
|
93
|
+
5,
|
|
94
|
+
9,
|
|
95
|
+
14,
|
|
96
|
+
20,
|
|
97
|
+
5,
|
|
98
|
+
9,
|
|
99
|
+
14,
|
|
100
|
+
20,
|
|
101
|
+
5,
|
|
102
|
+
9,
|
|
103
|
+
14,
|
|
104
|
+
20,
|
|
105
|
+
5,
|
|
106
|
+
9,
|
|
107
|
+
14,
|
|
108
|
+
20,
|
|
109
|
+
4,
|
|
110
|
+
11,
|
|
111
|
+
16,
|
|
112
|
+
23,
|
|
113
|
+
4,
|
|
114
|
+
11,
|
|
115
|
+
16,
|
|
116
|
+
23,
|
|
117
|
+
4,
|
|
118
|
+
11,
|
|
119
|
+
16,
|
|
120
|
+
23,
|
|
121
|
+
4,
|
|
122
|
+
11,
|
|
123
|
+
16,
|
|
124
|
+
23,
|
|
125
|
+
6,
|
|
126
|
+
10,
|
|
127
|
+
15,
|
|
128
|
+
21,
|
|
129
|
+
6,
|
|
130
|
+
10,
|
|
131
|
+
15,
|
|
132
|
+
21,
|
|
133
|
+
6,
|
|
134
|
+
10,
|
|
135
|
+
15,
|
|
136
|
+
21,
|
|
137
|
+
6,
|
|
138
|
+
10,
|
|
139
|
+
15,
|
|
140
|
+
21
|
|
141
|
+
];
|
|
142
|
+
const SHIFT = new DataView(new ArrayBuffer(SHIFT_VALUES.length));
|
|
143
|
+
SHIFT_VALUES.forEach((value, index) => {
|
|
144
|
+
SHIFT.setUint8(index, value);
|
|
145
|
+
});
|
|
146
|
+
const INITIAL_A = 1732584193;
|
|
147
|
+
const INITIAL_B = 4023233417;
|
|
148
|
+
const INITIAL_C = 2562383102;
|
|
149
|
+
const INITIAL_D = 271733878;
|
|
150
|
+
function rotl32(value, bits) {
|
|
151
|
+
return (value << bits | value >>> 32 - bits) >>> 0;
|
|
152
|
+
}
|
|
153
|
+
function padMessage(bytes) {
|
|
154
|
+
const paddedLength = (Math.floor((bytes.length + 8) / BLOCK_BYTES) + 1) * BLOCK_BYTES;
|
|
155
|
+
const padded = new Uint8Array(paddedLength);
|
|
156
|
+
padded.set(bytes);
|
|
157
|
+
padded[bytes.length] = 128;
|
|
158
|
+
const view = new DataView(padded.buffer);
|
|
159
|
+
const bitLength = bytes.length * 8;
|
|
160
|
+
let low = bitLength % 4294967296;
|
|
161
|
+
let high = Math.floor(bitLength / 4294967296);
|
|
162
|
+
for (let i = 0; i < 4; i++) {
|
|
163
|
+
view.setUint8(paddedLength - 8 + i, low & 255);
|
|
164
|
+
low = Math.floor(low / 256);
|
|
165
|
+
view.setUint8(paddedLength - 4 + i, high & 255);
|
|
166
|
+
high = Math.floor(high / 256);
|
|
167
|
+
}
|
|
168
|
+
return padded;
|
|
169
|
+
}
|
|
170
|
+
function md5(bytes) {
|
|
171
|
+
const padded = padMessage(bytes);
|
|
172
|
+
const paddedView = new DataView(padded.buffer);
|
|
173
|
+
let stateA = INITIAL_A;
|
|
174
|
+
let stateB = INITIAL_B;
|
|
175
|
+
let stateC = INITIAL_C;
|
|
176
|
+
let stateD = INITIAL_D;
|
|
177
|
+
const block = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(64));
|
|
178
|
+
for (let offset = 0; offset < padded.length; offset += BLOCK_BYTES) {
|
|
179
|
+
for (let i = 0; i < WORDS_PER_BLOCK; i++) block.setUint32(i * 4, paddedView.getUint32(offset + i * 4, true), true);
|
|
180
|
+
let a = stateA;
|
|
181
|
+
let b = stateB;
|
|
182
|
+
let c = stateC;
|
|
183
|
+
let d = stateD;
|
|
184
|
+
for (let i = 0; i < ROUNDS; i++) {
|
|
185
|
+
let f;
|
|
186
|
+
let g;
|
|
187
|
+
if (i < 16) {
|
|
188
|
+
f = b & c | ~b & d;
|
|
189
|
+
g = i;
|
|
190
|
+
} else if (i < 32) {
|
|
191
|
+
f = d & b | ~d & c;
|
|
192
|
+
g = (5 * i + 1) % WORDS_PER_BLOCK;
|
|
193
|
+
} else if (i < 48) {
|
|
194
|
+
f = b ^ c ^ d;
|
|
195
|
+
g = (3 * i + 5) % WORDS_PER_BLOCK;
|
|
196
|
+
} else {
|
|
197
|
+
f = c ^ (b | ~d);
|
|
198
|
+
g = 7 * i % WORDS_PER_BLOCK;
|
|
199
|
+
}
|
|
200
|
+
const rotated = rotl32(a + (f >>> 0) + T.getUint32(i * 4, true) + block.getUint32(g * 4, true) >>> 0, SHIFT.getUint8(i));
|
|
201
|
+
a = d;
|
|
202
|
+
d = c;
|
|
203
|
+
c = b;
|
|
204
|
+
b = b + rotated >>> 0;
|
|
205
|
+
}
|
|
206
|
+
stateA = stateA + a | 0;
|
|
207
|
+
stateB = stateB + b | 0;
|
|
208
|
+
stateC = stateC + c | 0;
|
|
209
|
+
stateD = stateD + d | 0;
|
|
210
|
+
}
|
|
211
|
+
const digest = new Uint8Array(DIGEST_BYTES);
|
|
212
|
+
const digestView = new DataView(digest.buffer);
|
|
213
|
+
digestView.setInt32(0, stateA, true);
|
|
214
|
+
digestView.setInt32(4, stateB, true);
|
|
215
|
+
digestView.setInt32(8, stateC, true);
|
|
216
|
+
digestView.setInt32(12, stateD, true);
|
|
217
|
+
return digest;
|
|
218
|
+
}
|
|
219
|
+
//#endregion
|
|
220
|
+
export { md5 };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_crypto_md5 = require("./md5.cjs");
|
|
3
|
+
const require_crypto_rc4 = require("./rc4.cjs");
|
|
4
|
+
//#region src/crypto/office-rc4.ts
|
|
5
|
+
/** [MS-OFFCRYPTO] 2.3.6.1's own EncryptedVerifier/EncryptedVerifierHash length, and the salt length the same header carries. */
|
|
6
|
+
const OFFICE_RC4_VERIFIER_LENGTH = 16;
|
|
7
|
+
/** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
|
|
8
|
+
const OFFICE_RC4_BLOCK_SIZE = 1024;
|
|
9
|
+
/** The real derived RC4 key length for this scheme (40 bits) -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own generic prose ("the first 128 bits of Hfinal") describes a different, longer-key variant this header shape does not use. */
|
|
10
|
+
const KEY_LENGTH_BYTES = 5;
|
|
11
|
+
function passwordToUtf16LeBytes(password) {
|
|
12
|
+
const bytes = new Uint8Array(password.length * 2);
|
|
13
|
+
for (let i = 0; i < password.length; i += 1) {
|
|
14
|
+
const code = password.charCodeAt(i);
|
|
15
|
+
bytes[i * 2] = code & 255;
|
|
16
|
+
bytes[i * 2 + 1] = code >>> 8 & 255;
|
|
17
|
+
}
|
|
18
|
+
return bytes;
|
|
19
|
+
}
|
|
20
|
+
/** The per-workbook intermediate hash (H1's own first 5 bytes) every block's key derives from -- computed once per password+salt pair, then reused across every block via deriveOfficeRc4BlockKey, since recomputing H0/H1 per block would be needless repeated work over the identical 336-byte buffer. */
|
|
21
|
+
function deriveOfficeRc4BaseHash(password, salt) {
|
|
22
|
+
const truncated = require_crypto_md5.md5(passwordToUtf16LeBytes(password)).subarray(0, KEY_LENGTH_BYTES);
|
|
23
|
+
const unit = /* @__PURE__ */ new Uint8Array(21);
|
|
24
|
+
unit.set(truncated, 0);
|
|
25
|
+
unit.set(salt, KEY_LENGTH_BYTES);
|
|
26
|
+
const buffer336 = new Uint8Array(unit.length * 16);
|
|
27
|
+
for (let i = 0; i < 16; i += 1) buffer336.set(unit, i * unit.length);
|
|
28
|
+
return require_crypto_md5.md5(buffer336).subarray(0, KEY_LENGTH_BYTES);
|
|
29
|
+
}
|
|
30
|
+
/** The real, block-specific 40-bit RC4 key: MD5(baseHash + the block number as 4 little-endian bytes), truncated to 5 bytes. */
|
|
31
|
+
function deriveOfficeRc4BlockKey(baseHash, blockNumber) {
|
|
32
|
+
const input = new Uint8Array(baseHash.length + 4);
|
|
33
|
+
input.set(baseHash, 0);
|
|
34
|
+
input[baseHash.length] = blockNumber & 255;
|
|
35
|
+
input[baseHash.length + 1] = blockNumber >>> 8 & 255;
|
|
36
|
+
input[baseHash.length + 2] = blockNumber >>> 16 & 255;
|
|
37
|
+
input[baseHash.length + 3] = blockNumber >>> 24 & 255;
|
|
38
|
+
return require_crypto_md5.md5(input).subarray(0, KEY_LENGTH_BYTES);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Decrypts `data` -- a byte range of the underlying OLE stream starting at `streamOffset` bytes from the very start of that stream -- against the RC4 encryption header scheme, re-deriving the block key at every 1024-byte boundary `data` crosses. RC4 is symmetric, so this same function also encrypts; nothing in this package uses it that way, since nothing in this family writes an encrypted legacy binary document.
|
|
42
|
+
*
|
|
43
|
+
* `streamOffset` matters because the block number is the byte's own absolute position in the stream divided by 1024, not its position within whatever slice `data` happens to be -- decrypting a stream in arbitrary chunks (not just from offset 0) still lands on the correct per-block key this way.
|
|
44
|
+
*/
|
|
45
|
+
function decryptOfficeRc4(baseHash, streamOffset, data) {
|
|
46
|
+
const out = new Uint8Array(data.length);
|
|
47
|
+
let position = 0;
|
|
48
|
+
while (position < data.length) {
|
|
49
|
+
const absolute = streamOffset + position;
|
|
50
|
+
const blockNumber = Math.floor(absolute / OFFICE_RC4_BLOCK_SIZE);
|
|
51
|
+
const offsetWithinBlock = absolute - blockNumber * OFFICE_RC4_BLOCK_SIZE;
|
|
52
|
+
const bytesLeftInBlock = OFFICE_RC4_BLOCK_SIZE - offsetWithinBlock;
|
|
53
|
+
const chunkLength = Math.min(bytesLeftInBlock, data.length - position);
|
|
54
|
+
const key = deriveOfficeRc4BlockKey(baseHash, blockNumber);
|
|
55
|
+
const keystreamPrefixAndChunk = require_crypto_rc4.rc4(key, new Uint8Array(offsetWithinBlock + chunkLength));
|
|
56
|
+
const chunk = data.subarray(position, position + chunkLength);
|
|
57
|
+
const chunkView = new DataView(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
58
|
+
const keystreamView = new DataView(keystreamPrefixAndChunk.buffer, keystreamPrefixAndChunk.byteOffset, keystreamPrefixAndChunk.byteLength);
|
|
59
|
+
for (let i = 0; i < chunkLength; i += 1) out[position + i] = chunkView.getUint8(i) ^ keystreamView.getUint8(offsetWithinBlock + i);
|
|
60
|
+
position += chunkLength;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
exports.OFFICE_RC4_BLOCK_SIZE = OFFICE_RC4_BLOCK_SIZE;
|
|
66
|
+
exports.OFFICE_RC4_VERIFIER_LENGTH = OFFICE_RC4_VERIFIER_LENGTH;
|
|
67
|
+
exports.decryptOfficeRc4 = decryptOfficeRc4;
|
|
68
|
+
exports.deriveOfficeRc4BaseHash = deriveOfficeRc4BaseHash;
|
|
69
|
+
exports.deriveOfficeRc4BlockKey = deriveOfficeRc4BlockKey;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/crypto/office-rc4.d.ts
|
|
2
|
+
/** [MS-OFFCRYPTO] 2.3.6.1's own EncryptedVerifier/EncryptedVerifierHash length, and the salt length the same header carries. */
|
|
3
|
+
declare const OFFICE_RC4_VERIFIER_LENGTH = 16;
|
|
4
|
+
/** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
|
|
5
|
+
declare const OFFICE_RC4_BLOCK_SIZE = 1024;
|
|
6
|
+
/** The per-workbook intermediate hash (H1's own first 5 bytes) every block's key derives from -- computed once per password+salt pair, then reused across every block via deriveOfficeRc4BlockKey, since recomputing H0/H1 per block would be needless repeated work over the identical 336-byte buffer. */
|
|
7
|
+
declare function deriveOfficeRc4BaseHash(password: string, salt: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;
|
|
8
|
+
/** The real, block-specific 40-bit RC4 key: MD5(baseHash + the block number as 4 little-endian bytes), truncated to 5 bytes. */
|
|
9
|
+
declare function deriveOfficeRc4BlockKey(baseHash: Uint8Array<ArrayBuffer>, blockNumber: number): Uint8Array<ArrayBuffer>;
|
|
10
|
+
/**
|
|
11
|
+
* Decrypts `data` -- a byte range of the underlying OLE stream starting at `streamOffset` bytes from the very start of that stream -- against the RC4 encryption header scheme, re-deriving the block key at every 1024-byte boundary `data` crosses. RC4 is symmetric, so this same function also encrypts; nothing in this package uses it that way, since nothing in this family writes an encrypted legacy binary document.
|
|
12
|
+
*
|
|
13
|
+
* `streamOffset` matters because the block number is the byte's own absolute position in the stream divided by 1024, not its position within whatever slice `data` happens to be -- decrypting a stream in arbitrary chunks (not just from offset 0) still lands on the correct per-block key this way.
|
|
14
|
+
*/
|
|
15
|
+
declare function decryptOfficeRc4(baseHash: Uint8Array<ArrayBuffer>, streamOffset: number, data: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/crypto/office-rc4.d.ts
|
|
2
|
+
/** [MS-OFFCRYPTO] 2.3.6.1's own EncryptedVerifier/EncryptedVerifierHash length, and the salt length the same header carries. */
|
|
3
|
+
declare const OFFICE_RC4_VERIFIER_LENGTH = 16;
|
|
4
|
+
/** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
|
|
5
|
+
declare const OFFICE_RC4_BLOCK_SIZE = 1024;
|
|
6
|
+
/** The per-workbook intermediate hash (H1's own first 5 bytes) every block's key derives from -- computed once per password+salt pair, then reused across every block via deriveOfficeRc4BlockKey, since recomputing H0/H1 per block would be needless repeated work over the identical 336-byte buffer. */
|
|
7
|
+
declare function deriveOfficeRc4BaseHash(password: string, salt: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;
|
|
8
|
+
/** The real, block-specific 40-bit RC4 key: MD5(baseHash + the block number as 4 little-endian bytes), truncated to 5 bytes. */
|
|
9
|
+
declare function deriveOfficeRc4BlockKey(baseHash: Uint8Array<ArrayBuffer>, blockNumber: number): Uint8Array<ArrayBuffer>;
|
|
10
|
+
/**
|
|
11
|
+
* Decrypts `data` -- a byte range of the underlying OLE stream starting at `streamOffset` bytes from the very start of that stream -- against the RC4 encryption header scheme, re-deriving the block key at every 1024-byte boundary `data` crosses. RC4 is symmetric, so this same function also encrypts; nothing in this package uses it that way, since nothing in this family writes an encrypted legacy binary document.
|
|
12
|
+
*
|
|
13
|
+
* `streamOffset` matters because the block number is the byte's own absolute position in the stream divided by 1024, not its position within whatever slice `data` happens to be -- decrypting a stream in arbitrary chunks (not just from offset 0) still lands on the correct per-block key this way.
|
|
14
|
+
*/
|
|
15
|
+
declare function decryptOfficeRc4(baseHash: Uint8Array<ArrayBuffer>, streamOffset: number, data: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { md5 } from "./md5.js";
|
|
2
|
+
import { rc4 } from "./rc4.js";
|
|
3
|
+
//#region src/crypto/office-rc4.ts
|
|
4
|
+
/** [MS-OFFCRYPTO] 2.3.6.1's own EncryptedVerifier/EncryptedVerifierHash length, and the salt length the same header carries. */
|
|
5
|
+
const OFFICE_RC4_VERIFIER_LENGTH = 16;
|
|
6
|
+
/** The span of the underlying decrypted stream one derived RC4 key covers before the next block's key takes over -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own prose does not state it. */
|
|
7
|
+
const OFFICE_RC4_BLOCK_SIZE = 1024;
|
|
8
|
+
/** The real derived RC4 key length for this scheme (40 bits) -- confirmed against Apache POI's Biff8EncryptionKey, since [MS-OFFCRYPTO] 2.3.6.2's own generic prose ("the first 128 bits of Hfinal") describes a different, longer-key variant this header shape does not use. */
|
|
9
|
+
const KEY_LENGTH_BYTES = 5;
|
|
10
|
+
function passwordToUtf16LeBytes(password) {
|
|
11
|
+
const bytes = new Uint8Array(password.length * 2);
|
|
12
|
+
for (let i = 0; i < password.length; i += 1) {
|
|
13
|
+
const code = password.charCodeAt(i);
|
|
14
|
+
bytes[i * 2] = code & 255;
|
|
15
|
+
bytes[i * 2 + 1] = code >>> 8 & 255;
|
|
16
|
+
}
|
|
17
|
+
return bytes;
|
|
18
|
+
}
|
|
19
|
+
/** The per-workbook intermediate hash (H1's own first 5 bytes) every block's key derives from -- computed once per password+salt pair, then reused across every block via deriveOfficeRc4BlockKey, since recomputing H0/H1 per block would be needless repeated work over the identical 336-byte buffer. */
|
|
20
|
+
function deriveOfficeRc4BaseHash(password, salt) {
|
|
21
|
+
const truncated = md5(passwordToUtf16LeBytes(password)).subarray(0, KEY_LENGTH_BYTES);
|
|
22
|
+
const unit = /* @__PURE__ */ new Uint8Array(21);
|
|
23
|
+
unit.set(truncated, 0);
|
|
24
|
+
unit.set(salt, KEY_LENGTH_BYTES);
|
|
25
|
+
const buffer336 = new Uint8Array(unit.length * 16);
|
|
26
|
+
for (let i = 0; i < 16; i += 1) buffer336.set(unit, i * unit.length);
|
|
27
|
+
return md5(buffer336).subarray(0, KEY_LENGTH_BYTES);
|
|
28
|
+
}
|
|
29
|
+
/** The real, block-specific 40-bit RC4 key: MD5(baseHash + the block number as 4 little-endian bytes), truncated to 5 bytes. */
|
|
30
|
+
function deriveOfficeRc4BlockKey(baseHash, blockNumber) {
|
|
31
|
+
const input = new Uint8Array(baseHash.length + 4);
|
|
32
|
+
input.set(baseHash, 0);
|
|
33
|
+
input[baseHash.length] = blockNumber & 255;
|
|
34
|
+
input[baseHash.length + 1] = blockNumber >>> 8 & 255;
|
|
35
|
+
input[baseHash.length + 2] = blockNumber >>> 16 & 255;
|
|
36
|
+
input[baseHash.length + 3] = blockNumber >>> 24 & 255;
|
|
37
|
+
return md5(input).subarray(0, KEY_LENGTH_BYTES);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Decrypts `data` -- a byte range of the underlying OLE stream starting at `streamOffset` bytes from the very start of that stream -- against the RC4 encryption header scheme, re-deriving the block key at every 1024-byte boundary `data` crosses. RC4 is symmetric, so this same function also encrypts; nothing in this package uses it that way, since nothing in this family writes an encrypted legacy binary document.
|
|
41
|
+
*
|
|
42
|
+
* `streamOffset` matters because the block number is the byte's own absolute position in the stream divided by 1024, not its position within whatever slice `data` happens to be -- decrypting a stream in arbitrary chunks (not just from offset 0) still lands on the correct per-block key this way.
|
|
43
|
+
*/
|
|
44
|
+
function decryptOfficeRc4(baseHash, streamOffset, data) {
|
|
45
|
+
const out = new Uint8Array(data.length);
|
|
46
|
+
let position = 0;
|
|
47
|
+
while (position < data.length) {
|
|
48
|
+
const absolute = streamOffset + position;
|
|
49
|
+
const blockNumber = Math.floor(absolute / OFFICE_RC4_BLOCK_SIZE);
|
|
50
|
+
const offsetWithinBlock = absolute - blockNumber * OFFICE_RC4_BLOCK_SIZE;
|
|
51
|
+
const bytesLeftInBlock = OFFICE_RC4_BLOCK_SIZE - offsetWithinBlock;
|
|
52
|
+
const chunkLength = Math.min(bytesLeftInBlock, data.length - position);
|
|
53
|
+
const key = deriveOfficeRc4BlockKey(baseHash, blockNumber);
|
|
54
|
+
const keystreamPrefixAndChunk = rc4(key, new Uint8Array(offsetWithinBlock + chunkLength));
|
|
55
|
+
const chunk = data.subarray(position, position + chunkLength);
|
|
56
|
+
const chunkView = new DataView(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
57
|
+
const keystreamView = new DataView(keystreamPrefixAndChunk.buffer, keystreamPrefixAndChunk.byteOffset, keystreamPrefixAndChunk.byteLength);
|
|
58
|
+
for (let i = 0; i < chunkLength; i += 1) out[position + i] = chunkView.getUint8(i) ^ keystreamView.getUint8(offsetWithinBlock + i);
|
|
59
|
+
position += chunkLength;
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/crypto/rc4.ts
|
|
3
|
+
const STATE_SIZE = 256;
|
|
4
|
+
function rc4(key, data) {
|
|
5
|
+
if (key.length === 0) return Uint8Array.from(data);
|
|
6
|
+
const keyView = new DataView(key.buffer, key.byteOffset, key.byteLength);
|
|
7
|
+
const state = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(STATE_SIZE));
|
|
8
|
+
for (let i = 0; i < STATE_SIZE; i++) state.setUint8(i, i);
|
|
9
|
+
let j = 0;
|
|
10
|
+
for (let i = 0; i < STATE_SIZE; i++) {
|
|
11
|
+
j = j + state.getUint8(i) + keyView.getUint8(i % key.length) & 255;
|
|
12
|
+
const swap = state.getUint8(i);
|
|
13
|
+
state.setUint8(i, state.getUint8(j));
|
|
14
|
+
state.setUint8(j, swap);
|
|
15
|
+
}
|
|
16
|
+
const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
17
|
+
const out = new Uint8Array(data.length);
|
|
18
|
+
let x = 0;
|
|
19
|
+
let y = 0;
|
|
20
|
+
for (let n = 0; n < data.length; n++) {
|
|
21
|
+
x = x + 1 & 255;
|
|
22
|
+
y = y + state.getUint8(x) & 255;
|
|
23
|
+
const swap = state.getUint8(x);
|
|
24
|
+
state.setUint8(x, state.getUint8(y));
|
|
25
|
+
state.setUint8(y, swap);
|
|
26
|
+
out[n] = dataView.getUint8(n) ^ state.getUint8(state.getUint8(x) + state.getUint8(y) & 255);
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
exports.rc4 = rc4;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/crypto/rc4.ts
|
|
2
|
+
const STATE_SIZE = 256;
|
|
3
|
+
function rc4(key, data) {
|
|
4
|
+
if (key.length === 0) return Uint8Array.from(data);
|
|
5
|
+
const keyView = new DataView(key.buffer, key.byteOffset, key.byteLength);
|
|
6
|
+
const state = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(STATE_SIZE));
|
|
7
|
+
for (let i = 0; i < STATE_SIZE; i++) state.setUint8(i, i);
|
|
8
|
+
let j = 0;
|
|
9
|
+
for (let i = 0; i < STATE_SIZE; i++) {
|
|
10
|
+
j = j + state.getUint8(i) + keyView.getUint8(i % key.length) & 255;
|
|
11
|
+
const swap = state.getUint8(i);
|
|
12
|
+
state.setUint8(i, state.getUint8(j));
|
|
13
|
+
state.setUint8(j, swap);
|
|
14
|
+
}
|
|
15
|
+
const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
16
|
+
const out = new Uint8Array(data.length);
|
|
17
|
+
let x = 0;
|
|
18
|
+
let y = 0;
|
|
19
|
+
for (let n = 0; n < data.length; n++) {
|
|
20
|
+
x = x + 1 & 255;
|
|
21
|
+
y = y + state.getUint8(x) & 255;
|
|
22
|
+
const swap = state.getUint8(x);
|
|
23
|
+
state.setUint8(x, state.getUint8(y));
|
|
24
|
+
state.setUint8(y, swap);
|
|
25
|
+
out[n] = dataView.getUint8(n) ^ state.getUint8(state.getUint8(x) + state.getUint8(y) & 255);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { rc4 };
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,9 @@ const require_cfb_detect = require("./cfb/detect.cjs");
|
|
|
3
3
|
const require_cfb_ole_package = require("./cfb/ole-package.cjs");
|
|
4
4
|
const require_cfb_read = require("./cfb/read.cjs");
|
|
5
5
|
const require_cfb_write = require("./cfb/write.cjs");
|
|
6
|
+
const require_crypto_md5 = require("./crypto/md5.cjs");
|
|
7
|
+
const require_crypto_rc4 = require("./crypto/rc4.cjs");
|
|
8
|
+
const require_crypto_office_rc4 = require("./crypto/office-rc4.cjs");
|
|
6
9
|
const require_oleps_layout_metadata = require("./oleps/layout-metadata.cjs");
|
|
7
10
|
const require_oleps_read = require("./oleps/read.cjs");
|
|
8
11
|
const require_oleps_write = require("./oleps/write.cjs");
|
|
@@ -17,15 +20,22 @@ exports.FMTID_SUMMARY_INFORMATION = require_oleps_summary_information.FMTID_SUMM
|
|
|
17
20
|
exports.MAX_CFB_TOTAL_STREAM_BYTES = require_cfb_read.MAX_CFB_TOTAL_STREAM_BYTES;
|
|
18
21
|
exports.MAX_WALK_DEPTH = require_zip_walk.MAX_WALK_DEPTH;
|
|
19
22
|
exports.MAX_WALK_TOTAL_BYTES = require_zip_walk.MAX_WALK_TOTAL_BYTES;
|
|
23
|
+
exports.OFFICE_RC4_BLOCK_SIZE = require_crypto_office_rc4.OFFICE_RC4_BLOCK_SIZE;
|
|
24
|
+
exports.OFFICE_RC4_VERIFIER_LENGTH = require_crypto_office_rc4.OFFICE_RC4_VERIFIER_LENGTH;
|
|
20
25
|
exports.OlePackageFormatError = require_cfb_ole_package.OlePackageFormatError;
|
|
21
26
|
exports.OlePackageWriteError = require_cfb_ole_package.OlePackageWriteError;
|
|
22
27
|
exports.PropertySetFormatError = require_oleps_read.PropertySetFormatError;
|
|
23
28
|
exports.PropertySetWriteError = require_oleps_write.PropertySetWriteError;
|
|
29
|
+
exports.decryptOfficeRc4 = require_crypto_office_rc4.decryptOfficeRc4;
|
|
30
|
+
exports.deriveOfficeRc4BaseHash = require_crypto_office_rc4.deriveOfficeRc4BaseHash;
|
|
31
|
+
exports.deriveOfficeRc4BlockKey = require_crypto_office_rc4.deriveOfficeRc4BlockKey;
|
|
24
32
|
exports.detectArchiveFormat = require_zip_detect.detectArchiveFormat;
|
|
25
33
|
exports.hasSummaryInformationFields = require_oleps_layout_metadata.hasSummaryInformationFields;
|
|
26
34
|
exports.isCompoundFile = require_cfb_detect.isCompoundFile;
|
|
27
35
|
exports.isZipArchive = require_zip_detect.isZipArchive;
|
|
28
36
|
exports.layoutMetadataToSummaryInformation = require_oleps_layout_metadata.layoutMetadataToSummaryInformation;
|
|
37
|
+
exports.md5 = require_crypto_md5.md5;
|
|
38
|
+
exports.rc4 = require_crypto_rc4.rc4;
|
|
29
39
|
exports.readCompoundFile = require_cfb_read.readCompoundFile;
|
|
30
40
|
exports.readOlePackage = require_cfb_ole_package.readOlePackage;
|
|
31
41
|
exports.readPropertySetStream = require_oleps_read.readPropertySetStream;
|
package/dist/index.d.cts
CHANGED
|
@@ -2,6 +2,9 @@ import { isCompoundFile } from "./cfb/detect.cjs";
|
|
|
2
2
|
import { OlePackage, OlePackageFormatError, OlePackageWriteError, readOlePackage, writeOlePackage } from "./cfb/ole-package.cjs";
|
|
3
3
|
import { CompoundFileFormatError, CompoundFileStream, MAX_CFB_TOTAL_STREAM_BYTES, ReadCompoundFileOptions, readCompoundFile } from "./cfb/read.cjs";
|
|
4
4
|
import { CompoundFileWriteError, WriteCompoundFileOptions, writeCompoundFile } from "./cfb/write.cjs";
|
|
5
|
+
import { md5 } from "./crypto/md5.cjs";
|
|
6
|
+
import { OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey } from "./crypto/office-rc4.cjs";
|
|
7
|
+
import { rc4 } from "./crypto/rc4.cjs";
|
|
5
8
|
import { FMTID_SUMMARY_INFORMATION, SummaryInformationProperties, readSummaryInformation, writeSummaryInformationStream } from "./oleps/summary-information.cjs";
|
|
6
9
|
import { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata } from "./oleps/layout-metadata.cjs";
|
|
7
10
|
import { PropertySet, PropertyValue } from "./oleps/wire.cjs";
|
|
@@ -10,4 +13,4 @@ import { PropertySetWriteError, writePropertySetStream } from "./oleps/write.cjs
|
|
|
10
13
|
import { ZipEntry, unzipPackage, zipPackage } from "./zip/container.cjs";
|
|
11
14
|
import { ArchiveFormat, detectArchiveFormat, isZipArchive } from "./zip/detect.cjs";
|
|
12
15
|
import { ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, WalkArchiveOptions, walkArchive } from "./zip/walk.cjs";
|
|
13
|
-
export { ArchiveFormat, ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileStream, CompoundFileWriteError, FMTID_SUMMARY_INFORMATION, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackage, OlePackageFormatError, OlePackageWriteError, type PropertySet, PropertySetFormatError, PropertySetWriteError, type PropertyValue, ReadCompoundFileOptions, SummaryInformationProperties, WalkArchiveOptions, WriteCompoundFileOptions, ZipEntry, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, summaryInformationToLayoutMetadata, unzipPackage, walkArchive, writeCompoundFile, writeOlePackage, writePropertySetStream, writeSummaryInformationStream, zipPackage };
|
|
16
|
+
export { ArchiveFormat, ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileStream, CompoundFileWriteError, FMTID_SUMMARY_INFORMATION, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, OlePackage, OlePackageFormatError, OlePackageWriteError, type PropertySet, PropertySetFormatError, PropertySetWriteError, type PropertyValue, ReadCompoundFileOptions, SummaryInformationProperties, WalkArchiveOptions, WriteCompoundFileOptions, ZipEntry, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, md5, rc4, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, summaryInformationToLayoutMetadata, unzipPackage, walkArchive, writeCompoundFile, writeOlePackage, writePropertySetStream, writeSummaryInformationStream, zipPackage };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,9 @@ import { isCompoundFile } from "./cfb/detect.js";
|
|
|
2
2
|
import { OlePackage, OlePackageFormatError, OlePackageWriteError, readOlePackage, writeOlePackage } from "./cfb/ole-package.js";
|
|
3
3
|
import { CompoundFileFormatError, CompoundFileStream, MAX_CFB_TOTAL_STREAM_BYTES, ReadCompoundFileOptions, readCompoundFile } from "./cfb/read.js";
|
|
4
4
|
import { CompoundFileWriteError, WriteCompoundFileOptions, writeCompoundFile } from "./cfb/write.js";
|
|
5
|
+
import { md5 } from "./crypto/md5.js";
|
|
6
|
+
import { OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey } from "./crypto/office-rc4.js";
|
|
7
|
+
import { rc4 } from "./crypto/rc4.js";
|
|
5
8
|
import { FMTID_SUMMARY_INFORMATION, SummaryInformationProperties, readSummaryInformation, writeSummaryInformationStream } from "./oleps/summary-information.js";
|
|
6
9
|
import { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata } from "./oleps/layout-metadata.js";
|
|
7
10
|
import { PropertySet, PropertyValue } from "./oleps/wire.js";
|
|
@@ -10,4 +13,4 @@ import { PropertySetWriteError, writePropertySetStream } from "./oleps/write.js"
|
|
|
10
13
|
import { ZipEntry, unzipPackage, zipPackage } from "./zip/container.js";
|
|
11
14
|
import { ArchiveFormat, detectArchiveFormat, isZipArchive } from "./zip/detect.js";
|
|
12
15
|
import { ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, WalkArchiveOptions, walkArchive } from "./zip/walk.js";
|
|
13
|
-
export { ArchiveFormat, ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileStream, CompoundFileWriteError, FMTID_SUMMARY_INFORMATION, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackage, OlePackageFormatError, OlePackageWriteError, type PropertySet, PropertySetFormatError, PropertySetWriteError, type PropertyValue, ReadCompoundFileOptions, SummaryInformationProperties, WalkArchiveOptions, WriteCompoundFileOptions, ZipEntry, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, summaryInformationToLayoutMetadata, unzipPackage, walkArchive, writeCompoundFile, writeOlePackage, writePropertySetStream, writeSummaryInformationStream, zipPackage };
|
|
16
|
+
export { ArchiveFormat, ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileStream, CompoundFileWriteError, FMTID_SUMMARY_INFORMATION, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, OlePackage, OlePackageFormatError, OlePackageWriteError, type PropertySet, PropertySetFormatError, PropertySetWriteError, type PropertyValue, ReadCompoundFileOptions, SummaryInformationProperties, WalkArchiveOptions, WriteCompoundFileOptions, ZipEntry, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, md5, rc4, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, summaryInformationToLayoutMetadata, unzipPackage, walkArchive, writeCompoundFile, writeOlePackage, writePropertySetStream, writeSummaryInformationStream, zipPackage };
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,9 @@ import { isCompoundFile } from "./cfb/detect.js";
|
|
|
2
2
|
import { OlePackageFormatError, OlePackageWriteError, readOlePackage, writeOlePackage } from "./cfb/ole-package.js";
|
|
3
3
|
import { CompoundFileFormatError, MAX_CFB_TOTAL_STREAM_BYTES, readCompoundFile } from "./cfb/read.js";
|
|
4
4
|
import { CompoundFileWriteError, writeCompoundFile } from "./cfb/write.js";
|
|
5
|
+
import { md5 } from "./crypto/md5.js";
|
|
6
|
+
import { rc4 } from "./crypto/rc4.js";
|
|
7
|
+
import { OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey } from "./crypto/office-rc4.js";
|
|
5
8
|
import { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata } from "./oleps/layout-metadata.js";
|
|
6
9
|
import { PropertySetFormatError, readPropertySetStream } from "./oleps/read.js";
|
|
7
10
|
import { PropertySetWriteError, writePropertySetStream } from "./oleps/write.js";
|
|
@@ -9,4 +12,4 @@ import { FMTID_SUMMARY_INFORMATION, readSummaryInformation, writeSummaryInformat
|
|
|
9
12
|
import { unzipPackage, zipPackage } from "./zip/container.js";
|
|
10
13
|
import { detectArchiveFormat, isZipArchive } from "./zip/detect.js";
|
|
11
14
|
import { ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, walkArchive } from "./zip/walk.js";
|
|
12
|
-
export { ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileWriteError, FMTID_SUMMARY_INFORMATION, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackageFormatError, OlePackageWriteError, PropertySetFormatError, PropertySetWriteError, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, summaryInformationToLayoutMetadata, unzipPackage, walkArchive, writeCompoundFile, writeOlePackage, writePropertySetStream, writeSummaryInformationStream, zipPackage };
|
|
15
|
+
export { ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileWriteError, FMTID_SUMMARY_INFORMATION, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, OlePackageFormatError, OlePackageWriteError, PropertySetFormatError, PropertySetWriteError, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, md5, rc4, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, summaryInformationToLayoutMetadata, unzipPackage, walkArchive, writeCompoundFile, writeOlePackage, writePropertySetStream, writeSummaryInformationStream, zipPackage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archive-codec",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "ZIP-in-ZIP recursive walking with depth and cumulative decompressed-size guards, bounded classic OLE compound-file ([MS-CFB]) reading and writing, and [MS-OLEPS] Property Set Stream reading and writing - zero document-format knowledge, the archive and container utility package for the documents.js family.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -70,26 +70,23 @@
|
|
|
70
70
|
},
|
|
71
71
|
"packageManager": "pnpm@11.6.0",
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"document-schema.js": "
|
|
74
|
-
"fflate": "
|
|
73
|
+
"document-schema.js": "7.3.1",
|
|
74
|
+
"fflate": "0.8.3"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@arethetypeswrong/cli": "
|
|
78
|
-
"@cloudflare/vitest-pool-workers": "
|
|
79
|
-
"@stryker-mutator/core": "
|
|
80
|
-
"@stryker-mutator/typescript-checker": "
|
|
81
|
-
"@stryker-mutator/vitest-runner": "
|
|
82
|
-
"@types/node": "
|
|
83
|
-
"eslint": "
|
|
84
|
-
"husky": "
|
|
77
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
78
|
+
"@cloudflare/vitest-pool-workers": "0.21.2",
|
|
79
|
+
"@stryker-mutator/core": "10.0.0",
|
|
80
|
+
"@stryker-mutator/typescript-checker": "10.0.0",
|
|
81
|
+
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
82
|
+
"@types/node": "26.2.0",
|
|
83
|
+
"eslint": "10.8.1",
|
|
84
|
+
"husky": "9.1.7",
|
|
85
85
|
"jiti": "2.7.0",
|
|
86
|
-
"publint": "
|
|
87
|
-
"tsdown": "
|
|
88
|
-
"turbo": "
|
|
89
|
-
"typescript": "
|
|
90
|
-
"vitest": "
|
|
91
|
-
},
|
|
92
|
-
"lint-staged": {
|
|
93
|
-
"*.ts": "eslint --fix"
|
|
86
|
+
"publint": "0.3.22",
|
|
87
|
+
"tsdown": "0.22.14",
|
|
88
|
+
"turbo": "2.10.8",
|
|
89
|
+
"typescript": "6.0.3",
|
|
90
|
+
"vitest": "4.1.11"
|
|
94
91
|
}
|
|
95
92
|
}
|