@awacloud/pdf 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/CHANGELOG.md +609 -0
  2. package/LICENSE +661 -0
  3. package/NOTICE +77 -0
  4. package/README.md +363 -2
  5. package/dist/build/index.js +21 -0
  6. package/dist/build/pdf-full-rw.js +10972 -0
  7. package/dist/build/pdf-full-rw.meta.json +105 -0
  8. package/dist/build/pdf-full-rw.min.js +53 -0
  9. package/dist/build/pdf-full.js +6078 -0
  10. package/dist/build/pdf-full.meta.json +90 -0
  11. package/dist/build/pdf-full.min.js +32 -0
  12. package/dist/build/pdf-large-rw.js +10367 -0
  13. package/dist/build/pdf-large-rw.meta.json +99 -0
  14. package/dist/build/pdf-large-rw.min.js +53 -0
  15. package/dist/build/pdf-large.js +5473 -0
  16. package/dist/build/pdf-large.meta.json +84 -0
  17. package/dist/build/pdf-large.min.js +32 -0
  18. package/dist/build/pdf-legacy-rw.js +12402 -0
  19. package/dist/build/pdf-legacy-rw.meta.json +110 -0
  20. package/dist/build/pdf-legacy-rw.min.js +53 -0
  21. package/dist/build/pdf-legacy.js +7508 -0
  22. package/dist/build/pdf-legacy.meta.json +95 -0
  23. package/dist/build/pdf-legacy.min.js +32 -0
  24. package/dist/build/pdf-rw.js +7578 -0
  25. package/dist/build/pdf-rw.meta.json +77 -0
  26. package/dist/build/pdf-rw.min.js +53 -0
  27. package/dist/build/pdf.js +2684 -0
  28. package/dist/build/pdf.meta.json +62 -0
  29. package/dist/build/pdf.min.js +32 -0
  30. package/dist/standalone/pdf-full-rw.js +16798 -0
  31. package/dist/standalone/pdf-full-rw.meta.json +78 -0
  32. package/dist/standalone/pdf-full-rw.min.js +56 -0
  33. package/dist/standalone/pdf-full.js +11904 -0
  34. package/dist/standalone/pdf-full.meta.json +63 -0
  35. package/dist/standalone/pdf-full.min.js +35 -0
  36. package/dist/standalone/pdf-large-rw.js +16193 -0
  37. package/dist/standalone/pdf-large-rw.meta.json +72 -0
  38. package/dist/standalone/pdf-large-rw.min.js +56 -0
  39. package/dist/standalone/pdf-large.js +11299 -0
  40. package/dist/standalone/pdf-large.meta.json +57 -0
  41. package/dist/standalone/pdf-large.min.js +35 -0
  42. package/dist/standalone/pdf-legacy-rw.js +18228 -0
  43. package/dist/standalone/pdf-legacy-rw.meta.json +83 -0
  44. package/dist/standalone/pdf-legacy-rw.min.js +56 -0
  45. package/dist/standalone/pdf-legacy.js +13334 -0
  46. package/dist/standalone/pdf-legacy.meta.json +68 -0
  47. package/dist/standalone/pdf-legacy.min.js +35 -0
  48. package/dist/standalone/pdf-rw.js +13404 -0
  49. package/dist/standalone/pdf-rw.meta.json +50 -0
  50. package/dist/standalone/pdf-rw.min.js +56 -0
  51. package/dist/standalone/pdf.js +8510 -0
  52. package/dist/standalone/pdf.meta.json +35 -0
  53. package/dist/standalone/pdf.min.js +35 -0
  54. package/docs/README.md +53 -0
  55. package/docs/api/README.md +38 -0
  56. package/docs/api/_shared/README.md +91 -0
  57. package/docs/api/action/README.md +29 -0
  58. package/docs/api/action/action.md +81 -0
  59. package/docs/api/action/goTo.md +66 -0
  60. package/docs/api/action/launch.md +58 -0
  61. package/docs/api/action/named.md +55 -0
  62. package/docs/api/action/uri.md +54 -0
  63. package/docs/api/annot/README.md +53 -0
  64. package/docs/api/annot/annot.md +114 -0
  65. package/docs/api/annot/fileAttach.md +53 -0
  66. package/docs/api/annot/freeText.md +68 -0
  67. package/docs/api/annot/ink.md +69 -0
  68. package/docs/api/annot/link.md +74 -0
  69. package/docs/api/annot/markup.md +83 -0
  70. package/docs/api/annot/popup.md +52 -0
  71. package/docs/api/annot/projection.md +56 -0
  72. package/docs/api/annot/redact.md +67 -0
  73. package/docs/api/annot/square.md +87 -0
  74. package/docs/api/annot/stamp.md +54 -0
  75. package/docs/api/annot/text.md +69 -0
  76. package/docs/api/annot/widget.md +69 -0
  77. package/docs/api/associatedFiles/README.md +9 -0
  78. package/docs/api/associatedFiles/associatedFiles.md +78 -0
  79. package/docs/api/bundles/README.md +68 -0
  80. package/docs/api/bundles/dist-matrix.md +165 -0
  81. package/docs/api/bundles/pdf-full.md +148 -0
  82. package/docs/api/bundles/pdf-large.md +144 -0
  83. package/docs/api/bundles/pdf-legacy.md +169 -0
  84. package/docs/api/content/README.md +29 -0
  85. package/docs/api/content/color.md +99 -0
  86. package/docs/api/content/graphics.md +114 -0
  87. package/docs/api/content/images.md +124 -0
  88. package/docs/api/content/ops.md +100 -0
  89. package/docs/api/content/stream.md +107 -0
  90. package/docs/api/content/text.md +98 -0
  91. package/docs/api/crypto/README.md +29 -0
  92. package/docs/api/crypto/aesGcm.md +72 -0
  93. package/docs/api/crypto/permissions.md +79 -0
  94. package/docs/api/crypto/security.md +98 -0
  95. package/docs/api/crypto/standardV4.md +104 -0
  96. package/docs/api/crypto/standardV5.md +84 -0
  97. package/docs/api/crypto/standardV6.md +93 -0
  98. package/docs/api/destination/README.md +9 -0
  99. package/docs/api/destination/destination.md +79 -0
  100. package/docs/api/document/README.md +29 -0
  101. package/docs/api/document/builder.md +281 -0
  102. package/docs/api/document/catalog.md +98 -0
  103. package/docs/api/document/document.md +187 -0
  104. package/docs/api/document/encryptedWriter.md +149 -0
  105. package/docs/api/document/incrementalWriter.md +148 -0
  106. package/docs/api/document/page.md +99 -0
  107. package/docs/api/document/pages.md +82 -0
  108. package/docs/api/document/resources.md +102 -0
  109. package/docs/api/document/writer.md +157 -0
  110. package/docs/api/document/xrefStreamWriter.md +122 -0
  111. package/docs/api/embedded/README.md +13 -0
  112. package/docs/api/embedded/collection.md +80 -0
  113. package/docs/api/embedded/embeddedFile.md +86 -0
  114. package/docs/api/embedded/fileSpec.md +87 -0
  115. package/docs/api/errors.md +110 -0
  116. package/docs/api/extra/3d-richmedia.md +76 -0
  117. package/docs/api/extra/README.md +99 -0
  118. package/docs/api/extra/annot-extended.md +71 -0
  119. package/docs/api/extra/associated-files.md +70 -0
  120. package/docs/api/extra/ccitt-fax-decoder.md +74 -0
  121. package/docs/api/extra/color-spaces-extended.md +72 -0
  122. package/docs/api/extra/content-ops-extended.md +82 -0
  123. package/docs/api/extra/document-parts.md +69 -0
  124. package/docs/api/extra/embedded-files-portfolio.md +87 -0
  125. package/docs/api/extra/font-cid-typed.md +77 -0
  126. package/docs/api/extra/font-color-tagging.md +76 -0
  127. package/docs/api/extra/form-actions-extended.md +75 -0
  128. package/docs/api/extra/info-dict-deprecated.md +72 -0
  129. package/docs/api/extra/jbig2-read.md +80 -0
  130. package/docs/api/extra/legacy-deprecated-annots.md +89 -0
  131. package/docs/api/extra/legacy-deprecated-filters.md +78 -0
  132. package/docs/api/extra/legacy-rc4-read.md +74 -0
  133. package/docs/api/extra/legacy-xfa-read.md +65 -0
  134. package/docs/api/extra/linearization-write.md +71 -0
  135. package/docs/api/extra/misc.md +93 -0
  136. package/docs/api/extra/optional-content-extended.md +83 -0
  137. package/docs/api/extra/pdf-a-output-intent.md +65 -0
  138. package/docs/api/extra/pdf-sandbox.md +76 -0
  139. package/docs/api/extra/pdf-ua-tagged.md +63 -0
  140. package/docs/api/extra/pdf-x-prepress.md +65 -0
  141. package/docs/api/extra/redaction-iso32005.md +65 -0
  142. package/docs/api/extra/shading-typed.md +73 -0
  143. package/docs/api/extra/sig-aes-gcm.md +69 -0
  144. package/docs/api/extra/sig-pades.md +103 -0
  145. package/docs/api/extra/tagged-pdf-typed.md +78 -0
  146. package/docs/api/extra/transparency-typed.md +74 -0
  147. package/docs/api/extra/well-tagged-pdf.md +61 -0
  148. package/docs/api/extra/xmp-extended.md +65 -0
  149. package/docs/api/font/README.md +25 -0
  150. package/docs/api/font/embed.md +157 -0
  151. package/docs/api/font/encoding.md +95 -0
  152. package/docs/api/font/font.md +97 -0
  153. package/docs/api/font/type3.md +89 -0
  154. package/docs/api/form/README.md +35 -0
  155. package/docs/api/form/acroform.md +88 -0
  156. package/docs/api/form/appearance.md +87 -0
  157. package/docs/api/form/button.md +97 -0
  158. package/docs/api/form/choice.md +96 -0
  159. package/docs/api/form/fieldTree.md +93 -0
  160. package/docs/api/form/signature.md +90 -0
  161. package/docs/api/form/text.md +88 -0
  162. package/docs/api/linearization/README.md +11 -0
  163. package/docs/api/linearization/linearization.md +81 -0
  164. package/docs/api/main.md +116 -0
  165. package/docs/api/metadata/README.md +10 -0
  166. package/docs/api/metadata/info.md +70 -0
  167. package/docs/api/metadata/xmp.md +62 -0
  168. package/docs/api/ocg/README.md +23 -0
  169. package/docs/api/ocg/config.md +95 -0
  170. package/docs/api/ocg/ocg.md +77 -0
  171. package/docs/api/outline/README.md +11 -0
  172. package/docs/api/outline/outline.md +107 -0
  173. package/docs/api/pdf.md +152 -0
  174. package/docs/api/prepress/README.md +10 -0
  175. package/docs/api/prepress/outputIntent.md +79 -0
  176. package/docs/api/prepress/pageBoundary.md +75 -0
  177. package/docs/api/sig/README.md +32 -0
  178. package/docs/api/sig/byteRange.md +120 -0
  179. package/docs/api/sig/certChain.md +84 -0
  180. package/docs/api/sig/dss.md +111 -0
  181. package/docs/api/sig/oids.md +76 -0
  182. package/docs/api/sig/sha1.md +72 -0
  183. package/docs/api/sig/sign.md +317 -0
  184. package/docs/api/sig/signature.md +178 -0
  185. package/docs/api/sig/timestamp.md +84 -0
  186. package/docs/api/syntax/README.md +29 -0
  187. package/docs/api/syntax/crossRefStream.md +115 -0
  188. package/docs/api/syntax/filters/README.md +50 -0
  189. package/docs/api/syntax/filters/ascii85.md +76 -0
  190. package/docs/api/syntax/filters/asciiHex.md +73 -0
  191. package/docs/api/syntax/filters/dispatch.md +125 -0
  192. package/docs/api/syntax/filters/flate.md +134 -0
  193. package/docs/api/syntax/filters/runLength.md +78 -0
  194. package/docs/api/syntax/objStream.md +88 -0
  195. package/docs/api/syntax/parser-obj.md +97 -0
  196. package/docs/api/syntax/parser.md +151 -0
  197. package/docs/api/syntax/serializer.md +109 -0
  198. package/docs/api/syntax/tokenizer.md +104 -0
  199. package/docs/api/syntax/trailer.md +85 -0
  200. package/docs/api/syntax/xref.md +139 -0
  201. package/docs/api/tagged/README.md +25 -0
  202. package/docs/api/tagged/classMap.md +67 -0
  203. package/docs/api/tagged/markedContent.md +62 -0
  204. package/docs/api/tagged/parentTree.md +67 -0
  205. package/docs/api/tagged/roleMap.md +67 -0
  206. package/docs/api/tagged/structElement.md +76 -0
  207. package/docs/api/tagged/structTree.md +75 -0
  208. package/docs/guide/coverage.md +113 -0
  209. package/docs/guide/crypto.md +121 -0
  210. package/docs/guide/extending.md +76 -0
  211. package/docs/guide/getting-started.md +75 -0
  212. package/docs/guide/legacy-1.7.md +42 -0
  213. package/docs/guide/pades-integration.md +579 -0
  214. package/docs/guide/read-pdf.md +89 -0
  215. package/package.json +97 -4
  216. package/src/_shared/index.js +179 -0
  217. package/src/action/action.js +119 -0
  218. package/src/action/goTo.js +89 -0
  219. package/src/action/launch.js +61 -0
  220. package/src/action/named.js +54 -0
  221. package/src/action/uri.js +51 -0
  222. package/src/annot/annot.js +212 -0
  223. package/src/annot/fileAttach.js +55 -0
  224. package/src/annot/freeText.js +82 -0
  225. package/src/annot/ink.js +77 -0
  226. package/src/annot/link.js +77 -0
  227. package/src/annot/markup.js +91 -0
  228. package/src/annot/popup.js +53 -0
  229. package/src/annot/projection.js +52 -0
  230. package/src/annot/redact.js +87 -0
  231. package/src/annot/square.js +132 -0
  232. package/src/annot/stamp.js +48 -0
  233. package/src/annot/text.js +54 -0
  234. package/src/annot/widget.js +61 -0
  235. package/src/associatedFiles/associatedFiles.js +86 -0
  236. package/src/bundles/pdf-full.js +91 -0
  237. package/src/bundles/pdf-large.js +81 -0
  238. package/src/bundles/pdf-legacy.js +107 -0
  239. package/src/content/color.js +114 -0
  240. package/src/content/graphics.js +192 -0
  241. package/src/content/images.js +160 -0
  242. package/src/content/ops.js +137 -0
  243. package/src/content/stream.js +154 -0
  244. package/src/content/text.js +125 -0
  245. package/src/crypto/aesGcm.js +123 -0
  246. package/src/crypto/permissions.js +112 -0
  247. package/src/crypto/security.js +327 -0
  248. package/src/crypto/standardV4.js +443 -0
  249. package/src/crypto/standardV5.js +306 -0
  250. package/src/crypto/standardV6.js +334 -0
  251. package/src/destination/destination.js +183 -0
  252. package/src/document/builder.js +618 -0
  253. package/src/document/catalog.js +100 -0
  254. package/src/document/document.js +472 -0
  255. package/src/document/encryptedWriter.js +554 -0
  256. package/src/document/incrementalWriter.js +514 -0
  257. package/src/document/page.js +131 -0
  258. package/src/document/pages.js +103 -0
  259. package/src/document/resources.js +146 -0
  260. package/src/document/writer.js +211 -0
  261. package/src/document/xrefStreamWriter.js +353 -0
  262. package/src/embedded/collection.js +102 -0
  263. package/src/embedded/embeddedFile.js +99 -0
  264. package/src/embedded/fileSpec.js +137 -0
  265. package/src/errors.js +78 -0
  266. package/src/extra/3d-richmedia.js +171 -0
  267. package/src/extra/annot-extended.js +200 -0
  268. package/src/extra/associated-files.js +131 -0
  269. package/src/extra/ccitt-fax-decoder.js +776 -0
  270. package/src/extra/color-spaces-extended.js +196 -0
  271. package/src/extra/content-ops-extended.js +153 -0
  272. package/src/extra/document-parts.js +149 -0
  273. package/src/extra/embedded-files-portfolio.js +234 -0
  274. package/src/extra/font-cid-typed.js +185 -0
  275. package/src/extra/font-color-tagging.js +144 -0
  276. package/src/extra/form-actions-extended.js +196 -0
  277. package/src/extra/info-dict-deprecated.js +137 -0
  278. package/src/extra/jbig2-read.js +169 -0
  279. package/src/extra/legacy-deprecated-annots.js +198 -0
  280. package/src/extra/legacy-deprecated-filters.js +167 -0
  281. package/src/extra/legacy-rc4-read.js +235 -0
  282. package/src/extra/legacy-xfa-read.js +104 -0
  283. package/src/extra/linearization-write.js +97 -0
  284. package/src/extra/misc.js +217 -0
  285. package/src/extra/optional-content-extended.js +142 -0
  286. package/src/extra/pdf-a-output-intent.js +112 -0
  287. package/src/extra/pdf-sandbox.js +88 -0
  288. package/src/extra/pdf-ua-tagged.js +116 -0
  289. package/src/extra/pdf-x-prepress.js +114 -0
  290. package/src/extra/redaction-iso32005.js +136 -0
  291. package/src/extra/shading-typed.js +222 -0
  292. package/src/extra/sig-aes-gcm.js +135 -0
  293. package/src/extra/sig-pades.js +242 -0
  294. package/src/extra/tagged-pdf-typed.js +203 -0
  295. package/src/extra/transparency-typed.js +135 -0
  296. package/src/extra/well-tagged-pdf.js +138 -0
  297. package/src/extra/xmp-extended.js +190 -0
  298. package/src/font/embed.js +480 -0
  299. package/src/font/encoding.js +92 -0
  300. package/src/font/font.js +101 -0
  301. package/src/font/type3.js +75 -0
  302. package/src/form/acroform.js +94 -0
  303. package/src/form/appearance.js +90 -0
  304. package/src/form/button.js +105 -0
  305. package/src/form/choice.js +152 -0
  306. package/src/form/fieldTree.js +120 -0
  307. package/src/form/signature.js +100 -0
  308. package/src/form/text.js +101 -0
  309. package/src/linearization/linearization.js +107 -0
  310. package/src/main.js +411 -0
  311. package/src/metadata/info.js +87 -0
  312. package/src/metadata/xmp.js +62 -0
  313. package/src/ocg/config.js +156 -0
  314. package/src/ocg/ocg.js +124 -0
  315. package/src/outline/outline.js +157 -0
  316. package/src/pdf.js +133 -0
  317. package/src/prepress/outputIntent.js +118 -0
  318. package/src/prepress/pageBoundary.js +108 -0
  319. package/src/sig/byteRange.js +306 -0
  320. package/src/sig/certChain.js +247 -0
  321. package/src/sig/dss.js +317 -0
  322. package/src/sig/oids.js +157 -0
  323. package/src/sig/sha1.js +142 -0
  324. package/src/sig/sign.js +1899 -0
  325. package/src/sig/signature.js +1441 -0
  326. package/src/sig/timestamp.js +236 -0
  327. package/src/syntax/crossRefStream.js +133 -0
  328. package/src/syntax/filters/ascii85.js +122 -0
  329. package/src/syntax/filters/asciiHex.js +83 -0
  330. package/src/syntax/filters/dispatch.js +176 -0
  331. package/src/syntax/filters/flate.js +316 -0
  332. package/src/syntax/filters/runLength.js +96 -0
  333. package/src/syntax/objStream.js +99 -0
  334. package/src/syntax/parser-obj.js +52 -0
  335. package/src/syntax/parser.js +321 -0
  336. package/src/syntax/serializer.js +221 -0
  337. package/src/syntax/tokenizer.js +290 -0
  338. package/src/syntax/trailer.js +76 -0
  339. package/src/syntax/xref.js +341 -0
  340. package/src/tagged/classMap.js +81 -0
  341. package/src/tagged/markedContent.js +123 -0
  342. package/src/tagged/parentTree.js +126 -0
  343. package/src/tagged/roleMap.js +107 -0
  344. package/src/tagged/structElement.js +138 -0
  345. package/src/tagged/structTree.js +94 -0
