pdf-lite 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 (206) hide show
  1. package/.commitlintrc.cjs +25 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  4. package/.github/workflows/docs.yaml +93 -0
  5. package/.github/workflows/prepare-release.yaml +79 -0
  6. package/.github/workflows/release.yaml +80 -0
  7. package/.github/workflows/test.yaml +35 -0
  8. package/.husky/commit-msg +1 -0
  9. package/.husky/pre-commit +1 -0
  10. package/.prettierignore +4 -0
  11. package/.prettierrc +4 -0
  12. package/CONTRIBUTING.md +109 -0
  13. package/EXAMPLES.md +1515 -0
  14. package/LICENSE +21 -0
  15. package/README.md +285 -0
  16. package/examples/001-create-pdf.ts +112 -0
  17. package/examples/002-create-encrypted-pdf.ts +121 -0
  18. package/examples/003-sign-pdf.ts +347 -0
  19. package/examples/004-incremental-update.ts +206 -0
  20. package/examples/005-modify-acroform.ts +374 -0
  21. package/examples/006-tokeniser-example.ts +131 -0
  22. package/examples/007-decoder-example.ts +197 -0
  23. package/package.json +72 -0
  24. package/packages/pdf-lite/README.md +3 -0
  25. package/packages/pdf-lite/package.json +68 -0
  26. package/packages/pdf-lite/scripts/create-encryption-tests.sh +41 -0
  27. package/packages/pdf-lite/scripts/gen-signing-keys.sh +290 -0
  28. package/packages/pdf-lite/scripts/generate-all-signing-keys.sh +70 -0
  29. package/packages/pdf-lite/src/core/decoder.ts +454 -0
  30. package/packages/pdf-lite/src/core/generators.ts +128 -0
  31. package/packages/pdf-lite/src/core/incremental-parser.ts +221 -0
  32. package/packages/pdf-lite/src/core/index.ts +2 -0
  33. package/packages/pdf-lite/src/core/objects/pdf-array.ts +54 -0
  34. package/packages/pdf-lite/src/core/objects/pdf-boolean.ts +19 -0
  35. package/packages/pdf-lite/src/core/objects/pdf-comment.ts +50 -0
  36. package/packages/pdf-lite/src/core/objects/pdf-date.ts +74 -0
  37. package/packages/pdf-lite/src/core/objects/pdf-dictionary.ts +171 -0
  38. package/packages/pdf-lite/src/core/objects/pdf-hexadecimal.ts +54 -0
  39. package/packages/pdf-lite/src/core/objects/pdf-indirect-object.ts +137 -0
  40. package/packages/pdf-lite/src/core/objects/pdf-name.ts +19 -0
  41. package/packages/pdf-lite/src/core/objects/pdf-null.ts +15 -0
  42. package/packages/pdf-lite/src/core/objects/pdf-number.ts +98 -0
  43. package/packages/pdf-lite/src/core/objects/pdf-object-reference.ts +30 -0
  44. package/packages/pdf-lite/src/core/objects/pdf-object.ts +107 -0
  45. package/packages/pdf-lite/src/core/objects/pdf-start-xref.ts +39 -0
  46. package/packages/pdf-lite/src/core/objects/pdf-stream.ts +687 -0
  47. package/packages/pdf-lite/src/core/objects/pdf-string.ts +38 -0
  48. package/packages/pdf-lite/src/core/objects/pdf-trailer.ts +57 -0
  49. package/packages/pdf-lite/src/core/objects/pdf-xref-table.ts +264 -0
  50. package/packages/pdf-lite/src/core/parser.ts +22 -0
  51. package/packages/pdf-lite/src/core/ref.ts +102 -0
  52. package/packages/pdf-lite/src/core/serializer.ts +68 -0
  53. package/packages/pdf-lite/src/core/streams/object-stream.ts +20 -0
  54. package/packages/pdf-lite/src/core/tokeniser.ts +687 -0
  55. package/packages/pdf-lite/src/core/tokens/boolean-token.ts +20 -0
  56. package/packages/pdf-lite/src/core/tokens/byte-offset-token.ts +20 -0
  57. package/packages/pdf-lite/src/core/tokens/comment-token.ts +32 -0
  58. package/packages/pdf-lite/src/core/tokens/end-array-token.ts +10 -0
  59. package/packages/pdf-lite/src/core/tokens/end-dictionary-token.ts +10 -0
  60. package/packages/pdf-lite/src/core/tokens/end-object-token.ts +10 -0
  61. package/packages/pdf-lite/src/core/tokens/end-stream-token.ts +11 -0
  62. package/packages/pdf-lite/src/core/tokens/hexadecimal-token.ts +22 -0
  63. package/packages/pdf-lite/src/core/tokens/name-token.ts +19 -0
  64. package/packages/pdf-lite/src/core/tokens/null-token.ts +9 -0
  65. package/packages/pdf-lite/src/core/tokens/number-token.ts +164 -0
  66. package/packages/pdf-lite/src/core/tokens/object-reference-token.ts +24 -0
  67. package/packages/pdf-lite/src/core/tokens/start-array-token.ts +10 -0
  68. package/packages/pdf-lite/src/core/tokens/start-dictionary-token.ts +10 -0
  69. package/packages/pdf-lite/src/core/tokens/start-object-token.ts +28 -0
  70. package/packages/pdf-lite/src/core/tokens/start-stream-token.ts +52 -0
  71. package/packages/pdf-lite/src/core/tokens/start-xref-token.ts +10 -0
  72. package/packages/pdf-lite/src/core/tokens/stream-chunk-token.ts +8 -0
  73. package/packages/pdf-lite/src/core/tokens/string-token.ts +17 -0
  74. package/packages/pdf-lite/src/core/tokens/token.ts +43 -0
  75. package/packages/pdf-lite/src/core/tokens/trailer-token.ts +12 -0
  76. package/packages/pdf-lite/src/core/tokens/whitespace-token.ts +43 -0
  77. package/packages/pdf-lite/src/core/tokens/xref-table-entry-token.ts +65 -0
  78. package/packages/pdf-lite/src/core/tokens/xref-table-section-start-token.ts +31 -0
  79. package/packages/pdf-lite/src/core/tokens/xref-table-start-token.ts +13 -0
  80. package/packages/pdf-lite/src/crypto/ciphers/aes128.ts +63 -0
  81. package/packages/pdf-lite/src/crypto/ciphers/aes256.ts +50 -0
  82. package/packages/pdf-lite/src/crypto/ciphers/rc4.ts +82 -0
  83. package/packages/pdf-lite/src/crypto/constants.ts +10 -0
  84. package/packages/pdf-lite/src/crypto/key-derivation/key-derivation-aes256.ts +213 -0
  85. package/packages/pdf-lite/src/crypto/key-derivation/key-derivation.ts +122 -0
  86. package/packages/pdf-lite/src/crypto/key-gen/key-gen-aes256.ts +79 -0
  87. package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-128.ts +190 -0
  88. package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-40.ts +129 -0
  89. package/packages/pdf-lite/src/crypto/types.ts +6 -0
  90. package/packages/pdf-lite/src/crypto/utils.ts +81 -0
  91. package/packages/pdf-lite/src/filters/ascii85.ts +128 -0
  92. package/packages/pdf-lite/src/filters/asciihex.ts +55 -0
  93. package/packages/pdf-lite/src/filters/flate.ts +39 -0
  94. package/packages/pdf-lite/src/filters/lzw.ts +144 -0
  95. package/packages/pdf-lite/src/filters/pass-through.ts +37 -0
  96. package/packages/pdf-lite/src/filters/runlength.ts +92 -0
  97. package/packages/pdf-lite/src/filters/types.ts +21 -0
  98. package/packages/pdf-lite/src/index.ts +4 -0
  99. package/packages/pdf-lite/src/pdf/errors.ts +5 -0
  100. package/packages/pdf-lite/src/pdf/index.ts +4 -0
  101. package/packages/pdf-lite/src/pdf/pdf-document.ts +924 -0
  102. package/packages/pdf-lite/src/pdf/pdf-reader.ts +57 -0
  103. package/packages/pdf-lite/src/pdf/pdf-revision.ts +234 -0
  104. package/packages/pdf-lite/src/pdf/pdf-xref-lookup.ts +527 -0
  105. package/packages/pdf-lite/src/security/crypt-filters/aesv2.ts +58 -0
  106. package/packages/pdf-lite/src/security/crypt-filters/aesv3.ts +56 -0
  107. package/packages/pdf-lite/src/security/crypt-filters/base.ts +140 -0
  108. package/packages/pdf-lite/src/security/crypt-filters/identity.ts +40 -0
  109. package/packages/pdf-lite/src/security/crypt-filters/v2.ts +59 -0
  110. package/packages/pdf-lite/src/security/handlers/base.ts +625 -0
  111. package/packages/pdf-lite/src/security/handlers/pubSec.ts +413 -0
  112. package/packages/pdf-lite/src/security/handlers/utils.ts +304 -0
  113. package/packages/pdf-lite/src/security/handlers/v1.ts +225 -0
  114. package/packages/pdf-lite/src/security/handlers/v2.ts +128 -0
  115. package/packages/pdf-lite/src/security/handlers/v4.ts +379 -0
  116. package/packages/pdf-lite/src/security/handlers/v5.ts +298 -0
  117. package/packages/pdf-lite/src/security/types.ts +158 -0
  118. package/packages/pdf-lite/src/signing/document-security-store.ts +224 -0
  119. package/packages/pdf-lite/src/signing/index.ts +3 -0
  120. package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-detached.ts +154 -0
  121. package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-sha1.ts +161 -0
  122. package/packages/pdf-lite/src/signing/signatures/adbe-x509-rsa-sha1.ts +106 -0
  123. package/packages/pdf-lite/src/signing/signatures/base.ts +229 -0
  124. package/packages/pdf-lite/src/signing/signatures/etsi-cades-detached.ts +229 -0
  125. package/packages/pdf-lite/src/signing/signatures/etsi-rfc3161.ts +92 -0
  126. package/packages/pdf-lite/src/signing/signatures/index.ts +6 -0
  127. package/packages/pdf-lite/src/signing/signer.ts +120 -0
  128. package/packages/pdf-lite/src/signing/types.ts +86 -0
  129. package/packages/pdf-lite/src/signing/utils.ts +71 -0
  130. package/packages/pdf-lite/src/types.ts +44 -0
  131. package/packages/pdf-lite/src/utils/IterableReadableStream.ts +30 -0
  132. package/packages/pdf-lite/src/utils/algos.ts +446 -0
  133. package/packages/pdf-lite/src/utils/assert.ts +42 -0
  134. package/packages/pdf-lite/src/utils/bytesToHex.ts +18 -0
  135. package/packages/pdf-lite/src/utils/bytesToHexBytes.ts +27 -0
  136. package/packages/pdf-lite/src/utils/bytesToString.ts +17 -0
  137. package/packages/pdf-lite/src/utils/concatUint8Arrays.ts +26 -0
  138. package/packages/pdf-lite/src/utils/escapeString.ts +49 -0
  139. package/packages/pdf-lite/src/utils/hexBytesToBytes.ts +22 -0
  140. package/packages/pdf-lite/src/utils/hexBytesToString.ts +21 -0
  141. package/packages/pdf-lite/src/utils/hexToBytes.ts +18 -0
  142. package/packages/pdf-lite/src/utils/padBytes.ts +25 -0
  143. package/packages/pdf-lite/src/utils/predictors.ts +332 -0
  144. package/packages/pdf-lite/src/utils/replaceInBuffer.ts +56 -0
  145. package/packages/pdf-lite/src/utils/stringToBytes.ts +22 -0
  146. package/packages/pdf-lite/src/utils/stringToHexBytes.ts +23 -0
  147. package/packages/pdf-lite/src/utils/unescapeString.ts +123 -0
  148. package/packages/pdf-lite/test/acceptance/__snapshots__/versions.node.test.ts.snap +60766 -0
  149. package/packages/pdf-lite/test/acceptance/fixtures/1.3/basic.pdf +0 -0
  150. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-128.pdf +0 -0
  151. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-256.pdf +0 -0
  152. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-128.pdf +0 -0
  153. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-40.pdf +0 -0
  154. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic.pdf +0 -0
  155. package/packages/pdf-lite/test/acceptance/fixtures/1.5/basic.pdf +0 -0
  156. package/packages/pdf-lite/test/acceptance/fixtures/1.6/basic.pdf +0 -0
  157. package/packages/pdf-lite/test/acceptance/fixtures/1.7/basic.pdf +0 -0
  158. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-128.pdf +43 -0
  159. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-256.pdf +43 -0
  160. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-128.pdf +43 -0
  161. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-40.pdf +44 -0
  162. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic.pdf +79 -0
  163. package/packages/pdf-lite/test/acceptance/versions.node.test.ts +41 -0
  164. package/packages/pdf-lite/test/unit/__snapshots__/decoder.node.test.ts.snap +86947 -0
  165. package/packages/pdf-lite/test/unit/__snapshots__/tokeniser.node.test.ts.snap +131829 -0
  166. package/packages/pdf-lite/test/unit/ciphers.test.ts +61 -0
  167. package/packages/pdf-lite/test/unit/decoder.node.test.ts +21 -0
  168. package/packages/pdf-lite/test/unit/decoder.test.ts +567 -0
  169. package/packages/pdf-lite/test/unit/filters.test.ts +67 -0
  170. package/packages/pdf-lite/test/unit/fixtures/basic.pdf +0 -0
  171. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-128.pdf +0 -0
  172. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-256.pdf +0 -0
  173. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-128.pdf +0 -0
  174. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-40.pdf +43 -0
  175. package/packages/pdf-lite/test/unit/fixtures/protectedAdobeLivecycle.pdf +0 -0
  176. package/packages/pdf-lite/test/unit/fixtures/rsa-2048/index.ts +187 -0
  177. package/packages/pdf-lite/test/unit/fixtures/template.pdf +0 -0
  178. package/packages/pdf-lite/test/unit/incremental-update.test.ts +0 -0
  179. package/packages/pdf-lite/test/unit/objects.test.ts +0 -0
  180. package/packages/pdf-lite/test/unit/pdf-document-signing.test.ts +0 -0
  181. package/packages/pdf-lite/test/unit/pdf-revision.test.ts +195 -0
  182. package/packages/pdf-lite/test/unit/pdf.browser.test.ts +0 -0
  183. package/packages/pdf-lite/test/unit/predictors.test.ts +226 -0
  184. package/packages/pdf-lite/test/unit/ref.test.ts +158 -0
  185. package/packages/pdf-lite/test/unit/security-handlers.test.ts +645 -0
  186. package/packages/pdf-lite/test/unit/serializer.test.ts +81 -0
  187. package/packages/pdf-lite/test/unit/signature-objects.test.ts +814 -0
  188. package/packages/pdf-lite/test/unit/string-escaping.test.ts +84 -0
  189. package/packages/pdf-lite/test/unit/tokeniser.node.test.ts +38 -0
  190. package/packages/pdf-lite/test/unit/tokeniser.test.ts +1213 -0
  191. package/packages/pdf-lite/test/unit/utils.test.ts +248 -0
  192. package/packages/pdf-lite/test/unit/xref-lookup.test.ts +72 -0
  193. package/packages/pdf-lite/tsconfig.json +4 -0
  194. package/packages/pdf-lite/tsconfig.prod.json +8 -0
  195. package/packages/pdf-lite/typedoc.json +14 -0
  196. package/packages/pdf-lite/vitest.config.ts +43 -0
  197. package/pnpm-workspace.yaml +2 -0
  198. package/renovate.json +34 -0
  199. package/scripts/build-examples.ts +30 -0
  200. package/scripts/bump-version.sh +56 -0
  201. package/scripts/gen-html-docs.sh +21 -0
  202. package/scripts/gen-md-docs.sh +15 -0
  203. package/scripts/prepare-release.sh +33 -0
  204. package/tsconfig.json +22 -0
  205. package/tsconfig.prod.json +12 -0
  206. package/typedoc.json +34 -0
