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,190 @@
1
+ import { ByteArray } from '../../types'
2
+ import { md5 } from '../../utils/algos'
3
+ import { concatUint8Arrays } from '../../utils/concatUint8Arrays'
4
+ import { rc4 } from '../ciphers/rc4'
5
+ import { DEFAULT_PADDING } from '../constants'
6
+ import { padPassword } from '../key-derivation/key-derivation'
7
+ import { int32ToLittleEndianBytes, removePdfPasswordPadding } from '../utils'
8
+
9
+ /**
10
+ * Encrypts data with RC4 using the provided key.
11
+ *
12
+ * @param key - The encryption key.
13
+ * @param data - The data to encrypt.
14
+ * @returns A promise that resolves to the encrypted data.
15
+ */
16
+ async function rc4EncryptWithKey(
17
+ key: ByteArray,
18
+ data: ByteArray,
19
+ ): Promise<ByteArray> {
20
+ return await rc4(key).encrypt(data)
21
+ }
22
+
23
+ /**
24
+ * Decrypts the user password from the /O value using RC4-128.
25
+ * Used to recover the user password when the owner password is known.
26
+ *
27
+ * @param ownerPw - The owner password.
28
+ * @param ownerKey - The /O value from the encryption dictionary.
29
+ * @returns A promise that resolves to the decrypted user password (with padding removed).
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const userPassword = await decryptUserPasswordRc4_128(ownerPw, O)
34
+ * ```
35
+ */
36
+ export async function decryptUserPasswordRc4_128(
37
+ ownerPw: ByteArray,
38
+ ownerKey: ByteArray,
39
+ ): Promise<ByteArray> {
40
+ const ownerPad = padPassword(ownerPw)
41
+ let digest = await md5(ownerPad)
42
+
43
+ for (let i = 0; i < 50; i++) {
44
+ digest = await md5(digest)
45
+ }
46
+
47
+ const key = digest.subarray(0, 16)
48
+ let data = ownerKey
49
+ for (let i = 0; i < 20; i++) {
50
+ const roundKey = key.map((b) => b ^ i)
51
+ data = await rc4EncryptWithKey(roundKey, data)
52
+ }
53
+
54
+ return removePdfPasswordPadding(data)
55
+ }
56
+
57
+ /**
58
+ * Computes the /O value for RC4-128 PDF encryption.
59
+ * The /O value is used to verify the owner password.
60
+ *
61
+ * @param ownerPassword - The owner password.
62
+ * @param userPassword - The user password.
63
+ * @returns A promise that resolves to the 32-byte /O value.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * const O = await computeOValueRc4_128(ownerPassword, userPassword)
68
+ * ```
69
+ */
70
+ export async function computeOValueRc4_128(
71
+ ownerPassword: ByteArray,
72
+ userPassword: ByteArray,
73
+ ): Promise<ByteArray> {
74
+ const oPad = padPassword(ownerPassword)
75
+
76
+ let digest = await md5(oPad)
77
+ for (let i = 0; i < 50; i++) {
78
+ digest = await md5(digest)
79
+ }
80
+
81
+ const key = digest.subarray(0, 16)
82
+
83
+ const uPad = padPassword(userPassword)
84
+ let data = await rc4EncryptWithKey(key, uPad)
85
+
86
+ for (let i = 1; i <= 19; i++) {
87
+ const roundKey = key.map((b) => b ^ i)
88
+ data = await rc4EncryptWithKey(roundKey, data)
89
+ }
90
+
91
+ return data
92
+ }
93
+
94
+ /**
95
+ * Computes the 128-bit encryption key for RC4-128 PDF encryption.
96
+ *
97
+ * @param userPad - The padded user password.
98
+ * @param oValue - The /O value.
99
+ * @param permissions - The /P value (permissions flags).
100
+ * @param id - The first element of the /ID array.
101
+ * @param encryptMetadata - Whether to encrypt metadata.
102
+ * @param revision - The encryption revision number.
103
+ * @returns A promise that resolves to the 16-byte encryption key.
104
+ */
105
+ async function computeEncryptionKeyRc4_128(
106
+ userPad: ByteArray,
107
+ oValue: ByteArray,
108
+ permissions: number,
109
+ id: ByteArray,
110
+ encryptMetadata: boolean,
111
+ revision: number = 3,
112
+ ): Promise<ByteArray> {
113
+ const perms = int32ToLittleEndianBytes(permissions)
114
+
115
+ let digest = await md5(
116
+ concatUint8Arrays(
117
+ userPad,
118
+ oValue,
119
+ perms,
120
+ id,
121
+ revision >= 4 && !encryptMetadata
122
+ ? new Uint8Array([0xff, 0xff, 0xff, 0xff])
123
+ : new Uint8Array(),
124
+ ),
125
+ )
126
+
127
+ // 50 iterations
128
+ for (let i = 0; i < 50; i++) {
129
+ digest = await md5(digest)
130
+ }
131
+
132
+ return digest.subarray(0, 16) // RC4-128
133
+ }
134
+
135
+ /**
136
+ * Computes the /U value for RC4-128 PDF encryption.
137
+ * The /U value is used to verify the user password.
138
+ *
139
+ * @param userPassword - The user password.
140
+ * @param oValue - The /O value.
141
+ * @param permissions - The /P value (permissions flags).
142
+ * @param id - The first element of the /ID array.
143
+ * @param encryptMetadata - Whether to encrypt metadata.
144
+ * @param revision - The encryption revision number.
145
+ * @returns A promise that resolves to the 32-byte /U value.
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * const U = await computeUValueRc4_128(userPassword, O, permissions, fileId, true)
150
+ * ```
151
+ */
152
+ export async function computeUValueRc4_128(
153
+ userPassword: ByteArray,
154
+ oValue: ByteArray,
155
+ permissions: number,
156
+ id: ByteArray,
157
+ encryptMetadata: boolean,
158
+ revision?: number,
159
+ ): Promise<ByteArray> {
160
+ const userPad = padPassword(userPassword)
161
+
162
+ // Step 1: Compute encryption key
163
+ const encryptionKey = await computeEncryptionKeyRc4_128(
164
+ userPad,
165
+ oValue,
166
+ permissions,
167
+ id,
168
+ encryptMetadata,
169
+ revision,
170
+ )
171
+
172
+ // Step 2 & 3: MD5 of padding + file ID
173
+ const hash = await md5(concatUint8Arrays(DEFAULT_PADDING, id)) // 16 bytes
174
+
175
+ // Step 4: First RC4 encrypt with base key
176
+ let data = await rc4EncryptWithKey(encryptionKey, hash)
177
+
178
+ // Step 5: 19 more rounds of RC4 with key XOR i
179
+ for (let i = 1; i <= 19; i++) {
180
+ const roundKey = encryptionKey.map((b) => b ^ i)
181
+ data = await rc4EncryptWithKey(roundKey, data)
182
+ }
183
+
184
+ // Step 6: Append 16 more bytes (usually same padding again, or random)
185
+ const result = new Uint8Array(32)
186
+ result.set(data, 0)
187
+ result.set(DEFAULT_PADDING.subarray(0, 16), 16)
188
+
189
+ return result
190
+ }
@@ -0,0 +1,129 @@
1
+ import { padPassword } from '../key-derivation/key-derivation.js'
2
+ import { rc4 } from '../ciphers/rc4.js'
3
+ import { DEFAULT_PADDING } from '../constants.js'
4
+ import { int32ToLittleEndianBytes, removePdfPasswordPadding } from '../utils.js'
5
+ import { md5 } from '../../utils/algos.js'
6
+ import { concatUint8Arrays } from '../../utils/concatUint8Arrays.js'
7
+ import { ByteArray } from '../../types.js'
8
+
9
+ /**
10
+ * Encrypts data with RC4 using the provided key.
11
+ *
12
+ * @param key - The encryption key.
13
+ * @param data - The data to encrypt.
14
+ * @returns A promise that resolves to the encrypted data.
15
+ */
16
+ async function rc4EncryptWithKey(
17
+ key: ByteArray,
18
+ data: ByteArray,
19
+ ): Promise<ByteArray> {
20
+ return await rc4(key).encrypt(data)
21
+ }
22
+
23
+ /**
24
+ * Decrypts the user password from the /O value using RC4-40.
25
+ * Used to recover the user password when the owner password is known.
26
+ *
27
+ * @param ownerPw - The owner password.
28
+ * @param ownerKey - The /O value from the encryption dictionary.
29
+ * @returns A promise that resolves to the decrypted user password (with padding removed).
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const userPassword = await decryptUserPasswordRc4_40(ownerPw, O)
34
+ * ```
35
+ */
36
+ export async function decryptUserPasswordRc4_40(
37
+ ownerPw: ByteArray,
38
+ ownerKey: ByteArray,
39
+ ): Promise<ByteArray> {
40
+ const ownerPad = padPassword(ownerPw)
41
+ const digest = await md5(ownerPad)
42
+ const rc4Key = digest.slice(0, 5) // 40-bit key
43
+
44
+ return await removePdfPasswordPadding(
45
+ await rc4EncryptWithKey(rc4Key, ownerKey),
46
+ )
47
+ }
48
+
49
+ /**
50
+ * Computes the /O value for RC4-40 PDF encryption.
51
+ * The /O value is used to verify the owner password.
52
+ *
53
+ * @param ownerPw - The owner password.
54
+ * @param userPw - The user password.
55
+ * @returns A promise that resolves to the 32-byte /O value.
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const O = await computeORc4_40(ownerPassword, userPassword)
60
+ * ```
61
+ */
62
+ export async function computeORc4_40(
63
+ ownerPw: ByteArray,
64
+ userPw: ByteArray,
65
+ ): Promise<ByteArray> {
66
+ const ownerPad = padPassword(ownerPw)
67
+ const digest = await md5(ownerPad)
68
+ const rc4Key = digest.slice(0, 5) // 40-bit key
69
+
70
+ return await rc4EncryptWithKey(rc4Key, padPassword(userPw))
71
+ }
72
+
73
+ /**
74
+ * Computes the 40-bit encryption key for RC4-40 PDF encryption.
75
+ *
76
+ * @param userPw - The user password.
77
+ * @param oValue - The /O value.
78
+ * @param permissions - The /P value (permissions flags).
79
+ * @param fileId - The first element of the /ID array.
80
+ * @returns A promise that resolves to the 5-byte (40-bit) encryption key.
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const key = await computeEncryptionKeyRc4_40(userPw, O, permissions, fileId)
85
+ * ```
86
+ */
87
+ export async function computeEncryptionKeyRc4_40(
88
+ userPw: ByteArray,
89
+ oValue: ByteArray,
90
+ permissions: number,
91
+ fileId: ByteArray,
92
+ ): Promise<ByteArray> {
93
+ const userPad = padPassword(userPw)
94
+ const permissionsLE = int32ToLittleEndianBytes(permissions)
95
+ const digest = await md5(
96
+ concatUint8Arrays(userPad, oValue, permissionsLE, fileId),
97
+ )
98
+ return digest.slice(0, 5) // 40-bit key
99
+ }
100
+
101
+ /**
102
+ * Computes the /U value for RC4-40 PDF encryption.
103
+ * The /U value is used to verify the user password.
104
+ *
105
+ * @param userPw - The user password.
106
+ * @param oValue - The /O value.
107
+ * @param permissions - The /P value (permissions flags).
108
+ * @param fileId - The first element of the /ID array.
109
+ * @returns A promise that resolves to the 32-byte /U value.
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * const U = await computeURc4_40(userPassword, O, permissions, fileId)
114
+ * ```
115
+ */
116
+ export async function computeURc4_40(
117
+ userPw: ByteArray,
118
+ oValue: ByteArray,
119
+ permissions: number,
120
+ fileId: ByteArray,
121
+ ): Promise<ByteArray> {
122
+ const encryptionKey = await computeEncryptionKeyRc4_40(
123
+ userPw,
124
+ oValue,
125
+ permissions,
126
+ fileId,
127
+ )
128
+ return await rc4EncryptWithKey(encryptionKey, DEFAULT_PADDING)
129
+ }
@@ -0,0 +1,6 @@
1
+ import { ByteArray } from '../types'
2
+
3
+ export interface Cipher {
4
+ encrypt(data: ByteArray): Promise<ByteArray>
5
+ decrypt(data: ByteArray): Promise<ByteArray>
6
+ }
@@ -0,0 +1,81 @@
1
+ import { ByteArray } from '../types.js'
2
+ import { DEFAULT_PADDING } from './constants.js'
3
+
4
+ /**
5
+ * Pads a password to exactly 32 bytes using the PDF standard padding.
6
+ * If the password is shorter than 32 bytes, it is padded with bytes from DEFAULT_PADDING.
7
+ * If the password is 32 bytes or longer, only the first 32 bytes are used.
8
+ *
9
+ * @param password - The password to pad.
10
+ * @returns A 32-byte padded password.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const padded = padPassword(new Uint8Array([1, 2, 3])) // Returns 32-byte array
15
+ * ```
16
+ */
17
+ export function padPassword(password: ByteArray): ByteArray {
18
+ const padded = new Uint8Array(32)
19
+ if (password.length >= 32) {
20
+ padded.set(password.subarray(0, 32))
21
+ } else {
22
+ padded.set(password)
23
+ padded.set(
24
+ DEFAULT_PADDING.subarray(0, 32 - password.length),
25
+ password.length,
26
+ )
27
+ }
28
+ return padded
29
+ }
30
+
31
+ /**
32
+ * Converts a 32-bit integer to a 4-byte little-endian byte array.
33
+ *
34
+ * @param value - The 32-bit integer to convert.
35
+ * @returns A 4-byte array in little-endian order.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * int32ToLittleEndianBytes(0x12345678) // Returns [0x78, 0x56, 0x34, 0x12]
40
+ * ```
41
+ */
42
+ export function int32ToLittleEndianBytes(value: number): ByteArray {
43
+ const bytes = new Uint8Array(4)
44
+ bytes[0] = value & 0xff
45
+ bytes[1] = (value >> 8) & 0xff
46
+ bytes[2] = (value >> 16) & 0xff
47
+ bytes[3] = (value >> 24) & 0xff
48
+ return bytes
49
+ }
50
+
51
+ /**
52
+ * Removes PDF standard password padding from a buffer.
53
+ * Searches from the end of the buffer for the padding pattern and removes it.
54
+ *
55
+ * @param buffer - The buffer with potential password padding.
56
+ * @returns The buffer with padding removed.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const unpadded = removePdfPasswordPadding(paddedPassword)
61
+ * ```
62
+ */
63
+ export function removePdfPasswordPadding(buffer: ByteArray) {
64
+ const padding = DEFAULT_PADDING
65
+ const len = buffer.length
66
+
67
+ // Find where padding starts
68
+ let end = len
69
+ for (let i = len - 1; i >= 0; i--) {
70
+ const slice = buffer.slice(i)
71
+ const paddingStart = padding.slice(0, slice.length)
72
+ if (
73
+ slice.length === paddingStart.length &&
74
+ slice.every((v, j) => v === paddingStart[j])
75
+ ) {
76
+ end = i
77
+ break
78
+ }
79
+ }
80
+ return buffer.slice(0, end)
81
+ }
@@ -0,0 +1,128 @@
1
+ import { ByteArray } from '../types.js'
2
+ import { stringToBytes } from '../utils/stringToBytes.js'
3
+ import { PdfFilter } from './types.js'
4
+
5
+ /**
6
+ * Creates an ASCII85 filter for encoding and decoding PDF stream data.
7
+ * ASCII85 (also known as Base85) encodes binary data into ASCII characters,
8
+ * resulting in a 25% expansion compared to the original data.
9
+ *
10
+ * @returns A PdfFilter object with encode and decode methods.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const filter = ascii85()
15
+ * const encoded = filter.encode(binaryData)
16
+ * const decoded = filter.decode(encoded)
17
+ * ```
18
+ */
19
+ export function ascii85(): PdfFilter {
20
+ return {
21
+ /**
22
+ * Encodes binary data to ASCII85 format.
23
+ *
24
+ * @param data - The binary data to encode.
25
+ * @returns The ASCII85 encoded data as a byte array.
26
+ */
27
+ encode: (data: ByteArray): ByteArray => {
28
+ let result = ''
29
+ let i = 0
30
+
31
+ while (i < data.length) {
32
+ // Read up to 4 bytes
33
+ const chunk = new Array(4).fill(0)
34
+ let chunkLen = 0
35
+ for (; chunkLen < 4 && i < data.length; chunkLen++, i++) {
36
+ chunk[chunkLen] = data[i]
37
+ }
38
+
39
+ // Check for special case: 4 zero bytes encode to 'z'
40
+ if (chunkLen === 4 && chunk.every((b) => b === 0)) {
41
+ result += 'z'
42
+ continue
43
+ }
44
+
45
+ // Convert 4 bytes to 32-bit value
46
+ let value = 0
47
+ for (let j = 0; j < 4; j++) {
48
+ value = (value << 8) | chunk[j]
49
+ }
50
+
51
+ // Convert to base 85
52
+ const encoded = new Array(5)
53
+ for (let j = 4; j >= 0; j--) {
54
+ encoded[j] = String.fromCharCode((value % 85) + 33)
55
+ value = Math.floor(value / 85)
56
+ }
57
+
58
+ // For incomplete groups, only output chunkLen + 1 characters
59
+ result += encoded.slice(0, chunkLen + 1).join('')
60
+ }
61
+
62
+ return stringToBytes(result)
63
+ },
64
+
65
+ /**
66
+ * Decodes ASCII85 encoded data back to binary format.
67
+ *
68
+ * @param data - The ASCII85 encoded data.
69
+ * @returns The decoded binary data as a byte array.
70
+ * @throws Error if invalid ASCII85 characters are encountered.
71
+ */
72
+ decode: (data: ByteArray): ByteArray => {
73
+ let str = new TextDecoder().decode(data)
74
+
75
+ // Remove delimiters if present
76
+ str = str.replace(/^<~/, '').replace(/~>$/, '')
77
+
78
+ // Remove whitespace
79
+ str = str.replace(/\s/g, '')
80
+
81
+ const out: number[] = []
82
+ let i = 0
83
+
84
+ while (i < str.length) {
85
+ const char = str[i]
86
+
87
+ // Handle special 'z' case (represents 4 zero bytes)
88
+ if (char === 'z') {
89
+ out.push(0, 0, 0, 0)
90
+ i++
91
+ continue
92
+ }
93
+
94
+ // Read up to 5 characters
95
+ const chunk = new Array(5).fill(84) // Fill with 'u' (84 + 33 = 117 = 'u')
96
+ let chunkLen = 0
97
+
98
+ for (
99
+ ;
100
+ chunkLen < 5 && i < str.length && str[i] !== 'z';
101
+ chunkLen++, i++
102
+ ) {
103
+ const code = str.charCodeAt(i) - 33
104
+ if (code < 0 || code > 84) {
105
+ throw new Error(`Invalid ASCII85 character: ${str[i]}`)
106
+ }
107
+ chunk[chunkLen] = code
108
+ }
109
+
110
+ if (chunkLen === 0) break
111
+
112
+ // Convert from base 85 to 32-bit value
113
+ let value = 0
114
+ for (let j = 0; j < 5; j++) {
115
+ value = value * 85 + chunk[j]
116
+ }
117
+
118
+ // Extract bytes (but only the ones we should have based on input length)
119
+ const bytesToOutput = Math.min(4, chunkLen - 1)
120
+ for (let j = 3; j >= 4 - bytesToOutput; j--) {
121
+ out.push((value >> (j * 8)) & 0xff)
122
+ }
123
+ }
124
+
125
+ return new Uint8Array(out)
126
+ },
127
+ }
128
+ }
@@ -0,0 +1,55 @@
1
+ import { ByteArray } from '../types'
2
+ import { bytesToString } from '../utils/bytesToString'
3
+ import { stringToBytes } from '../utils/stringToBytes'
4
+ import { PdfFilter } from './types'
5
+
6
+ /**
7
+ * Creates an ASCIIHex filter for encoding and decoding PDF stream data.
8
+ * ASCIIHex encodes binary data as pairs of hexadecimal digits,
9
+ * resulting in a 100% expansion compared to the original data.
10
+ *
11
+ * @returns A PdfFilter object with encode and decode methods.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const filter = asciiHex()
16
+ * const encoded = filter.encode(binaryData)
17
+ * const decoded = filter.decode(encoded)
18
+ * ```
19
+ */
20
+ export function asciiHex(): PdfFilter {
21
+ return {
22
+ /**
23
+ * Encodes binary data to ASCIIHex format.
24
+ * Appends '>' as the end-of-data marker.
25
+ *
26
+ * @param data - The binary data to encode.
27
+ * @returns The ASCIIHex encoded data as a byte array.
28
+ */
29
+ encode: (data: ByteArray) => {
30
+ let out = ''
31
+ for (let i = 0; i < data.length; i++) {
32
+ out += data[i].toString(16).padStart(2, '0').toUpperCase()
33
+ }
34
+ out += '>'
35
+ return stringToBytes(out)
36
+ },
37
+ /**
38
+ * Decodes ASCIIHex encoded data back to binary format.
39
+ * Handles whitespace and the '>' end-of-data marker.
40
+ *
41
+ * @param data - The ASCIIHex encoded data.
42
+ * @returns The decoded binary data as a byte array.
43
+ */
44
+ decode: (data: ByteArray) => {
45
+ let hex = bytesToString(data).replace(/\s+/g, '')
46
+ if (hex.endsWith('>')) hex = hex.slice(0, -1)
47
+ if (hex.length % 2 !== 0) hex += '0'
48
+ const out = new Uint8Array(hex.length / 2)
49
+ for (let i = 0; i < hex.length; i += 2) {
50
+ out[i / 2] = parseInt(hex.substr(i, 2), 16)
51
+ }
52
+ return out
53
+ },
54
+ }
55
+ }
@@ -0,0 +1,39 @@
1
+ import { ByteArray } from '../types.js'
2
+ import { inflateData, deflateData } from '../utils/algos.js'
3
+ import { PdfFilter } from './types.js'
4
+
5
+ /**
6
+ * Creates a Flate filter for compressing and decompressing PDF stream data.
7
+ * Uses the DEFLATE compression algorithm (same as zlib/gzip).
8
+ *
9
+ * @returns A PdfFilter object with encode and decode methods.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const filter = flate()
14
+ * const compressed = filter.encode(rawData)
15
+ * const decompressed = filter.decode(compressed)
16
+ * ```
17
+ */
18
+ export function flate(): PdfFilter {
19
+ return {
20
+ /**
21
+ * Compresses data using the DEFLATE algorithm.
22
+ *
23
+ * @param data - The data to compress.
24
+ * @returns The compressed data as a byte array.
25
+ */
26
+ encode: (data: ByteArray) => {
27
+ return deflateData(data)
28
+ },
29
+ /**
30
+ * Decompresses DEFLATE-compressed data.
31
+ *
32
+ * @param data - The compressed data to decompress.
33
+ * @returns The decompressed data as a byte array.
34
+ */
35
+ decode: (data: ByteArray) => {
36
+ return inflateData(data)
37
+ },
38
+ }
39
+ }