@@ -0,0 +1,306 @@
1
+ // Copyright (c) 2026 AwaCloud SAS
2
+ // Author: Matthieu Bouilloux
3
+ // SPDX-License-Identifier: AGPL-3.0-only
4
+ // Dual-licensed; see the NOTICE file for licensing and any additional terms.
5
+
6
+ /**
7
+ * @fileoverview Standard Security Handler v5 (V=5, R=5).
8
+ *
9
+ * Implements Algorithm 2.A (ISO 32000-1 §7.6.4.3 / Adobe Extension Level 3),
10
+ * the precursor to the PDF 2.0 v6 handler. SHA-256 over
11
+ * password ‖ validation-salt (and ‖ /U on owner-password path);
12
+ * no hardening loop. AES-256-CBC with a zero IV decrypts /OE or /UE.
13
+ *
14
+ * @module pdf/crypto/standardV5
15
+ */
16
+
17
+ /**
18
+ * Module factory — worker-safe, self-contained.
19
+ */
20
+ import { pdfErrors } from '../errors.js';
21
+ import { aes } from '@awacloud/fw/crypto/cipher/aes.js';
22
+ import { cbc } from '@awacloud/fw/crypto/mode/cbc.js';
23
+ import { sha256 } from '@awacloud/fw/crypto/hash/sha256.js';
24
+ import { bitArray } from '@awacloud/fw/crypto/utils/bitArray.js';
25
+ import { pdfAesGcm } from './aesGcm.js';
26
+
27
+ export const pdfStandardV5 = {
28
+ name: 'pdfStandardV5',
29
+ dependencies: ['pdfErrors', 'aes', 'cbc', 'sha256', 'bitArray', 'pdfAesGcm'],
30
+ deps: [pdfErrors, aes, cbc, sha256, bitArray, pdfAesGcm],
31
+ factory(errors, aes, cbc, sha256, bitArray, pdfAesGcm) {
32
+ const { EncryptionError } = errors;
33
+
34
+ if (!aes || !cbc || !sha256 || !bitArray) {
35
+ throw new EncryptionError('pdf/crypto/v5/missing-fw',
36
+ 'pdfStandardV5 requires aes, cbc, sha256, bitArray fw modules');
37
+ }
38
+ // pdfAesGcm is optional at construction (some callers do not need
39
+ // AESV4 support). The dispatcher below will throw a clearer error
40
+ // if a caller actually requests AESV4 without having provided it.
41
+
42
+ const ZERO_IV = new Uint8Array(16);
43
+
44
+ function bytesToWords(b, off, len) {
45
+ const n = len >>> 2;
46
+ const out = new Array(n);
47
+ for (let i = 0; i < n; i++) {
48
+ const j = off + (i << 2);
49
+ out[i] = ((b[j] << 24) | (b[j + 1] << 16) | (b[j + 2] << 8) | b[j + 3]) | 0;
50
+ }
51
+ return out;
52
+ }
53
+ function wordsToBytes(w, into, off) {
54
+ for (let i = 0; i < w.length; i++) {
55
+ const j = off + (i << 2);
56
+ into[j] = (w[i] >>> 24) & 0xff;
57
+ into[j + 1] = (w[i] >>> 16) & 0xff;
58
+ into[j + 2] = (w[i] >>> 8) & 0xff;
59
+ into[j + 3] = w[i] & 0xff;
60
+ }
61
+ }
62
+
63
+ function passwordToBytes(password) {
64
+ if (password === undefined || password === null || password === '') {
65
+ return new Uint8Array(0);
66
+ }
67
+ if (password instanceof Uint8Array) {
68
+ return password.length > 127 ? password.subarray(0, 127) : password;
69
+ }
70
+ if (typeof password === 'string') {
71
+ const enc = new TextEncoder().encode(password);
72
+ return enc.length > 127 ? enc.subarray(0, 127) : enc;
73
+ }
74
+ throw new EncryptionError('pdf/crypto/v5/bad-password',
75
+ 'password must be string or Uint8Array');
76
+ }
77
+
78
+ function sha256Bytes(bytes) {
79
+ const ba = bitArray.ui8_to_ba(bytes);
80
+ const out = sha256.hash(ba);
81
+ return bitArray.ba_to_ui8(out);
82
+ }
83
+
84
+ function concatBytes(parts) {
85
+ let n = 0;
86
+ for (const p of parts) n += p.length;
87
+ const out = new Uint8Array(n);
88
+ let off = 0;
89
+ for (const p of parts) { out.set(p, off); off += p.length; }
90
+ return out;
91
+ }
92
+
93
+ function aesCbcDecrypt(key, iv, ciphertext) {
94
+ if (ciphertext.length % 16 !== 0) {
95
+ throw new EncryptionError('pdf/crypto/v5/bad-ciphertext-len',
96
+ 'AES-CBC ciphertext length must be a multiple of 16',
97
+ { context: { length: ciphertext.length } });
98
+ }
99
+ const cipher = aes.fn(bytesToWords(key, 0, 32), true);
100
+ if (cipher === false) {
101
+ throw new EncryptionError('pdf/crypto/v5/aes-schedule-failed',
102
+ 'AES-256 key schedule failed');
103
+ }
104
+ const ctWords = bytesToWords(ciphertext, 0, ciphertext.length);
105
+ const ivWords = bytesToWords(iv, 0, 16);
106
+ const ptWords = cbc.decrypt(cipher, ctWords, ivWords);
107
+ if (ptWords === false) {
108
+ throw new EncryptionError('pdf/crypto/v5/cbc-decrypt-failed',
109
+ 'AES-256-CBC decryption failed');
110
+ }
111
+ const out = new Uint8Array(ciphertext.length);
112
+ wordsToBytes(ptWords, out, 0);
113
+ return out;
114
+ }
115
+
116
+ function stripPkcs7(buf) {
117
+ if (buf.length === 0) return buf;
118
+ const pad = buf[buf.length - 1];
119
+ if (pad < 1 || pad > 16 || pad > buf.length) return buf;
120
+ for (let i = buf.length - pad; i < buf.length; i++) {
121
+ if (buf[i] !== pad) return buf;
122
+ }
123
+ return buf.subarray(0, buf.length - pad);
124
+ }
125
+
126
+ function tryPassword(typedEncrypt, password, isOwner) {
127
+ const O = typedEncrypt.O, U = typedEncrypt.U;
128
+ const OE = typedEncrypt.OE, UE = typedEncrypt.UE;
129
+ if (!(O instanceof Uint8Array) || O.length < 48 ||
130
+ !(U instanceof Uint8Array) || U.length < 48) {
131
+ throw new EncryptionError('pdf/crypto/v5/bad-O-U',
132
+ '/O and /U must be at least 48 bytes for v5');
133
+ }
134
+ const pw = passwordToBytes(password);
135
+ const target = isOwner ? O : U;
136
+ const valSalt = target.subarray(32, 40);
137
+ const keySalt = target.subarray(40, 48);
138
+ let h;
139
+ if (isOwner) {
140
+ h = sha256Bytes(concatBytes([pw, valSalt, U.subarray(0, 48)]));
141
+ for (let i = 0; i < 32; i++) {
142
+ if (h[i] !== O[i]) return { fileEncryptionKey: null };
143
+ }
144
+ } else {
145
+ h = sha256Bytes(concatBytes([pw, valSalt]));
146
+ for (let i = 0; i < 32; i++) {
147
+ if (h[i] !== U[i]) return { fileEncryptionKey: null };
148
+ }
149
+ }
150
+ const intermediate = isOwner
151
+ ? sha256Bytes(concatBytes([pw, keySalt, U.subarray(0, 48)]))
152
+ : sha256Bytes(concatBytes([pw, keySalt]));
153
+ const oeue = isOwner ? OE : UE;
154
+ if (!(oeue instanceof Uint8Array) || oeue.length !== 32) {
155
+ throw new EncryptionError('pdf/crypto/v5/bad-OE-UE',
156
+ '/OE and /UE must be exactly 32 bytes for v5',
157
+ { context: { length: oeue && oeue.length } });
158
+ }
159
+ const fek = aesCbcDecrypt(intermediate, ZERO_IV, oeue);
160
+ return { fileEncryptionKey: fek };
161
+ }
162
+
163
+ function methodOf(typedEncrypt, fallback) {
164
+ const m = typedEncrypt && typedEncrypt.method;
165
+ if (m === 'AESV3' || m === 'AESV4') return m;
166
+ return fallback || 'AESV3';
167
+ }
168
+ function requireGcm() {
169
+ if (!pdfAesGcm || typeof pdfAesGcm.decryptObjectGcm !== 'function') {
170
+ throw new EncryptionError('pdf/crypto/v5/missing-gcm',
171
+ 'AESV4 requires pdfAesGcm dependency');
172
+ }
173
+ return pdfAesGcm;
174
+ }
175
+ function decryptString(typedEncrypt, fek, _objNum, _gen, ciphertext) {
176
+ const method = methodOf(typedEncrypt, 'AESV3');
177
+ if (method === 'AESV4') {
178
+ return requireGcm().decryptObjectGcm(fek, ciphertext);
179
+ }
180
+ if (!(ciphertext instanceof Uint8Array) || ciphertext.length < 32) {
181
+ throw new EncryptionError('pdf/crypto/v5/bad-string',
182
+ 'AES-256 encrypted string must be at least 32 bytes (IV+1 block)');
183
+ }
184
+ const iv = ciphertext.subarray(0, 16);
185
+ const body = ciphertext.subarray(16);
186
+ const pt = aesCbcDecrypt(fek, iv, body);
187
+ return stripPkcs7(pt);
188
+ }
189
+ function decryptStream(typedEncrypt, fek, objNum, gen, ciphertext) {
190
+ return decryptString(typedEncrypt, fek, objNum, gen, ciphertext);
191
+ }
192
+
193
+ // ---- ENCRYPT helpers (encrypted write side) ----
194
+
195
+ function aesCbcEncrypt(key, iv, plaintext) {
196
+ if (plaintext.length % 16 !== 0) {
197
+ throw new EncryptionError('pdf/crypto/v5/bad-pt-len',
198
+ 'AES-CBC plaintext length must be a multiple of 16',
199
+ { context: { length: plaintext.length } });
200
+ }
201
+ const cipher = aes.fn(bytesToWords(key, 0, key.length), false);
202
+ if (cipher === false || typeof cipher.encrypt !== 'function') {
203
+ throw new EncryptionError('pdf/crypto/v5/aes-encrypt-schedule',
204
+ 'AES key schedule failed for encrypt');
205
+ }
206
+ const ptWords = bytesToWords(plaintext, 0, plaintext.length);
207
+ const ivWords = bytesToWords(iv, 0, 16);
208
+ const ctWords = cbc.encrypt(cipher, ptWords, ivWords);
209
+ if (ctWords === false) {
210
+ throw new EncryptionError('pdf/crypto/v5/cbc-encrypt-failed',
211
+ 'AES-CBC encryption failed');
212
+ }
213
+ const out = new Uint8Array(plaintext.length);
214
+ wordsToBytes(ctWords, out, 0);
215
+ return out;
216
+ }
217
+
218
+ function padPkcs7(buf) {
219
+ const pad = 16 - (buf.length % 16);
220
+ const out = new Uint8Array(buf.length + pad);
221
+ out.set(buf);
222
+ for (let i = buf.length; i < out.length; i++) out[i] = pad;
223
+ return out;
224
+ }
225
+
226
+ function encryptString(typedEncrypt, fek, _objNum, _gen, plaintext, iv) {
227
+ if (!(plaintext instanceof Uint8Array)) {
228
+ throw new EncryptionError('pdf/crypto/v5/encrypt-bad-input',
229
+ 'plaintext must be Uint8Array');
230
+ }
231
+ const method = methodOf(typedEncrypt, 'AESV3');
232
+ if (method === 'AESV4') {
233
+ if (!(iv instanceof Uint8Array) || iv.length !== 12) {
234
+ throw new EncryptionError('pdf/crypto/v5/encrypt-bad-iv',
235
+ 'AESV4 IV must be a 12-byte Uint8Array');
236
+ }
237
+ return requireGcm().encryptObjectGcm(fek, plaintext, () => iv);
238
+ }
239
+ if (!(iv instanceof Uint8Array) || iv.length !== 16) {
240
+ throw new EncryptionError('pdf/crypto/v5/encrypt-bad-iv',
241
+ 'IV must be a 16-byte Uint8Array');
242
+ }
243
+ const padded = padPkcs7(plaintext);
244
+ const body = aesCbcEncrypt(fek, iv, padded);
245
+ const out = new Uint8Array(16 + body.length);
246
+ out.set(iv, 0);
247
+ out.set(body, 16);
248
+ return out;
249
+ }
250
+ function encryptStream(typedEncrypt, fek, objNum, gen, plaintext, iv) {
251
+ return encryptString(typedEncrypt, fek, objNum, gen, plaintext, iv);
252
+ }
253
+
254
+ // Build /O, /U, /OE, /UE per Algorithm 2.A (v5).
255
+ // valSalt + keySalt are 8-byte randoms.
256
+ function buildUUE(password, fek, valSalt, keySalt) {
257
+ const pw = passwordToBytes(password);
258
+ const hVal = sha256Bytes(concatBytes([pw, valSalt]));
259
+ const U = new Uint8Array(48);
260
+ U.set(hVal, 0); U.set(valSalt, 32); U.set(keySalt, 40);
261
+ const intermediate = sha256Bytes(concatBytes([pw, keySalt]));
262
+ const UE = aesCbcEncrypt(intermediate, ZERO_IV, fek);
263
+ return { U, UE };
264
+ }
265
+ function buildOOE(password, fek, U48, valSalt, keySalt) {
266
+ const pw = passwordToBytes(password);
267
+ const hVal = sha256Bytes(concatBytes([pw, valSalt, U48]));
268
+ const O = new Uint8Array(48);
269
+ O.set(hVal, 0); O.set(valSalt, 32); O.set(keySalt, 40);
270
+ const intermediate = sha256Bytes(concatBytes([pw, keySalt, U48]));
271
+ const OE = aesCbcEncrypt(intermediate, ZERO_IV, fek);
272
+ return { O, OE };
273
+ }
274
+
275
+ // Algorithm 10 — /Perms (16-byte AES-256-ECB block encrypted with FEK).
276
+ function buildPerms(p, fek, encryptMetadata, randomBytes) {
277
+ const buf = new Uint8Array(16);
278
+ const v = p | 0;
279
+ buf[0] = v & 0xff;
280
+ buf[1] = (v >>> 8) & 0xff;
281
+ buf[2] = (v >>> 16) & 0xff;
282
+ buf[3] = (v >>> 24) & 0xff;
283
+ buf[4] = 0xff; buf[5] = 0xff; buf[6] = 0xff; buf[7] = 0xff;
284
+ buf[8] = encryptMetadata ? 0x54 : 0x46;
285
+ buf[9] = 0x61; buf[10] = 0x64; buf[11] = 0x62;
286
+ const rand = randomBytes(4);
287
+ buf[12] = rand[0]; buf[13] = rand[1]; buf[14] = rand[2]; buf[15] = rand[3];
288
+ // AES-256-ECB single block, no IV.
289
+ const cipher = aes.fn(bytesToWords(fek, 0, 32), false);
290
+ if (cipher === false || typeof cipher.encrypt !== 'function') {
291
+ throw new EncryptionError('pdf/crypto/v5/perms-schedule',
292
+ 'AES-256-ECB schedule failed for /Perms');
293
+ }
294
+ const blk = cipher.encrypt(bytesToWords(buf, 0, 16));
295
+ const out = new Uint8Array(16);
296
+ wordsToBytes(blk, out, 0);
297
+ return out;
298
+ }
299
+
300
+ return {
301
+ tryPassword, decryptString, decryptStream,
302
+ encryptString, encryptStream,
303
+ buildUUE, buildOOE, buildPerms
304
+ };
305
+ }
306
+ };
@@ -0,0 +1,334 @@
1
+ // Copyright (c) 2026 AwaCloud SAS
2
+ // Author: Matthieu Bouilloux
3
+ // SPDX-License-Identifier: AGPL-3.0-only
4
+ // Dual-licensed; see the NOTICE file for licensing and any additional terms.
5
+
6
+ /**
7
+ * @fileoverview Standard Security Handler v6 (V=5, R=6, PDF 2.0).
8
+ *
9
+ * Implements Algorithm 2.B (password validation with the hardening loop)
10
+ * and Algorithm 8 (File Encryption Key derivation) per ISO 32000-2:2020
11
+ * §7.6.4.3.3 and §7.6.4.4.7.
12
+ *
13
+ * String/stream decryption shape is identical to v5 (IV-prefixed
14
+ * AES-256-CBC, no obj/gen rekeying).
15
+ *
16
+ * @module pdf/crypto/standardV6
17
+ */
18
+
19
+ /**
20
+ * Module factory — worker-safe, self-contained.
21
+ */
22
+ import { pdfErrors } from '../errors.js';
23
+ import { aes } from '@awacloud/fw/crypto/cipher/aes.js';
24
+ import { cbc } from '@awacloud/fw/crypto/mode/cbc.js';
25
+ import { sha256 } from '@awacloud/fw/crypto/hash/sha256.js';
26
+ import { sha384 } from '@awacloud/fw/crypto/hash/sha384.js';
27
+ import { sha512 } from '@awacloud/fw/crypto/hash/sha512.js';
28
+ import { bitArray } from '@awacloud/fw/crypto/utils/bitArray.js';
29
+ import { pdfAesGcm } from './aesGcm.js';
30
+
31
+ export const pdfStandardV6 = {
32
+ name: 'pdfStandardV6',
33
+ dependencies: ['pdfErrors', 'aes', 'cbc', 'sha256', 'sha384', 'sha512', 'bitArray', 'pdfAesGcm'],
34
+ deps: [pdfErrors, aes, cbc, sha256, sha384, sha512, bitArray, pdfAesGcm],
35
+ factory(errors, aes, cbc, sha256, sha384, sha512, bitArray, pdfAesGcm) {
36
+ const { EncryptionError } = errors;
37
+
38
+ if (!aes || !cbc || !sha256 || !sha384 || !sha512 || !bitArray) {
39
+ throw new EncryptionError('pdf/crypto/v6/missing-fw',
40
+ 'pdfStandardV6 requires aes, cbc, sha256, sha384, sha512, bitArray');
41
+ }
42
+ // pdfAesGcm optional — only required when /CFM = AESV4.
43
+
44
+ const ZERO_IV = new Uint8Array(16);
45
+ // Reusable scratch buffer for AES-128 key schedule (Algorithm 2.B).
46
+ const _AES128_KEY_SCRATCH = new Array(4);
47
+
48
+ function bytesToWords(b, off, len, scratch) {
49
+ const n = len >>> 2;
50
+ const out = (scratch && scratch.length >= n) ? scratch : new Array(n);
51
+ for (let i = 0; i < n; i++) {
52
+ const j = off + (i << 2);
53
+ out[i] = ((b[j] << 24) | (b[j + 1] << 16) | (b[j + 2] << 8) | b[j + 3]) | 0;
54
+ }
55
+ if (out.length !== n && out !== scratch) out.length = n;
56
+ if (scratch && out === scratch) out.length = n;
57
+ return out;
58
+ }
59
+ function wordsToBytes(w, into, off, count) {
60
+ const n = count === undefined ? w.length : count;
61
+ for (let i = 0; i < n; i++) {
62
+ const j = off + (i << 2);
63
+ into[j] = (w[i] >>> 24) & 0xff;
64
+ into[j + 1] = (w[i] >>> 16) & 0xff;
65
+ into[j + 2] = (w[i] >>> 8) & 0xff;
66
+ into[j + 3] = w[i] & 0xff;
67
+ }
68
+ }
69
+ function concatBytes(parts) {
70
+ let n = 0;
71
+ for (const p of parts) n += p.length;
72
+ const out = new Uint8Array(n);
73
+ let off = 0;
74
+ for (const p of parts) { out.set(p, off); off += p.length; }
75
+ return out;
76
+ }
77
+ function passwordToBytes(password) {
78
+ if (password === undefined || password === null || password === '') return new Uint8Array(0);
79
+ if (password instanceof Uint8Array) {
80
+ return password.length > 127 ? password.subarray(0, 127) : password;
81
+ }
82
+ if (typeof password === 'string') {
83
+ const enc = new TextEncoder().encode(password);
84
+ return enc.length > 127 ? enc.subarray(0, 127) : enc;
85
+ }
86
+ throw new EncryptionError('pdf/crypto/v6/bad-password',
87
+ 'password must be string or Uint8Array');
88
+ }
89
+ function hashBytes(hashMod, bytes, outLen) {
90
+ const ba = bitArray.ui8_to_ba(bytes);
91
+ const digest = hashMod.hash(ba);
92
+ const u8 = bitArray.ba_to_ui8(digest);
93
+ return u8.length === outLen ? u8 : u8.subarray(0, outLen);
94
+ }
95
+
96
+ function aesCbcEncryptRaw(key, iv, plaintext) {
97
+ if (plaintext.length % 16 !== 0) {
98
+ throw new EncryptionError('pdf/crypto/v6/bad-pt-len',
99
+ 'AES-CBC plaintext length must be a multiple of 16',
100
+ { context: { length: plaintext.length } });
101
+ }
102
+ const cipher = aes.fn(
103
+ bytesToWords(key, 0, key.length, _AES128_KEY_SCRATCH), false);
104
+ if (cipher === false) {
105
+ throw new EncryptionError('pdf/crypto/v6/aes-schedule-failed', 'AES key schedule failed');
106
+ }
107
+ const out = new Uint8Array(plaintext.length);
108
+ let p0 = (iv[0] << 24) | (iv[1] << 16) | (iv[2] << 8) | iv[3];
109
+ let p1 = (iv[4] << 24) | (iv[5] << 16) | (iv[6] << 8) | iv[7];
110
+ let p2 = (iv[8] << 24) | (iv[9] << 16) | (iv[10] << 8) | iv[11];
111
+ let p3 = (iv[12] << 24) | (iv[13] << 16) | (iv[14] << 8) | iv[15];
112
+ for (let off = 0; off < plaintext.length; off += 16) {
113
+ const b0 = (plaintext[off] << 24) | (plaintext[off+1] << 16) | (plaintext[off+2] << 8) | plaintext[off+3];
114
+ const b1 = (plaintext[off+4] << 24) | (plaintext[off+5] << 16) | (plaintext[off+6] << 8) | plaintext[off+7];
115
+ const b2 = (plaintext[off+8] << 24) | (plaintext[off+9] << 16) | (plaintext[off+10] << 8) | plaintext[off+11];
116
+ const b3 = (plaintext[off+12] << 24) | (plaintext[off+13] << 16) | (plaintext[off+14] << 8) | plaintext[off+15];
117
+ const blk = cipher.encrypt([b0 ^ p0, b1 ^ p1, b2 ^ p2, b3 ^ p3]);
118
+ p0 = blk[0]; p1 = blk[1]; p2 = blk[2]; p3 = blk[3];
119
+ wordsToBytes(blk, out, off, 4);
120
+ }
121
+ return out;
122
+ }
123
+
124
+ function aesCbcDecrypt(key, iv, ciphertext) {
125
+ if (ciphertext.length % 16 !== 0) {
126
+ throw new EncryptionError('pdf/crypto/v6/bad-ct-len',
127
+ 'AES-CBC ciphertext length must be a multiple of 16');
128
+ }
129
+ const cipher = aes.fn(bytesToWords(key, 0, key.length), true);
130
+ if (cipher === false || typeof cipher.decrypt !== 'function') {
131
+ throw new EncryptionError('pdf/crypto/v6/aes-decrypt-schedule',
132
+ 'AES-256 decrypt key schedule failed');
133
+ }
134
+ const ctWords = bytesToWords(ciphertext, 0, ciphertext.length);
135
+ const ivWords = bytesToWords(iv, 0, 16);
136
+ const ptWords = cbc.decrypt(cipher, ctWords, ivWords);
137
+ if (ptWords === false) {
138
+ throw new EncryptionError('pdf/crypto/v6/cbc-decrypt-failed',
139
+ 'AES-256-CBC decryption failed');
140
+ }
141
+ const out = new Uint8Array(ciphertext.length);
142
+ wordsToBytes(ptWords, out, 0, ptWords.length);
143
+ return out;
144
+ }
145
+
146
+ function stripPkcs7(buf) {
147
+ if (buf.length === 0) return buf;
148
+ const pad = buf[buf.length - 1];
149
+ if (pad < 1 || pad > 16 || pad > buf.length) return buf;
150
+ for (let i = buf.length - pad; i < buf.length; i++) {
151
+ if (buf[i] !== pad) return buf;
152
+ }
153
+ return buf.subarray(0, buf.length - pad);
154
+ }
155
+
156
+ function hardenKey(password, salt, extra) {
157
+ let K = hashBytes(sha256, concatBytes([password, salt, extra]), 32);
158
+ let round = 0;
159
+ while (true) {
160
+ const block = concatBytes([password, K, extra]);
161
+ const K1 = new Uint8Array(block.length * 64);
162
+ for (let i = 0; i < 64; i++) K1.set(block, i * block.length);
163
+ const aesKey = K.subarray(0, 16);
164
+ const iv = K.subarray(16, 32);
165
+ const E = aesCbcEncryptRaw(aesKey, iv, K1);
166
+ let sum = 0;
167
+ for (let i = 0; i < 16; i++) sum = (sum + E[i]) % 3;
168
+ let nextHash, outLen;
169
+ if (sum === 0) { nextHash = sha256; outLen = 32; }
170
+ else if (sum === 1) { nextHash = sha384; outLen = 48; }
171
+ else { nextHash = sha512; outLen = 64; }
172
+ K = hashBytes(nextHash, E, outLen);
173
+ round++;
174
+ if (round >= 64 && E[E.length - 1] <= round - 32) break;
175
+ if (round > 1024) {
176
+ throw new EncryptionError('pdf/crypto/v6/hardening-runaway',
177
+ 'Algorithm 2.B exceeded 1024 rounds',
178
+ { context: { round } });
179
+ }
180
+ }
181
+ return K.subarray(0, 32);
182
+ }
183
+
184
+ function tryPassword(typedEncrypt, password, isOwner) {
185
+ const O = typedEncrypt.O, U = typedEncrypt.U;
186
+ const OE = typedEncrypt.OE, UE = typedEncrypt.UE;
187
+ if (!(O instanceof Uint8Array) || O.length < 48 ||
188
+ !(U instanceof Uint8Array) || U.length < 48) {
189
+ throw new EncryptionError('pdf/crypto/v6/bad-O-U',
190
+ '/O and /U must be at least 48 bytes for v6');
191
+ }
192
+ const pw = passwordToBytes(password);
193
+ const target = isOwner ? O : U;
194
+ const valSalt = target.subarray(32, 40);
195
+ const keySalt = target.subarray(40, 48);
196
+ const extra = isOwner ? U.subarray(0, 48) : new Uint8Array(0);
197
+ const valHash = hardenKey(pw, valSalt, extra);
198
+ for (let i = 0; i < 32; i++) {
199
+ if (valHash[i] !== target[i]) return { fileEncryptionKey: null };
200
+ }
201
+ const intermediate = hardenKey(pw, keySalt, extra);
202
+ const oeue = isOwner ? OE : UE;
203
+ if (!(oeue instanceof Uint8Array) || oeue.length !== 32) {
204
+ throw new EncryptionError('pdf/crypto/v6/bad-OE-UE',
205
+ '/OE and /UE must be exactly 32 bytes for v6');
206
+ }
207
+ const fek = aesCbcDecrypt(intermediate, ZERO_IV, oeue);
208
+ return { fileEncryptionKey: fek };
209
+ }
210
+
211
+ function methodOf(typedEncrypt, fallback) {
212
+ const m = typedEncrypt && typedEncrypt.method;
213
+ if (m === 'AESV3' || m === 'AESV4') return m;
214
+ return fallback || 'AESV3';
215
+ }
216
+ function requireGcm() {
217
+ if (!pdfAesGcm || typeof pdfAesGcm.decryptObjectGcm !== 'function') {
218
+ throw new EncryptionError('pdf/crypto/v6/missing-gcm',
219
+ 'AESV4 requires pdfAesGcm dependency');
220
+ }
221
+ return pdfAesGcm;
222
+ }
223
+ function decryptString(typedEncrypt, fek, _objNum, _gen, ciphertext) {
224
+ const method = methodOf(typedEncrypt, 'AESV3');
225
+ if (method === 'AESV4') {
226
+ return requireGcm().decryptObjectGcm(fek, ciphertext);
227
+ }
228
+ if (!(ciphertext instanceof Uint8Array) || ciphertext.length < 32) {
229
+ throw new EncryptionError('pdf/crypto/v6/bad-string',
230
+ 'AES-256 encrypted string must be at least 32 bytes');
231
+ }
232
+ const iv = ciphertext.subarray(0, 16);
233
+ const body = ciphertext.subarray(16);
234
+ const pt = aesCbcDecrypt(fek, iv, body);
235
+ return stripPkcs7(pt);
236
+ }
237
+ function decryptStream(typedEncrypt, fek, objNum, gen, ciphertext) {
238
+ return decryptString(typedEncrypt, fek, objNum, gen, ciphertext);
239
+ }
240
+
241
+ // ---- ENCRYPT helpers (encrypted write side) ----
242
+
243
+ function aesCbcEncrypt256(key, iv, plaintext) {
244
+ // Uses the existing aesCbcEncryptRaw which expects multiple-of-16.
245
+ return aesCbcEncryptRaw(key, iv, plaintext);
246
+ }
247
+
248
+ function padPkcs7(buf) {
249
+ const pad = 16 - (buf.length % 16);
250
+ const out = new Uint8Array(buf.length + pad);
251
+ out.set(buf);
252
+ for (let i = buf.length; i < out.length; i++) out[i] = pad;
253
+ return out;
254
+ }
255
+
256
+ function encryptString(typedEncrypt, fek, _objNum, _gen, plaintext, iv) {
257
+ if (!(plaintext instanceof Uint8Array)) {
258
+ throw new EncryptionError('pdf/crypto/v6/encrypt-bad-input',
259
+ 'plaintext must be Uint8Array');
260
+ }
261
+ const method = methodOf(typedEncrypt, 'AESV3');
262
+ if (method === 'AESV4') {
263
+ if (!(iv instanceof Uint8Array) || iv.length !== 12) {
264
+ throw new EncryptionError('pdf/crypto/v6/encrypt-bad-iv',
265
+ 'AESV4 IV must be a 12-byte Uint8Array');
266
+ }
267
+ return requireGcm().encryptObjectGcm(fek, plaintext, () => iv);
268
+ }
269
+ if (!(iv instanceof Uint8Array) || iv.length !== 16) {
270
+ throw new EncryptionError('pdf/crypto/v6/encrypt-bad-iv',
271
+ 'IV must be a 16-byte Uint8Array');
272
+ }
273
+ const padded = padPkcs7(plaintext);
274
+ const body = aesCbcEncrypt256(fek, iv, padded);
275
+ const out = new Uint8Array(16 + body.length);
276
+ out.set(iv, 0);
277
+ out.set(body, 16);
278
+ return out;
279
+ }
280
+ function encryptStream(typedEncrypt, fek, objNum, gen, plaintext, iv) {
281
+ return encryptString(typedEncrypt, fek, objNum, gen, plaintext, iv);
282
+ }
283
+
284
+ // Build /U, /UE, /O, /OE per Algorithm 8 / 2.B with hardening.
285
+ function buildUUE(password, fek, valSalt, keySalt) {
286
+ const pw = passwordToBytes(password);
287
+ const hVal = hardenKey(pw, valSalt, new Uint8Array(0));
288
+ const U = new Uint8Array(48);
289
+ U.set(hVal, 0); U.set(valSalt, 32); U.set(keySalt, 40);
290
+ const intermediate = hardenKey(pw, keySalt, new Uint8Array(0));
291
+ const UE = aesCbcEncrypt256(intermediate, ZERO_IV, fek);
292
+ return { U, UE };
293
+ }
294
+ function buildOOE(password, fek, U48, valSalt, keySalt) {
295
+ const pw = passwordToBytes(password);
296
+ const hVal = hardenKey(pw, valSalt, U48);
297
+ const O = new Uint8Array(48);
298
+ O.set(hVal, 0); O.set(valSalt, 32); O.set(keySalt, 40);
299
+ const intermediate = hardenKey(pw, keySalt, U48);
300
+ const OE = aesCbcEncrypt256(intermediate, ZERO_IV, fek);
301
+ return { O, OE };
302
+ }
303
+
304
+ // Algorithm 10 — /Perms.
305
+ function buildPerms(p, fek, encryptMetadata, randomBytes) {
306
+ const buf = new Uint8Array(16);
307
+ const v = p | 0;
308
+ buf[0] = v & 0xff;
309
+ buf[1] = (v >>> 8) & 0xff;
310
+ buf[2] = (v >>> 16) & 0xff;
311
+ buf[3] = (v >>> 24) & 0xff;
312
+ buf[4] = 0xff; buf[5] = 0xff; buf[6] = 0xff; buf[7] = 0xff;
313
+ buf[8] = encryptMetadata ? 0x54 : 0x46;
314
+ buf[9] = 0x61; buf[10] = 0x64; buf[11] = 0x62;
315
+ const rand = randomBytes(4);
316
+ buf[12] = rand[0]; buf[13] = rand[1]; buf[14] = rand[2]; buf[15] = rand[3];
317
+ const cipher = aes.fn(bytesToWords(fek, 0, 32, _AES128_KEY_SCRATCH), false);
318
+ if (cipher === false || typeof cipher.encrypt !== 'function') {
319
+ throw new EncryptionError('pdf/crypto/v6/perms-schedule',
320
+ 'AES-256-ECB schedule failed for /Perms');
321
+ }
322
+ const blk = cipher.encrypt(bytesToWords(buf, 0, 16));
323
+ const out = new Uint8Array(16);
324
+ wordsToBytes(blk, out, 0, 4);
325
+ return out;
326
+ }
327
+
328
+ return {
329
+ tryPassword, decryptString, decryptStream,
330
+ encryptString, encryptStream,
331
+ buildUUE, buildOOE, buildPerms
332
+ };
333
+ }
334
+ };