@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,579 @@
1
+ # PAdES integration — sign and verify in the browser
2
+
3
+ > From PDF bytes to a signed document to a machine-readable verification
4
+ > report — the wiring, the trust-model boundaries, and what you may claim.
5
+
6
+ ## What this guide covers
7
+
8
+ Bytes in, signed PDF out, then a verification report back: this guide
9
+ walks the whole PAdES (ETSI EN 319 142-1) path through `@awacloud/pdf` —
10
+ wiring the runtime, bringing your own key material, signing at levels
11
+ B/T/LT/LTA, timestamping through the `tsaSign` seam, verifying, and
12
+ reading the trust-model boundaries that gate every claim below.
13
+
14
+ **Prerequisites** — the package `@awacloud/pdf` (root entry
15
+ `@awacloud/pdf`) and its `@awacloud/fw` / `@awacloud/fonts` dependencies,
16
+ registered on one `@awacloud/fw` `ModuleRuntime`; any modern JavaScript
17
+ runtime (browser main thread or Worker, Bun, Node.js 18+). The cryptography
18
+ is synchronous and does not use Web Crypto.
19
+
20
+ The demo `apps/demo/pades` (the source monorepo's PAdES demo application,
21
+ not published) is this guide executed: every snippet below is the wiring, sign and
22
+ verify path the demo actually runs, minus its UI. Where the demo departs
23
+ from a snippet (the mock TSA, the demo certificate emitter), this guide
24
+ says so explicitly.
25
+
26
+ ## Wiring
27
+
28
+ `@awacloud/pdf` publishes a declarative 5-array manifest from its `.`
29
+ export — `fw_require`, `pkg_require`, `modules`, `extras`, `bundle` — and
30
+ no runtime bootstrap of its own. An integrator wires it into one
31
+ `@awacloud/fw` `ModuleRuntime`, registering the five arrays in that
32
+ order:
33
+
34
+ ```js
35
+ import { ModuleRuntime } from '@awacloud/fw/core/runtime.js';
36
+ import {
37
+ fw_require, pkg_require, modules, extras, bundle
38
+ } from '@awacloud/pdf';
39
+
40
+ const runtime = new ModuleRuntime();
41
+ for (const m of fw_require) runtime.register(m);
42
+ for (const m of pkg_require) runtime.register(m);
43
+ for (const m of modules) runtime.register(m);
44
+ for (const m of extras) runtime.register(m);
45
+ for (const m of bundle) runtime.register(m);
46
+
47
+ const pdfSign = runtime.resolve('pdfSign');
48
+ const pdfSignature = runtime.resolve('pdfSignature');
49
+ const pdfSigPades = runtime.resolve('pdfSigPades');
50
+ ```
51
+
52
+ `pdfSign` and `pdfSignature` are registered in `src/main.js` `modules`,
53
+ but none of the three source bundles (`pdf-large` / `pdf-full` /
54
+ `pdf-legacy`) wires them: those carry only the read-only `pdfSigPades`
55
+ extra. Among the committed prebuilt files, only the four Read+Write roots
56
+ (`dist/standalone/pdf-rw.js` and the other `-rw` roots) carry both the
57
+ signer and the verifier (see the package README, "Committed dist"). This
58
+ guide — like the demo — uses the manifest + `ModuleRuntime` path above,
59
+ which reaches signing without a prebuilt file.
60
+
61
+ Loading `@awacloud/pdf` on a zero-bundler page uses a bare import map. The
62
+ map below is the one the demo ships:
63
+
64
+ ```html
65
+ <script type="importmap">
66
+ { "imports": {
67
+ "@awacloud/fw": "/node_modules/@awacloud/fw/src/main.js",
68
+ "@awacloud/fw/": "/node_modules/@awacloud/fw/src/",
69
+ "@awacloud/pdf": "/node_modules/@awacloud/pdf/src/main.js",
70
+ "@awacloud/pdf/": "/node_modules/@awacloud/pdf/src/",
71
+ "@awacloud/fonts": "/node_modules/@awacloud/fonts/src/main.js",
72
+ "@awacloud/fonts/": "/node_modules/@awacloud/fonts/src/"
73
+ }}
74
+ </script>
75
+ ```
76
+
77
+ The trailing-slash keys are required because `@awacloud/pdf`'s `main.js`
78
+ imports `@awacloud/fw/crypto/...` subpaths and `pkg_require` reaches into
79
+ `@awacloud/fonts`.
80
+
81
+ **`fw_require` carries the full crypto closure today.** An earlier
82
+ measurement of this path found `@awacloud/pdf`'s `fw_require` missing four
83
+ of `rsa`/`ecc`'s own transitive dependencies (`bn`, `random`, `hex`,
84
+ `hmac`), which made `runtime.resolve('pdfSign')` throw `Module not
85
+ found: bn` unless a consumer patched the closure itself. That gap is
86
+ closed: `fw_require` (`src/main.js`) now registers `random`, `bn`,
87
+ `hmac` and `hex` alongside `rsa`, `ecc` and `ed25519`, so the loop above
88
+ is complete as written — **do not** add a four-module patch before
89
+ registering `modules`; there is nothing left to patch. The source
90
+ monorepo's PAdES demo pins this integrator path in a test that registers
91
+ the five arrays exactly as above and asserts `pdfSign`/`pdfSignature`
92
+ resolve with no extra registration step.
93
+
94
+ ## Key material
95
+
96
+ `@awacloud/pdf` does not generate or manage keys for you:
97
+
98
+ > Bring your own key material. `@awacloud/pdf` accepts a DER or PEM
99
+ > certificate and an algorithm-specific private key; unwrapping a
100
+ > PKCS#12 / `.pfx` container is the integrator's responsibility.
101
+
102
+ — Do not claim otherwise. No PKCS#12 parser exists. Say: "bring your
103
+ own key material; PKCS#12 unwrapping is integrator-side." PKCS#8 / SEC1
104
+ / SPKI / PEM *key* encoding IS available via
105
+ `@awacloud/fw/crypto/utils/{keyformat,pem}.js`.
106
+
107
+ `opts.privateKey`'s shape depends on the algorithm — the dispatch is
108
+ `_signDigest` in `src/sig/sign.js`:
109
+
110
+ | Algorithm | `opts.privateKey` shape |
111
+ |---|---|
112
+ | RSA-PSS | `{ n, e, d }` (`Uint8Array` each) |
113
+ | ECDSA | `{ curve: ecc.curves.c256\|c384\|c521, secretKey: new ecc.ecdsa.secretKey(curve, k) }` |
114
+ | Ed25519 | 64-byte `Uint8Array` (seed ‖ public key) |
115
+
116
+ If your key material arrives as PKCS#8, SEC1 or SPKI DER, or as PEM,
117
+ `@awacloud/fw` publishes the conversion — both subpaths resolve through
118
+ `@awacloud/fw`'s `exports` map's `./crypto/*.js` wildcard entry, so they
119
+ are reachable as ordinary subpath imports, not only from inside the
120
+ package:
121
+
122
+ ```js
123
+ import { keyformat } from '@awacloud/fw/crypto/utils/keyformat.js';
124
+ import { pem } from '@awacloud/fw/crypto/utils/pem.js';
125
+ import { b64 } from '@awacloud/fw/io/codec/b64.js';
126
+
127
+ // keyformat needs its own `asn1` dependency when instantiated directly
128
+ // (it is not part of @awacloud/pdf's own fw_require closure — it is an
129
+ // integrator-side helper for key material, not something pdfSign needs).
130
+ const kf = keyformat.factory(asn1Instance);
131
+ const { d, curveOid, publicKey } = kf.decodePkcs8Ec(pkcs8Der);
132
+
133
+ // pem needs its own `b64` dependency when instantiated directly, same
134
+ // reason as keyformat above — and it is not optional here: `pem.js`'s
135
+ // `factory(b64)` parameter shadows the module's own top-level `b64`
136
+ // import, so calling `pem.factory()` with no argument leaves the
137
+ // in-scope `b64` undefined and `decode` throws on `b64.toBytes(...)`.
138
+ const b64Instance = b64.factory();
139
+ const { bytes } = pem.factory(b64Instance).decode(pemText, 'PRIVATE KEY');
140
+ ```
141
+
142
+ `keyformat.js` covers PKCS#8 / SEC1 encode+decode for EC keys and
143
+ PKCS#8 / SPKI encode+decode for Edwards keys; `pem.js` covers generic
144
+ PEM block encode/decode. Neither handles a password-protected PKCS#12
145
+ container.
146
+
147
+ For a certificate, this guide's own snippets — and the demo — use a
148
+ throwaway self-signed certificate: **the demo generates a throwaway
149
+ demo certificate in your browser**, never "the library issues
150
+ certificates" — `@awacloud/fw` and `@awacloud/pdf` publish no X.509
151
+ emitter. Bring your own CA-issued certificate for anything beyond a
152
+ demo.
153
+
154
+ ## Signing
155
+
156
+ A minimal PAdES-B signature, run through this guide's own verification
157
+ script under Bun:
158
+
159
+ ```js
160
+ const signed = pdfSign.sign(pdfBytes, {
161
+ cert, privateKey, algorithm: 'ed25519',
162
+ hashAlg: 'sha512', level: 'B',
163
+ useSignedAttrs: true,
164
+ subFilter: 'ETSI.CAdES.detached'
165
+ });
166
+ ```
167
+
168
+ **`subFilter: 'ETSI.CAdES.detached'` is not optional.** Quoting the
169
+ measured trap verbatim:
170
+
171
+ > `sign()` emits a non-PAdES SubFilter by default. With no
172
+ > `opts.subFilter`, the emitted `/SubFilter` is `adbe.pkcs7.detached`,
173
+ > which `pdfSigPades.detectPadesProfile` classifies `{ isPades: false,
174
+ > level: null }` — for **all four** levels. Passing `subFilter:
175
+ > 'ETSI.CAdES.detached'` yields `isPades: true` and levels `B-B` /
176
+ > `B-T` / `B-LT` / `B-LTA`, and the signature still verifies. Measured
177
+ > both ways.
178
+
179
+ Ed25519 always signs with SHA-512 (RFC 8419 §3.1): `hashAlg` defaults to
180
+ `'sha512'` for Ed25519, so passing it is optional, and any other value
181
+ throws `pdf/sign/ed25519-requires-sha512`. RSA-PSS and ECDSA take
182
+ `'sha256'` (the default), `'sha384'` or `'sha512'`.
183
+
184
+ ### Interoperability: Ed25519 in PDF viewers
185
+
186
+ An Ed25519 signature is an ISO/TS 32002 enhancement of PDF 2.0, and the
187
+ standard asks the document to say so (ISO/TS 32002 §4). `sign()` does it
188
+ in the signing update: the Catalog's `/Extensions` declares the `ISO_`
189
+ developer extension at `/ExtensionLevel 32002`, and a document below PDF
190
+ 2.0 gets `/Version /2.0` (see [`pdfSign`](../api/sig/sign.md)). ECDSA and
191
+ RSA-PSS signatures leave the Catalog's `/Extensions` and `/Version` alone.
192
+
193
+ **Adobe Acrobat Reader does not validate Ed25519 (EdDSA) signatures.**
194
+ Measured on 2026-10-05: Acrobat Reader reports an error about the
195
+ formatting of the signature for our Ed25519 output, with or without the
196
+ ISO/TS 32002 declaration, and the same error for an Ed25519 signature
197
+ produced by OpenSSL. In the same run it reports the ECDSA P-256 signature
198
+ as unmodified, with trust and time remarks only (the certificate was
199
+ self-signed). OpenSSL 3.5 and later verify the Ed25519 output with
200
+ `openssl cms -verify`, and so does `pdfSignature.verifyAllSignatures`.
201
+
202
+ Choose the algorithm by where the document will be checked: **ECDSA
203
+ P-256 when Acrobat must validate the signature**; Ed25519 when the
204
+ verifiers are known to support EdDSA.
205
+
206
+ `useSignedAttrs: true` adds the ESS `signing-certificate-v2` attribute
207
+ (RFC 5035); it is required for T/LT/LTA (the timestamp token lives in
208
+ `unsignedAttrs`, only emitted alongside `signedAttrs`) and optional for
209
+ B, where this guide always passes it for a PAdES baseline signature.
210
+
211
+ Levels, in the exact wording every public sentence about them must
212
+ quote:
213
+
214
+ | Level | Claim |
215
+ |---|---|
216
+ | B | "Produces PAdES-B (B-B) signatures in the browser." |
217
+ | T | "Produces PAdES-T signatures; the RFC 3161 timestamp token is supplied by the integrator through a `tsaSign` callback — the library performs no network call." |
218
+ | LT | "Emits the LT structure (DSS with the signer certificates). Revocation material (CRL/OCSP) is supplied by the integrator; the library neither fetches nor validates it." |
219
+ | LTA | "Emits the LTA structure (DSS + document timestamp) and re-verifies the document timestamp on read. The archival *policy* (renewal before algorithm expiry) is the integrator's." |
220
+
221
+ LT and LTA additionally take `opts.dss = { certs: [...] }` — the
222
+ certificates to embed in the Document Security Store:
223
+
224
+ ```js
225
+ const signedLt = pdfSign.sign(pdfBytes, {
226
+ cert, privateKey, algorithm: 'ed25519',
227
+ hashAlg: 'sha512', level: 'LT',
228
+ useSignedAttrs: true,
229
+ subFilter: 'ETSI.CAdES.detached',
230
+ tsaSign, // see "The tsaSign seam" below
231
+ dss: { certs: [cert] }
232
+ });
233
+ ```
234
+
235
+ **The signature sits in an invisible signature field.** In the same
236
+ incremental update as the `<< /Type /Sig /Filter /Adobe.PPKLite
237
+ /SubFilter /... /ByteRange [...] /Contents <...> >>` dictionary, `sign()`
238
+ writes a signature field merged with its widget annotation (`/FT /Sig`,
239
+ `/T (Signature1)` — the next free `Signature<n>` when the document
240
+ already has signature fields — `/V` pointing at that dictionary,
241
+ `/Rect [0 0 0 0]`, `/F 132`, `/P` page 1). It also adds the widget to
242
+ page 1's `/Annots` and creates or extends the Catalog's `/AcroForm` with
243
+ `/Fields` and `/SigFlags 3`, as ISO 32000-2 §12.7.5.5 requires. Existing
244
+ form fields and annotations are kept, and the LTA `/DocTimeStamp` gets a
245
+ field of its own. The field has a zero-size rectangle and no `/AP`
246
+ appearance stream. On an encrypted base the field name is encrypted too
247
+ (see [Encrypted documents](#encrypted-documents) below).
248
+
249
+ **The `/ByteRange` leaves out the whole `/Contents <…>` token.** The
250
+ gap between the two signed ranges starts at the `<` and ends after the
251
+ `>`, as ISO 32000-2 §12.8.3.3.1 requires ("it shall fit precisely in the
252
+ space between the ranges") and as PDFBox and pyHanko emit — for the
253
+ `/Sig` and for the LTA `/DocTimeStamp` alike. Signatures `sign()` wrote
254
+ by earlier versions left out the hex digits only; they still verify (see
255
+ [Verifying](#verifying)).
256
+ The signature is invisible; a visible appearance is the integrator's
257
+ document work.
258
+
259
+ ### Encrypted documents
260
+
261
+ An encrypted PDF (standard security handler, AES) is signed like any
262
+ other, with its password:
263
+
264
+ ```js
265
+ const signedEnc = pdfSign.sign(encryptedBytes, {
266
+ cert, privateKey, algorithm: 'ed25519',
267
+ useSignedAttrs: true,
268
+ subFilter: 'ETSI.CAdES.detached',
269
+ password: 'user-pwd' // owner or user password; '' for an empty user password
270
+ });
271
+ ```
272
+
273
+ `sign()` derives the document key from `password`, encrypts the new
274
+ field's `/T` with it and repeats the document's `/Encrypt` in the update's
275
+ trailer. With the user password, the document's permissions must allow
276
+ modifying it and adding annotations or form fields; the owner password
277
+ always signs. AES-128 (V=4, `AESV2`) and AES-256 (V=5, R=5 or R=6,
278
+ `AESV3`) are supported. RC4 and AES-GCM documents, handlers other than
279
+ the standard one, a missing or wrong password and missing permissions are
280
+ refused with a `pdf/sign/encrypted-*` error, and nothing is written. The
281
+ IV comes from `crypto.getRandomValues`, or from `opts.randomBytes` when
282
+ you pass one. Levels LT and LTA work on an encrypted document too: the DSS
283
+ streams and strings and the document timestamp's field name are encrypted
284
+ with the same key, and the hexadecimal `/Contents` of the signature and of
285
+ the document timestamp stays clear, as ISO 32000-2 §7.6.2 requires. See
286
+ the [`pdfSign` API page](../api/sig/sign.md#encrypted-base).
287
+
288
+ ## Timestamping — the `tsaSign` seam
289
+
290
+ `@awacloud/pdf` never calls a Timestamp Authority itself. The contract:
291
+
292
+ - `pdfSign.sign` never performs a network call and has no TSA client.
293
+ For `level: 'T'|'LT'|'LTA'` it requires a caller-supplied callback, and
294
+ throws `pdf/sign/tsa-required-for-level-T` without one:
295
+
296
+ ```js
297
+ opts.tsaSign({ digest, hashAlg }) -> Uint8Array // RFC 3161 TimeStampToken DER
298
+ ```
299
+
300
+ - `digest` is the hash of the **signature value** (RFC 3161 §2.4.1
301
+ messageImprint over the signature OCTET STRING); the returned token is
302
+ wrapped verbatim into the CMS `unsignedAttrs` id-aa-timeStampToken
303
+ attribute (`_buildUnsignedAttrsTsa`). LTA calls the same callback a
304
+ second time to build the standalone DocTimeStamp.
305
+
306
+ This is the decisive structural fact: **the library's TSA seam is a
307
+ callback, so the network exception is entirely the integrator's**. It is
308
+ never inside `@awacloud/pdf`.
309
+
310
+ This is exactly what row 2 of the claim matrix (§ "What you may claim"
311
+ below) commits to: "Produces PAdES-T signatures; the RFC 3161 timestamp
312
+ token is supplied by the integrator through a `tsaSign` callback — the
313
+ library performs no network call."
314
+
315
+ ### Example — an external-TSA `tsaSign`
316
+
317
+ A worked external-TSA `tsaSign` implementation, built with fw's `asn1`
318
+ primitives. The package test suite runs this exact snippet against a
319
+ stubbed TSA reply (no network); a real deployment needs a reachable TSA.
320
+
321
+ ```js
322
+ import { asn1 as asn1Descriptor } from '@awacloud/fw/crypto/utils/asn1.js';
323
+ const asn1 = asn1Descriptor.factory(); // the descriptor carries no encoders
324
+
325
+ // fw's asn1 has no BOOLEAN encoder (see the guide's Key material
326
+ // section for the same gap on primitive string/time types) — hand-roll
327
+ // the one TLV TimeStampReq needs.
328
+ function encodeBoolean(v) {
329
+ return Uint8Array.of(0x01, 0x01, v ? 0xff : 0x00);
330
+ }
331
+
332
+ // The messageImprint AlgorithmIdentifier follows the hashAlg sign() passes.
333
+ const HASH_OIDS = {
334
+ sha256: '2.16.840.1.101.3.4.2.1',
335
+ sha384: '2.16.840.1.101.3.4.2.2',
336
+ sha512: '2.16.840.1.101.3.4.2.3'
337
+ };
338
+
339
+ function buildTimeStampReq(digest, hashAlg) {
340
+ const messageImprint = asn1.encodeSequence([
341
+ asn1.encodeSequence([ asn1.encodeOid(HASH_OIDS[hashAlg]), asn1.encodeNull() ]),
342
+ asn1.encodeOctetString(digest)
343
+ ]);
344
+ return asn1.encodeSequence([
345
+ asn1.encodeInteger(1), // version
346
+ messageImprint,
347
+ encodeBoolean(true) // certReq
348
+ ]);
349
+ }
350
+
351
+ async function tsaSign({ digest, hashAlg }) {
352
+ const body = buildTimeStampReq(digest, hashAlg);
353
+ const res = await fetch(tsaUrl, {
354
+ method: 'POST',
355
+ headers: { 'content-type': 'application/timestamp-query' },
356
+ body
357
+ });
358
+ if (!res.ok || res.headers.get('content-type') !== 'application/timestamp-reply') {
359
+ throw new Error('TSA request failed: ' + res.status);
360
+ }
361
+ const replyBytes = new Uint8Array(await res.arrayBuffer());
362
+ // TimeStampResp ::= SEQUENCE { status PKIStatusInfo, timeStampToken TimeStampToken OPTIONAL }
363
+ const resp = asn1.parseOne(replyBytes, 0);
364
+ const [statusInfo, token] = (resp && asn1.parseChildren(resp.value)) || [];
365
+ if (!statusInfo) throw new Error('TSA reply is not a TimeStampResp');
366
+ // PKIStatusInfo.status: 0 = granted, 1 = grantedWithMods — both usable.
367
+ // readInteger returns the raw INTEGER bytes; a PKIStatus is a single byte.
368
+ const statusNode = asn1.parseChildren(statusInfo.value)[0];
369
+ const statusBytes = statusNode && asn1.readInteger(statusNode);
370
+ if (!statusBytes || statusBytes.length !== 1) {
371
+ throw new Error('TSA reply carries no PKIStatus');
372
+ }
373
+ const status = statusBytes[0];
374
+ if (status !== 0 && status !== 1) {
375
+ throw new Error('TSA refused: status ' + status);
376
+ }
377
+ if (!token) throw new Error('TSA granted the request but returned no token');
378
+ // The token is the TLV to return, as-is: slice it out of the reply.
379
+ // parseChildren offsets are relative to resp.value, which starts at resp.valueOff.
380
+ const tokenOff = resp.valueOff + statusInfo.next;
381
+ const tokenNode = asn1.parseOne(replyBytes, tokenOff);
382
+ return replyBytes.subarray(tokenOff, tokenNode.next);
383
+ }
384
+ ```
385
+
386
+ `sign()` is synchronous, so an async `fetch`-based `tsaSign` cannot be
387
+ plugged in directly — this is a constraint on the integration, not a
388
+ recipe to follow verbatim. A production integration either pre-fetches
389
+ the timestamp token before calling `sign()` and returns it from a
390
+ synchronous callback, or runs the whole signing step as a two-pass
391
+ integration (compute the digest, `await` the TSA, then call `sign()`
392
+ with a synchronous callback closing over the already-fetched token).
393
+
394
+ The demo itself ships neither of these — it uses a mock TSA instead,
395
+ by deliberate decision:
396
+
397
+ > **Mock TSA in-demo. Default and only path shipped. Zero network.**
398
+ >
399
+ > Rationale: the whole product story is "nothing leaves your machine",
400
+ > and the demo's headline artifact is the empty network tab. A live
401
+ > TSA call would spend that proof to demonstrate a feature the
402
+ > integrator supplies anyway. An external TSA also drags in an
403
+ > availability dependency, a CORS surface and, in the EU, a policy
404
+ > discussion the demo should not host.
405
+ >
406
+ > **Demo-UX consequence**: the timestamped levels must
407
+ > be labelled in the UI as demonstrating the *mechanism*, not a
408
+ > trusted time. Required wording, verbatim: *"Timestamped by an
409
+ > in-page demo TSA — the mechanism is real, the time source is not
410
+ > trusted. A production deployment supplies its own TSA through the
411
+ > `tsaSign` callback."* The privacy panel keeps its "0 network
412
+ > requests" claim at every level. The integration guide documents the
413
+ > external-TSA callback with a worked `fetch` example the demo never
414
+ > executes.
415
+
416
+ ## Verifying
417
+
418
+ A single call verifies every signature and timestamp object in a
419
+ document:
420
+
421
+ ```js
422
+ const report = pdfSignature.verifyAllSignatures(documentBytes);
423
+ // { signatures: [...], timestamps: [...] }
424
+ ```
425
+
426
+ (`fwBundle` is an optional second argument — the module carries its own
427
+ default bundle.)
428
+
429
+ Each entry of `report.signatures` carries these fields, exactly as
430
+ measured:
431
+ `computedDigest`, `errors`, `hashAlg`, `objGen`, `objNum`,
432
+ `pkVerified`, `signatureAlg`, `signerCerts`, `verified` — plus one more
433
+ boolean field, `valid`, a deprecated alias of `verified`, kept for
434
+ compatibility; read `verified`.
435
+
436
+ **`verified` also requires a well-formed `/ByteRange` gap.** Each
437
+ `/Sig`'s gap must be exactly its `/Contents` value: the whole `<…>`
438
+ token (what `sign()` emits) or its hex digits only (what `sign()`
439
+ emitted in earlier versions). Any other gap — off by one byte at either
440
+ end, a delimiter only, part of the digits — gives `verified: false`
441
+ with `pdf/sig/byterange/gap-start-mismatch` and/or
442
+ `pdf/sig/byterange/gap-end-mismatch` in `errors`, even when the
443
+ public-key check itself passed (`pkVerified: true`). The digest is
444
+ always computed over the ranges the file declares. The `/DocTimeStamp`
445
+ entries of `report.timestamps` run the same gap check: a
446
+ non-exact gap gives `verified: false` with the same two codes in
447
+ `errors`, even when the imprint matched (`imprintVerified: true`), and
448
+ each entry names the accepted form in `gapForm` — `'token'`,
449
+ `'digits'`, or `null` for any other gap.
450
+
451
+ This is row 5 of the claim matrix, in the exact wording every public
452
+ sentence about verification must quote:
453
+
454
+ > "Reconstructs the PKCS#7, recomputes the `/ByteRange` digest and
455
+ > verifies the signer's public-key signature — reported as `verified`.
456
+ > This is a cryptographic check, **not** signature validation per EN
457
+ > 319 102."
458
+
459
+ The algorithms this covers (row 6): "RSA-PSS and ECDSA with
460
+ SHA-256/384/512; Ed25519 with SHA-512 (RFC 8419), declared in the document
461
+ per ISO/TS 32002. Adobe Acrobat Reader does not validate Ed25519
462
+ signatures: use ECDSA P-256 where Acrobat must validate the signature."
463
+
464
+ **The PAdES level is not on the verify result.** `verifyAllSignatures`
465
+ never returns a `level` or a `subFilter`; `pdfSigPades.detectPadesProfile`
466
+ is the only level source, and it takes three caller-supplied context
467
+ flags, derived from the raw bytes:
468
+
469
+ ```js
470
+ const TS_OID_HEX = '2A864886F70D010910020E'; // id-aa-timeStampToken
471
+
472
+ function levelContext(documentBytes) {
473
+ const txt = new TextDecoder('latin1').decode(documentBytes);
474
+ return {
475
+ // Case-insensitive: sign() itself emits uppercase hex, but a
476
+ // /Contents literal from another producer is not guaranteed to be.
477
+ hasSignatureTimestamp: new RegExp(TS_OID_HEX, 'i').test(txt),
478
+ dss: /\/DSS/.test(txt),
479
+ hasDocTimestampOverDss: /\/DocTimeStamp/.test(txt)
480
+ };
481
+ }
482
+
483
+ const level = pdfSigPades.detectPadesProfile(sigDict, levelContext(documentBytes)).level;
484
+ // 'B-B' | 'B-T' | 'B-LT' | 'B-LTA' | null
485
+ ```
486
+
487
+ ## The verification report
488
+
489
+ A demo-shaped report layers the raw verify result with the three
490
+ derived context flags into one object. The shape below is the literal
491
+ measured output for a PAdES-T run, as the source monorepo's PAdES demo
492
+ produces it (`apps/demo/pades/src/report.js`, not published). It is the
493
+ output of one demo run; values such as `documentBytes` and the digest
494
+ differ per run.
495
+
496
+ ```json
497
+ {
498
+ "levelClaimed": "T",
499
+ "levelDetected": "B-T",
500
+ "isPades": true,
501
+ "subFilter": "ETSI.CAdES.detached",
502
+ "documentBytes": 18671,
503
+ "signatureCount": 1,
504
+ "byteRangeDigest": "b788862920399635f08a6e9b21b6c89b2a131854d9258657e2a3acb6a4f298772a9bb018b0b2a35d700d5827d8c5d8dbaffaad24ab59149f16e4a0f0d196451e",
505
+ "signatureValid": "verified",
506
+ "pkVerified": true,
507
+ "signatureAlg": "ed25519",
508
+ "hashAlg": "sha512",
509
+ "signerCertCount": 1,
510
+ "chain": "not-evaluated",
511
+ "revocation": "not-evaluated",
512
+ "qualified": "not-evaluated",
513
+ "timestamps": [],
514
+ "errors": []
515
+ }
516
+ ```
517
+
518
+ Design notes:
519
+
520
+ - `signatureValid` is a **string enum**, not a boolean:
521
+ `'verified' | 'not-verified' | 'no-signature'`. It must never be
522
+ rendered as "valid".
523
+ - `chain`, `revocation`, `qualified` are always present and always
524
+ `'not-evaluated'` today. Keeping the keys is what makes the omission
525
+ visible rather than silent; a future version may widen the enum, never
526
+ drop the key.
527
+ - `levelClaimed` vs `levelDetected` are distinct on purpose — the
528
+ first is what the signer asked for, the second is what the bytes
529
+ say.
530
+ - `timestamps[]` entries are only ever DocTimeStamp objects.
531
+ - `errors` carries the typed `pdf/sig/*` codes verbatim; the demo maps
532
+ them to human text, it does not invent them.
533
+
534
+ ## Trust-model boundaries
535
+
536
+ `@awacloud/pdf`'s signing surface draws a hard line between what it
537
+ verifies cryptographically and what your PKI has to supply separately:
538
+
539
+ | The library checks | Your PKI must provide |
540
+ |---|---|
541
+ | The `/ByteRange` digest and the signer's public-key signature over it; for each `/Sig` and each `/DocTimeStamp`, that the `/ByteRange` gap is exactly the `/Contents` value (the `<…>` token, or its hex digits for signatures written by earlier versions). | A certificate chain to a trust anchor. `certChain.validateChainOrder` compares issuer/subject **strings** only; it verifies no certificate signature, no validity dates, no name constraints, no trust anchor. Report as `chain: 'not-evaluated'`. |
542
+ | — nothing on read. | Revocation status. DSS `/CRLs` and `/OCSPs` are *typed on read* (`pdfSigPades.typeDSS`); nothing is fetched, parsed for status, or checked. Report as `revocation: 'not-evaluated'`. |
543
+ | — nothing; the concept does not exist in this component. | Qualified / eIDAS status. eIDAS is market context only — "qualified" is a property of a certificate and a validation process your QTSP provides, never a property of `@awacloud/pdf`. No trust-list handling exists here, in any form. |
544
+ | That an RFC 3161 token is well-formed, once written (T/LT); the DocTimeStamp timestamp, on read (LTA). | Trust in the TSA itself, and — for T/LT — verification of the embedded signature-timestamp: the id-aa-timeStampToken attribute is written but never verified on read; only standalone `/DocTimeStamp` objects (LTA) are. |
545
+ | That LT/LTA structures are emitted and, for LTA, that the document timestamp verifies on read. | Archival policy: renewal before algorithm expiry, and any decision about how long a signature must remain provable, are the integrator's. |
546
+
547
+ ## What you may claim
548
+
549
+ This table is normative for every public sentence about the component.
550
+
551
+ | # | Capability | Verdict | Exact wording to use | Evidence |
552
+ |---|---|---|---|---|
553
+ | 1 | PAdES-B signing | **C** | "Produces PAdES-B (B-B) signatures in the browser." | measured B sign + verify round trip |
554
+ | 2 | PAdES-T signing | **CWC** | "Produces PAdES-T signatures; the RFC 3161 timestamp token is supplied by the integrator through a `tsaSign` callback — the library performs no network call." | measured T sign; `sign()` throws `pdf/sign/tsa-required-for-level-T` without `tsaSign` |
555
+ | 3 | PAdES-LT signing | **CWC** | "Emits the LT structure (DSS with the signer certificates). Revocation material (CRL/OCSP) is supplied by the integrator; the library neither fetches nor validates it." | measured LT sign |
556
+ | 4 | PAdES-LTA signing | **CWC** | "Emits the LTA structure (DSS + document timestamp) and re-verifies the document timestamp on read. The archival *policy* (renewal before algorithm expiry) is the integrator's." | measured LTA sign; the DocTimeStamp verifies on read |
557
+ | 5 | Signature verification | **CWC** | "Reconstructs the PKCS#7, recomputes the `/ByteRange` digest and verifies the signer's public-key signature — reported as `verified`. This is a cryptographic check, **not** signature validation per EN 319 102." | measured `verified` / `pkVerified` on a signed document |
558
+ | 6 | Algorithms | **CWC** | "RSA-PSS and ECDSA with SHA-256/384/512; Ed25519 with SHA-512 (RFC 8419), declared in the document per ISO/TS 32002. Adobe Acrobat Reader does not validate Ed25519 signatures: use ECDSA P-256 where Acrobat must validate the signature." | `HASH_TABLE` + `_signDigest` in `src/sig/sign.js`; Ed25519 (SHA-512) and ECDSA P-256 / P-384 (DER `ECDSA-Sig-Value`) measured end to end; Acrobat Reader rejection of Ed25519 measured 2026-10-05, also on an OpenSSL-produced Ed25519 signature, which OpenSSL 3.5.6 verifies |
559
+ | 7 | PAdES level detection | **CWC** | "Detects B-B / B-T / B-LT / B-LTA from the signature dictionary — only for `/SubFilter /ETSI.CAdES.detached`; `adbe.pkcs7.detached` is reported as non-PAdES." | measured `detectPadesProfile` on both SubFilters |
560
+ | 8 | Signature-timestamp verification (T/LT) | **NC** | — Do not claim. The embedded id-aa-timeStampToken attribute is written but never verified on read; only standalone `/DocTimeStamp` objects (LTA) are. | no read path verifies the embedded timestamp attribute |
561
+ | 9 | Certificate-chain validation | **NC** | — Do not claim. `certChain.validateChainOrder` compares issuer/subject **strings** only; it verifies no certificate signature, no validity dates, no name constraints, no trust anchor. Report as `chain: 'not-evaluated'`. | `validateChainOrder` in `src/sig/certChain.js` |
562
+ | 10 | Revocation (CRL/OCSP) | **NC** | — Do not claim. DSS `/CRLs` and `/OCSPs` are *typed on read* (`pdfSigPades.typeDSS`); nothing is fetched, parsed for status, or checked. Report as `revocation: 'not-evaluated'`. | `typeDSS` in `src/extra/sig-pades.js` |
563
+ | 11 | Qualified / eIDAS status | **NC** | — Do not claim, in any form, including "eIDAS-ready" or "qualified-capable". No trust-list handling exists. eIDAS may be named only as market context, never as a property of the component. | no code |
564
+ | 12 | Certificate generation | **NC (as a library claim)** | — The demo generates its own self-signed certificate; **`@awacloud/fw` and `@awacloud/pdf` publish no X.509 emitter**. Phrase as "the demo generates a throwaway demo certificate in your browser", never "the library issues certificates". | no X.509 emitter in `@awacloud/fw` or `@awacloud/pdf` |
565
+ | 13 | PKCS#12 import | **NC** | — Do not claim. No PKCS#12 parser exists. Say: "bring your own key material; PKCS#12 unwrapping is integrator-side." PKCS#8 / SEC1 / SPKI / PEM *key* encoding IS available via `@awacloud/fw/crypto/utils/{keyformat,pem}.js`. | no PKCS#12 parser in `@awacloud/fw` or `@awacloud/pdf` |
566
+ | 14 | Client-side / zero-upload | **C** | "The document never leaves the page: signing and verification are pure computation, with zero network calls." | measured zero network calls; `src/sig/sign.js` has no fetch seam |
567
+ | 15 | Zero npm runtime dependency | **C** | "Zero npm runtime dependency: `@awacloud/pdf` depends only on `@awacloud/fw` and `@awacloud/fonts`, both first-party." | `package.json` `dependencies` |
568
+ | 16 | B-LTA end-to-end assurance | **CWC** | "LTA is emitted and its document timestamp verified; a full real-chain PKCS#7 + nested DocTimeStamp end-to-end test is still an open follow-up." | `validateBLtaChain` B→T→LT→LTA round trip (`CHANGELOG.md`) |
569
+
570
+ Legend: **C** = claimable; **CWC** = claimable with the exact caveat
571
+ quoted; **NC** = not claimable.
572
+
573
+ ## See also
574
+
575
+ - [Crypto — risks and limitations](./crypto.md)
576
+ - [`pdfSigPades` API reference](../api/extra/sig-pades.md)
577
+ - [`pdfSignature` API reference](../api/sig/signature.md)
578
+ - The `apps/demo/pades` README — the demo application's own guide, in the
579
+ source monorepo (not published)
@@ -0,0 +1,89 @@
1
+ # Read pipeline
2
+
3
+ `api.read(bytes)` runs five successive steps, each wired to an independently documented module. This guide walks them in order and ends with a complete example.
4
+
5
+ **Prerequisites** — the package `@awacloud/pdf` (root entry
6
+ `@awacloud/pdf`, or the committed `@awacloud/pdf/standalone/*` build) and its
7
+ `@awacloud/fw` / `@awacloud/fonts` dependencies; any modern JavaScript runtime
8
+ (browser main thread or Worker, Bun, Node.js 18+).
9
+
10
+ ## Overview
11
+
12
+ ```
13
+ bytes (Uint8Array)
14
+ │
15
+ ▼
16
+ [1] readHeader → %PDF-x.y §7.5.2 → pdfDocument
17
+ │
18
+ ▼
19
+ [2] locate + parse xref + /Prev chain §7.5.4-7.5.5 → pdfXref
20
+ │
21
+ ▼
22
+ [3] typeTrailer → { size, root, info?, prev?, … } → pdfTrailer
23
+ │
24
+ ▼
25
+ [4] resolve(root) + typeCatalog §7.7.2 → pdfCatalog
26
+ │
27
+ ▼
28
+ [5] walkPageTree + typePage §7.7.3 → pdfPages / pdfPage
29
+ │
30
+ ▼
31
+ doc = { version, catalog, pages, trailer, xref, _raw }
32
+ ```
33
+
34
+ ## Step by step
35
+
36
+ ### 1. Header (`readHeader`)
37
+
38
+ Searches for `%PDF-x.y` in the first 1024 bytes (ISO §7.5.2 tolerance). Returns `{ version, end }`. See [`pdfDocument`](../api/document/document.md).
39
+
40
+ ### 2. Xref
41
+
42
+ `locateStartXref(bytes)` scans the last 8192 bytes for `startxref`. `readStartXref` reads the numeric offset. The section walk then follows the `/Prev` chain, choosing a form per section from the bytes it finds: `parseXrefTable` for a classical `xref` section, `pdfCrossRefStream.parseCrossRefStream` for a `/Type /XRef` stream (§7.5.8), with `pdfObjStream` materialising anything stored in a `/Type /ObjStm` container (§7.5.7). A classical trailer's `/XRefStm` pointer (hybrid-reference file) is followed as well. See [`pdfXref`](../api/syntax/xref.md).
43
+
44
+ ### 3. Trailer
45
+
46
+ `parseTrailerDict` reads the dict, `typeTrailer` extracts `/Size`, `/Root`, and the optional `/Info`, `/Prev`, `/ID`, `/Encrypt`. See [`pdfTrailer`](../api/syntax/trailer.md).
47
+
48
+ ### 4. Catalog
49
+
50
+ `resolve(trailer.root)` materialises the Catalog's indirect object; `typeCatalog` extracts `/Pages`, `/Version`, `/PageLayout`, `/Outlines`, `/Metadata`, … See [`pdfCatalog`](../api/document/catalog.md).
51
+
52
+ ### 5. Pages
53
+
54
+ `walkPageTree(catalog.pages, resolve)` produces the ordered list of leaf refs; each is resolved then passed to `typePage`. See [`pdfPages`](../api/document/pages.md) and [`pdfPage`](../api/document/page.md).
55
+
56
+ ## Indirect-object resolver
57
+
58
+ The document exposes `doc._raw.resolve(ref)` to follow any untyped `{ type: 'ref', num, gen }` (Metadata, Outlines, Annots, …). The `indirects` cache avoids re-materialising the same object twice.
59
+
60
+ ## End-to-end example
61
+
62
+ ```js
63
+ import { ModuleRuntime } from '@awacloud/fw/core/runtime.js';
64
+ import { fw_require, pkg_require, modules } from '@awacloud/pdf';
65
+
66
+ const rt = new ModuleRuntime();
67
+ for (const m of fw_require) rt.register(m);
68
+ for (const m of pkg_require) rt.register(m);
69
+ for (const m of modules) rt.register(m);
70
+
71
+ const api = rt.resolve('pdf');
72
+ const doc = api.read(bytes);
73
+
74
+ for (const page of doc.pages) {
75
+ console.log(page.mediaBox, page.rotate, page.contents.length);
76
+ }
77
+
78
+ // Follow an untyped ref (e.g. outlines)
79
+ if (doc.catalog.outlines) {
80
+ const outlines = doc._raw.resolve(doc.catalog.outlines);
81
+ console.log(outlines.entries);
82
+ }
83
+ ```
84
+
85
+ ## See also
86
+
87
+ - [Getting started](./getting-started.md)
88
+ - [Coverage](./coverage.md)
89
+ - [`pdfDocument`](../api/document/document.md)