@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,247 @@
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 X.509 certificate chain extraction from a PKCS#7
8
+ * SignedData blob.
9
+ *
10
+ * - `extractCertsFromCms(blob, asn1)` → typed records for each cert in
11
+ * the SignedData `certificates [0]` field.
12
+ * - `extractCertFromPem(pem, pemMod, asn1)` → typed record from a PEM
13
+ * block.
14
+ * - `findIssuer(cert, candidates)` / `validateChainOrder(chain)` →
15
+ * structural chain checks (subject = next.issuer), no signature
16
+ * verification at this layer.
17
+ *
18
+ * @module pdf/sig/certChain
19
+ */
20
+
21
+ /**
22
+ * Module factory.
23
+ */
24
+ import { pdfErrors } from '../errors.js';
25
+ import { pdfSigOids } from './oids.js';
26
+ import { asn1 } from '@awacloud/fw/crypto/utils/asn1.js';
27
+ import { pem } from '@awacloud/fw/crypto/utils/pem.js';
28
+
29
+ export const pdfCertChain = {
30
+ name: 'pdfCertChain',
31
+ dependencies: ['pdfErrors', 'pdfSigOids', 'asn1', 'pem'],
32
+ deps: [pdfErrors, pdfSigOids, asn1, pem],
33
+ factory(errors, sigOids, asn1, pem) {
34
+ const { EncryptionError, ParseError } = errors;
35
+ const {
36
+ KEY_ALG_OIDS,
37
+ SIG_ALG_OIDS_VERBOSE: SIG_ALG_OIDS,
38
+ shortOid: _shortOid
39
+ } = sigOids;
40
+
41
+ function _parseUtcOrGeneralizedTime(node) {
42
+ if (!node) return null;
43
+ const s = String.fromCharCode.apply(null, Array.from(node.value));
44
+ let yr, mo, da, hr, mi, se;
45
+ if (node.tag === 0x17) { // UTCTime: YYMMDDhhmmssZ
46
+ const m = /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(s);
47
+ if (!m) return null;
48
+ yr = +m[1]; yr += yr < 50 ? 2000 : 1900;
49
+ mo = +m[2] - 1; da = +m[3]; hr = +m[4]; mi = +m[5]; se = +m[6];
50
+ } else if (node.tag === 0x18) { // GeneralizedTime: YYYYMMDDhhmmssZ
51
+ const m = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(s);
52
+ if (!m) return null;
53
+ yr = +m[1]; mo = +m[2] - 1; da = +m[3];
54
+ hr = +m[4]; mi = +m[5]; se = +m[6];
55
+ } else { return null; }
56
+ return new Date(Date.UTC(yr, mo, da, hr, mi, se));
57
+ }
58
+
59
+ function _renderName(seqValue, asn1Mod) {
60
+ const rdns = asn1Mod.parseChildren(seqValue);
61
+ if (!rdns) return '';
62
+ const parts = [];
63
+ for (let i = 0; i < rdns.length; i++) {
64
+ const set = asn1Mod.parseChildren(rdns[i].value);
65
+ if (!set) continue;
66
+ for (let j = 0; j < set.length; j++) {
67
+ const tv = asn1Mod.parseChildren(set[j].value);
68
+ if (!tv || tv.length < 2) continue;
69
+ const oid = asn1Mod.readOid(tv[0]);
70
+ const valBytes = tv[1].value;
71
+ const val = String.fromCharCode.apply(null, Array.from(valBytes));
72
+ parts.push(_shortOid(oid) + '=' + val);
73
+ }
74
+ }
75
+ return parts.join(',');
76
+ }
77
+
78
+ function _reconstructDer(node, parentBuf) {
79
+ const len = node.length;
80
+ let lenLen;
81
+ if (len < 0x80) lenLen = 1;
82
+ else if (len < 0x100) lenLen = 2;
83
+ else if (len < 0x10000) lenLen = 3;
84
+ else if (len < 0x1000000) lenLen = 4;
85
+ else lenLen = 5;
86
+ return parentBuf.subarray(node.valueOff - 1 - lenLen, node.next);
87
+ }
88
+
89
+ function parseCertificate(der, asn1Mod) {
90
+ const a1 = asn1Mod || asn1;
91
+ const top = a1.parseOne(der, 0);
92
+ if (!top) {
93
+ throw new ParseError('pdf/cert/malformed',
94
+ 'Certificate top-level SEQUENCE parse failed');
95
+ }
96
+ const cert = a1.parseChildren(top.value);
97
+ if (!cert || cert.length < 3) {
98
+ throw new ParseError('pdf/cert/short',
99
+ 'Certificate has fewer than 3 children');
100
+ }
101
+ const tbs = a1.parseChildren(cert[0].value);
102
+ if (!tbs) {
103
+ throw new ParseError('pdf/cert/bad-tbs',
104
+ 'tbsCertificate parse failed');
105
+ }
106
+ let idx = 0;
107
+ let version = 1;
108
+ if (tbs[idx] && tbs[idx].tag === 0xA0) { // [0] EXPLICIT version
109
+ const v = a1.parseOne(tbs[idx].value, 0);
110
+ if (v && v.value && v.value.length) version = v.value[0] + 1;
111
+ idx++;
112
+ }
113
+ const serialNode = tbs[idx++];
114
+ idx++;
115
+ const issuerNode = tbs[idx++];
116
+ const validityNode = tbs[idx++];
117
+ const subjectNode = tbs[idx++];
118
+ const spkiNode = tbs[idx];
119
+ const validity = a1.parseChildren(validityNode.value);
120
+ const sigAlgChildren = a1.parseChildren(cert[1].value);
121
+ const sigAlgOid = sigAlgChildren && sigAlgChildren[0]
122
+ ? a1.readOid(sigAlgChildren[0]) : null;
123
+ const spki = a1.parseChildren(spkiNode.value);
124
+ const keyAlgChildren = spki && spki[0]
125
+ ? a1.parseChildren(spki[0].value) : null;
126
+ const keyAlgOid = keyAlgChildren && keyAlgChildren[0]
127
+ ? a1.readOid(keyAlgChildren[0]) : null;
128
+ return {
129
+ version,
130
+ serialNumber: serialNode ? serialNode.value : null,
131
+ issuer: _renderName(issuerNode.value, a1),
132
+ subject: _renderName(subjectNode.value, a1),
133
+ notBefore: validity ? _parseUtcOrGeneralizedTime(validity[0]) : null,
134
+ notAfter: validity ? _parseUtcOrGeneralizedTime(validity[1]) : null,
135
+ publicKey: spki && spki[1] ? spki[1].value : null,
136
+ keyAlgorithm: KEY_ALG_OIDS[keyAlgOid] || keyAlgOid,
137
+ sigAlgorithm: SIG_ALG_OIDS[sigAlgOid] || sigAlgOid,
138
+ raw: der
139
+ };
140
+ }
141
+
142
+ function extractCertsFromCms(blob, asn1Mod, opts) {
143
+ const a1 = asn1Mod || asn1;
144
+ if (!(blob instanceof Uint8Array)) {
145
+ throw new ParseError('pdf/cert/bad-input',
146
+ 'extractCertsFromCms expects a Uint8Array');
147
+ }
148
+ if (!a1 || typeof a1.parseOne !== 'function') {
149
+ throw new EncryptionError('pdf/cert/missing-fw',
150
+ 'extractCertsFromCms requires the fw asn1 module');
151
+ }
152
+ const warnings = opts && Array.isArray(opts.warnings) ? opts.warnings : null;
153
+ const ci = a1.parseOne(blob, 0);
154
+ if (!ci) return [];
155
+ const ciKids = a1.parseChildren(ci.value);
156
+ if (!ciKids || ciKids.length < 2) return [];
157
+ const sdNode = a1.parseOne(ciKids[1].value, 0);
158
+ if (!sdNode) return [];
159
+ const sd = a1.parseChildren(sdNode.value);
160
+ if (!sd) return [];
161
+ let certsField = null;
162
+ for (let i = 0; i < sd.length; i++) {
163
+ if (sd[i].tag === 0xA0) { certsField = sd[i]; break; }
164
+ }
165
+ if (!certsField) return [];
166
+ const certs = a1.parseChildren(certsField.value);
167
+ if (!certs) return [];
168
+ const out = [];
169
+ for (let i = 0; i < certs.length; i++) {
170
+ if (certs[i].tag !== 0x30) continue;
171
+ const der = _reconstructDer(certs[i], certsField.value);
172
+ try {
173
+ out.push(parseCertificate(der, a1));
174
+ } catch (e) {
175
+ if (warnings) {
176
+ warnings.push({
177
+ code: e && e.code ? e.code : 'pdf/cert/malformed',
178
+ message: 'skipped malformed cert at index ' + i
179
+ + ': ' + (e && e.message),
180
+ context: { index: i }
181
+ });
182
+ }
183
+ }
184
+ }
185
+ return out;
186
+ }
187
+
188
+ function extractCertFromPem(pemString, pemMod, asn1Mod) {
189
+ const pm = pemMod || pem;
190
+ const a1 = asn1Mod || asn1;
191
+ if (typeof pemString !== 'string') {
192
+ throw new ParseError('pdf/cert/bad-pem-input',
193
+ 'extractCertFromPem expects a string');
194
+ }
195
+ if (!pm || typeof pm.decode !== 'function') {
196
+ throw new EncryptionError('pdf/cert/missing-pem',
197
+ 'extractCertFromPem requires the fw pem module');
198
+ }
199
+ const dec = pm.decode(pemString);
200
+ if (!dec || !dec.bytes) {
201
+ throw new ParseError('pdf/cert/pem-decode-failed',
202
+ 'PEM decode failed');
203
+ }
204
+ return parseCertificate(dec.bytes, a1);
205
+ }
206
+
207
+ function findIssuer(cert, candidates) {
208
+ if (!cert || !Array.isArray(candidates)) return null;
209
+ for (let i = 0; i < candidates.length; i++) {
210
+ if (candidates[i] === cert) continue;
211
+ if (candidates[i].subject === cert.issuer) return candidates[i];
212
+ }
213
+ return null;
214
+ }
215
+
216
+ function validateChainOrder(chain) {
217
+ const errs = [];
218
+ if (!Array.isArray(chain) || chain.length === 0) {
219
+ errs.push({ code: 'pdf/cert/empty-chain',
220
+ message: 'chain must be a non-empty array' });
221
+ return { valid: false, errors: errs };
222
+ }
223
+ for (let i = 0; i < chain.length - 1; i++) {
224
+ if (chain[i].issuer !== chain[i + 1].subject) {
225
+ errs.push({
226
+ code: 'pdf/cert/chain-break',
227
+ message: 'cert[' + i + '].issuer != cert['
228
+ + (i + 1) + '].subject',
229
+ context: {
230
+ issuer: chain[i].issuer,
231
+ subject: chain[i + 1].subject
232
+ }
233
+ });
234
+ }
235
+ }
236
+ return { valid: errs.length === 0, errors: errs };
237
+ }
238
+
239
+ return {
240
+ extractCertsFromCms,
241
+ extractCertFromPem,
242
+ parseCertificate,
243
+ findIssuer,
244
+ validateChainOrder
245
+ };
246
+ }
247
+ };
package/src/sig/dss.js ADDED
@@ -0,0 +1,317 @@
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 PAdES-LT Document Security Store builder — produces
8
+ * the indirect objects (certs / OCSPs / CRLs / DSS dict / VRI dict)
9
+ * that a level-LT signer appends via the incremental writer to the
10
+ * already-signed (level B/T) PDF bytes.
11
+ *
12
+ * Spec : ETSI EN 319 142-1 V1.1.1 §5.4, ISO 32000-2:2020 §12.8.4.3.
13
+ *
14
+ * DSS ::= <<
15
+ * /Type /DSS
16
+ * /Certs [N₁ 0 R N₂ 0 R …] -- optional
17
+ * /OCSPs [Nk 0 R …] -- optional
18
+ * /CRLs [Nj 0 R …] -- optional
19
+ * /VRI << /<sigHashHex> << /Cert [...] /OCSP [...] /CRL [...]
20
+ * /TU? (M:...) /TS? Nₜ 0 R >> … >>
21
+ * >>
22
+ *
23
+ * Each referenced cert/OCSP/CRL is an indirect *stream* whose body is
24
+ * the raw DER bytes and whose dict carries the `/Type /CertVal`,
25
+ * `/OCSPVal` or `/CRLVal` marker (Adobe extension, widely deployed).
26
+ *
27
+ * The factory does NOT touch the byte buffer. It produces a list of
28
+ * `{ num, gen, value }` updates compatible with
29
+ * `pdfIncrementalWriter.appendIncremental`, plus the DSS object number
30
+ * so the caller can also append an updated Catalog carrying
31
+ * `/DSS dssNum 0 R`.
32
+ *
33
+ * @module pdf/sig/dss
34
+ */
35
+
36
+ /**
37
+ * Module factory.
38
+ */
39
+ import { pdfErrors } from '../errors.js';
40
+ import { pdfSha1 } from './sha1.js';
41
+ import { bitArray } from '@awacloud/fw/crypto/utils/bitArray.js';
42
+
43
+ export const pdfDssBuilder = {
44
+ name: 'pdfDssBuilder',
45
+ dependencies: ['pdfErrors', 'pdfSha1', 'bitArray'],
46
+ deps: [pdfErrors, pdfSha1, bitArray],
47
+ factory(errors, sha1Mod, bitArray) {
48
+ const { ContractError } = errors;
49
+
50
+ // ── SHA-1 hoisted to fw (FIPS 180-4 §6.1) ────────────────────
51
+ // Used solely for VRI key derivation per ISO 32000-2 §12.8.4.3
52
+ // (the VRI dict keys are the uppercase hex SHA-1 of each
53
+ // signature value bytes). SHA-1 is deprecated for general
54
+ // cryptographic use but PDF VRI keys still mandate it.
55
+ function _sha1(bytes) {
56
+ const digestBa = sha1Mod.hash(bitArray.ui8_to_ba(bytes));
57
+ return bitArray.ba_to_ui8(digestBa);
58
+ }
59
+ // PDF date string per ISO 32000-2:2020 §7.9.4 :
60
+ // (D:YYYYMMDDHHmmSS+HH'mm') — used here for VRI `TU` entries
61
+ // (ISO 32000-2 §12.8.4.3 : the time at which validation data
62
+ // was obtained). Always emitted in UTC (`+00'00'`) so the
63
+ // value is stable across runs and machine timezones.
64
+ function _pdfDate(date) {
65
+ const d = date instanceof Date ? date : new Date();
66
+ function p2(n) { return (n < 10 ? '0' : '') + n; }
67
+ return '(D:'
68
+ + d.getUTCFullYear()
69
+ + p2(d.getUTCMonth() + 1)
70
+ + p2(d.getUTCDate())
71
+ + p2(d.getUTCHours())
72
+ + p2(d.getUTCMinutes())
73
+ + p2(d.getUTCSeconds())
74
+ + "+00'00')";
75
+ }
76
+ function _toHexUpper(bytes) {
77
+ let s = '';
78
+ for (let i = 0; i < bytes.length; i++) {
79
+ const b = bytes[i];
80
+ s += (b < 16 ? '0' : '') + b.toString(16).toUpperCase();
81
+ }
82
+ return s;
83
+ }
84
+ function _scanSignatureContentsHex(bytes) {
85
+ // Find every `/Type /Sig` (incl. /DocTimeStamp) and extract
86
+ // the `/Contents <…hex…>` literal. Returns an array of
87
+ // uppercase hex strings (the raw hex from /Contents).
88
+ let s = '';
89
+ const CHUNK = 0x8000;
90
+ for (let i = 0; i < bytes.length; i += CHUNK) {
91
+ s += String.fromCharCode.apply(null,
92
+ bytes.subarray(i, Math.min(i + CHUNK, bytes.length)));
93
+ }
94
+ const out = [];
95
+ const re = /(\d+)\s+(\d+)\s+obj\s*<<([\s\S]*?)>>\s*endobj/g;
96
+ let m;
97
+ while ((m = re.exec(s)) !== null) {
98
+ const body = m[3];
99
+ if (!/\/Type\s*\/(?:Sig|DocTimeStamp)\b/.test(body)) continue;
100
+ const cm = body.match(/\/Contents\s*<([0-9A-Fa-f]*)>/);
101
+ if (!cm) continue;
102
+ out.push(cm[1]);
103
+ }
104
+ return out;
105
+ }
106
+
107
+
108
+ function _refObj(num, gen) {
109
+ return { type: 'ref', num: num | 0, gen: gen | 0 };
110
+ }
111
+ function _intObj(v) { return { type: 'int', value: v | 0 }; }
112
+ function _nameObj(v) { return { type: 'name', value: String(v) }; }
113
+ function _strHexObj(s) {
114
+ // s is an ASCII hex string (e.g. SHA-1 of signature value).
115
+ // Emit it as a regular PDF string ; PDF VRI keys are NAMES
116
+ // composed of the hex characters per ISO 32000-2 §12.8.4.3.
117
+ return { type: 'name', value: String(s) };
118
+ }
119
+ function _arrayObj(items) { return { type: 'array', items }; }
120
+ function _dictObj(entries) { return { type: 'dict', entries }; }
121
+ function _streamObj(rawBytes, typeName) {
122
+ // Stream dict carries /Type — the writer auto-adds /Length.
123
+ return {
124
+ type: 'stream',
125
+ dict: _dictObj({
126
+ Type: _nameObj(typeName)
127
+ }),
128
+ raw: rawBytes
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Build DSS + supporting streams for an incremental append.
134
+ *
135
+ * @param {Object} args
136
+ * @param {Uint8Array[]} [args.certs] Cert DER bytes — required for
137
+ * a useful DSS (at least one signer cert + chain).
138
+ * @param {Uint8Array[]} [args.ocsps] OCSP response DER bytes.
139
+ * @param {Uint8Array[]} [args.crls] CRL DER bytes.
140
+ * @param {Object<string, Object>} [args.vri] Map of signature-hash
141
+ * hex → { certs?, ocsps?, crls?, tu?, ts? } (each list is an
142
+ * array of *indices* into the parent certs/ocsps/crls lists, or
143
+ * raw DER bytes; ts? is a TimeStampToken DER as a stream).
144
+ * @param {number} args.startNum First free object number to
145
+ * allocate (subsequent objects are sequential).
146
+ * @returns {{ updates: Array, dssNum: number, lastNum: number,
147
+ * certNums: number[], ocspNums: number[],
148
+ * crlNums: number[] }}
149
+ */
150
+ function buildDss(args) {
151
+ args = args || {};
152
+ const startNum = args.startNum | 0;
153
+ if (!Number.isFinite(startNum) || startNum < 2) {
154
+ throw new ContractError('pdf/dss/bad-startNum',
155
+ 'buildDss requires opts.startNum >= 2',
156
+ { context: { startNum } });
157
+ }
158
+ const certs = Array.isArray(args.certs) ? args.certs : [];
159
+ const ocsps = Array.isArray(args.ocsps) ? args.ocsps : [];
160
+ const crls = Array.isArray(args.crls) ? args.crls : [];
161
+
162
+ for (const c of certs) {
163
+ if (!(c instanceof Uint8Array)) {
164
+ throw new ContractError('pdf/dss/bad-cert',
165
+ 'each cert must be a Uint8Array (DER)');
166
+ }
167
+ }
168
+ for (const o of ocsps) {
169
+ if (!(o instanceof Uint8Array)) {
170
+ throw new ContractError('pdf/dss/bad-ocsp',
171
+ 'each ocsp must be a Uint8Array (DER)');
172
+ }
173
+ }
174
+ for (const c of crls) {
175
+ if (!(c instanceof Uint8Array)) {
176
+ throw new ContractError('pdf/dss/bad-crl',
177
+ 'each crl must be a Uint8Array (DER)');
178
+ }
179
+ }
180
+
181
+ const updates = [];
182
+ let n = startNum;
183
+
184
+ const certNums = [];
185
+ for (const der of certs) {
186
+ certNums.push(n);
187
+ updates.push({ num: n, gen: 0, value: _streamObj(der, 'CertVal') });
188
+ n++;
189
+ }
190
+ const ocspNums = [];
191
+ for (const der of ocsps) {
192
+ ocspNums.push(n);
193
+ updates.push({ num: n, gen: 0, value: _streamObj(der, 'OCSPVal') });
194
+ n++;
195
+ }
196
+ const crlNums = [];
197
+ for (const der of crls) {
198
+ crlNums.push(n);
199
+ updates.push({ num: n, gen: 0, value: _streamObj(der, 'CRLVal') });
200
+ n++;
201
+ }
202
+
203
+ // Optional VRI auto-population : scan parent PDF bytes for
204
+ // every signature, derive the SHA-1(/Contents hex) key, and
205
+ // synthesise a VRI entry referencing all certs/ocsps/crls.
206
+ let vriArg = args.vri;
207
+ if (args.autoVri && args.parentBytes instanceof Uint8Array) {
208
+ const sigContents = _scanSignatureContentsHex(args.parentBytes);
209
+ const auto = vriArg && typeof vriArg === 'object'
210
+ ? Object.assign({}, vriArg) : {};
211
+ const allCertIdx = certs.map((_, i) => i);
212
+ const allOcspIdx = ocsps.map((_, i) => i);
213
+ const allCrlIdx = crls.map((_, i) => i);
214
+ // VRI `TU` = time at which validation data was obtained
215
+ // (ISO 32000-2 §12.8.4.3). PDF date string per §7.9.4.
216
+ // `args.vriTime` overrides the default of `new Date()` so
217
+ // tests can pin a deterministic value.
218
+ const tuDate = args.vriTime instanceof Date
219
+ ? args.vriTime : new Date();
220
+ const tuPdfStr = _pdfDate(tuDate);
221
+ for (const hex of sigContents) {
222
+ // SHA-1 of the *raw signature value bytes* (the binary
223
+ // decoded from the /Contents hex literal), uppercase hex.
224
+ const sigBytes = new Uint8Array(hex.length >>> 1);
225
+ for (let i = 0; i < sigBytes.length; i++) {
226
+ sigBytes[i] = parseInt(hex.substr(i * 2, 2), 16);
227
+ }
228
+ const key = _toHexUpper(_sha1(sigBytes));
229
+ if (auto[key]) continue;
230
+ const entry = { tu: tuPdfStr };
231
+ if (allCertIdx.length) entry.certs = allCertIdx;
232
+ if (allOcspIdx.length) entry.ocsps = allOcspIdx;
233
+ if (allCrlIdx.length) entry.crls = allCrlIdx;
234
+ auto[key] = entry;
235
+ }
236
+ vriArg = auto;
237
+ }
238
+ let vriEntries = null;
239
+ if (vriArg && typeof vriArg === 'object') {
240
+ vriEntries = {};
241
+ for (const k of Object.keys(vriArg)) {
242
+ const v = vriArg[k] || {};
243
+ const e = {};
244
+ function _refsFrom(spec, parentNums) {
245
+ if (!Array.isArray(spec)) return null;
246
+ const refs = [];
247
+ for (const it of spec) {
248
+ if (typeof it === 'number') {
249
+ if (it < 0 || it >= parentNums.length) {
250
+ throw new ContractError('pdf/dss/vri-bad-index',
251
+ 'VRI index out of bounds',
252
+ { context: { index: it } });
253
+ }
254
+ refs.push(_refObj(parentNums[it], 0));
255
+ } else if (it instanceof Uint8Array) {
256
+ // Promote raw DER to a new stream.
257
+ const newNum = n++;
258
+ updates.push({ num: newNum, gen: 0,
259
+ value: _streamObj(it, 'CertVal') });
260
+ refs.push(_refObj(newNum, 0));
261
+ }
262
+ }
263
+ return _arrayObj(refs);
264
+ }
265
+ const c = _refsFrom(v.certs, certNums);
266
+ const o = _refsFrom(v.ocsps, ocspNums);
267
+ const r = _refsFrom(v.crls, crlNums);
268
+ if (c) e.Cert = c;
269
+ if (o) e.OCSP = o;
270
+ if (r) e.CRL = r;
271
+ if (typeof v.tu === 'string') {
272
+ e.TU = { type: 'string',
273
+ value: new TextEncoder().encode(v.tu) };
274
+ }
275
+ if (v.ts instanceof Uint8Array) {
276
+ const newNum = n++;
277
+ updates.push({ num: newNum, gen: 0,
278
+ value: _streamObj(v.ts, 'TS') });
279
+ e.TS = _refObj(newNum, 0);
280
+ }
281
+ vriEntries[k] = _dictObj(e);
282
+ }
283
+ }
284
+
285
+ const dssEntries = { Type: _nameObj('DSS') };
286
+ if (certNums.length) {
287
+ dssEntries.Certs = _arrayObj(certNums.map(num => _refObj(num, 0)));
288
+ }
289
+ if (ocspNums.length) {
290
+ dssEntries.OCSPs = _arrayObj(ocspNums.map(num => _refObj(num, 0)));
291
+ }
292
+ if (crlNums.length) {
293
+ dssEntries.CRLs = _arrayObj(crlNums.map(num => _refObj(num, 0)));
294
+ }
295
+ if (vriEntries) {
296
+ dssEntries.VRI = _dictObj(vriEntries);
297
+ }
298
+ const dssNum = n++;
299
+ updates.push({ num: dssNum, gen: 0, value: _dictObj(dssEntries) });
300
+
301
+ return {
302
+ updates,
303
+ dssNum,
304
+ lastNum: n - 1,
305
+ certNums, ocspNums, crlNums
306
+ };
307
+ }
308
+
309
+ return {
310
+ buildDss,
311
+ // Exposed for test wiring + helpers.
312
+ _refObj, _intObj, _nameObj, _strHexObj,
313
+ _arrayObj, _dictObj, _streamObj,
314
+ _sha1, _toHexUpper, _scanSignatureContentsHex, _pdfDate
315
+ };
316
+ }
317
+ };