documonster 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README_zh.md +1 -1
- package/dist/esm/modules/archive/compression/compress.base.js +20 -6
- package/dist/esm/modules/archive/compression/compress.browser.js +45 -32
- package/dist/esm/modules/archive/compression/compress.js +74 -18
- package/dist/esm/modules/archive/compression/deflate-fallback.js +14 -3
- package/dist/esm/modules/archive/compression/worker-pool/pool.browser.js +9 -3
- package/dist/esm/modules/archive/compression/worker-pool/worker-script.js +35 -7
- package/dist/esm/modules/archive/core/errors.js +26 -4
- package/dist/esm/modules/archive/index.base.js +1 -1
- package/dist/esm/modules/archive/unzip/zip-extract-core.js +74 -34
- package/dist/esm/modules/archive/unzip/zip-parser.js +55 -9
- package/dist/esm/modules/archive/zip-spec/zip-parser-core.js +14 -2
- package/dist/esm/modules/csv/parse/scanner/scanner.js +3 -0
- package/dist/esm/modules/csv/parse/sync.js +3 -2
- package/dist/esm/modules/csv/stream/parser.js +40 -4
- package/dist/esm/modules/csv/worker/worker-script.generated.js +1 -1
- package/dist/esm/modules/excel/core/cell.js +22 -2
- package/dist/esm/modules/excel/core/table.js +39 -2
- package/dist/esm/modules/excel/core/worksheet-core.js +39 -0
- package/dist/esm/modules/excel/core/worksheet-load.js +3 -1
- package/dist/esm/modules/excel/stream/workbook-reader.browser.js +34 -11
- package/dist/esm/modules/excel/surface/cell.js +64 -23
- package/dist/esm/modules/excel/utils/cell-format.js +3 -2
- package/dist/esm/modules/excel/xlsb/read/package.js +3 -1
- package/dist/esm/modules/excel/xlsx/read/package.js +17 -1
- package/dist/esm/modules/pdf/builder/document-builder.js +3 -1
- package/dist/esm/modules/pdf/builder/pdf-editor.js +218 -58
- package/dist/esm/modules/pdf/core/pdf-kdf.browser.js +9 -1
- package/dist/esm/modules/pdf/core/pdf-kdf.js +8 -0
- package/dist/esm/modules/pdf/core/pdf-writer.js +159 -149
- package/dist/esm/modules/pdf/core/pdfa.js +7 -3
- package/dist/esm/modules/pdf/errors.js +25 -0
- package/dist/esm/modules/pdf/index.js +1 -1
- package/dist/esm/modules/pdf/reader/pdf-decrypt.js +136 -74
- package/dist/esm/modules/pdf/reader/pdf-document.js +314 -105
- package/dist/esm/modules/pdf/reader/pdf-parser.js +29 -11
- package/dist/esm/modules/pdf/reader/pdf-reader.js +8 -1
- package/dist/esm/modules/pdf/reader/stream-filters.js +139 -77
- package/dist/esm/modules/pdf/render/pdf-exporter.js +3 -1
- package/dist/esm/modules/pdf/word-bridge.js +22 -48
- package/dist/esm/modules/word/advanced/field-engine.js +75 -56
- package/dist/esm/modules/word/builder/document-handle.js +15 -1
- package/dist/esm/modules/word/convert/docx-to-semantic.js +84 -83
- package/dist/esm/modules/word/convert/html/html-renderer.js +107 -78
- package/dist/esm/modules/word/convert/markdown/markdown-import.js +43 -10
- package/dist/esm/modules/word/convert/markdown/markdown-renderer.js +98 -130
- package/dist/esm/modules/word/convert/odt/odt.js +149 -55
- package/dist/esm/modules/word/core/text-utils.js +9 -0
- package/dist/esm/modules/word/errors.js +1 -1
- package/dist/esm/modules/word/layout/layout-constants.js +20 -23
- package/dist/esm/modules/word/layout/layout-full.js +259 -280
- package/dist/esm/modules/word/layout/layout.js +140 -197
- package/dist/esm/modules/word/query/final-view.js +49 -0
- package/dist/esm/modules/word/query/heading.js +81 -0
- package/dist/esm/modules/word/query/merge.js +39 -22
- package/dist/esm/modules/word/query/numbering-counter.js +108 -0
- package/dist/esm/modules/word/query/page-field.js +212 -0
- package/dist/esm/modules/word/query/search.js +15 -14
- package/dist/esm/modules/word/query/split.js +15 -25
- package/dist/esm/modules/word/query/style-resolve.js +196 -53
- package/dist/esm/modules/word/query/style-resolver.js +24 -0
- package/dist/esm/modules/word/reader/chart-parser.js +17 -7
- package/dist/esm/modules/word/reader/comments-parser.js +4 -4
- package/dist/esm/modules/word/reader/doc-props-parsers.js +10 -9
- package/dist/esm/modules/word/reader/docx-reader.js +46 -32
- package/dist/esm/modules/word/reader/metadata-parsers.js +5 -5
- package/dist/esm/modules/word/reader/numbering-parser.js +4 -3
- package/dist/esm/modules/word/reader/part-xml.js +26 -0
- package/dist/esm/modules/word/reader/reader-context.js +3 -3
- package/dist/esm/modules/word/reader/settings-parser.js +3 -3
- package/dist/esm/modules/word/reader/styles-parser.js +3 -3
- package/dist/esm/modules/word/reader/theme-parser.js +4 -3
- package/dist/esm/modules/word/surface/query.js +1 -1
- package/dist/esm/modules/word/writer/docx-packager.js +4 -1
- package/dist/esm/modules/word/writer/streaming-writer.js +11 -2
- package/dist/esm/modules/xml/dom.js +4 -8
- package/dist/esm/modules/xml/errors.js +1 -3
- package/dist/esm/modules/xml/sax.js +8 -7
- package/dist/esm/modules/xml/to-object.js +4 -8
- package/dist/esm/utils/crypto.browser.js +147 -0
- package/dist/esm/utils/crypto.js +14 -0
- package/dist/esm/utils/limits.js +19 -0
- package/dist/iife/documonster.archive.iife.min.js +39 -11
- package/dist/iife/documonster.csv.iife.min.js +17 -17
- package/dist/iife/documonster.draw.iife.min.js +1 -1
- package/dist/iife/documonster.excel.iife.min.js +88 -60
- package/dist/iife/documonster.formula.iife.min.js +1 -1
- package/dist/iife/documonster.markdown.iife.min.js +1 -1
- package/dist/iife/documonster.mermaid.iife.min.js +1 -1
- package/dist/iife/documonster.pdf.iife.min.js +44 -42
- package/dist/iife/documonster.stream.iife.min.js +1 -1
- package/dist/iife/documonster.word.iife.min.js +95 -67
- package/dist/iife/documonster.xml.iife.min.js +6 -6
- package/dist/types/modules/archive/compression/compress.base.d.ts +12 -3
- package/dist/types/modules/archive/compression/compress.browser.d.ts +7 -4
- package/dist/types/modules/archive/compression/compress.d.ts +9 -4
- package/dist/types/modules/archive/compression/deflate-fallback.d.ts +3 -1
- package/dist/types/modules/archive/compression/worker-pool/pool.browser.d.ts +4 -1
- package/dist/types/modules/archive/compression/worker-pool/types.d.ts +4 -0
- package/dist/types/modules/archive/core/errors.d.ts +31 -2
- package/dist/types/modules/archive/index.base.d.ts +1 -1
- package/dist/types/modules/archive/unzip/zip-extract-core.d.ts +9 -2
- package/dist/types/modules/archive/unzip/zip-parser.d.ts +33 -0
- package/dist/types/modules/archive/zip-spec/zip-parser-core.d.ts +6 -0
- package/dist/types/modules/csv/parse/scanner/types.d.ts +5 -0
- package/dist/types/modules/csv/stream/parser.d.ts +16 -0
- package/dist/types/modules/csv/worker/worker-script.generated.d.ts +1 -1
- package/dist/types/modules/excel/core/table.d.ts +15 -0
- package/dist/types/modules/excel/core/worksheet-core.d.ts +26 -0
- package/dist/types/modules/excel/stream/workbook-reader.browser.d.ts +11 -0
- package/dist/types/modules/excel/surface/cell.d.ts +8 -8
- package/dist/types/modules/excel/xlsx/types.d.ts +16 -0
- package/dist/types/modules/pdf/builder/document-builder.d.ts +7 -0
- package/dist/types/modules/pdf/builder/pdf-editor.d.ts +49 -5
- package/dist/types/modules/pdf/core/pdf-kdf.browser.d.ts +4 -0
- package/dist/types/modules/pdf/core/pdf-kdf.d.ts +4 -0
- package/dist/types/modules/pdf/core/pdf-writer.d.ts +49 -10
- package/dist/types/modules/pdf/core/pdfa.d.ts +2 -0
- package/dist/types/modules/pdf/errors.d.ts +19 -0
- package/dist/types/modules/pdf/index.d.ts +2 -2
- package/dist/types/modules/pdf/reader/pdf-document.d.ts +83 -5
- package/dist/types/modules/pdf/reader/pdf-parser.d.ts +2 -2
- package/dist/types/modules/pdf/reader/pdf-reader.d.ts +7 -0
- package/dist/types/modules/pdf/reader/stream-filters.d.ts +5 -1
- package/dist/types/modules/pdf/types.d.ts +7 -0
- package/dist/types/modules/word/convert/conversion-ir.d.ts +2 -0
- package/dist/types/modules/word/core/text-utils.d.ts +7 -1
- package/dist/types/modules/word/errors.d.ts +3 -3
- package/dist/types/modules/word/index.base.d.ts +1 -1
- package/dist/types/modules/word/layout/layout-constants.d.ts +13 -9
- package/dist/types/modules/word/query/final-view.d.ts +26 -0
- package/dist/types/modules/word/query/heading.d.ts +30 -0
- package/dist/types/modules/word/query/numbering-counter.d.ts +47 -0
- package/dist/types/modules/word/query/page-field.d.ts +79 -0
- package/dist/types/modules/word/query/search.d.ts +8 -2
- package/dist/types/modules/word/query/split.d.ts +4 -1
- package/dist/types/modules/word/query/style-resolve.d.ts +58 -11
- package/dist/types/modules/word/query/style-resolver.d.ts +29 -0
- package/dist/types/modules/word/reader/chart-parser.d.ts +2 -2
- package/dist/types/modules/word/reader/comments-parser.d.ts +1 -1
- package/dist/types/modules/word/reader/doc-props-parsers.d.ts +4 -4
- package/dist/types/modules/word/reader/metadata-parsers.d.ts +2 -2
- package/dist/types/modules/word/reader/numbering-parser.d.ts +1 -1
- package/dist/types/modules/word/reader/part-xml.d.ts +14 -0
- package/dist/types/modules/word/reader/reader-context.d.ts +1 -1
- package/dist/types/modules/word/reader/settings-parser.d.ts +1 -1
- package/dist/types/modules/word/reader/styles-parser.d.ts +1 -1
- package/dist/types/modules/word/reader/theme-parser.d.ts +1 -1
- package/dist/types/modules/word/surface/query.d.ts +2 -1
- package/dist/types/modules/word/types.d.ts +5 -1
- package/dist/types/modules/word/writer/docx-packager.d.ts +7 -0
- package/dist/types/modules/word/writer/streaming-writer.d.ts +7 -0
- package/dist/types/modules/xml/errors.d.ts +9 -0
- package/dist/types/modules/xml/index.d.ts +1 -0
- package/dist/types/modules/xml/sax.d.ts +2 -1
- package/dist/types/utils/crypto.browser.d.ts +10 -0
- package/dist/types/utils/crypto.d.ts +10 -0
- package/dist/types/utils/limits.d.ts +12 -0
- package/package.json +1 -1
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
* @see PDF Reference 1.7, §3.5 - Encryption
|
|
11
11
|
* @see PDF 2.0 (ISO 32000-2), §7.6 - Encryption
|
|
12
12
|
*/
|
|
13
|
-
import { pdfMd5, pdfSha256 } from "#platform/modules/pdf/core/pdf-kdf";
|
|
13
|
+
import { pdfMd5, pdfSha256, pdfSha384, pdfSha512 } from "#platform/modules/pdf/core/pdf-kdf";
|
|
14
14
|
import { PdfStructureError } from "../errors.js";
|
|
15
15
|
import { dictGetNumber, dictGetName, dictGetBytes, dictGetArray, dictGetBool } from "./pdf-parser.js";
|
|
16
16
|
import { concatUint8Arrays } from "../../../utils/binary.js";
|
|
17
|
-
import { rc4, aesCbcDecrypt, aesCbcDecryptRaw } from "#platform/utils/crypto";
|
|
17
|
+
import { rc4, aesCbcDecrypt, aesCbcDecryptRaw, aesCbcEncrypt, aesCbcEncryptRaw, randomBytes } from "#platform/utils/crypto";
|
|
18
18
|
// =============================================================================
|
|
19
19
|
// Constants
|
|
20
20
|
// =============================================================================
|
|
@@ -65,8 +65,10 @@ export function initDecryption(doc, password = "") {
|
|
|
65
65
|
if (v === 5) {
|
|
66
66
|
return initDecryptionV5(doc, encryptDict, password, r, oValue, uValue, permissions, fileId);
|
|
67
67
|
}
|
|
68
|
-
//
|
|
69
|
-
|
|
68
|
+
// V4 names a crypt filter for strings and one for streams; either may be
|
|
69
|
+
// /Identity. Resolve them before the password so a malformed /CF fails loudly.
|
|
70
|
+
const stringMethod = cryptFilterMethod(encryptDict, v, "StrF");
|
|
71
|
+
const streamMethod = cryptFilterMethod(encryptDict, v, "StmF");
|
|
70
72
|
// Try user password first, then owner password
|
|
71
73
|
let encryptionKey = tryUserPassword(password, oValue, permissions, fileId, r, keyLength, uValue, encryptMetadata);
|
|
72
74
|
if (!encryptionKey) {
|
|
@@ -83,14 +85,9 @@ export function initDecryption(doc, password = "") {
|
|
|
83
85
|
if (!encryptionKey) {
|
|
84
86
|
return false; // Password incorrect
|
|
85
87
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
doc.decryptFn = (data, objNum, gen) => decryptAes128(data, objNum, gen, finalKey);
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
doc.decryptFn = (data, objNum, gen) => decryptRc4PerObject(data, objNum, gen, finalKey);
|
|
93
|
-
}
|
|
88
|
+
installSecurityHandler(doc, createCipher(stringMethod, encryptionKey), createCipher(streamMethod, encryptionKey),
|
|
89
|
+
// /EncryptMetadata is only defined from V4 on (ISO 32000-1 Table 20).
|
|
90
|
+
v >= 4 ? encryptMetadata : true);
|
|
94
91
|
return true;
|
|
95
92
|
}
|
|
96
93
|
/**
|
|
@@ -107,10 +104,7 @@ export function isEncrypted(doc) {
|
|
|
107
104
|
* Supports R=5 using SHA-256 based key derivation (Algorithm 2.A).
|
|
108
105
|
*/
|
|
109
106
|
function initDecryptionV5(doc, encryptDict, password, revision, oValue, uValue, _permissions, _fileId) {
|
|
110
|
-
if (revision
|
|
111
|
-
throw new PdfStructureError("R=6 (PDF 2.0 extension) requires SHA-384/SHA-512 which is not yet supported");
|
|
112
|
-
}
|
|
113
|
-
if (revision !== 5) {
|
|
107
|
+
if (revision !== 5 && revision !== 6) {
|
|
114
108
|
throw new PdfStructureError(`Unsupported revision ${revision} for V=5 encryption`);
|
|
115
109
|
}
|
|
116
110
|
const oeValue = dictGetBytes(encryptDict, "OE");
|
|
@@ -125,27 +119,31 @@ function initDecryptionV5(doc, encryptDict, password, revision, oValue, uValue,
|
|
|
125
119
|
}
|
|
126
120
|
const passwordBytes = truncatePassword(password);
|
|
127
121
|
// Try user password (Algorithm 2.A step a - user)
|
|
128
|
-
|
|
122
|
+
// R=5 hashes once with SHA-256; R=6 uses Algorithm 2.B.
|
|
123
|
+
const kdf = revision === 6
|
|
124
|
+
? hashR6
|
|
125
|
+
: (pwd, salt, udata) => pdfSha256(concatUint8Arrays([pwd, salt, udata]));
|
|
126
|
+
let encryptionKey = tryUserPasswordV5(passwordBytes, uValue, ueValue, kdf);
|
|
129
127
|
if (!encryptionKey) {
|
|
130
128
|
// Try owner password (Algorithm 2.A step a - owner)
|
|
131
|
-
encryptionKey = tryOwnerPasswordV5(passwordBytes, oValue, oeValue, uValue);
|
|
129
|
+
encryptionKey = tryOwnerPasswordV5(passwordBytes, oValue, oeValue, uValue, kdf);
|
|
132
130
|
}
|
|
133
131
|
if (!encryptionKey) {
|
|
134
132
|
// Try empty password
|
|
135
133
|
if (password !== "") {
|
|
136
134
|
const emptyBytes = new Uint8Array(0);
|
|
137
|
-
encryptionKey = tryUserPasswordV5(emptyBytes, uValue, ueValue);
|
|
135
|
+
encryptionKey = tryUserPasswordV5(emptyBytes, uValue, ueValue, kdf);
|
|
138
136
|
if (!encryptionKey) {
|
|
139
|
-
encryptionKey = tryOwnerPasswordV5(emptyBytes, oValue, oeValue, uValue);
|
|
137
|
+
encryptionKey = tryOwnerPasswordV5(emptyBytes, oValue, oeValue, uValue, kdf);
|
|
140
138
|
}
|
|
141
139
|
}
|
|
142
140
|
}
|
|
143
141
|
if (!encryptionKey) {
|
|
144
142
|
return false;
|
|
145
143
|
}
|
|
146
|
-
// V=5
|
|
147
|
-
|
|
148
|
-
doc
|
|
144
|
+
// V=5 uses AES-256 with the file encryption key directly (no per-object
|
|
145
|
+
// key derivation), through whichever crypt filters /StrF and /StmF name.
|
|
146
|
+
installSecurityHandler(doc, createCipher(cryptFilterMethod(encryptDict, 5, "StrF"), encryptionKey), createCipher(cryptFilterMethod(encryptDict, 5, "StmF"), encryptionKey), readEncryptMetadata(encryptDict));
|
|
149
147
|
return true;
|
|
150
148
|
}
|
|
151
149
|
/**
|
|
@@ -160,20 +158,18 @@ function truncatePassword(password) {
|
|
|
160
158
|
* Validates using SHA-256(password + validation salt from U).
|
|
161
159
|
* If valid, derives file encryption key using SHA-256(password + key salt from U).
|
|
162
160
|
*/
|
|
163
|
-
function tryUserPasswordV5(passwordBytes, uValue, ueValue) {
|
|
161
|
+
function tryUserPasswordV5(passwordBytes, uValue, ueValue, kdf) {
|
|
164
162
|
// U = hash(32) + validation salt(8) + key salt(8)
|
|
165
163
|
const uHash = uValue.subarray(0, 32);
|
|
166
164
|
const uValidationSalt = uValue.subarray(32, 40);
|
|
167
165
|
const uKeySalt = uValue.subarray(40, 48);
|
|
168
166
|
// Validate: SHA-256(password + validation salt) == first 32 bytes of U
|
|
169
|
-
const
|
|
170
|
-
const computedHash = pdfSha256(validateInput);
|
|
167
|
+
const computedHash = kdf(passwordBytes, uValidationSalt, EMPTY);
|
|
171
168
|
if (!arraysEqual(computedHash, uHash)) {
|
|
172
169
|
return null;
|
|
173
170
|
}
|
|
174
171
|
// Derive key: SHA-256(password + key salt) => use as AES-256 key to decrypt UE
|
|
175
|
-
const
|
|
176
|
-
const keyHash = pdfSha256(keyInput);
|
|
172
|
+
const keyHash = kdf(passwordBytes, uKeySalt, EMPTY);
|
|
177
173
|
// Decrypt UE with this key using AES-256-CBC with zero IV
|
|
178
174
|
const zeroIv = new Uint8Array(16);
|
|
179
175
|
return aesCbcDecryptRaw(ueValue.subarray(0, 32), keyHash, zeroIv);
|
|
@@ -183,25 +179,51 @@ function tryUserPasswordV5(passwordBytes, uValue, ueValue) {
|
|
|
183
179
|
* Validates using SHA-256(password + validation salt from O + U(48)).
|
|
184
180
|
* If valid, derives file encryption key using SHA-256(password + key salt from O + U(48)).
|
|
185
181
|
*/
|
|
186
|
-
function tryOwnerPasswordV5(passwordBytes, oValue, oeValue, uValue) {
|
|
182
|
+
function tryOwnerPasswordV5(passwordBytes, oValue, oeValue, uValue, kdf) {
|
|
187
183
|
// O = hash(32) + validation salt(8) + key salt(8)
|
|
188
184
|
const oHash = oValue.subarray(0, 32);
|
|
189
185
|
const oValidationSalt = oValue.subarray(32, 40);
|
|
190
186
|
const oKeySalt = oValue.subarray(40, 48);
|
|
191
187
|
const u48 = uValue.subarray(0, 48);
|
|
192
188
|
// Validate: SHA-256(password + validation salt + U(0..47)) == first 32 bytes of O
|
|
193
|
-
const
|
|
194
|
-
const computedHash = pdfSha256(validateInput);
|
|
189
|
+
const computedHash = kdf(passwordBytes, oValidationSalt, u48);
|
|
195
190
|
if (!arraysEqual(computedHash, oHash)) {
|
|
196
191
|
return null;
|
|
197
192
|
}
|
|
198
193
|
// Derive key: SHA-256(password + key salt + U(0..47))
|
|
199
|
-
const
|
|
200
|
-
const keyHash = pdfSha256(keyInput);
|
|
194
|
+
const keyHash = kdf(passwordBytes, oKeySalt, u48);
|
|
201
195
|
// Decrypt OE with this key using AES-256-CBC with zero IV
|
|
202
196
|
const zeroIv = new Uint8Array(16);
|
|
203
197
|
return aesCbcDecryptRaw(oeValue.subarray(0, 32), keyHash, zeroIv);
|
|
204
198
|
}
|
|
199
|
+
const EMPTY = new Uint8Array(0);
|
|
200
|
+
/**
|
|
201
|
+
* Algorithm 2.B (ISO 32000-2 §7.6.4.3.4), the R=6 password hash: SHA-256 of
|
|
202
|
+
* the input, then at least 64 rounds that AES-128-CBC encrypt 64 copies of
|
|
203
|
+
* `password ‖ K ‖ userData` under K and rehash with SHA-256/384/512 chosen by
|
|
204
|
+
* the first 16 bytes of the result mod 3.
|
|
205
|
+
*/
|
|
206
|
+
function hashR6(password, salt, userData) {
|
|
207
|
+
let k = pdfSha256(concatUint8Arrays([password, salt, userData]));
|
|
208
|
+
for (let round = 0;; round++) {
|
|
209
|
+
const unit = concatUint8Arrays([password, k, userData]);
|
|
210
|
+
const k1 = new Uint8Array(unit.length * 64);
|
|
211
|
+
for (let i = 0; i < 64; i++) {
|
|
212
|
+
k1.set(unit, i * unit.length);
|
|
213
|
+
}
|
|
214
|
+
const e = aesCbcEncryptRaw(k1, k.subarray(0, 16), k.subarray(16, 32));
|
|
215
|
+
let sum = 0;
|
|
216
|
+
for (let i = 0; i < 16; i++) {
|
|
217
|
+
sum += e[i];
|
|
218
|
+
}
|
|
219
|
+
const pick = sum % 3;
|
|
220
|
+
k = pick === 0 ? pdfSha256(e) : pick === 1 ? pdfSha384(e) : pdfSha512(e);
|
|
221
|
+
if (round >= 63 && e[e.length - 1] <= round - 31) {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return k.subarray(0, 32);
|
|
226
|
+
}
|
|
205
227
|
/**
|
|
206
228
|
* Decrypt data using AES-256 directly (no per-object key derivation).
|
|
207
229
|
* For V=5, the file encryption key is used directly. The first 16 bytes are IV.
|
|
@@ -333,52 +355,47 @@ function deriveUserPasswordFromOwner(ownerPassword, oValue, revision, keyLength)
|
|
|
333
355
|
// AES-128 Decryption
|
|
334
356
|
// =============================================================================
|
|
335
357
|
/**
|
|
336
|
-
*
|
|
337
|
-
*
|
|
358
|
+
* Per-object key (Algorithm 1, ISO 32000-1 §7.6.2): MD5 of the file key, the
|
|
359
|
+
* object number (3 bytes LE), the generation (2 bytes LE) and, for AES, the
|
|
360
|
+
* salt "sAlT"; truncated to min(n + 5, 16) bytes.
|
|
338
361
|
*/
|
|
339
|
-
function
|
|
340
|
-
const
|
|
362
|
+
function objectKey(encryptionKey, objectNumber, generation, aes) {
|
|
363
|
+
const n = encryptionKey.length;
|
|
364
|
+
const keyInput = new Uint8Array(n + 5 + (aes ? 4 : 0));
|
|
341
365
|
keyInput.set(encryptionKey);
|
|
342
|
-
keyInput[
|
|
343
|
-
keyInput[
|
|
344
|
-
keyInput[
|
|
345
|
-
keyInput[
|
|
346
|
-
keyInput[
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
366
|
+
keyInput[n] = objectNumber & 0xff;
|
|
367
|
+
keyInput[n + 1] = (objectNumber >> 8) & 0xff;
|
|
368
|
+
keyInput[n + 2] = (objectNumber >> 16) & 0xff;
|
|
369
|
+
keyInput[n + 3] = generation & 0xff;
|
|
370
|
+
keyInput[n + 4] = (generation >> 8) & 0xff;
|
|
371
|
+
if (aes) {
|
|
372
|
+
keyInput.set([0x73, 0x41, 0x6c, 0x54], n + 5); // "sAlT"
|
|
373
|
+
}
|
|
374
|
+
return pdfMd5(keyInput).subarray(0, Math.min(n + 5, 16));
|
|
375
|
+
}
|
|
376
|
+
/** Decrypt data using RC4 with the per-object key. */
|
|
377
|
+
function decryptRc4PerObject(data, objectNumber, generation, encryptionKey) {
|
|
378
|
+
return rc4(objectKey(encryptionKey, objectNumber, generation, false), data);
|
|
350
379
|
}
|
|
351
380
|
/**
|
|
352
|
-
* Decrypt data using AES-128-CBC.
|
|
381
|
+
* Decrypt data using AES-128-CBC with the per-object key.
|
|
353
382
|
* Per PDF spec, the first 16 bytes of the data are the IV.
|
|
354
383
|
*/
|
|
355
384
|
function decryptAes128(data, objectNumber, generation, encryptionKey) {
|
|
356
385
|
if (data.length < 16) {
|
|
357
386
|
return data;
|
|
358
387
|
}
|
|
359
|
-
// Compute per-object key: MD5(encryptionKey + objNum(3LE) + genNum(2LE) + "sAlT")
|
|
360
|
-
const keyInput = new Uint8Array(encryptionKey.length + 5 + 4);
|
|
361
|
-
keyInput.set(encryptionKey);
|
|
362
|
-
keyInput[encryptionKey.length] = objectNumber & 0xff;
|
|
363
|
-
keyInput[encryptionKey.length + 1] = (objectNumber >> 8) & 0xff;
|
|
364
|
-
keyInput[encryptionKey.length + 2] = (objectNumber >> 16) & 0xff;
|
|
365
|
-
keyInput[encryptionKey.length + 3] = generation & 0xff;
|
|
366
|
-
keyInput[encryptionKey.length + 4] = (generation >> 8) & 0xff;
|
|
367
|
-
// AES salt
|
|
368
|
-
keyInput[encryptionKey.length + 5] = 0x73; // s
|
|
369
|
-
keyInput[encryptionKey.length + 6] = 0x41; // A
|
|
370
|
-
keyInput[encryptionKey.length + 7] = 0x6c; // l
|
|
371
|
-
keyInput[encryptionKey.length + 8] = 0x54; // T
|
|
372
|
-
const objKey = pdfMd5(keyInput);
|
|
373
|
-
const keyLen = Math.min(encryptionKey.length + 5, 16);
|
|
374
|
-
const aesKey = objKey.subarray(0, keyLen);
|
|
375
|
-
// Extract IV (first 16 bytes) and ciphertext
|
|
376
388
|
const iv = data.subarray(0, 16);
|
|
377
389
|
const ciphertext = data.subarray(16);
|
|
378
390
|
if (ciphertext.length === 0 || ciphertext.length % 16 !== 0) {
|
|
379
391
|
return data;
|
|
380
392
|
}
|
|
381
|
-
return aesCbcDecrypt(ciphertext,
|
|
393
|
+
return aesCbcDecrypt(ciphertext, objectKey(encryptionKey, objectNumber, generation, true), iv);
|
|
394
|
+
}
|
|
395
|
+
/** AES-CBC with PKCS#7 padding and a random IV prepended, as PDF stores it. */
|
|
396
|
+
function aesCbcEncryptWithIv(data, key) {
|
|
397
|
+
const iv = randomBytes(16);
|
|
398
|
+
return concatUint8Arrays([iv, aesCbcEncrypt(data, key, iv)]);
|
|
382
399
|
}
|
|
383
400
|
// =============================================================================
|
|
384
401
|
// Helpers
|
|
@@ -428,18 +445,63 @@ function readEncryptMetadata(encryptDict) {
|
|
|
428
445
|
return true;
|
|
429
446
|
}
|
|
430
447
|
/**
|
|
431
|
-
*
|
|
448
|
+
* Resolve the crypt filter `/StrF` or `/StmF` names to its method. Before V4
|
|
449
|
+
* there are no crypt filters and everything is RC4. From V4 on an absent
|
|
450
|
+
* entry means `/Identity` (Table 20), and a named filter is looked up in
|
|
451
|
+
* `/CF` (Table 25), where `/CFM /None` also leaves the data as it is.
|
|
432
452
|
*/
|
|
433
|
-
function
|
|
453
|
+
function cryptFilterMethod(encryptDict, v, entry) {
|
|
454
|
+
if (v < 4) {
|
|
455
|
+
return "rc4";
|
|
456
|
+
}
|
|
457
|
+
const name = dictGetName(encryptDict, entry) ?? "Identity";
|
|
458
|
+
if (name === "Identity") {
|
|
459
|
+
return "identity";
|
|
460
|
+
}
|
|
434
461
|
const cf = encryptDict.get("CF");
|
|
435
|
-
|
|
436
|
-
|
|
462
|
+
const filter = cf instanceof Map ? cf.get(name) : undefined;
|
|
463
|
+
if (!(filter instanceof Map)) {
|
|
464
|
+
throw new PdfStructureError(`/${entry} names crypt filter /${name}, which /CF does not define`);
|
|
465
|
+
}
|
|
466
|
+
const cfm = filter.get("CFM") ?? "None";
|
|
467
|
+
switch (cfm) {
|
|
468
|
+
case "None":
|
|
469
|
+
return "identity";
|
|
470
|
+
case "V2":
|
|
471
|
+
return "rc4";
|
|
472
|
+
case "AESV2":
|
|
473
|
+
return "aesv2";
|
|
474
|
+
case "AESV3":
|
|
475
|
+
return "aesv3";
|
|
476
|
+
default:
|
|
477
|
+
throw new PdfStructureError(`Unsupported crypt filter method: ${String(cfm)}`);
|
|
437
478
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
479
|
+
}
|
|
480
|
+
function createCipher(method, key) {
|
|
481
|
+
switch (method) {
|
|
482
|
+
case "identity":
|
|
483
|
+
return undefined;
|
|
484
|
+
case "rc4": {
|
|
485
|
+
// RC4 is its own inverse.
|
|
486
|
+
const apply = (data, objNum, gen) => decryptRc4PerObject(data, objNum, gen, key);
|
|
487
|
+
return { decrypt: apply, encrypt: apply };
|
|
488
|
+
}
|
|
489
|
+
case "aesv2":
|
|
490
|
+
return {
|
|
491
|
+
decrypt: (data, objNum, gen) => decryptAes128(data, objNum, gen, key),
|
|
492
|
+
encrypt: (data, objNum, gen) => aesCbcEncryptWithIv(data, objectKey(key, objNum, gen, true))
|
|
493
|
+
};
|
|
494
|
+
case "aesv3":
|
|
495
|
+
return {
|
|
496
|
+
decrypt: data => decryptAes256Direct(data, key),
|
|
497
|
+
encrypt: data => aesCbcEncryptWithIv(data, key)
|
|
498
|
+
};
|
|
442
499
|
}
|
|
443
|
-
|
|
444
|
-
|
|
500
|
+
}
|
|
501
|
+
/** Install the document's security handler: Identity filters pass data through. */
|
|
502
|
+
function installSecurityHandler(doc, strings, streams, encryptMetadata) {
|
|
503
|
+
const pick = (kind) => (kind === "string" ? strings : streams);
|
|
504
|
+
doc.decryptFn = (data, objNum, gen, kind) => pick(kind)?.decrypt(data, objNum, gen) ?? data;
|
|
505
|
+
doc.encryptFn = (data, objNum, gen, kind) => pick(kind)?.encrypt(data, objNum, gen) ?? data;
|
|
506
|
+
doc.encryptMetadata = encryptMetadata;
|
|
445
507
|
}
|