archive-codec 1.9.2 → 1.10.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 +58 -21
- package/dist/crypto/xor-obfuscation.cjs +267 -0
- package/dist/crypto/xor-obfuscation.d.cts +27 -0
- package/dist/crypto/xor-obfuscation.d.ts +27 -0
- package/dist/crypto/xor-obfuscation.js +258 -0
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/package.json +2 -2
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, [MS-OLEPS] Property Set Stream reading and writing, and the RC4/MD5/SHA-1 primitives the legacy OLE-based binary formats use 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.
|
|
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/SHA-1/XOR-obfuscation primitives the legacy OLE-based binary formats use 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
|
|
|
@@ -20,7 +20,9 @@ Created for [documents.js#564](https://github.com/ExaDev/documents.js/issues/564
|
|
|
20
20
|
|
|
21
21
|
[documents.js#1116](https://github.com/ExaDev/documents.js/issues/1116) is the second: `.ppt`'s own encryption turned out to be a genuinely different scheme, not `ppt-codec`'s eventual integration against #1108's own primitives as originally assumed. [MS-OFFCRYPTO] 2.3.5 "RC4 CryptoAPI Encryption" is SHA-1-based rather than MD5-based, derives its key with no intermediate-hash iteration, and re-keys per persist object rather than at a fixed byte interval — different enough that it needed its own primitives (`crypto/sha1`, `crypto/office-rc4-cryptoapi`) rather than a parameter on the existing ones. See [RC4 CryptoAPI encryption](#rc4-cryptoapi-encryption-ppt).
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
[documents.js#922](https://github.com/ExaDev/documents.js/issues/922) added the older, much weaker XOR obfuscation scheme both `xls-codec` and `doc-codec` still had no support for: `crypto/xor-obfuscation`'s `createXorObfuscationKey`/`createXorObfuscationPasswordVerifier` (matching [MS-OFFCRYPTO]'s own published pseudocode exactly, cross-checked against Apache POI's `createXorKey1`/`createXorVerifier1` and a real Excel-generated fixture's own stored FilePass fields), `createXorObfuscationArray`, `decryptXorObfuscationMethod1` (`.xls`), and `decryptXorObfuscationMethod2` (`.doc`). The array-construction and data-transform steps genuinely diverge from the published spec text for Method 1 — see [XOR obfuscation](#xor-obfuscation-xlsdoc) below for the full account, including the real-file cross-check that caught it, mirroring the RC4 "40-bit" documentation bug this package already found and corrected once (above).
|
|
24
|
+
|
|
25
|
+
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/SHA-1/XOR-obfuscation primitives** the legacy OLE-based binary formats use for password-to-open encryption — `.doc`/`.xls` share one MD5-based RC4 scheme and one XOR obfuscation scheme (two methods, one per format), `.ppt` uses a genuinely different SHA-1-based RC4 CryptoAPI scheme. **tar and gzip, DocumentSummaryInformation's extended and user-defined property sets, and writing VT_LPSTR (ANSI-codepage) string properties are explicitly out of scope.**
|
|
24
26
|
|
|
25
27
|
## Getting started
|
|
26
28
|
|
|
@@ -50,24 +52,25 @@ import { walkArchive } from "archive-codec/zip/walk";
|
|
|
50
52
|
|
|
51
53
|
The smoke suite (`test/smoke.test.mjs`) is the guard on that advertisement: it loads each module below from the built `dist/` in both module systems, so a build config that stops serving an advertised subpath fails the suite — neither publint nor `attw` catches a wildcard whose targets are missing.
|
|
52
54
|
|
|
53
|
-
| Module | Exports
|
|
54
|
-
| ----------------------------- |
|
|
55
|
-
| `zip/container` | `zipPackage` (ordered-entries ZIP write with stored-uncompressed support), `unzipPackage`, `ZipEntry`
|
|
56
|
-
| `zip/detect` | `detectArchiveFormat` (`'zip' \| 'cfb' \| 'unknown'`), `isZipArchive`, `ArchiveFormat`
|
|
57
|
-
| `zip/walk` | `walkArchive` (recursive ZIP-in-ZIP walking), `ArchiveWalkEntry`, `ArchiveWalkLimitError`, `MAX_WALK_DEPTH`, `MAX_WALK_TOTAL_BYTES`, `WalkArchiveOptions`
|
|
58
|
-
| `cfb/detect` | `isCompoundFile` (the `D0 CF 11 E0 …` magic-byte check)
|
|
59
|
-
| `cfb/read` | `readCompoundFile` (bounded [MS-CFB] stream extraction), `CompoundFileStream`, `CompoundFileFormatError`, `MAX_CFB_TOTAL_STREAM_BYTES`, `ReadCompoundFileOptions`
|
|
60
|
-
| `cfb/write` | `writeCompoundFile` ([MS-CFB] container generation), `CompoundFileWriteError`, `WriteCompoundFileOptions` — takes the `CompoundFileStream` array `cfb/read` returns
|
|
61
|
-
| `cfb/ole-package` | `readOlePackage` (OLE Package stream unwrapping), `writeOlePackage` (the mirror), `OlePackage`, `OlePackageFormatError`, `OlePackageWriteError`
|
|
62
|
-
| `oleps/read` | `readPropertySetStream` (generic [MS-OLEPS] property-set decoding), `PropertySetFormatError`
|
|
63
|
-
| `oleps/write` | `writePropertySetStream` (generic [MS-OLEPS] property-set encoding), `PropertySetWriteError` — takes the `PropertySet` shape `oleps/read` returns
|
|
64
|
-
| `oleps/summary-information` | `readSummaryInformation`, `writeSummaryInformationStream`, `SummaryInformationProperties`, `FMTID_SUMMARY_INFORMATION`
|
|
65
|
-
| `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))
|
|
66
|
-
| `crypto/md5` | `md5` — RFC 1321 MD5, hand-written (see [Legacy Office encryption](#legacy-office-encryption))
|
|
67
|
-
| `crypto/sha1` | `sha1` — RFC 3174/FIPS 180-1 SHA-1, hand-written (see [RC4 CryptoAPI encryption](#rc4-cryptoapi-encryption-ppt))
|
|
68
|
-
| `crypto/rc4` | `rc4` — the RC4 stream cipher, hand-written; symmetric, so also the decrypt operation
|
|
69
|
-
| `crypto/office-rc4` | `deriveOfficeRc4BaseHash`, `deriveOfficeRc4BlockKey`, `decryptOfficeRc4`, `OFFICE_RC4_VERIFIER_LENGTH`, `OFFICE_RC4_BLOCK_SIZE`, `OFFICE_RC4_DOC_BLOCK_SIZE` — [MS-OFFCRYPTO] 2.3.6.2's own key derivation and the block-keyed stream decryption built on it
|
|
70
|
-
| `crypto/office-rc4-cryptoapi` | `deriveRc4CryptoApiBlockKey`, `verifyRc4CryptoApiPassword`, `RC4_CRYPTOAPI_SALT_LENGTH`, `RC4_CRYPTOAPI_VERIFIER_LENGTH`, `RC4_CRYPTOAPI_VERIFIER_HASH_LENGTH`, `RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS` — [MS-OFFCRYPTO] 2.3.5.2's own key derivation, `.ppt`'s own encryption scheme
|
|
55
|
+
| Module | Exports |
|
|
56
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
57
|
+
| `zip/container` | `zipPackage` (ordered-entries ZIP write with stored-uncompressed support), `unzipPackage`, `ZipEntry` |
|
|
58
|
+
| `zip/detect` | `detectArchiveFormat` (`'zip' \| 'cfb' \| 'unknown'`), `isZipArchive`, `ArchiveFormat` |
|
|
59
|
+
| `zip/walk` | `walkArchive` (recursive ZIP-in-ZIP walking), `ArchiveWalkEntry`, `ArchiveWalkLimitError`, `MAX_WALK_DEPTH`, `MAX_WALK_TOTAL_BYTES`, `WalkArchiveOptions` |
|
|
60
|
+
| `cfb/detect` | `isCompoundFile` (the `D0 CF 11 E0 …` magic-byte check) |
|
|
61
|
+
| `cfb/read` | `readCompoundFile` (bounded [MS-CFB] stream extraction), `CompoundFileStream`, `CompoundFileFormatError`, `MAX_CFB_TOTAL_STREAM_BYTES`, `ReadCompoundFileOptions` |
|
|
62
|
+
| `cfb/write` | `writeCompoundFile` ([MS-CFB] container generation), `CompoundFileWriteError`, `WriteCompoundFileOptions` — takes the `CompoundFileStream` array `cfb/read` returns |
|
|
63
|
+
| `cfb/ole-package` | `readOlePackage` (OLE Package stream unwrapping), `writeOlePackage` (the mirror), `OlePackage`, `OlePackageFormatError`, `OlePackageWriteError` |
|
|
64
|
+
| `oleps/read` | `readPropertySetStream` (generic [MS-OLEPS] property-set decoding), `PropertySetFormatError` |
|
|
65
|
+
| `oleps/write` | `writePropertySetStream` (generic [MS-OLEPS] property-set encoding), `PropertySetWriteError` — takes the `PropertySet` shape `oleps/read` returns |
|
|
66
|
+
| `oleps/summary-information` | `readSummaryInformation`, `writeSummaryInformationStream`, `SummaryInformationProperties`, `FMTID_SUMMARY_INFORMATION` |
|
|
67
|
+
| `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)) |
|
|
68
|
+
| `crypto/md5` | `md5` — RFC 1321 MD5, hand-written (see [Legacy Office encryption](#legacy-office-encryption)) |
|
|
69
|
+
| `crypto/sha1` | `sha1` — RFC 3174/FIPS 180-1 SHA-1, hand-written (see [RC4 CryptoAPI encryption](#rc4-cryptoapi-encryption-ppt)) |
|
|
70
|
+
| `crypto/rc4` | `rc4` — the RC4 stream cipher, hand-written; symmetric, so also the decrypt operation |
|
|
71
|
+
| `crypto/office-rc4` | `deriveOfficeRc4BaseHash`, `deriveOfficeRc4BlockKey`, `decryptOfficeRc4`, `OFFICE_RC4_VERIFIER_LENGTH`, `OFFICE_RC4_BLOCK_SIZE`, `OFFICE_RC4_DOC_BLOCK_SIZE` — [MS-OFFCRYPTO] 2.3.6.2's own key derivation and the block-keyed stream decryption built on it |
|
|
72
|
+
| `crypto/office-rc4-cryptoapi` | `deriveRc4CryptoApiBlockKey`, `verifyRc4CryptoApiPassword`, `RC4_CRYPTOAPI_SALT_LENGTH`, `RC4_CRYPTOAPI_VERIFIER_LENGTH`, `RC4_CRYPTOAPI_VERIFIER_HASH_LENGTH`, `RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS` — [MS-OFFCRYPTO] 2.3.5.2's own key derivation, `.ppt`'s own encryption scheme |
|
|
73
|
+
| `crypto/xor-obfuscation` | `createXorObfuscationKey`, `createXorObfuscationPasswordVerifier`, `createXorObfuscationArray`, `decryptXorObfuscationMethod1` (`.xls`), `decryptXorObfuscationMethod2` (`.doc`), `XOR_OBFUSCATION_ARRAY_LENGTH`, `XOR_OBFUSCATION_MAX_PASSWORD_LENGTH`, `XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1`, `XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2` — [MS-OFFCRYPTO] 2.3.7's own weaker, non-cipher legacy obfuscation scheme (see [XOR obfuscation](#xor-obfuscation-xlsdoc)) |
|
|
71
74
|
|
|
72
75
|
### Recursive walking
|
|
73
76
|
|
|
@@ -224,6 +227,40 @@ const plaintext = rc4(key, ciphertext);
|
|
|
224
227
|
|
|
225
228
|
`deriveRc4CryptoApiBlockKey`/`verifyRc4CryptoApiPassword` implement [MS-OFFCRYPTO] 2.3.5.1/2.3.5.2's "RC4 CryptoAPI Encryption" scheme, `.ppt`'s own encryption ([documents.js#1116](https://github.com/ExaDev/documents.js/issues/1116)) -- genuinely different from the [Legacy Office encryption](#legacy-office-encryption) scheme above despite both ending in an RC4 keystream: SHA-1 rather than MD5, one hash of the salt and password folded with the block number rather than an intermediate 336-byte buffer, and (per the spec's own prose) explicitly not iterated. There is no `decryptOfficeRc4`-style entry point with its own block-boundary loop here, because RC4 CryptoAPI does not re-key at fixed byte intervals within one continuous stream the way the legacy scheme does: it re-keys per persist object, using that object's own persist ID as the "block number" -- a caller derives the one key its own object needs and applies this package's own `rc4` directly, exactly as `ppt-codec`'s `decryptPptDocumentStream` does. Cross-checked against Apache POI's `EncryptionInfo`/`StandardEncryptionHeader`/`CryptoAPIEncryptionHeader` and nolze/msoffcrypto-tool's `method/rc4_cryptoapi.py`, whose own `_makekey`/`verifypw` this module's derivation and verification mirror.
|
|
226
229
|
|
|
230
|
+
### XOR obfuscation (.xls/.doc)
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import {
|
|
234
|
+
createXorObfuscationArray,
|
|
235
|
+
createXorObfuscationKey,
|
|
236
|
+
createXorObfuscationPasswordVerifier,
|
|
237
|
+
decryptXorObfuscationMethod1,
|
|
238
|
+
XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1,
|
|
239
|
+
} from "archive-codec";
|
|
240
|
+
|
|
241
|
+
// key/verificationBytes come from the caller's own FilePass-record reading
|
|
242
|
+
// (xls-codec's own [MS-XLS] 2.4.117 handling, not this package's concern).
|
|
243
|
+
if (
|
|
244
|
+
createXorObfuscationKey(password) !== key ||
|
|
245
|
+
createXorObfuscationPasswordVerifier(password) !== verificationBytes
|
|
246
|
+
) {
|
|
247
|
+
throw new Error("wrong password");
|
|
248
|
+
}
|
|
249
|
+
const array = createXorObfuscationArray(
|
|
250
|
+
password,
|
|
251
|
+
XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1,
|
|
252
|
+
);
|
|
253
|
+
// initialIndex is (streamOffset + recordDataLength) % 16 -- a BIFF-record
|
|
254
|
+
// concept xls-codec's own [MS-XLS] 2.2.10 handling computes, not this package's.
|
|
255
|
+
const plaintext = decryptXorObfuscationMethod1(array, ciphertext, initialIndex);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`createXorObfuscationKey`/`createXorObfuscationPasswordVerifier` implement [MS-OFFCRYPTO] 2.3.7.2/2.3.7.1's own key/verifier derivation exactly as published — confirmed against Apache POI's `createXorKey1`/`createXorVerifier1` and, for `"123456789012345"`, against a genuine Excel-generated XOR-obfuscated `.xls` fixture's own stored FilePass `key`/`verificationBytes` fields (nolze/msoffcrypto-tool's own test corpus, itself sourced from the openwall/john-samples password-cracking corpus). `createXorObfuscationArray` and `decryptXorObfuscationMethod1`, by contrast, do **not** follow [MS-OFFCRYPTO]'s own published pseudocode: decrypting that same real fixture's Workbook stream under the literal spec text (`CreateXorArray_Method1`'s reverse-order `XorRor` construction, `DecryptData_Method1`'s 5-bit rotation) produces garbage, while the algorithm this module actually implements — construct the array from the password's own bytes in forward order plus padding, XOR the alternating low/high byte of the same key, rotate left 2 (`.xls`) or 7 (`.doc`), then decrypt by rotating each byte left 3 before XORing against the array — recovers the fixture's real content (a readable sheet name, a `CodePage` record decrypting to the genuine value 1200, a `Dimensions` row count matching the file's own `Row` record count exactly). This is not a guess: Apache POI's own `CryptoFunctions.createXorArray1` carries the comment _"this code is based on the libre office implementation. The MS-OFFCRYPTO misses some infos about the various rotation sizes"_, and its `XORDecryptor.invokeCipher` states _"It seems that the encrypt and decrypt method is mixed up in the MS-OFFCRYPTO docs"_ — two independent, real, actively-maintained implementations (Apache POI, LibreOffice) converged on the same non-spec algorithm ahead of this package, which now confirms it a third time against genuine Excel output. See `crypto/xor-obfuscation.ts`'s own top comment for the full account, mirroring [Legacy Office encryption](#legacy-office-encryption)'s own "40-bit RC4" documentation-bug precedent above.
|
|
259
|
+
|
|
260
|
+
`.doc`'s own Method 2 (`decryptXorObfuscationMethod2`) has no equivalent real-file cross-check available — its data transform (plain XOR with a zero-byte exception, no rotation) matches both the published spec and LibreOffice's own `MSCodec_XorWord95::Decode` exactly, but its array-construction rotate distance (7, `XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2`) is taken from LibreOffice/Apache POI alone, without an independently-encrypted `.doc` fixture to confirm it against the way Method 1 was.
|
|
261
|
+
|
|
262
|
+
Password verification differs by method: RC4's own `EncryptedVerifier`/`EncryptedVerifierHash` are themselves encrypted, decrypted then compared (see [Legacy Office encryption](#legacy-office-encryption) above); XOR obfuscation's own `key`/`verificationBytes` (Method 1) or 32-bit verifier (Method 2, the high/low halves of the same two functions) are plain, unencrypted checksums of the password, recomputed and compared directly, matching Apache POI's own `XORDecryptor.verifyPassword` — which checks both fields, not either alone.
|
|
263
|
+
|
|
227
264
|
### ZIP container
|
|
228
265
|
|
|
229
266
|
`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.
|
|
@@ -232,7 +269,7 @@ const plaintext = rc4(key, ciphertext);
|
|
|
232
269
|
|
|
233
270
|
- 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.
|
|
234
271
|
- 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.
|
|
235
|
-
- 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
|
|
272
|
+
- 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 RC4/XOR-obfuscation cryptography schemes 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).
|
|
236
273
|
|
|
237
274
|
## Install
|
|
238
275
|
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/crypto/xor-obfuscation.ts
|
|
3
|
+
function u16Table(values) {
|
|
4
|
+
const view = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(values.length * 2));
|
|
5
|
+
values.forEach((value, index) => {
|
|
6
|
+
view.setUint16(index * 2, value);
|
|
7
|
+
});
|
|
8
|
+
return view;
|
|
9
|
+
}
|
|
10
|
+
function u8Table(values) {
|
|
11
|
+
const view = new DataView(new ArrayBuffer(values.length));
|
|
12
|
+
values.forEach((value, index) => {
|
|
13
|
+
view.setUint8(index, value);
|
|
14
|
+
});
|
|
15
|
+
return view;
|
|
16
|
+
}
|
|
17
|
+
/** [MS-OFFCRYPTO] 2.3.7.2's own PadArray: the fixed 15-byte filler used once a password's own bytes are exhausted while building either method's 16-byte array. */
|
|
18
|
+
const PAD_ARRAY = u8Table([
|
|
19
|
+
187,
|
|
20
|
+
255,
|
|
21
|
+
255,
|
|
22
|
+
186,
|
|
23
|
+
255,
|
|
24
|
+
255,
|
|
25
|
+
185,
|
|
26
|
+
128,
|
|
27
|
+
0,
|
|
28
|
+
190,
|
|
29
|
+
15,
|
|
30
|
+
0,
|
|
31
|
+
191,
|
|
32
|
+
15,
|
|
33
|
+
0
|
|
34
|
+
]);
|
|
35
|
+
/** [MS-OFFCRYPTO] 2.3.7.2's own InitialCode table, indexed by `password.length - 1` (a 1-15 character password) to seed createXorObfuscationKey. */
|
|
36
|
+
const INITIAL_CODE = u16Table([
|
|
37
|
+
57840,
|
|
38
|
+
7439,
|
|
39
|
+
52380,
|
|
40
|
+
33984,
|
|
41
|
+
4364,
|
|
42
|
+
3600,
|
|
43
|
+
61902,
|
|
44
|
+
12606,
|
|
45
|
+
6258,
|
|
46
|
+
57657,
|
|
47
|
+
54287,
|
|
48
|
+
34041,
|
|
49
|
+
10252,
|
|
50
|
+
43370,
|
|
51
|
+
20163
|
|
52
|
+
]);
|
|
53
|
+
/** [MS-OFFCRYPTO] 2.3.7.2's own XorMatrix: a 15x7 table of 16-bit constants (flattened here to 105 entries, row-major) createXorObfuscationKey folds into the running key for every set bit of every password character. */
|
|
54
|
+
const XOR_MATRIX = u16Table([
|
|
55
|
+
44796,
|
|
56
|
+
19929,
|
|
57
|
+
39858,
|
|
58
|
+
10053,
|
|
59
|
+
20106,
|
|
60
|
+
40212,
|
|
61
|
+
10761,
|
|
62
|
+
31585,
|
|
63
|
+
63170,
|
|
64
|
+
64933,
|
|
65
|
+
60267,
|
|
66
|
+
50935,
|
|
67
|
+
40399,
|
|
68
|
+
11199,
|
|
69
|
+
17763,
|
|
70
|
+
35526,
|
|
71
|
+
1453,
|
|
72
|
+
2906,
|
|
73
|
+
5812,
|
|
74
|
+
11624,
|
|
75
|
+
23248,
|
|
76
|
+
885,
|
|
77
|
+
1770,
|
|
78
|
+
3540,
|
|
79
|
+
7080,
|
|
80
|
+
14160,
|
|
81
|
+
28320,
|
|
82
|
+
56640,
|
|
83
|
+
55369,
|
|
84
|
+
41139,
|
|
85
|
+
20807,
|
|
86
|
+
41614,
|
|
87
|
+
21821,
|
|
88
|
+
43642,
|
|
89
|
+
17621,
|
|
90
|
+
28485,
|
|
91
|
+
56970,
|
|
92
|
+
44341,
|
|
93
|
+
19019,
|
|
94
|
+
38038,
|
|
95
|
+
14605,
|
|
96
|
+
29210,
|
|
97
|
+
60195,
|
|
98
|
+
50791,
|
|
99
|
+
40175,
|
|
100
|
+
10751,
|
|
101
|
+
21502,
|
|
102
|
+
43004,
|
|
103
|
+
24537,
|
|
104
|
+
18387,
|
|
105
|
+
36774,
|
|
106
|
+
3949,
|
|
107
|
+
7898,
|
|
108
|
+
15796,
|
|
109
|
+
31592,
|
|
110
|
+
63184,
|
|
111
|
+
47201,
|
|
112
|
+
24803,
|
|
113
|
+
49606,
|
|
114
|
+
37805,
|
|
115
|
+
14203,
|
|
116
|
+
28406,
|
|
117
|
+
56812,
|
|
118
|
+
17824,
|
|
119
|
+
35648,
|
|
120
|
+
1697,
|
|
121
|
+
3394,
|
|
122
|
+
6788,
|
|
123
|
+
13576,
|
|
124
|
+
27152,
|
|
125
|
+
43601,
|
|
126
|
+
17539,
|
|
127
|
+
35078,
|
|
128
|
+
557,
|
|
129
|
+
1114,
|
|
130
|
+
2228,
|
|
131
|
+
4456,
|
|
132
|
+
30388,
|
|
133
|
+
60776,
|
|
134
|
+
51953,
|
|
135
|
+
34243,
|
|
136
|
+
7079,
|
|
137
|
+
14158,
|
|
138
|
+
28316,
|
|
139
|
+
14128,
|
|
140
|
+
28256,
|
|
141
|
+
56512,
|
|
142
|
+
43425,
|
|
143
|
+
17251,
|
|
144
|
+
34502,
|
|
145
|
+
7597,
|
|
146
|
+
13105,
|
|
147
|
+
26210,
|
|
148
|
+
52420,
|
|
149
|
+
35241,
|
|
150
|
+
883,
|
|
151
|
+
1766,
|
|
152
|
+
3532,
|
|
153
|
+
4129,
|
|
154
|
+
8258,
|
|
155
|
+
16516,
|
|
156
|
+
33032,
|
|
157
|
+
4657,
|
|
158
|
+
9314,
|
|
159
|
+
18628
|
|
160
|
+
]);
|
|
161
|
+
/** The length every XOR obfuscation array (either method) has, and the period both methods' XorArrayIndex wraps at. */
|
|
162
|
+
const XOR_OBFUSCATION_ARRAY_LENGTH = 16;
|
|
163
|
+
/** The maximum password length [MS-OFFCRYPTO] 2.3.7.2/2.3.7.5 both state ("Password MUST NOT be longer than 15 characters"), shared by both methods since both build their array from the same InitialCode/PadArray tables sized for it. */
|
|
164
|
+
const XOR_OBFUSCATION_MAX_PASSWORD_LENGTH = 15;
|
|
165
|
+
/** [MS-XLS]'s FilePass/XORObfuscation own array-construction rotate distance -- confirmed against Apache POI's `CryptoFunctions.createXorArray1` ("Excel = 2") and LibreOffice's `MSCodec_XorXLS95` (`MSCodec_Xor95(2)`); see this file's own top comment for the real-file cross-check. */
|
|
166
|
+
const XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1 = 2;
|
|
167
|
+
/** [MS-DOC]'s own array-construction rotate distance -- confirmed against Apache POI's `CryptoFunctions.createXorArray1` ("Word = 7") and LibreOffice's `MSCodec_XorWord95` (`MSCodec_Xor95(7)`); see this file's own top comment for the caveat that this one has no real-file cross-check available. */
|
|
168
|
+
const XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2 = 7;
|
|
169
|
+
/** ASCII/Latin-1 single-byte password encoding: both XOR obfuscation methods' own pseudocode declares its password parameter an ASCII string (2.3.7.1/2.3.7.2), unlike office-rc4.ts/office-rc4-cryptoapi.ts's UTF-16LE passwords. Throws rather than silently truncating a character that does not fit in one byte or a password past the 15-character limit both methods share -- a caller passing a password XOR obfuscation cannot represent has passed the wrong password, not one this module should guess at re-encoding. */
|
|
170
|
+
function passwordToAsciiBytes(password) {
|
|
171
|
+
if (password.length === 0 || password.length > 15) throw new RangeError(`XOR obfuscation passwords must be 1-15 characters, got ${password.length}`);
|
|
172
|
+
const bytes = new Uint8Array(password.length);
|
|
173
|
+
for (let i = 0; i < password.length; i += 1) {
|
|
174
|
+
const code = password.charCodeAt(i);
|
|
175
|
+
if (code > 255) throw new RangeError(`XOR obfuscation passwords must be single-byte ASCII/Latin-1 characters, got code point ${code} at index ${i}`);
|
|
176
|
+
bytes[i] = code;
|
|
177
|
+
}
|
|
178
|
+
return bytes;
|
|
179
|
+
}
|
|
180
|
+
/** [MS-OFFCRYPTO] 2.3.7.2's own CreateXorKey_Method1: a 16-bit key folded from InitialCode (seeded by password length) and XorMatrix (one row per password character, walked in reverse, one column per set bit of that character read least-significant-bit first). Despite the "_Method1" name this is genuinely shared by both methods -- 2.3.7.5's own CreateXorArray_Method2 splits this exact same key's high/low bytes for its own array construction, and 2.3.7.4's CreatePasswordVerifier_Method2 uses it as the high word of Method 2's own 32-bit verifier -- confirmed against Apache POI's `createXorKey1`, which is implemented as `createXorVerifier2(password) >>> 16` for exactly this reason. */
|
|
181
|
+
function createXorObfuscationKey(password) {
|
|
182
|
+
const bytes = passwordToAsciiBytes(password);
|
|
183
|
+
const bytesView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
184
|
+
let xorKey = INITIAL_CODE.getUint16((bytes.length - 1) * 2);
|
|
185
|
+
let currentElement = 104;
|
|
186
|
+
for (let i = bytes.length - 1; i >= 0; i -= 1) {
|
|
187
|
+
let ch = bytesView.getUint8(i);
|
|
188
|
+
for (let bit = 0; bit < 7; bit += 1) {
|
|
189
|
+
if ((ch & 64) !== 0) xorKey = (xorKey ^ XOR_MATRIX.getUint16(currentElement * 2)) & 65535;
|
|
190
|
+
ch = ch << 1 & 255;
|
|
191
|
+
currentElement -= 1;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return xorKey;
|
|
195
|
+
}
|
|
196
|
+
/** [MS-OFFCRYPTO] 2.3.7.1's own CreatePasswordVerifier_Method1: a 16-bit checksum of the password's own length and bytes, folded in reverse order through a 15-bit rotating XOR, finished off with a fixed 0xCE4B mask. Shared by both methods -- 2.3.7.4's CreatePasswordVerifier_Method2 uses this as the low word of Method 2's own 32-bit verifier -- confirmed against Apache POI's `createXorVerifier1`. */
|
|
197
|
+
function createXorObfuscationPasswordVerifier(password) {
|
|
198
|
+
const bytes = passwordToAsciiBytes(password);
|
|
199
|
+
const bytesView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
200
|
+
let verifier = 0;
|
|
201
|
+
for (let i = bytes.length - 1; i >= 0; i -= 1) verifier = ((verifier & 16384) === 0 ? 0 : 1) ^ verifier * 2 & 32767 ^ bytesView.getUint8(i);
|
|
202
|
+
verifier = ((verifier & 16384) === 0 ? 0 : 1) ^ verifier * 2 & 32767 ^ bytes.length;
|
|
203
|
+
return (verifier ^ 52811) & 65535;
|
|
204
|
+
}
|
|
205
|
+
function rotateLeft8(byte, distance) {
|
|
206
|
+
const b = byte & 255;
|
|
207
|
+
return (b << distance | b >>> 8 - distance) & 255;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The real, cross-validated 16-byte XOR obfuscation array (see this file's own top comment): the password's own ASCII bytes followed by PAD_ARRAY filler up to 16 bytes, each byte then XORed with the alternating low/high byte of createXorObfuscationKey's own output (even index takes the low byte, odd the high byte) and rotated left by `rotateDistance` -- XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1 (2) for [MS-XLS], METHOD2 (7) for [MS-DOC].
|
|
211
|
+
*/
|
|
212
|
+
function createXorObfuscationArray(password, rotateDistance) {
|
|
213
|
+
const passwordBytes = passwordToAsciiBytes(password);
|
|
214
|
+
const array = /* @__PURE__ */ new Uint8Array(16);
|
|
215
|
+
array.set(passwordBytes, 0);
|
|
216
|
+
for (let i = passwordBytes.length; i < 16; i += 1) array[i] = PAD_ARRAY.getUint8(i - passwordBytes.length);
|
|
217
|
+
const xorKey = createXorObfuscationKey(password);
|
|
218
|
+
const keyLow = xorKey & 255;
|
|
219
|
+
const keyHigh = xorKey >>> 8 & 255;
|
|
220
|
+
const arrayView = new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
221
|
+
for (let i = 0; i < 16; i += 1) {
|
|
222
|
+
const withKey = arrayView.getUint8(i) ^ (i % 2 === 0 ? keyLow : keyHigh);
|
|
223
|
+
array[i] = rotateLeft8(withKey, rotateDistance);
|
|
224
|
+
}
|
|
225
|
+
return array;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Decrypts (and, since the transform is not self-inverse, only decrypts -- see this file's own top comment on why this package implements no corresponding encrypt direction) `data` against Method 1's own per-byte transform: for each byte, rotate left 3 bits, then XOR against `array[index]`, incrementing `index` (wrapping at XOR_OBFUSCATION_ARRAY_LENGTH) after every byte. `initialIndex` is the caller's own responsibility -- xls-codec's own [MS-XLS] 2.2.10 per-record `(streamOffset + recordDataLength) % 16` rule lives there, not here, since it is a BIFF-record concept this package has no knowledge of.
|
|
229
|
+
*/
|
|
230
|
+
function decryptXorObfuscationMethod1(array, data, initialIndex) {
|
|
231
|
+
const out = new Uint8Array(data.length);
|
|
232
|
+
const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
233
|
+
const arrayView = new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
234
|
+
let index = initialIndex % 16;
|
|
235
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
236
|
+
const rotated = rotateLeft8(dataView.getUint8(i), 3);
|
|
237
|
+
out[i] = rotated ^ arrayView.getUint8(index);
|
|
238
|
+
index = (index + 1) % 16;
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Decrypts `data` against Method 2's own per-byte transform ([MS-OFFCRYPTO] 2.3.7.6, matching LibreOffice's `MSCodec_XorWord95::Decode` exactly -- see this file's own top comment): for each byte, XOR against `array[index]` -- unless the original byte was already 0x00, or the XOR result is 0x00, in which case the byte is left unmodified -- incrementing `index` (wrapping at XOR_OBFUSCATION_ARRAY_LENGTH) after every byte regardless of whether it was modified. This transform is its own inverse (plain XOR, no rotation), so the same function also encrypts; nothing in this package uses it that way, since nothing in this family writes encrypted legacy binary documents.
|
|
244
|
+
*/
|
|
245
|
+
function decryptXorObfuscationMethod2(array, data, initialIndex) {
|
|
246
|
+
const out = new Uint8Array(data.length);
|
|
247
|
+
const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
248
|
+
const arrayView = new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
249
|
+
let index = initialIndex % 16;
|
|
250
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
251
|
+
const original = dataView.getUint8(i);
|
|
252
|
+
const transformed = original ^ arrayView.getUint8(index);
|
|
253
|
+
out[i] = original === 0 || transformed === 0 ? original : transformed;
|
|
254
|
+
index = (index + 1) % 16;
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
exports.XOR_OBFUSCATION_ARRAY_LENGTH = XOR_OBFUSCATION_ARRAY_LENGTH;
|
|
260
|
+
exports.XOR_OBFUSCATION_MAX_PASSWORD_LENGTH = XOR_OBFUSCATION_MAX_PASSWORD_LENGTH;
|
|
261
|
+
exports.XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1 = XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1;
|
|
262
|
+
exports.XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2 = XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2;
|
|
263
|
+
exports.createXorObfuscationArray = createXorObfuscationArray;
|
|
264
|
+
exports.createXorObfuscationKey = createXorObfuscationKey;
|
|
265
|
+
exports.createXorObfuscationPasswordVerifier = createXorObfuscationPasswordVerifier;
|
|
266
|
+
exports.decryptXorObfuscationMethod1 = decryptXorObfuscationMethod1;
|
|
267
|
+
exports.decryptXorObfuscationMethod2 = decryptXorObfuscationMethod2;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/crypto/xor-obfuscation.d.ts
|
|
2
|
+
/** The length every XOR obfuscation array (either method) has, and the period both methods' XorArrayIndex wraps at. */
|
|
3
|
+
declare const XOR_OBFUSCATION_ARRAY_LENGTH = 16;
|
|
4
|
+
/** The maximum password length [MS-OFFCRYPTO] 2.3.7.2/2.3.7.5 both state ("Password MUST NOT be longer than 15 characters"), shared by both methods since both build their array from the same InitialCode/PadArray tables sized for it. */
|
|
5
|
+
declare const XOR_OBFUSCATION_MAX_PASSWORD_LENGTH = 15;
|
|
6
|
+
/** [MS-XLS]'s FilePass/XORObfuscation own array-construction rotate distance -- confirmed against Apache POI's `CryptoFunctions.createXorArray1` ("Excel = 2") and LibreOffice's `MSCodec_XorXLS95` (`MSCodec_Xor95(2)`); see this file's own top comment for the real-file cross-check. */
|
|
7
|
+
declare const XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1 = 2;
|
|
8
|
+
/** [MS-DOC]'s own array-construction rotate distance -- confirmed against Apache POI's `CryptoFunctions.createXorArray1` ("Word = 7") and LibreOffice's `MSCodec_XorWord95` (`MSCodec_Xor95(7)`); see this file's own top comment for the caveat that this one has no real-file cross-check available. */
|
|
9
|
+
declare const XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2 = 7;
|
|
10
|
+
/** [MS-OFFCRYPTO] 2.3.7.2's own CreateXorKey_Method1: a 16-bit key folded from InitialCode (seeded by password length) and XorMatrix (one row per password character, walked in reverse, one column per set bit of that character read least-significant-bit first). Despite the "_Method1" name this is genuinely shared by both methods -- 2.3.7.5's own CreateXorArray_Method2 splits this exact same key's high/low bytes for its own array construction, and 2.3.7.4's CreatePasswordVerifier_Method2 uses it as the high word of Method 2's own 32-bit verifier -- confirmed against Apache POI's `createXorKey1`, which is implemented as `createXorVerifier2(password) >>> 16` for exactly this reason. */
|
|
11
|
+
declare function createXorObfuscationKey(password: string): number;
|
|
12
|
+
/** [MS-OFFCRYPTO] 2.3.7.1's own CreatePasswordVerifier_Method1: a 16-bit checksum of the password's own length and bytes, folded in reverse order through a 15-bit rotating XOR, finished off with a fixed 0xCE4B mask. Shared by both methods -- 2.3.7.4's CreatePasswordVerifier_Method2 uses this as the low word of Method 2's own 32-bit verifier -- confirmed against Apache POI's `createXorVerifier1`. */
|
|
13
|
+
declare function createXorObfuscationPasswordVerifier(password: string): number;
|
|
14
|
+
/**
|
|
15
|
+
* The real, cross-validated 16-byte XOR obfuscation array (see this file's own top comment): the password's own ASCII bytes followed by PAD_ARRAY filler up to 16 bytes, each byte then XORed with the alternating low/high byte of createXorObfuscationKey's own output (even index takes the low byte, odd the high byte) and rotated left by `rotateDistance` -- XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1 (2) for [MS-XLS], METHOD2 (7) for [MS-DOC].
|
|
16
|
+
*/
|
|
17
|
+
declare function createXorObfuscationArray(password: string, rotateDistance: number): Uint8Array<ArrayBuffer>;
|
|
18
|
+
/**
|
|
19
|
+
* Decrypts (and, since the transform is not self-inverse, only decrypts -- see this file's own top comment on why this package implements no corresponding encrypt direction) `data` against Method 1's own per-byte transform: for each byte, rotate left 3 bits, then XOR against `array[index]`, incrementing `index` (wrapping at XOR_OBFUSCATION_ARRAY_LENGTH) after every byte. `initialIndex` is the caller's own responsibility -- xls-codec's own [MS-XLS] 2.2.10 per-record `(streamOffset + recordDataLength) % 16` rule lives there, not here, since it is a BIFF-record concept this package has no knowledge of.
|
|
20
|
+
*/
|
|
21
|
+
declare function decryptXorObfuscationMethod1(array: Uint8Array<ArrayBuffer>, data: Uint8Array<ArrayBuffer>, initialIndex: number): Uint8Array<ArrayBuffer>;
|
|
22
|
+
/**
|
|
23
|
+
* Decrypts `data` against Method 2's own per-byte transform ([MS-OFFCRYPTO] 2.3.7.6, matching LibreOffice's `MSCodec_XorWord95::Decode` exactly -- see this file's own top comment): for each byte, XOR against `array[index]` -- unless the original byte was already 0x00, or the XOR result is 0x00, in which case the byte is left unmodified -- incrementing `index` (wrapping at XOR_OBFUSCATION_ARRAY_LENGTH) after every byte regardless of whether it was modified. This transform is its own inverse (plain XOR, no rotation), so the same function also encrypts; nothing in this package uses it that way, since nothing in this family writes encrypted legacy binary documents.
|
|
24
|
+
*/
|
|
25
|
+
declare function decryptXorObfuscationMethod2(array: Uint8Array<ArrayBuffer>, data: Uint8Array<ArrayBuffer>, initialIndex: number): Uint8Array<ArrayBuffer>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_MAX_PASSWORD_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptXorObfuscationMethod1, decryptXorObfuscationMethod2 };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/crypto/xor-obfuscation.d.ts
|
|
2
|
+
/** The length every XOR obfuscation array (either method) has, and the period both methods' XorArrayIndex wraps at. */
|
|
3
|
+
declare const XOR_OBFUSCATION_ARRAY_LENGTH = 16;
|
|
4
|
+
/** The maximum password length [MS-OFFCRYPTO] 2.3.7.2/2.3.7.5 both state ("Password MUST NOT be longer than 15 characters"), shared by both methods since both build their array from the same InitialCode/PadArray tables sized for it. */
|
|
5
|
+
declare const XOR_OBFUSCATION_MAX_PASSWORD_LENGTH = 15;
|
|
6
|
+
/** [MS-XLS]'s FilePass/XORObfuscation own array-construction rotate distance -- confirmed against Apache POI's `CryptoFunctions.createXorArray1` ("Excel = 2") and LibreOffice's `MSCodec_XorXLS95` (`MSCodec_Xor95(2)`); see this file's own top comment for the real-file cross-check. */
|
|
7
|
+
declare const XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1 = 2;
|
|
8
|
+
/** [MS-DOC]'s own array-construction rotate distance -- confirmed against Apache POI's `CryptoFunctions.createXorArray1` ("Word = 7") and LibreOffice's `MSCodec_XorWord95` (`MSCodec_Xor95(7)`); see this file's own top comment for the caveat that this one has no real-file cross-check available. */
|
|
9
|
+
declare const XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2 = 7;
|
|
10
|
+
/** [MS-OFFCRYPTO] 2.3.7.2's own CreateXorKey_Method1: a 16-bit key folded from InitialCode (seeded by password length) and XorMatrix (one row per password character, walked in reverse, one column per set bit of that character read least-significant-bit first). Despite the "_Method1" name this is genuinely shared by both methods -- 2.3.7.5's own CreateXorArray_Method2 splits this exact same key's high/low bytes for its own array construction, and 2.3.7.4's CreatePasswordVerifier_Method2 uses it as the high word of Method 2's own 32-bit verifier -- confirmed against Apache POI's `createXorKey1`, which is implemented as `createXorVerifier2(password) >>> 16` for exactly this reason. */
|
|
11
|
+
declare function createXorObfuscationKey(password: string): number;
|
|
12
|
+
/** [MS-OFFCRYPTO] 2.3.7.1's own CreatePasswordVerifier_Method1: a 16-bit checksum of the password's own length and bytes, folded in reverse order through a 15-bit rotating XOR, finished off with a fixed 0xCE4B mask. Shared by both methods -- 2.3.7.4's CreatePasswordVerifier_Method2 uses this as the low word of Method 2's own 32-bit verifier -- confirmed against Apache POI's `createXorVerifier1`. */
|
|
13
|
+
declare function createXorObfuscationPasswordVerifier(password: string): number;
|
|
14
|
+
/**
|
|
15
|
+
* The real, cross-validated 16-byte XOR obfuscation array (see this file's own top comment): the password's own ASCII bytes followed by PAD_ARRAY filler up to 16 bytes, each byte then XORed with the alternating low/high byte of createXorObfuscationKey's own output (even index takes the low byte, odd the high byte) and rotated left by `rotateDistance` -- XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1 (2) for [MS-XLS], METHOD2 (7) for [MS-DOC].
|
|
16
|
+
*/
|
|
17
|
+
declare function createXorObfuscationArray(password: string, rotateDistance: number): Uint8Array<ArrayBuffer>;
|
|
18
|
+
/**
|
|
19
|
+
* Decrypts (and, since the transform is not self-inverse, only decrypts -- see this file's own top comment on why this package implements no corresponding encrypt direction) `data` against Method 1's own per-byte transform: for each byte, rotate left 3 bits, then XOR against `array[index]`, incrementing `index` (wrapping at XOR_OBFUSCATION_ARRAY_LENGTH) after every byte. `initialIndex` is the caller's own responsibility -- xls-codec's own [MS-XLS] 2.2.10 per-record `(streamOffset + recordDataLength) % 16` rule lives there, not here, since it is a BIFF-record concept this package has no knowledge of.
|
|
20
|
+
*/
|
|
21
|
+
declare function decryptXorObfuscationMethod1(array: Uint8Array<ArrayBuffer>, data: Uint8Array<ArrayBuffer>, initialIndex: number): Uint8Array<ArrayBuffer>;
|
|
22
|
+
/**
|
|
23
|
+
* Decrypts `data` against Method 2's own per-byte transform ([MS-OFFCRYPTO] 2.3.7.6, matching LibreOffice's `MSCodec_XorWord95::Decode` exactly -- see this file's own top comment): for each byte, XOR against `array[index]` -- unless the original byte was already 0x00, or the XOR result is 0x00, in which case the byte is left unmodified -- incrementing `index` (wrapping at XOR_OBFUSCATION_ARRAY_LENGTH) after every byte regardless of whether it was modified. This transform is its own inverse (plain XOR, no rotation), so the same function also encrypts; nothing in this package uses it that way, since nothing in this family writes encrypted legacy binary documents.
|
|
24
|
+
*/
|
|
25
|
+
declare function decryptXorObfuscationMethod2(array: Uint8Array<ArrayBuffer>, data: Uint8Array<ArrayBuffer>, initialIndex: number): Uint8Array<ArrayBuffer>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_MAX_PASSWORD_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptXorObfuscationMethod1, decryptXorObfuscationMethod2 };
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
//#region src/crypto/xor-obfuscation.ts
|
|
2
|
+
function u16Table(values) {
|
|
3
|
+
const view = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(values.length * 2));
|
|
4
|
+
values.forEach((value, index) => {
|
|
5
|
+
view.setUint16(index * 2, value);
|
|
6
|
+
});
|
|
7
|
+
return view;
|
|
8
|
+
}
|
|
9
|
+
function u8Table(values) {
|
|
10
|
+
const view = new DataView(new ArrayBuffer(values.length));
|
|
11
|
+
values.forEach((value, index) => {
|
|
12
|
+
view.setUint8(index, value);
|
|
13
|
+
});
|
|
14
|
+
return view;
|
|
15
|
+
}
|
|
16
|
+
/** [MS-OFFCRYPTO] 2.3.7.2's own PadArray: the fixed 15-byte filler used once a password's own bytes are exhausted while building either method's 16-byte array. */
|
|
17
|
+
const PAD_ARRAY = u8Table([
|
|
18
|
+
187,
|
|
19
|
+
255,
|
|
20
|
+
255,
|
|
21
|
+
186,
|
|
22
|
+
255,
|
|
23
|
+
255,
|
|
24
|
+
185,
|
|
25
|
+
128,
|
|
26
|
+
0,
|
|
27
|
+
190,
|
|
28
|
+
15,
|
|
29
|
+
0,
|
|
30
|
+
191,
|
|
31
|
+
15,
|
|
32
|
+
0
|
|
33
|
+
]);
|
|
34
|
+
/** [MS-OFFCRYPTO] 2.3.7.2's own InitialCode table, indexed by `password.length - 1` (a 1-15 character password) to seed createXorObfuscationKey. */
|
|
35
|
+
const INITIAL_CODE = u16Table([
|
|
36
|
+
57840,
|
|
37
|
+
7439,
|
|
38
|
+
52380,
|
|
39
|
+
33984,
|
|
40
|
+
4364,
|
|
41
|
+
3600,
|
|
42
|
+
61902,
|
|
43
|
+
12606,
|
|
44
|
+
6258,
|
|
45
|
+
57657,
|
|
46
|
+
54287,
|
|
47
|
+
34041,
|
|
48
|
+
10252,
|
|
49
|
+
43370,
|
|
50
|
+
20163
|
|
51
|
+
]);
|
|
52
|
+
/** [MS-OFFCRYPTO] 2.3.7.2's own XorMatrix: a 15x7 table of 16-bit constants (flattened here to 105 entries, row-major) createXorObfuscationKey folds into the running key for every set bit of every password character. */
|
|
53
|
+
const XOR_MATRIX = u16Table([
|
|
54
|
+
44796,
|
|
55
|
+
19929,
|
|
56
|
+
39858,
|
|
57
|
+
10053,
|
|
58
|
+
20106,
|
|
59
|
+
40212,
|
|
60
|
+
10761,
|
|
61
|
+
31585,
|
|
62
|
+
63170,
|
|
63
|
+
64933,
|
|
64
|
+
60267,
|
|
65
|
+
50935,
|
|
66
|
+
40399,
|
|
67
|
+
11199,
|
|
68
|
+
17763,
|
|
69
|
+
35526,
|
|
70
|
+
1453,
|
|
71
|
+
2906,
|
|
72
|
+
5812,
|
|
73
|
+
11624,
|
|
74
|
+
23248,
|
|
75
|
+
885,
|
|
76
|
+
1770,
|
|
77
|
+
3540,
|
|
78
|
+
7080,
|
|
79
|
+
14160,
|
|
80
|
+
28320,
|
|
81
|
+
56640,
|
|
82
|
+
55369,
|
|
83
|
+
41139,
|
|
84
|
+
20807,
|
|
85
|
+
41614,
|
|
86
|
+
21821,
|
|
87
|
+
43642,
|
|
88
|
+
17621,
|
|
89
|
+
28485,
|
|
90
|
+
56970,
|
|
91
|
+
44341,
|
|
92
|
+
19019,
|
|
93
|
+
38038,
|
|
94
|
+
14605,
|
|
95
|
+
29210,
|
|
96
|
+
60195,
|
|
97
|
+
50791,
|
|
98
|
+
40175,
|
|
99
|
+
10751,
|
|
100
|
+
21502,
|
|
101
|
+
43004,
|
|
102
|
+
24537,
|
|
103
|
+
18387,
|
|
104
|
+
36774,
|
|
105
|
+
3949,
|
|
106
|
+
7898,
|
|
107
|
+
15796,
|
|
108
|
+
31592,
|
|
109
|
+
63184,
|
|
110
|
+
47201,
|
|
111
|
+
24803,
|
|
112
|
+
49606,
|
|
113
|
+
37805,
|
|
114
|
+
14203,
|
|
115
|
+
28406,
|
|
116
|
+
56812,
|
|
117
|
+
17824,
|
|
118
|
+
35648,
|
|
119
|
+
1697,
|
|
120
|
+
3394,
|
|
121
|
+
6788,
|
|
122
|
+
13576,
|
|
123
|
+
27152,
|
|
124
|
+
43601,
|
|
125
|
+
17539,
|
|
126
|
+
35078,
|
|
127
|
+
557,
|
|
128
|
+
1114,
|
|
129
|
+
2228,
|
|
130
|
+
4456,
|
|
131
|
+
30388,
|
|
132
|
+
60776,
|
|
133
|
+
51953,
|
|
134
|
+
34243,
|
|
135
|
+
7079,
|
|
136
|
+
14158,
|
|
137
|
+
28316,
|
|
138
|
+
14128,
|
|
139
|
+
28256,
|
|
140
|
+
56512,
|
|
141
|
+
43425,
|
|
142
|
+
17251,
|
|
143
|
+
34502,
|
|
144
|
+
7597,
|
|
145
|
+
13105,
|
|
146
|
+
26210,
|
|
147
|
+
52420,
|
|
148
|
+
35241,
|
|
149
|
+
883,
|
|
150
|
+
1766,
|
|
151
|
+
3532,
|
|
152
|
+
4129,
|
|
153
|
+
8258,
|
|
154
|
+
16516,
|
|
155
|
+
33032,
|
|
156
|
+
4657,
|
|
157
|
+
9314,
|
|
158
|
+
18628
|
|
159
|
+
]);
|
|
160
|
+
/** The length every XOR obfuscation array (either method) has, and the period both methods' XorArrayIndex wraps at. */
|
|
161
|
+
const XOR_OBFUSCATION_ARRAY_LENGTH = 16;
|
|
162
|
+
/** The maximum password length [MS-OFFCRYPTO] 2.3.7.2/2.3.7.5 both state ("Password MUST NOT be longer than 15 characters"), shared by both methods since both build their array from the same InitialCode/PadArray tables sized for it. */
|
|
163
|
+
const XOR_OBFUSCATION_MAX_PASSWORD_LENGTH = 15;
|
|
164
|
+
/** [MS-XLS]'s FilePass/XORObfuscation own array-construction rotate distance -- confirmed against Apache POI's `CryptoFunctions.createXorArray1` ("Excel = 2") and LibreOffice's `MSCodec_XorXLS95` (`MSCodec_Xor95(2)`); see this file's own top comment for the real-file cross-check. */
|
|
165
|
+
const XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1 = 2;
|
|
166
|
+
/** [MS-DOC]'s own array-construction rotate distance -- confirmed against Apache POI's `CryptoFunctions.createXorArray1` ("Word = 7") and LibreOffice's `MSCodec_XorWord95` (`MSCodec_Xor95(7)`); see this file's own top comment for the caveat that this one has no real-file cross-check available. */
|
|
167
|
+
const XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2 = 7;
|
|
168
|
+
/** ASCII/Latin-1 single-byte password encoding: both XOR obfuscation methods' own pseudocode declares its password parameter an ASCII string (2.3.7.1/2.3.7.2), unlike office-rc4.ts/office-rc4-cryptoapi.ts's UTF-16LE passwords. Throws rather than silently truncating a character that does not fit in one byte or a password past the 15-character limit both methods share -- a caller passing a password XOR obfuscation cannot represent has passed the wrong password, not one this module should guess at re-encoding. */
|
|
169
|
+
function passwordToAsciiBytes(password) {
|
|
170
|
+
if (password.length === 0 || password.length > 15) throw new RangeError(`XOR obfuscation passwords must be 1-15 characters, got ${password.length}`);
|
|
171
|
+
const bytes = new Uint8Array(password.length);
|
|
172
|
+
for (let i = 0; i < password.length; i += 1) {
|
|
173
|
+
const code = password.charCodeAt(i);
|
|
174
|
+
if (code > 255) throw new RangeError(`XOR obfuscation passwords must be single-byte ASCII/Latin-1 characters, got code point ${code} at index ${i}`);
|
|
175
|
+
bytes[i] = code;
|
|
176
|
+
}
|
|
177
|
+
return bytes;
|
|
178
|
+
}
|
|
179
|
+
/** [MS-OFFCRYPTO] 2.3.7.2's own CreateXorKey_Method1: a 16-bit key folded from InitialCode (seeded by password length) and XorMatrix (one row per password character, walked in reverse, one column per set bit of that character read least-significant-bit first). Despite the "_Method1" name this is genuinely shared by both methods -- 2.3.7.5's own CreateXorArray_Method2 splits this exact same key's high/low bytes for its own array construction, and 2.3.7.4's CreatePasswordVerifier_Method2 uses it as the high word of Method 2's own 32-bit verifier -- confirmed against Apache POI's `createXorKey1`, which is implemented as `createXorVerifier2(password) >>> 16` for exactly this reason. */
|
|
180
|
+
function createXorObfuscationKey(password) {
|
|
181
|
+
const bytes = passwordToAsciiBytes(password);
|
|
182
|
+
const bytesView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
183
|
+
let xorKey = INITIAL_CODE.getUint16((bytes.length - 1) * 2);
|
|
184
|
+
let currentElement = 104;
|
|
185
|
+
for (let i = bytes.length - 1; i >= 0; i -= 1) {
|
|
186
|
+
let ch = bytesView.getUint8(i);
|
|
187
|
+
for (let bit = 0; bit < 7; bit += 1) {
|
|
188
|
+
if ((ch & 64) !== 0) xorKey = (xorKey ^ XOR_MATRIX.getUint16(currentElement * 2)) & 65535;
|
|
189
|
+
ch = ch << 1 & 255;
|
|
190
|
+
currentElement -= 1;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return xorKey;
|
|
194
|
+
}
|
|
195
|
+
/** [MS-OFFCRYPTO] 2.3.7.1's own CreatePasswordVerifier_Method1: a 16-bit checksum of the password's own length and bytes, folded in reverse order through a 15-bit rotating XOR, finished off with a fixed 0xCE4B mask. Shared by both methods -- 2.3.7.4's CreatePasswordVerifier_Method2 uses this as the low word of Method 2's own 32-bit verifier -- confirmed against Apache POI's `createXorVerifier1`. */
|
|
196
|
+
function createXorObfuscationPasswordVerifier(password) {
|
|
197
|
+
const bytes = passwordToAsciiBytes(password);
|
|
198
|
+
const bytesView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
199
|
+
let verifier = 0;
|
|
200
|
+
for (let i = bytes.length - 1; i >= 0; i -= 1) verifier = ((verifier & 16384) === 0 ? 0 : 1) ^ verifier * 2 & 32767 ^ bytesView.getUint8(i);
|
|
201
|
+
verifier = ((verifier & 16384) === 0 ? 0 : 1) ^ verifier * 2 & 32767 ^ bytes.length;
|
|
202
|
+
return (verifier ^ 52811) & 65535;
|
|
203
|
+
}
|
|
204
|
+
function rotateLeft8(byte, distance) {
|
|
205
|
+
const b = byte & 255;
|
|
206
|
+
return (b << distance | b >>> 8 - distance) & 255;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The real, cross-validated 16-byte XOR obfuscation array (see this file's own top comment): the password's own ASCII bytes followed by PAD_ARRAY filler up to 16 bytes, each byte then XORed with the alternating low/high byte of createXorObfuscationKey's own output (even index takes the low byte, odd the high byte) and rotated left by `rotateDistance` -- XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1 (2) for [MS-XLS], METHOD2 (7) for [MS-DOC].
|
|
210
|
+
*/
|
|
211
|
+
function createXorObfuscationArray(password, rotateDistance) {
|
|
212
|
+
const passwordBytes = passwordToAsciiBytes(password);
|
|
213
|
+
const array = /* @__PURE__ */ new Uint8Array(16);
|
|
214
|
+
array.set(passwordBytes, 0);
|
|
215
|
+
for (let i = passwordBytes.length; i < 16; i += 1) array[i] = PAD_ARRAY.getUint8(i - passwordBytes.length);
|
|
216
|
+
const xorKey = createXorObfuscationKey(password);
|
|
217
|
+
const keyLow = xorKey & 255;
|
|
218
|
+
const keyHigh = xorKey >>> 8 & 255;
|
|
219
|
+
const arrayView = new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
220
|
+
for (let i = 0; i < 16; i += 1) {
|
|
221
|
+
const withKey = arrayView.getUint8(i) ^ (i % 2 === 0 ? keyLow : keyHigh);
|
|
222
|
+
array[i] = rotateLeft8(withKey, rotateDistance);
|
|
223
|
+
}
|
|
224
|
+
return array;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Decrypts (and, since the transform is not self-inverse, only decrypts -- see this file's own top comment on why this package implements no corresponding encrypt direction) `data` against Method 1's own per-byte transform: for each byte, rotate left 3 bits, then XOR against `array[index]`, incrementing `index` (wrapping at XOR_OBFUSCATION_ARRAY_LENGTH) after every byte. `initialIndex` is the caller's own responsibility -- xls-codec's own [MS-XLS] 2.2.10 per-record `(streamOffset + recordDataLength) % 16` rule lives there, not here, since it is a BIFF-record concept this package has no knowledge of.
|
|
228
|
+
*/
|
|
229
|
+
function decryptXorObfuscationMethod1(array, data, initialIndex) {
|
|
230
|
+
const out = new Uint8Array(data.length);
|
|
231
|
+
const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
232
|
+
const arrayView = new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
233
|
+
let index = initialIndex % 16;
|
|
234
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
235
|
+
const rotated = rotateLeft8(dataView.getUint8(i), 3);
|
|
236
|
+
out[i] = rotated ^ arrayView.getUint8(index);
|
|
237
|
+
index = (index + 1) % 16;
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Decrypts `data` against Method 2's own per-byte transform ([MS-OFFCRYPTO] 2.3.7.6, matching LibreOffice's `MSCodec_XorWord95::Decode` exactly -- see this file's own top comment): for each byte, XOR against `array[index]` -- unless the original byte was already 0x00, or the XOR result is 0x00, in which case the byte is left unmodified -- incrementing `index` (wrapping at XOR_OBFUSCATION_ARRAY_LENGTH) after every byte regardless of whether it was modified. This transform is its own inverse (plain XOR, no rotation), so the same function also encrypts; nothing in this package uses it that way, since nothing in this family writes encrypted legacy binary documents.
|
|
243
|
+
*/
|
|
244
|
+
function decryptXorObfuscationMethod2(array, data, initialIndex) {
|
|
245
|
+
const out = new Uint8Array(data.length);
|
|
246
|
+
const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
247
|
+
const arrayView = new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
248
|
+
let index = initialIndex % 16;
|
|
249
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
250
|
+
const original = dataView.getUint8(i);
|
|
251
|
+
const transformed = original ^ arrayView.getUint8(index);
|
|
252
|
+
out[i] = original === 0 || transformed === 0 ? original : transformed;
|
|
253
|
+
index = (index + 1) % 16;
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
258
|
+
export { XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_MAX_PASSWORD_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptXorObfuscationMethod1, decryptXorObfuscationMethod2 };
|
package/dist/index.cjs
CHANGED
|
@@ -8,6 +8,7 @@ const require_crypto_sha1 = require("./crypto/sha1.cjs");
|
|
|
8
8
|
const require_crypto_rc4 = require("./crypto/rc4.cjs");
|
|
9
9
|
const require_crypto_office_rc4 = require("./crypto/office-rc4.cjs");
|
|
10
10
|
const require_crypto_office_rc4_cryptoapi = require("./crypto/office-rc4-cryptoapi.cjs");
|
|
11
|
+
const require_crypto_xor_obfuscation = require("./crypto/xor-obfuscation.cjs");
|
|
11
12
|
const require_oleps_layout_metadata = require("./oleps/layout-metadata.cjs");
|
|
12
13
|
const require_oleps_read = require("./oleps/read.cjs");
|
|
13
14
|
const require_oleps_write = require("./oleps/write.cjs");
|
|
@@ -33,7 +34,16 @@ exports.RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS = require_crypto_office_rc4_cryptoap
|
|
|
33
34
|
exports.RC4_CRYPTOAPI_SALT_LENGTH = require_crypto_office_rc4_cryptoapi.RC4_CRYPTOAPI_SALT_LENGTH;
|
|
34
35
|
exports.RC4_CRYPTOAPI_VERIFIER_HASH_LENGTH = require_crypto_office_rc4_cryptoapi.RC4_CRYPTOAPI_VERIFIER_HASH_LENGTH;
|
|
35
36
|
exports.RC4_CRYPTOAPI_VERIFIER_LENGTH = require_crypto_office_rc4_cryptoapi.RC4_CRYPTOAPI_VERIFIER_LENGTH;
|
|
37
|
+
exports.XOR_OBFUSCATION_ARRAY_LENGTH = require_crypto_xor_obfuscation.XOR_OBFUSCATION_ARRAY_LENGTH;
|
|
38
|
+
exports.XOR_OBFUSCATION_MAX_PASSWORD_LENGTH = require_crypto_xor_obfuscation.XOR_OBFUSCATION_MAX_PASSWORD_LENGTH;
|
|
39
|
+
exports.XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1 = require_crypto_xor_obfuscation.XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1;
|
|
40
|
+
exports.XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2 = require_crypto_xor_obfuscation.XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2;
|
|
41
|
+
exports.createXorObfuscationArray = require_crypto_xor_obfuscation.createXorObfuscationArray;
|
|
42
|
+
exports.createXorObfuscationKey = require_crypto_xor_obfuscation.createXorObfuscationKey;
|
|
43
|
+
exports.createXorObfuscationPasswordVerifier = require_crypto_xor_obfuscation.createXorObfuscationPasswordVerifier;
|
|
36
44
|
exports.decryptOfficeRc4 = require_crypto_office_rc4.decryptOfficeRc4;
|
|
45
|
+
exports.decryptXorObfuscationMethod1 = require_crypto_xor_obfuscation.decryptXorObfuscationMethod1;
|
|
46
|
+
exports.decryptXorObfuscationMethod2 = require_crypto_xor_obfuscation.decryptXorObfuscationMethod2;
|
|
37
47
|
exports.deriveOfficeRc4BaseHash = require_crypto_office_rc4.deriveOfficeRc4BaseHash;
|
|
38
48
|
exports.deriveOfficeRc4BlockKey = require_crypto_office_rc4.deriveOfficeRc4BlockKey;
|
|
39
49
|
exports.deriveRc4CryptoApiBlockKey = require_crypto_office_rc4_cryptoapi.deriveRc4CryptoApiBlockKey;
|
package/dist/index.d.cts
CHANGED
|
@@ -7,6 +7,7 @@ import { RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS, RC4_CRYPTOAPI_SALT_LENGTH, RC4_CRY
|
|
|
7
7
|
import { OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, passwordToUtf16LeBytes } from "./crypto/office-rc4.cjs";
|
|
8
8
|
import { rc4 } from "./crypto/rc4.cjs";
|
|
9
9
|
import { sha1 } from "./crypto/sha1.cjs";
|
|
10
|
+
import { XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_MAX_PASSWORD_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptXorObfuscationMethod1, decryptXorObfuscationMethod2 } from "./crypto/xor-obfuscation.cjs";
|
|
10
11
|
import { FMTID_SUMMARY_INFORMATION, SummaryInformationProperties, readSummaryInformation, writeSummaryInformationStream } from "./oleps/summary-information.cjs";
|
|
11
12
|
import { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata } from "./oleps/layout-metadata.cjs";
|
|
12
13
|
import { PropertySet, PropertyValue } from "./oleps/wire.cjs";
|
|
@@ -15,4 +16,4 @@ import { PropertySetWriteError, writePropertySetStream } from "./oleps/write.cjs
|
|
|
15
16
|
import { ZipEntry, unzipPackage, zipPackage } from "./zip/container.cjs";
|
|
16
17
|
import { ArchiveFormat, detectArchiveFormat, isZipArchive } from "./zip/detect.cjs";
|
|
17
18
|
import { ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, WalkArchiveOptions, walkArchive } from "./zip/walk.cjs";
|
|
18
|
-
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_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, OlePackage, OlePackageFormatError, OlePackageWriteError, type PropertySet, PropertySetFormatError, PropertySetWriteError, type PropertyValue, RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS, RC4_CRYPTOAPI_SALT_LENGTH, RC4_CRYPTOAPI_VERIFIER_HASH_LENGTH, RC4_CRYPTOAPI_VERIFIER_LENGTH, ReadCompoundFileOptions, SummaryInformationProperties, WalkArchiveOptions, WriteCompoundFileOptions, ZipEntry, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, deriveRc4CryptoApiBlockKey, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, md5, passwordToUtf16LeBytes, rc4, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, sha1, summaryInformationToLayoutMetadata, unzipPackage, verifyRc4CryptoApiPassword, walkArchive, writeCompoundFile, writeOlePackage, writePropertySetStream, writeSummaryInformationStream, zipPackage };
|
|
19
|
+
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_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, OlePackage, OlePackageFormatError, OlePackageWriteError, type PropertySet, PropertySetFormatError, PropertySetWriteError, type PropertyValue, RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS, RC4_CRYPTOAPI_SALT_LENGTH, RC4_CRYPTOAPI_VERIFIER_HASH_LENGTH, RC4_CRYPTOAPI_VERIFIER_LENGTH, ReadCompoundFileOptions, SummaryInformationProperties, WalkArchiveOptions, WriteCompoundFileOptions, XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_MAX_PASSWORD_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2, ZipEntry, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptOfficeRc4, decryptXorObfuscationMethod1, decryptXorObfuscationMethod2, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, deriveRc4CryptoApiBlockKey, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, md5, passwordToUtf16LeBytes, rc4, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, sha1, summaryInformationToLayoutMetadata, unzipPackage, verifyRc4CryptoApiPassword, walkArchive, writeCompoundFile, writeOlePackage, writePropertySetStream, writeSummaryInformationStream, zipPackage };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS, RC4_CRYPTOAPI_SALT_LENGTH, RC4_CRY
|
|
|
7
7
|
import { OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, passwordToUtf16LeBytes } from "./crypto/office-rc4.js";
|
|
8
8
|
import { rc4 } from "./crypto/rc4.js";
|
|
9
9
|
import { sha1 } from "./crypto/sha1.js";
|
|
10
|
+
import { XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_MAX_PASSWORD_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptXorObfuscationMethod1, decryptXorObfuscationMethod2 } from "./crypto/xor-obfuscation.js";
|
|
10
11
|
import { FMTID_SUMMARY_INFORMATION, SummaryInformationProperties, readSummaryInformation, writeSummaryInformationStream } from "./oleps/summary-information.js";
|
|
11
12
|
import { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata } from "./oleps/layout-metadata.js";
|
|
12
13
|
import { PropertySet, PropertyValue } from "./oleps/wire.js";
|
|
@@ -15,4 +16,4 @@ import { PropertySetWriteError, writePropertySetStream } from "./oleps/write.js"
|
|
|
15
16
|
import { ZipEntry, unzipPackage, zipPackage } from "./zip/container.js";
|
|
16
17
|
import { ArchiveFormat, detectArchiveFormat, isZipArchive } from "./zip/detect.js";
|
|
17
18
|
import { ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, WalkArchiveOptions, walkArchive } from "./zip/walk.js";
|
|
18
|
-
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_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, OlePackage, OlePackageFormatError, OlePackageWriteError, type PropertySet, PropertySetFormatError, PropertySetWriteError, type PropertyValue, RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS, RC4_CRYPTOAPI_SALT_LENGTH, RC4_CRYPTOAPI_VERIFIER_HASH_LENGTH, RC4_CRYPTOAPI_VERIFIER_LENGTH, ReadCompoundFileOptions, SummaryInformationProperties, WalkArchiveOptions, WriteCompoundFileOptions, ZipEntry, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, deriveRc4CryptoApiBlockKey, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, md5, passwordToUtf16LeBytes, rc4, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, sha1, summaryInformationToLayoutMetadata, unzipPackage, verifyRc4CryptoApiPassword, walkArchive, writeCompoundFile, writeOlePackage, writePropertySetStream, writeSummaryInformationStream, zipPackage };
|
|
19
|
+
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_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, OlePackage, OlePackageFormatError, OlePackageWriteError, type PropertySet, PropertySetFormatError, PropertySetWriteError, type PropertyValue, RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS, RC4_CRYPTOAPI_SALT_LENGTH, RC4_CRYPTOAPI_VERIFIER_HASH_LENGTH, RC4_CRYPTOAPI_VERIFIER_LENGTH, ReadCompoundFileOptions, SummaryInformationProperties, WalkArchiveOptions, WriteCompoundFileOptions, XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_MAX_PASSWORD_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2, ZipEntry, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptOfficeRc4, decryptXorObfuscationMethod1, decryptXorObfuscationMethod2, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, deriveRc4CryptoApiBlockKey, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, md5, passwordToUtf16LeBytes, rc4, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, sha1, summaryInformationToLayoutMetadata, unzipPackage, verifyRc4CryptoApiPassword, walkArchive, writeCompoundFile, writeOlePackage, writePropertySetStream, writeSummaryInformationStream, zipPackage };
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { sha1 } from "./crypto/sha1.js";
|
|
|
7
7
|
import { rc4 } from "./crypto/rc4.js";
|
|
8
8
|
import { OFFICE_RC4_BLOCK_SIZE, OFFICE_RC4_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, passwordToUtf16LeBytes } from "./crypto/office-rc4.js";
|
|
9
9
|
import { RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS, RC4_CRYPTOAPI_SALT_LENGTH, RC4_CRYPTOAPI_VERIFIER_HASH_LENGTH, RC4_CRYPTOAPI_VERIFIER_LENGTH, deriveRc4CryptoApiBlockKey, verifyRc4CryptoApiPassword } from "./crypto/office-rc4-cryptoapi.js";
|
|
10
|
+
import { XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_MAX_PASSWORD_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptXorObfuscationMethod1, decryptXorObfuscationMethod2 } from "./crypto/xor-obfuscation.js";
|
|
10
11
|
import { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata } from "./oleps/layout-metadata.js";
|
|
11
12
|
import { PropertySetFormatError, readPropertySetStream } from "./oleps/read.js";
|
|
12
13
|
import { PropertySetWriteError, writePropertySetStream } from "./oleps/write.js";
|
|
@@ -14,4 +15,4 @@ import { FMTID_SUMMARY_INFORMATION, readSummaryInformation, writeSummaryInformat
|
|
|
14
15
|
import { unzipPackage, zipPackage } from "./zip/container.js";
|
|
15
16
|
import { detectArchiveFormat, isZipArchive } from "./zip/detect.js";
|
|
16
17
|
import { ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, walkArchive } from "./zip/walk.js";
|
|
17
|
-
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_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, OlePackageFormatError, OlePackageWriteError, PropertySetFormatError, PropertySetWriteError, RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS, RC4_CRYPTOAPI_SALT_LENGTH, RC4_CRYPTOAPI_VERIFIER_HASH_LENGTH, RC4_CRYPTOAPI_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, deriveRc4CryptoApiBlockKey, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, md5, passwordToUtf16LeBytes, rc4, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, sha1, summaryInformationToLayoutMetadata, unzipPackage, verifyRc4CryptoApiPassword, walkArchive, writeCompoundFile, writeOlePackage, writePropertySetStream, writeSummaryInformationStream, zipPackage };
|
|
18
|
+
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_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, OlePackageFormatError, OlePackageWriteError, PropertySetFormatError, PropertySetWriteError, RC4_CRYPTOAPI_DEFAULT_KEY_SIZE_BITS, RC4_CRYPTOAPI_SALT_LENGTH, RC4_CRYPTOAPI_VERIFIER_HASH_LENGTH, RC4_CRYPTOAPI_VERIFIER_LENGTH, XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_MAX_PASSWORD_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD2, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptOfficeRc4, decryptXorObfuscationMethod1, decryptXorObfuscationMethod2, deriveOfficeRc4BaseHash, deriveOfficeRc4BlockKey, deriveRc4CryptoApiBlockKey, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, md5, passwordToUtf16LeBytes, rc4, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, sha1, summaryInformationToLayoutMetadata, unzipPackage, verifyRc4CryptoApiPassword, 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.10.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,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"packageManager": "pnpm@11.6.0",
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"document-schema.js": "7.5.
|
|
73
|
+
"document-schema.js": "7.5.1",
|
|
74
74
|
"fflate": "0.8.3"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|