@@ -0,0 +1,645 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { rsaSigningKeys } from './fixtures/rsa-2048'
3
+ import { PdfV1SecurityHandler } from '../../src/security/handlers/v1'
4
+ import { PdfV2SecurityHandler } from '../../src/security/handlers/v2'
5
+ import { PdfV4SecurityHandler } from '../../src/security/handlers/v4'
6
+ import { PdfV5SecurityHandler } from '../../src/security/handlers/v5'
7
+ import { PdfPublicKeySecurityHandler } from '../../src/security/handlers/pubSec'
8
+ import { PdfEncryptionDictionary } from '../../src/security/types'
9
+ import { createFromDictionary } from '../../src/security/handlers/utils'
10
+ import { PdfSecurityHandler } from '../../src/security/handlers/base'
11
+ import { AesV2CryptFilter } from '../../src/security/crypt-filters/aesv2'
12
+ import { PdfCryptFilter } from '../../src/security/crypt-filters/base'
13
+ import { V2CryptFilter } from '../../src/security/crypt-filters/v2'
14
+ import { AesV3CryptFilter } from '../../src/security/crypt-filters/aesv3'
15
+ import { PdfDictionary } from '../../src/core/objects/pdf-dictionary'
16
+ import { PdfName } from '../../src/core/objects/pdf-name'
17
+ import { PdfNumber } from '../../src/core/objects/pdf-number'
18
+ import { PdfHexadecimal } from '../../src/core/objects/pdf-hexadecimal'
19
+ import { PdfArray } from '../../src/core/objects/pdf-array'
20
+ import { ByteArray } from '../../src/types'
21
+ import { stringToBytes } from '../../src/utils/stringToBytes'
22
+
23
+ describe('Security Handlers', () => {
24
+ const handlerSettings: {
25
+ [key: string]: {
26
+ handler: new (options: any) => PdfSecurityHandler
27
+ r: number
28
+ v: number
29
+ length: number
30
+ filter: `Standard` | `Adobe.PubSec`
31
+ recipients?: ByteArray[]
32
+ testCryptFilters?: boolean
33
+ }
34
+ } = {
35
+ 'RC4-40': {
36
+ handler: PdfV1SecurityHandler,
37
+ r: 2,
38
+ v: 1,
39
+ length: 40,
40
+ filter: 'Standard',
41
+ },
42
+ 'RC4-128': {
43
+ handler: PdfV2SecurityHandler,
44
+ r: 3,
45
+ v: 2,
46
+ length: 128,
47
+ filter: 'Standard',
48
+ },
49
+ 'AES-128': {
50
+ handler: PdfV4SecurityHandler,
51
+ r: 4,
52
+ v: 4,
53
+ length: 128,
54
+ filter: 'Standard',
55
+ testCryptFilters: true,
56
+ },
57
+ 'AES-256': {
58
+ handler: PdfV5SecurityHandler,
59
+ r: 5,
60
+ v: 5,
61
+ length: 256,
62
+ filter: 'Standard',
63
+ testCryptFilters: true,
64
+ },
65
+ PubSec: {
66
+ handler: PdfPublicKeySecurityHandler,
67
+ r: 5,
68
+ v: 5,
69
+ filter: 'Adobe.PubSec',
70
+ length: 256,
71
+ recipients: [new Uint8Array([])],
72
+ testCryptFilters: true,
73
+ },
74
+ }
75
+
76
+ describe('Create Handler from Dictionary', () => {
77
+ for (const [name, settings] of Object.entries(handlerSettings)) {
78
+ it(`${name}: should create handler from dictionary`, async () => {
79
+ const dict: PdfEncryptionDictionary = new PdfDictionary({
80
+ Filter: new PdfName(settings.filter),
81
+ V: new PdfNumber(settings.v),
82
+ R: new PdfNumber(settings.r),
83
+ O: new PdfHexadecimal(
84
+ new Uint8Array([
85
+ 0x4c, 0x5c, 0x7a, 0x1c, 0x3f, 0x3d, 0x55, 0x4e,
86
+ 0x5f, 0x4b, 0x6c, 0x6f, 0x6d, 0x2c, 0x7a, 0x1c,
87
+ 0x3f, 0x3d, 0x55, 0x4e, 0x5f, 0x4b, 0x6c, 0x6f,
88
+ 0x6d, 0x2c, 0x7a, 0x1c, 0x3f, 0x3d, 0x55, 0x4e,
89
+ 0x5f, 0x4b, 0x6c, 0x6f, 0x6d, 0x2c,
90
+ ]),
91
+ ),
92
+ U: new PdfHexadecimal(
93
+ new Uint8Array([
94
+ 0x28, 0x3c, 0x4e, 0x5f, 0x4b, 0x6c, 0x6f, 0x6d,
95
+ 0x2c, 0x7a, 0x1c, 0x3f, 0x3d, 0x55, 0x4e, 0x5f,
96
+ 0x4b, 0x6c, 0x6f, 0x6d, 0x2c,
97
+ ]),
98
+ ),
99
+ UE: new PdfHexadecimal(
100
+ new Uint8Array([
101
+ 0x28, 0x3c, 0x4e, 0x5f, 0x4b, 0x6c, 0x6f, 0x6d,
102
+ 0x2c, 0x7a, 0x1c, 0x3f, 0x3d, 0x55, 0x4e, 0x5f,
103
+ 0x4b, 0x6c, 0x6f, 0x6d, 0x2c,
104
+ ]),
105
+ ),
106
+ OE: new PdfHexadecimal(
107
+ new Uint8Array([
108
+ 0x28, 0x3c, 0x4e, 0x5f, 0x4b, 0x6c, 0x6f, 0x6d,
109
+ 0x2c, 0x7a, 0x1c, 0x3f, 0x3d, 0x55, 0x4e, 0x5f,
110
+ 0x4b, 0x6c, 0x6f, 0x6d, 0x2c,
111
+ ]),
112
+ ),
113
+ Perms: new PdfHexadecimal(
114
+ new Uint8Array([
115
+ 0x28, 0x3c, 0x4e, 0x5f, 0x4b, 0x6c, 0x6f, 0x6d,
116
+ 0x2c, 0x7a, 0x1c, 0x3f, 0x3d, 0x55, 0x4e, 0x5f,
117
+ 0x4b, 0x6c, 0x6f, 0x6d, 0x2c,
118
+ ]),
119
+ ),
120
+ P: new PdfNumber(-44),
121
+ Recipients: settings.recipients
122
+ ? new PdfArray(
123
+ settings.recipients.map(
124
+ (r) => new PdfHexadecimal(r),
125
+ ),
126
+ )
127
+ : undefined,
128
+ Length: new PdfNumber(settings.length),
129
+ })
130
+
131
+ const handler = await createFromDictionary(dict, {
132
+ password: 'userpassword',
133
+ recipients:
134
+ settings.filter === 'Adobe.PubSec'
135
+ ? [
136
+ {
137
+ certificate: rsaSigningKeys.cert,
138
+ },
139
+ ]
140
+ : [],
141
+ })
142
+
143
+ expect(handler).toBeInstanceOf(settings.handler)
144
+ })
145
+
146
+ it(`${name}: should encrypt and decrypt data correctly`, async () => {
147
+ const data = stringToBytes('Hello, World!')
148
+ const objectNumber = 1
149
+ const generationNumber = 0
150
+ const password = 'userpassword'
151
+
152
+ const HandlerClass = settings.handler
153
+ const handler = new HandlerClass({
154
+ password,
155
+ documentId: 'test',
156
+ recipients:
157
+ settings.filter === 'Adobe.PubSec'
158
+ ? [
159
+ {
160
+ certificate: rsaSigningKeys.cert,
161
+ },
162
+ ]
163
+ : [],
164
+ })
165
+
166
+ await handler.write()
167
+
168
+ const dict = handler.dict
169
+ const parsedHandler = await createFromDictionary(dict, {
170
+ password,
171
+ recipients:
172
+ settings.filter === 'Adobe.PubSec'
173
+ ? [
174
+ {
175
+ certificate: rsaSigningKeys.cert,
176
+ privateKey: rsaSigningKeys.privateKey,
177
+ },
178
+ ]
179
+ : [],
180
+ })
181
+
182
+ expect(parsedHandler).toBeInstanceOf(settings.handler)
183
+ const encryptedData = await handler.encrypt(
184
+ 'stream',
185
+ data,
186
+ objectNumber,
187
+ generationNumber,
188
+ )
189
+ const decryptedData = await parsedHandler.decrypt(
190
+ 'stream',
191
+ encryptedData,
192
+ objectNumber,
193
+ generationNumber,
194
+ )
195
+ expect(new TextDecoder().decode(decryptedData)).toBe(
196
+ 'Hello, World!',
197
+ )
198
+ })
199
+
200
+ it(`${name}: should work with owner password`, async () => {
201
+ if (settings.filter === 'Adobe.PubSec') {
202
+ // Skip owner password test for PubSec handlers as they don't use owner passwords
203
+ return
204
+ }
205
+
206
+ const data = stringToBytes('Hello, World!')
207
+ const objectNumber = 1
208
+ const generationNumber = 0
209
+ const userPassword = 'userpassword'
210
+ const ownerPassword = 'ownerpassword'
211
+
212
+ const HandlerClass = settings.handler
213
+ const handler = new HandlerClass({
214
+ password: userPassword,
215
+ ownerPassword: ownerPassword,
216
+ documentId: 'test',
217
+ })
218
+
219
+ await handler.write()
220
+
221
+ const dict = handler.dict
222
+
223
+ // Test that owner password can decrypt
224
+ const ownerHandler = await createFromDictionary(dict, {
225
+ ownerPassword: ownerPassword,
226
+ })
227
+
228
+ expect(ownerHandler).toBeInstanceOf(settings.handler)
229
+
230
+ const encryptedData = await handler.encrypt(
231
+ 'stream',
232
+ data,
233
+ objectNumber,
234
+ generationNumber,
235
+ )
236
+ const decryptedDataWithOwner = await ownerHandler.decrypt(
237
+ 'stream',
238
+ encryptedData,
239
+ objectNumber,
240
+ generationNumber,
241
+ )
242
+ expect(new TextDecoder().decode(decryptedDataWithOwner)).toBe(
243
+ 'Hello, World!',
244
+ )
245
+
246
+ // Test that user password can also decrypt
247
+ const userHandler = await createFromDictionary(dict, {
248
+ password: userPassword,
249
+ })
250
+
251
+ expect(userHandler).toBeInstanceOf(settings.handler)
252
+
253
+ const decryptedDataWithUser = await userHandler.decrypt(
254
+ 'stream',
255
+ encryptedData,
256
+ objectNumber,
257
+ generationNumber,
258
+ )
259
+ expect(new TextDecoder().decode(decryptedDataWithUser)).toBe(
260
+ 'Hello, World!',
261
+ )
262
+ })
263
+
264
+ it(`${name}: should fail with incorrect password`, async () => {
265
+ if (settings.filter === 'Adobe.PubSec') {
266
+ // Skip password test for PubSec handlers as they use certificates
267
+ return
268
+ }
269
+
270
+ const userPassword = 'userpassword'
271
+ const wrongPassword = 'wrongpassword'
272
+
273
+ const HandlerClass = settings.handler
274
+ const handler = new HandlerClass({
275
+ password: userPassword,
276
+ documentId: 'test',
277
+ })
278
+
279
+ await handler.write()
280
+
281
+ const dict = handler.dict
282
+
283
+ // Test that wrong password fails
284
+ await expect(
285
+ createFromDictionary(dict, {
286
+ password: wrongPassword,
287
+ }).computeObjectKey(),
288
+ ).rejects.toThrow()
289
+ })
290
+
291
+ if (settings.testCryptFilters) {
292
+ it(`${name}: should support Identity crypt filter meaning no encryption`, async () => {
293
+ const userPassword = 'userpassword'
294
+ const handler = new handlerSettings[name].handler({
295
+ password: userPassword,
296
+ documentId: 'test',
297
+ recipients:
298
+ settings.filter === 'Adobe.PubSec'
299
+ ? [
300
+ {
301
+ certificate: rsaSigningKeys.cert,
302
+ privateKey: rsaSigningKeys.privateKey,
303
+ },
304
+ ]
305
+ : [],
306
+ })
307
+
308
+ if (
309
+ !(handler instanceof PdfV4SecurityHandler) &&
310
+ !(handler instanceof PdfPublicKeySecurityHandler)
311
+ ) {
312
+ throw new Error(
313
+ 'Handler is not a PdfV4SecurityHandler or PdfPublicKeySecurityHandler',
314
+ )
315
+ }
316
+
317
+ if (handler instanceof PdfPublicKeySecurityHandler) {
318
+ const standardHandler =
319
+ handler.getStandardSecurityHandler()
320
+ if (
321
+ !(standardHandler instanceof PdfV4SecurityHandler)
322
+ ) {
323
+ throw new Error(
324
+ 'Standard handler is not a PdfV4SecurityHandler',
325
+ )
326
+ }
327
+ standardHandler.setCryptFilterForType(
328
+ 'stream',
329
+ 'Identity',
330
+ )
331
+ } else {
332
+ handler.setCryptFilterForType('stream', 'Identity')
333
+ }
334
+
335
+ const data = stringToBytes('Hello, World!')
336
+ const objectNumber = 1
337
+ const generationNumber = 0
338
+
339
+ await handler.write()
340
+
341
+ const dict = handler.dict
342
+ expect(
343
+ dict.values['CF']?.values['Identity'],
344
+ ).not.toBeDefined() // Identity filter should not be listed in /CF
345
+ const parsedHandler = await createFromDictionary(dict, {
346
+ password: userPassword,
347
+ recipients:
348
+ settings.filter === 'Adobe.PubSec'
349
+ ? [
350
+ {
351
+ certificate: rsaSigningKeys.cert,
352
+ privateKey: rsaSigningKeys.privateKey,
353
+ },
354
+ ]
355
+ : [],
356
+ })
357
+
358
+ expect(parsedHandler).toBeInstanceOf(settings.handler)
359
+
360
+ const encryptedData = await handler.encrypt(
361
+ 'stream',
362
+ data,
363
+ objectNumber,
364
+ generationNumber,
365
+ )
366
+
367
+ // Data should be unchanged as /Identity means no encryption
368
+ expect(encryptedData).toEqual(data)
369
+
370
+ const decryptedData = await parsedHandler.decrypt(
371
+ 'stream',
372
+ encryptedData,
373
+ objectNumber,
374
+ generationNumber,
375
+ )
376
+
377
+ expect(new TextDecoder().decode(decryptedData)).toBe(
378
+ 'Hello, World!',
379
+ )
380
+ })
381
+
382
+ const cryptFilters: PdfCryptFilter[] = [
383
+ new V2CryptFilter({ authEvent: 'DocOpen' }),
384
+ ]
385
+
386
+ if (name !== 'RC4-40') {
387
+ cryptFilters.push(
388
+ new AesV2CryptFilter({ authEvent: 'DocOpen' }),
389
+ )
390
+ cryptFilters.push(
391
+ new AesV3CryptFilter({ authEvent: 'EFOpen' }),
392
+ )
393
+ }
394
+
395
+ for (const cf of cryptFilters) {
396
+ it(`${name}: should support ${cf.constructor.name} crypt filter`, async () => {
397
+ const userPassword = 'userpassword'
398
+ const handler = new handlerSettings[name].handler({
399
+ password: userPassword,
400
+ documentId: 'test',
401
+ recipients:
402
+ settings.filter === 'Adobe.PubSec'
403
+ ? [
404
+ {
405
+ certificate: rsaSigningKeys.cert,
406
+ privateKey:
407
+ rsaSigningKeys.privateKey,
408
+ },
409
+ ]
410
+ : [],
411
+ })
412
+
413
+ if (
414
+ !(handler instanceof PdfV4SecurityHandler) &&
415
+ !(handler instanceof PdfPublicKeySecurityHandler)
416
+ ) {
417
+ throw new Error(
418
+ 'Handler is not a PdfV4SecurityHandler or PdfPublicKeySecurityHandler',
419
+ )
420
+ }
421
+
422
+ cf.encrypt = vi.fn(cf.encrypt)
423
+ cf.decrypt = vi.fn(cf.decrypt)
424
+
425
+ if (handler instanceof PdfPublicKeySecurityHandler) {
426
+ const standardHandler =
427
+ handler.getStandardSecurityHandler()
428
+ if (
429
+ !(
430
+ standardHandler instanceof
431
+ PdfV4SecurityHandler
432
+ )
433
+ ) {
434
+ throw new Error(
435
+ 'Standard handler is not a PdfV4SecurityHandler',
436
+ )
437
+ }
438
+ standardHandler.setCryptFilter('StdCF', cf)
439
+ standardHandler.setCryptFilterForType(
440
+ 'stream',
441
+ 'StdCF',
442
+ )
443
+ } else {
444
+ handler.setCryptFilter('StdCF', cf)
445
+ handler.setCryptFilterForType('stream', 'StdCF')
446
+ }
447
+
448
+ const data = stringToBytes('Hello, World!')
449
+ const objectNumber = 1
450
+ const generationNumber = 0
451
+
452
+ await handler.write()
453
+ const dict = handler.dict
454
+ const parsedHandler = await createFromDictionary(dict, {
455
+ password: userPassword,
456
+ recipients:
457
+ settings.filter === 'Adobe.PubSec'
458
+ ? [
459
+ {
460
+ certificate: rsaSigningKeys.cert,
461
+ privateKey:
462
+ rsaSigningKeys.privateKey,
463
+ },
464
+ ]
465
+ : [],
466
+ })
467
+
468
+ expect(parsedHandler).toBeInstanceOf(settings.handler)
469
+
470
+ const encryptedData = await handler.encrypt(
471
+ 'stream',
472
+ data,
473
+ objectNumber,
474
+ generationNumber,
475
+ )
476
+
477
+ const decryptedData = await parsedHandler.decrypt(
478
+ 'stream',
479
+ encryptedData,
480
+ objectNumber,
481
+ generationNumber,
482
+ )
483
+
484
+ expect(new TextDecoder().decode(decryptedData)).toBe(
485
+ 'Hello, World!',
486
+ )
487
+ expect(cf.encrypt).toHaveBeenCalled()
488
+ })
489
+ }
490
+ }
491
+ }
492
+ })
493
+
494
+ describe('Owner Password Logic', () => {
495
+ it('should derive user password from owner password correctly', async () => {
496
+ const userPassword = 'user123'
497
+ const ownerPassword = 'owner456'
498
+
499
+ // Test with V2 handler (RC4-128)
500
+ const handler = new PdfV2SecurityHandler({
501
+ password: userPassword,
502
+ ownerPassword: ownerPassword,
503
+ documentId: 'test',
504
+ })
505
+
506
+ await handler.write()
507
+ const dict = handler.dict
508
+
509
+ // Create handler using owner password - it should be able to derive the user password
510
+ const ownerHandler = await createFromDictionary(dict, {
511
+ ownerPassword: ownerPassword,
512
+ })
513
+
514
+ expect(ownerHandler).toBeInstanceOf(PdfV2SecurityHandler)
515
+
516
+ // Test encryption/decryption works
517
+ const data = stringToBytes('Test data')
518
+ const encryptedData = await handler.encrypt('stream', data, 1, 0)
519
+ const decryptedData = await ownerHandler.decrypt(
520
+ 'stream',
521
+ encryptedData,
522
+ 1,
523
+ 0,
524
+ )
525
+
526
+ expect(new TextDecoder().decode(decryptedData)).toBe('Test data')
527
+ })
528
+
529
+ it('should handle owner password same as user password', async () => {
530
+ const password = 'samepassword'
531
+
532
+ // Test when owner password is the same as user password
533
+ const handler = new PdfV2SecurityHandler({
534
+ password: password,
535
+ ownerPassword: password,
536
+ documentId: 'test',
537
+ })
538
+
539
+ await handler.write()
540
+ const dict = handler.dict
541
+
542
+ // Should work with the same password
543
+ const samePasswordHandler = await createFromDictionary(dict, {
544
+ password: password,
545
+ })
546
+
547
+ expect(samePasswordHandler).toBeInstanceOf(PdfV2SecurityHandler)
548
+
549
+ // Test encryption/decryption works
550
+ const data = stringToBytes('Same password test')
551
+ const encryptedData = await handler.encrypt('stream', data, 1, 0)
552
+ const decryptedData = await samePasswordHandler.decrypt(
553
+ 'stream',
554
+ encryptedData,
555
+ 1,
556
+ 0,
557
+ )
558
+
559
+ expect(new TextDecoder().decode(decryptedData)).toBe(
560
+ 'Same password test',
561
+ )
562
+ })
563
+
564
+ it('should handle empty owner password (defaults to user password)', async () => {
565
+ const userPassword = 'userpass'
566
+
567
+ // Test when no owner password is provided (should default to user password)
568
+ const handler = new PdfV2SecurityHandler({
569
+ password: userPassword,
570
+ documentId: 'test',
571
+ })
572
+
573
+ await handler.write()
574
+ const dict = handler.dict
575
+
576
+ // Should work with user password as owner password
577
+ const userAsOwnerHandler = await createFromDictionary(dict, {
578
+ password: userPassword,
579
+ })
580
+
581
+ expect(userAsOwnerHandler).toBeInstanceOf(PdfV2SecurityHandler)
582
+
583
+ // Test encryption/decryption works
584
+ const data = stringToBytes('Default owner password test')
585
+ const encryptedData = await handler.encrypt('stream', data, 1, 0)
586
+ const decryptedData = await userAsOwnerHandler.decrypt(
587
+ 'stream',
588
+ encryptedData,
589
+ 1,
590
+ 0,
591
+ )
592
+
593
+ expect(new TextDecoder().decode(decryptedData)).toBe(
594
+ 'Default owner password test',
595
+ )
596
+ })
597
+ })
598
+ })
599
+
600
+ describe('PubSec Security Handler', () => {
601
+ it('should encrypt and decrypt data correctly', async () => {
602
+ const standardHandler = new PdfV4SecurityHandler({
603
+ documentId: 'test',
604
+ })
605
+
606
+ const encryptionHandler = new PdfPublicKeySecurityHandler({
607
+ standardSecurityHandler: standardHandler,
608
+ recipients: [
609
+ {
610
+ certificate: rsaSigningKeys.cert,
611
+ },
612
+ ],
613
+ })
614
+
615
+ const decryptionHandler = new PdfPublicKeySecurityHandler({
616
+ standardSecurityHandler: standardHandler,
617
+ recipients: [
618
+ {
619
+ certificate: rsaSigningKeys.cert,
620
+ privateKey: rsaSigningKeys.privateKey,
621
+ },
622
+ ],
623
+ })
624
+
625
+ await encryptionHandler.write()
626
+ const dict = encryptionHandler.dict
627
+ decryptionHandler.readEncryptionDictionary(dict)
628
+
629
+ const data = stringToBytes('Hello, World!')
630
+ const encryptedData = await encryptionHandler.encrypt(
631
+ 'stream',
632
+ data,
633
+ 1,
634
+ 0,
635
+ )
636
+ const decryptedData = await decryptionHandler.decrypt(
637
+ 'stream',
638
+ encryptedData,
639
+ 1,
640
+ 0,
641
+ )
642
+
643
+ expect(new TextDecoder().decode(decryptedData)).toBe('Hello, World!')
644
+ })
645
+ })