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.
- package/.commitlintrc.cjs +25 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/workflows/docs.yaml +93 -0
- package/.github/workflows/prepare-release.yaml +79 -0
- package/.github/workflows/release.yaml +80 -0
- package/.github/workflows/test.yaml +35 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +1 -0
- package/.prettierignore +4 -0
- package/.prettierrc +4 -0
- package/CONTRIBUTING.md +109 -0
- package/EXAMPLES.md +1515 -0
- package/LICENSE +21 -0
- package/README.md +285 -0
- package/examples/001-create-pdf.ts +112 -0
- package/examples/002-create-encrypted-pdf.ts +121 -0
- package/examples/003-sign-pdf.ts +347 -0
- package/examples/004-incremental-update.ts +206 -0
- package/examples/005-modify-acroform.ts +374 -0
- package/examples/006-tokeniser-example.ts +131 -0
- package/examples/007-decoder-example.ts +197 -0
- package/package.json +72 -0
- package/packages/pdf-lite/README.md +3 -0
- package/packages/pdf-lite/package.json +68 -0
- package/packages/pdf-lite/scripts/create-encryption-tests.sh +41 -0
- package/packages/pdf-lite/scripts/gen-signing-keys.sh +290 -0
- package/packages/pdf-lite/scripts/generate-all-signing-keys.sh +70 -0
- package/packages/pdf-lite/src/core/decoder.ts +454 -0
- package/packages/pdf-lite/src/core/generators.ts +128 -0
- package/packages/pdf-lite/src/core/incremental-parser.ts +221 -0
- package/packages/pdf-lite/src/core/index.ts +2 -0
- package/packages/pdf-lite/src/core/objects/pdf-array.ts +54 -0
- package/packages/pdf-lite/src/core/objects/pdf-boolean.ts +19 -0
- package/packages/pdf-lite/src/core/objects/pdf-comment.ts +50 -0
- package/packages/pdf-lite/src/core/objects/pdf-date.ts +74 -0
- package/packages/pdf-lite/src/core/objects/pdf-dictionary.ts +171 -0
- package/packages/pdf-lite/src/core/objects/pdf-hexadecimal.ts +54 -0
- package/packages/pdf-lite/src/core/objects/pdf-indirect-object.ts +137 -0
- package/packages/pdf-lite/src/core/objects/pdf-name.ts +19 -0
- package/packages/pdf-lite/src/core/objects/pdf-null.ts +15 -0
- package/packages/pdf-lite/src/core/objects/pdf-number.ts +98 -0
- package/packages/pdf-lite/src/core/objects/pdf-object-reference.ts +30 -0
- package/packages/pdf-lite/src/core/objects/pdf-object.ts +107 -0
- package/packages/pdf-lite/src/core/objects/pdf-start-xref.ts +39 -0
- package/packages/pdf-lite/src/core/objects/pdf-stream.ts +687 -0
- package/packages/pdf-lite/src/core/objects/pdf-string.ts +38 -0
- package/packages/pdf-lite/src/core/objects/pdf-trailer.ts +57 -0
- package/packages/pdf-lite/src/core/objects/pdf-xref-table.ts +264 -0
- package/packages/pdf-lite/src/core/parser.ts +22 -0
- package/packages/pdf-lite/src/core/ref.ts +102 -0
- package/packages/pdf-lite/src/core/serializer.ts +68 -0
- package/packages/pdf-lite/src/core/streams/object-stream.ts +20 -0
- package/packages/pdf-lite/src/core/tokeniser.ts +687 -0
- package/packages/pdf-lite/src/core/tokens/boolean-token.ts +20 -0
- package/packages/pdf-lite/src/core/tokens/byte-offset-token.ts +20 -0
- package/packages/pdf-lite/src/core/tokens/comment-token.ts +32 -0
- package/packages/pdf-lite/src/core/tokens/end-array-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/end-dictionary-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/end-object-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/end-stream-token.ts +11 -0
- package/packages/pdf-lite/src/core/tokens/hexadecimal-token.ts +22 -0
- package/packages/pdf-lite/src/core/tokens/name-token.ts +19 -0
- package/packages/pdf-lite/src/core/tokens/null-token.ts +9 -0
- package/packages/pdf-lite/src/core/tokens/number-token.ts +164 -0
- package/packages/pdf-lite/src/core/tokens/object-reference-token.ts +24 -0
- package/packages/pdf-lite/src/core/tokens/start-array-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/start-dictionary-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/start-object-token.ts +28 -0
- package/packages/pdf-lite/src/core/tokens/start-stream-token.ts +52 -0
- package/packages/pdf-lite/src/core/tokens/start-xref-token.ts +10 -0
- package/packages/pdf-lite/src/core/tokens/stream-chunk-token.ts +8 -0
- package/packages/pdf-lite/src/core/tokens/string-token.ts +17 -0
- package/packages/pdf-lite/src/core/tokens/token.ts +43 -0
- package/packages/pdf-lite/src/core/tokens/trailer-token.ts +12 -0
- package/packages/pdf-lite/src/core/tokens/whitespace-token.ts +43 -0
- package/packages/pdf-lite/src/core/tokens/xref-table-entry-token.ts +65 -0
- package/packages/pdf-lite/src/core/tokens/xref-table-section-start-token.ts +31 -0
- package/packages/pdf-lite/src/core/tokens/xref-table-start-token.ts +13 -0
- package/packages/pdf-lite/src/crypto/ciphers/aes128.ts +63 -0
- package/packages/pdf-lite/src/crypto/ciphers/aes256.ts +50 -0
- package/packages/pdf-lite/src/crypto/ciphers/rc4.ts +82 -0
- package/packages/pdf-lite/src/crypto/constants.ts +10 -0
- package/packages/pdf-lite/src/crypto/key-derivation/key-derivation-aes256.ts +213 -0
- package/packages/pdf-lite/src/crypto/key-derivation/key-derivation.ts +122 -0
- package/packages/pdf-lite/src/crypto/key-gen/key-gen-aes256.ts +79 -0
- package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-128.ts +190 -0
- package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-40.ts +129 -0
- package/packages/pdf-lite/src/crypto/types.ts +6 -0
- package/packages/pdf-lite/src/crypto/utils.ts +81 -0
- package/packages/pdf-lite/src/filters/ascii85.ts +128 -0
- package/packages/pdf-lite/src/filters/asciihex.ts +55 -0
- package/packages/pdf-lite/src/filters/flate.ts +39 -0
- package/packages/pdf-lite/src/filters/lzw.ts +144 -0
- package/packages/pdf-lite/src/filters/pass-through.ts +37 -0
- package/packages/pdf-lite/src/filters/runlength.ts +92 -0
- package/packages/pdf-lite/src/filters/types.ts +21 -0
- package/packages/pdf-lite/src/index.ts +4 -0
- package/packages/pdf-lite/src/pdf/errors.ts +5 -0
- package/packages/pdf-lite/src/pdf/index.ts +4 -0
- package/packages/pdf-lite/src/pdf/pdf-document.ts +924 -0
- package/packages/pdf-lite/src/pdf/pdf-reader.ts +57 -0
- package/packages/pdf-lite/src/pdf/pdf-revision.ts +234 -0
- package/packages/pdf-lite/src/pdf/pdf-xref-lookup.ts +527 -0
- package/packages/pdf-lite/src/security/crypt-filters/aesv2.ts +58 -0
- package/packages/pdf-lite/src/security/crypt-filters/aesv3.ts +56 -0
- package/packages/pdf-lite/src/security/crypt-filters/base.ts +140 -0
- package/packages/pdf-lite/src/security/crypt-filters/identity.ts +40 -0
- package/packages/pdf-lite/src/security/crypt-filters/v2.ts +59 -0
- package/packages/pdf-lite/src/security/handlers/base.ts +625 -0
- package/packages/pdf-lite/src/security/handlers/pubSec.ts +413 -0
- package/packages/pdf-lite/src/security/handlers/utils.ts +304 -0
- package/packages/pdf-lite/src/security/handlers/v1.ts +225 -0
- package/packages/pdf-lite/src/security/handlers/v2.ts +128 -0
- package/packages/pdf-lite/src/security/handlers/v4.ts +379 -0
- package/packages/pdf-lite/src/security/handlers/v5.ts +298 -0
- package/packages/pdf-lite/src/security/types.ts +158 -0
- package/packages/pdf-lite/src/signing/document-security-store.ts +224 -0
- package/packages/pdf-lite/src/signing/index.ts +3 -0
- package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-detached.ts +154 -0
- package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-sha1.ts +161 -0
- package/packages/pdf-lite/src/signing/signatures/adbe-x509-rsa-sha1.ts +106 -0
- package/packages/pdf-lite/src/signing/signatures/base.ts +229 -0
- package/packages/pdf-lite/src/signing/signatures/etsi-cades-detached.ts +229 -0
- package/packages/pdf-lite/src/signing/signatures/etsi-rfc3161.ts +92 -0
- package/packages/pdf-lite/src/signing/signatures/index.ts +6 -0
- package/packages/pdf-lite/src/signing/signer.ts +120 -0
- package/packages/pdf-lite/src/signing/types.ts +86 -0
- package/packages/pdf-lite/src/signing/utils.ts +71 -0
- package/packages/pdf-lite/src/types.ts +44 -0
- package/packages/pdf-lite/src/utils/IterableReadableStream.ts +30 -0
- package/packages/pdf-lite/src/utils/algos.ts +446 -0
- package/packages/pdf-lite/src/utils/assert.ts +42 -0
- package/packages/pdf-lite/src/utils/bytesToHex.ts +18 -0
- package/packages/pdf-lite/src/utils/bytesToHexBytes.ts +27 -0
- package/packages/pdf-lite/src/utils/bytesToString.ts +17 -0
- package/packages/pdf-lite/src/utils/concatUint8Arrays.ts +26 -0
- package/packages/pdf-lite/src/utils/escapeString.ts +49 -0
- package/packages/pdf-lite/src/utils/hexBytesToBytes.ts +22 -0
- package/packages/pdf-lite/src/utils/hexBytesToString.ts +21 -0
- package/packages/pdf-lite/src/utils/hexToBytes.ts +18 -0
- package/packages/pdf-lite/src/utils/padBytes.ts +25 -0
- package/packages/pdf-lite/src/utils/predictors.ts +332 -0
- package/packages/pdf-lite/src/utils/replaceInBuffer.ts +56 -0
- package/packages/pdf-lite/src/utils/stringToBytes.ts +22 -0
- package/packages/pdf-lite/src/utils/stringToHexBytes.ts +23 -0
- package/packages/pdf-lite/src/utils/unescapeString.ts +123 -0
- package/packages/pdf-lite/test/acceptance/__snapshots__/versions.node.test.ts.snap +60766 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.3/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-128.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-256.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-128.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-40.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.5/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.6/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/1.7/basic.pdf +0 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-128.pdf +43 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-256.pdf +43 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-128.pdf +43 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-40.pdf +44 -0
- package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic.pdf +79 -0
- package/packages/pdf-lite/test/acceptance/versions.node.test.ts +41 -0
- package/packages/pdf-lite/test/unit/__snapshots__/decoder.node.test.ts.snap +86947 -0
- package/packages/pdf-lite/test/unit/__snapshots__/tokeniser.node.test.ts.snap +131829 -0
- package/packages/pdf-lite/test/unit/ciphers.test.ts +61 -0
- package/packages/pdf-lite/test/unit/decoder.node.test.ts +21 -0
- package/packages/pdf-lite/test/unit/decoder.test.ts +567 -0
- package/packages/pdf-lite/test/unit/filters.test.ts +67 -0
- package/packages/pdf-lite/test/unit/fixtures/basic.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-128.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-256.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-128.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-40.pdf +43 -0
- package/packages/pdf-lite/test/unit/fixtures/protectedAdobeLivecycle.pdf +0 -0
- package/packages/pdf-lite/test/unit/fixtures/rsa-2048/index.ts +187 -0
- package/packages/pdf-lite/test/unit/fixtures/template.pdf +0 -0
- package/packages/pdf-lite/test/unit/incremental-update.test.ts +0 -0
- package/packages/pdf-lite/test/unit/objects.test.ts +0 -0
- package/packages/pdf-lite/test/unit/pdf-document-signing.test.ts +0 -0
- package/packages/pdf-lite/test/unit/pdf-revision.test.ts +195 -0
- package/packages/pdf-lite/test/unit/pdf.browser.test.ts +0 -0
- package/packages/pdf-lite/test/unit/predictors.test.ts +226 -0
- package/packages/pdf-lite/test/unit/ref.test.ts +158 -0
- package/packages/pdf-lite/test/unit/security-handlers.test.ts +645 -0
- package/packages/pdf-lite/test/unit/serializer.test.ts +81 -0
- package/packages/pdf-lite/test/unit/signature-objects.test.ts +814 -0
- package/packages/pdf-lite/test/unit/string-escaping.test.ts +84 -0
- package/packages/pdf-lite/test/unit/tokeniser.node.test.ts +38 -0
- package/packages/pdf-lite/test/unit/tokeniser.test.ts +1213 -0
- package/packages/pdf-lite/test/unit/utils.test.ts +248 -0
- package/packages/pdf-lite/test/unit/xref-lookup.test.ts +72 -0
- package/packages/pdf-lite/tsconfig.json +4 -0
- package/packages/pdf-lite/tsconfig.prod.json +8 -0
- package/packages/pdf-lite/typedoc.json +14 -0
- package/packages/pdf-lite/vitest.config.ts +43 -0
- package/pnpm-workspace.yaml +2 -0
- package/renovate.json +34 -0
- package/scripts/build-examples.ts +30 -0
- package/scripts/bump-version.sh +56 -0
- package/scripts/gen-html-docs.sh +21 -0
- package/scripts/gen-md-docs.sh +15 -0
- package/scripts/prepare-release.sh +33 -0
- package/tsconfig.json +22 -0
- package/tsconfig.prod.json +12 -0
- package/typedoc.json +34 -0
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
it,
|
|
4
|
+
expect,
|
|
5
|
+
vi,
|
|
6
|
+
beforeEach,
|
|
7
|
+
afterEach,
|
|
8
|
+
beforeAll,
|
|
9
|
+
} from 'vitest'
|
|
10
|
+
import {
|
|
11
|
+
PdfSignatureDictionary,
|
|
12
|
+
PdfSignatureObject,
|
|
13
|
+
} from '../../src/signing/signatures/base'
|
|
14
|
+
import { PdfAdbePkcs7DetachedSignatureObject } from '../../src/signing/signatures/adbe-pkcs7-detached'
|
|
15
|
+
import { PdfAdbePkcs7Sha1SignatureObject } from '../../src/signing/signatures/adbe-pkcs7-sha1'
|
|
16
|
+
import { PdfAdbePkcsX509RsaSha1SignatureObject } from '../../src/signing/signatures/adbe-x509-rsa-sha1'
|
|
17
|
+
import { PdfEtsiCadesDetachedSignatureObject } from '../../src/signing/signatures/etsi-cades-detached'
|
|
18
|
+
import { PdfEtsiRfc3161SignatureObject } from '../../src/signing/signatures/etsi-rfc3161'
|
|
19
|
+
import { PdfName } from '../../src/core/objects/pdf-name'
|
|
20
|
+
import { PdfString } from '../../src/core/objects/pdf-string'
|
|
21
|
+
import { PdfDate } from '../../src/core/objects/pdf-date'
|
|
22
|
+
import { PdfArray } from '../../src/core/objects/pdf-array'
|
|
23
|
+
import { PdfNumber } from '../../src/core/objects/pdf-number'
|
|
24
|
+
import { PdfHexadecimal } from '../../src/core/objects/pdf-hexadecimal'
|
|
25
|
+
import { rsaSigningKeys } from './fixtures/rsa-2048'
|
|
26
|
+
import { stringToBytes } from '../../src/utils/stringToBytes'
|
|
27
|
+
import { SignedData } from 'pki-lite/pkcs7/SignedData'
|
|
28
|
+
import { RevocationInfoArchival } from 'pki-lite/adobe/RevocationInfoArchival'
|
|
29
|
+
import { OIDs } from 'pki-lite/core/OIDs'
|
|
30
|
+
import { bytesToHex } from '../../src/utils/bytesToHex'
|
|
31
|
+
|
|
32
|
+
describe('PdfSignatureDictionary', () => {
|
|
33
|
+
it('should create a signature dictionary with required fields', () => {
|
|
34
|
+
const dict = new PdfSignatureDictionary({
|
|
35
|
+
Type: new PdfName('Sig'),
|
|
36
|
+
Filter: new PdfName('Adobe.PPKLite'),
|
|
37
|
+
SubFilter: new PdfName('adbe.pkcs7.detached'),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
expect(dict.get('Type')).toBeInstanceOf(PdfName)
|
|
41
|
+
expect(dict.get('Type')?.toString()).toBe('/Sig')
|
|
42
|
+
expect(dict.get('Filter')).toBeInstanceOf(PdfName)
|
|
43
|
+
expect(dict.get('Filter')?.toString()).toBe('/Adobe.PPKLite')
|
|
44
|
+
expect(dict.get('SubFilter')).toBeInstanceOf(PdfName)
|
|
45
|
+
expect(dict.get('SubFilter')?.toString()).toBe('/adbe.pkcs7.detached')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should automatically add ByteRange placeholder', () => {
|
|
49
|
+
const dict = new PdfSignatureDictionary({
|
|
50
|
+
Type: new PdfName('Sig'),
|
|
51
|
+
Filter: new PdfName('Adobe.PPKLite'),
|
|
52
|
+
SubFilter: new PdfName('adbe.pkcs7.detached'),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const byteRange = dict.get('ByteRange')
|
|
56
|
+
expect(byteRange).toBeInstanceOf(PdfArray)
|
|
57
|
+
expect(byteRange?.items.length).toBe(4)
|
|
58
|
+
expect(byteRange?.items[0]).toBeInstanceOf(PdfNumber)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('should automatically add Contents placeholder', () => {
|
|
62
|
+
const dict = new PdfSignatureDictionary({
|
|
63
|
+
Type: new PdfName('Sig'),
|
|
64
|
+
Filter: new PdfName('Adobe.PPKLite'),
|
|
65
|
+
SubFilter: new PdfName('adbe.pkcs7.detached'),
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const contents = dict.get('Contents')
|
|
69
|
+
expect(contents).toBeInstanceOf(PdfHexadecimal)
|
|
70
|
+
expect((contents as PdfHexadecimal).bytes.length).toBeGreaterThan(0)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should accept optional metadata fields', () => {
|
|
74
|
+
const testDate = new Date('2024-01-01T12:00:00Z')
|
|
75
|
+
const dict = new PdfSignatureDictionary({
|
|
76
|
+
Type: new PdfName('Sig'),
|
|
77
|
+
Filter: new PdfName('Adobe.PPKLite'),
|
|
78
|
+
SubFilter: new PdfName('adbe.pkcs7.detached'),
|
|
79
|
+
Name: new PdfString('John Doe'),
|
|
80
|
+
Reason: new PdfString('I approve this document'),
|
|
81
|
+
Location: new PdfString('New York'),
|
|
82
|
+
ContactInfo: new PdfString('john@example.com'),
|
|
83
|
+
M: new PdfDate(testDate),
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
expect(dict.get('Name')?.toString()).toContain('John Doe')
|
|
87
|
+
expect(dict.get('Reason')?.toString()).toContain(
|
|
88
|
+
'I approve this document',
|
|
89
|
+
)
|
|
90
|
+
expect(dict.get('Location')?.toString()).toContain('New York')
|
|
91
|
+
expect(dict.get('ContactInfo')?.toString()).toContain(
|
|
92
|
+
'john@example.com',
|
|
93
|
+
)
|
|
94
|
+
expect(dict.get('M')).toBeInstanceOf(PdfDate)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe('PdfSignatureObject', () => {
|
|
99
|
+
class TestSignatureObject extends PdfSignatureObject {
|
|
100
|
+
async sign() {
|
|
101
|
+
return {
|
|
102
|
+
signedBytes: stringToBytes('test-signature'),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
it('should set signed bytes correctly', async () => {
|
|
108
|
+
const sigObj = new TestSignatureObject(
|
|
109
|
+
new PdfSignatureDictionary({
|
|
110
|
+
Type: new PdfName('Sig'),
|
|
111
|
+
Filter: new PdfName('Adobe.PPKLite'),
|
|
112
|
+
SubFilter: new PdfName('adbe.pkcs7.detached'),
|
|
113
|
+
}),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
const testSignature = stringToBytes('test-signature-data')
|
|
117
|
+
sigObj.setSignedBytes(testSignature)
|
|
118
|
+
|
|
119
|
+
const contents = sigObj.content.get('Contents') as PdfHexadecimal
|
|
120
|
+
expect(contents).toBeInstanceOf(PdfHexadecimal)
|
|
121
|
+
// Signature should be padded to match placeholder length
|
|
122
|
+
expect(contents.bytes.length).toBeGreaterThanOrEqual(
|
|
123
|
+
testSignature.length,
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should set byte range correctly', () => {
|
|
128
|
+
const sigObj = new TestSignatureObject(
|
|
129
|
+
new PdfSignatureDictionary({
|
|
130
|
+
Type: new PdfName('Sig'),
|
|
131
|
+
Filter: new PdfName('Adobe.PPKLite'),
|
|
132
|
+
SubFilter: new PdfName('adbe.pkcs7.detached'),
|
|
133
|
+
}),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const byteRange = [0, 1234, 5678, 9012]
|
|
137
|
+
sigObj.setByteRange(byteRange)
|
|
138
|
+
|
|
139
|
+
const byteRangeObj = sigObj.content.get('ByteRange') as PdfArray
|
|
140
|
+
expect(byteRangeObj).toBeInstanceOf(PdfArray)
|
|
141
|
+
expect(byteRangeObj.items.length).toBe(4)
|
|
142
|
+
expect((byteRangeObj.items[0] as PdfNumber).value).toBe(0)
|
|
143
|
+
expect((byteRangeObj.items[1] as PdfNumber).value).toBe(1234)
|
|
144
|
+
expect((byteRangeObj.items[2] as PdfNumber).value).toBe(5678)
|
|
145
|
+
expect((byteRangeObj.items[3] as PdfNumber).value).toBe(9012)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('should throw error when setting signed bytes without Contents', () => {
|
|
149
|
+
const sigObj = new TestSignatureObject(
|
|
150
|
+
new PdfSignatureDictionary({
|
|
151
|
+
Type: new PdfName('Sig'),
|
|
152
|
+
Filter: new PdfName('Adobe.PPKLite'),
|
|
153
|
+
SubFilter: new PdfName('adbe.pkcs7.detached'),
|
|
154
|
+
}),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
// Remove Contents to trigger error
|
|
158
|
+
sigObj.content.delete('Contents')
|
|
159
|
+
|
|
160
|
+
expect(() => {
|
|
161
|
+
sigObj.setSignedBytes(stringToBytes('test'))
|
|
162
|
+
}).toThrow('Signature dictionary is missing Contents entry')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('should throw error when setting byte range without ByteRange', () => {
|
|
166
|
+
const sigObj = new TestSignatureObject(
|
|
167
|
+
new PdfSignatureDictionary({
|
|
168
|
+
Type: new PdfName('Sig'),
|
|
169
|
+
Filter: new PdfName('Adobe.PPKLite'),
|
|
170
|
+
SubFilter: new PdfName('adbe.pkcs7.detached'),
|
|
171
|
+
}),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
// Remove ByteRange to trigger error
|
|
175
|
+
sigObj.content.delete('ByteRange')
|
|
176
|
+
|
|
177
|
+
expect(() => {
|
|
178
|
+
sigObj.setByteRange([0, 100, 200, 300])
|
|
179
|
+
}).toThrow('Signature dictionary is missing ByteRange entry')
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
describe('PdfAdbePkcs7DetachedSignatureObject', () => {
|
|
184
|
+
beforeAll(() => {
|
|
185
|
+
vi.stubGlobal('fetch', vi.fn(fetch))
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('should create Adobe PKCS7 signature with required properties', () => {
|
|
189
|
+
const sigObj = new PdfAdbePkcs7DetachedSignatureObject({
|
|
190
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
191
|
+
certificate: rsaSigningKeys.cert,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
expect(sigObj.content.get('Filter')?.toString()).toBe('/Adobe.PPKLite')
|
|
195
|
+
expect(sigObj.content.get('SubFilter')?.toString()).toBe(
|
|
196
|
+
'/adbe.pkcs7.detached',
|
|
197
|
+
)
|
|
198
|
+
expect(sigObj.content.get('Type')?.toString()).toBe('/Sig')
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('should include optional metadata in signature dictionary', () => {
|
|
202
|
+
const testDate = new Date('2024-01-01T12:00:00Z')
|
|
203
|
+
const sigObj = new PdfAdbePkcs7DetachedSignatureObject({
|
|
204
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
205
|
+
certificate: rsaSigningKeys.cert,
|
|
206
|
+
name: 'Jake Shirley',
|
|
207
|
+
reason: 'I am the author',
|
|
208
|
+
location: 'Earth',
|
|
209
|
+
contactInfo: 'test@test.com',
|
|
210
|
+
date: testDate,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
expect(sigObj.content.get('Name')?.toString()).toContain('Jake Shirley')
|
|
214
|
+
expect(sigObj.content.get('Reason')?.toString()).toContain(
|
|
215
|
+
'I am the author',
|
|
216
|
+
)
|
|
217
|
+
expect(sigObj.content.get('Location')?.toString()).toContain('Earth')
|
|
218
|
+
expect(sigObj.content.get('ContactInfo')?.toString()).toContain(
|
|
219
|
+
'test@test.com',
|
|
220
|
+
)
|
|
221
|
+
expect(sigObj.content.get('M')).toBeInstanceOf(PdfDate)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('should accept additional certificates', async () => {
|
|
225
|
+
const sigObj = new PdfAdbePkcs7DetachedSignatureObject({
|
|
226
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
227
|
+
certificate: rsaSigningKeys.cert,
|
|
228
|
+
issuerCertificate: rsaSigningKeys.caCert,
|
|
229
|
+
additionalCertificates: [rsaSigningKeys.caCert],
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
const signed = await sigObj.sign({ bytes: stringToBytes('test') })
|
|
233
|
+
const signedData = SignedData.fromCms(signed.signedBytes)
|
|
234
|
+
|
|
235
|
+
expect(signedData.certificates).toHaveLength(2)
|
|
236
|
+
expect(signedData.certificates?.[0].toDer()).toEqual(
|
|
237
|
+
rsaSigningKeys.cert,
|
|
238
|
+
)
|
|
239
|
+
expect(signedData.certificates?.[1].toDer()).toEqual(
|
|
240
|
+
rsaSigningKeys.caCert,
|
|
241
|
+
)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('should support revocation info configuration', async () => {
|
|
245
|
+
const sigObj = new PdfAdbePkcs7DetachedSignatureObject({
|
|
246
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
247
|
+
certificate: rsaSigningKeys.cert,
|
|
248
|
+
revocationInfo: {
|
|
249
|
+
crls: [rsaSigningKeys.caCrl],
|
|
250
|
+
ocsps: [rsaSigningKeys.ocspResponse],
|
|
251
|
+
otherRevInfo: [],
|
|
252
|
+
},
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
const signed = await sigObj.sign({
|
|
256
|
+
bytes: stringToBytes('test'),
|
|
257
|
+
embedRevocationInfo: true,
|
|
258
|
+
})
|
|
259
|
+
const signedData = SignedData.fromCms(signed.signedBytes)
|
|
260
|
+
const revocationInfoAttribute =
|
|
261
|
+
signedData.signerInfos[0].signedAttrs?.find((x) =>
|
|
262
|
+
x.type.is(OIDs.ADOBE.REVOCATION_INFO_ARCHIVAL),
|
|
263
|
+
)
|
|
264
|
+
const revocationInfo = revocationInfoAttribute?.values[0].parseAs(
|
|
265
|
+
RevocationInfoArchival,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
expect(revocationInfo?.crls?.[0].toDer()).toEqual(rsaSigningKeys.caCrl)
|
|
269
|
+
expect(revocationInfo?.ocsps?.[0].toDer()).toEqual(
|
|
270
|
+
rsaSigningKeys.ocspResponse,
|
|
271
|
+
)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('should support automatic revocation info fetching', async () => {
|
|
275
|
+
const sigObj = new PdfAdbePkcs7DetachedSignatureObject({
|
|
276
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
277
|
+
certificate: rsaSigningKeys.cert,
|
|
278
|
+
revocationInfo: 'fetch',
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
await sigObj.sign({ bytes: stringToBytes('test') })
|
|
282
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:8080/ca.crl')
|
|
283
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:8080/ca.crt')
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('should support custom timestamp authority URL', async () => {
|
|
287
|
+
const customTSA = {
|
|
288
|
+
url: 'https://freetsa.org/tsr',
|
|
289
|
+
}
|
|
290
|
+
const sigObj = new PdfAdbePkcs7DetachedSignatureObject({
|
|
291
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
292
|
+
certificate: rsaSigningKeys.cert,
|
|
293
|
+
timeStampAuthority: customTSA,
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
const signed = await sigObj.sign({ bytes: stringToBytes('test') })
|
|
297
|
+
const signedData = SignedData.fromCms(signed.signedBytes)
|
|
298
|
+
const tsaAttr = signedData.signerInfos[0].unsignedAttrs?.find((attr) =>
|
|
299
|
+
attr.type.is('1.2.840.113549.1.9.16.2.14'),
|
|
300
|
+
)
|
|
301
|
+
expect(tsaAttr).toBeDefined()
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it('should generate valid PKCS7 signature', async () => {
|
|
305
|
+
const sigObj = new PdfAdbePkcs7DetachedSignatureObject({
|
|
306
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
307
|
+
certificate: rsaSigningKeys.cert,
|
|
308
|
+
issuerCertificate: rsaSigningKeys.caCert,
|
|
309
|
+
date: new Date('2024-01-01T12:00:00Z'),
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
const testData = stringToBytes('Hello, PDF!')
|
|
313
|
+
const result = await sigObj.sign({ bytes: testData })
|
|
314
|
+
|
|
315
|
+
expect(result.signedBytes).toBeInstanceOf(Uint8Array)
|
|
316
|
+
expect(result.signedBytes.length).toBeGreaterThan(0)
|
|
317
|
+
// PKCS7 signature should start with DER sequence tag (0x30)
|
|
318
|
+
expect(result.signedBytes[0]).toBe(0x30)
|
|
319
|
+
expect(bytesToHex(result.signedBytes)).toMatchInlineSnapshot(
|
|
320
|
+
`"308207D106092A864886F70D010702A08207C2308207BE020101310D300B0609608648016503040201300B06092A864886F70D010701A082058F3082058B30820373A00302010202140AD1000D5C4FCFA5D3F51739F2FACAE3817A281A300D06092A864886F70D01010B0500305C310B3009060355040613025553310D300B06035504080C0454657374310E300C06035504070C054C6F63616C310E300C060355040A0C054D794F7267310B3009060355040B0C0243413111300F06035504030C084D79526F6F744341301E170D3235313132343137333835345A170D3236313132343137333835345A3061310B3009060355040613025553310D300B06035504080C0454657374310E300C06035504070C054C6F63616C310E300C060355040A0C054D794F72673110300E060355040B0C075369676E696E673111300F06035504030C084A6F686E20446F6530820122300D06092A864886F70D01010105000382010F003082010A028201010091E6734DB5BC6DA8AE1833DEE522762929E6354EF53FF06B1BD576AC87FA48161F52A14BE2D622505232539B5BB47E6CF09021057230E6D62076DFD26856128378AE7FE79F62E1EBCBC9C47A2BE752794844460A31E5887A19F03A6B2569EEC91E80718F18E7E5EF78DC33E100CE2ED50BC1C358F43AC93EDA3F77E78A97E0B089A917E9A36F3734E51D1C63CFB5C2504E77FC227AAE881624033468B8BE3E1F0A5C6E2D20528CAD7DAEA7E2A6EC7A4DCF846A97174827EF48D0166720A089CA20F765479C44945E91503B3F071DF3D7EE795CB04C43876D3B52872DF135116AC52621DFEAA2691ADC84960F76EAD089B591798F67D6F96DC75483B4BB5CC7930203010001A382013E3082013A30090603551D1304023000300B0603551D0F0404030206C0301D0603551D250416301406082B0601050507030406082B06010505070303302D0603551D1F042630243022A020A01E861C687474703A2F2F6C6F63616C686F73743A383038302F63612E63726C30819106082B06010505070101048184308181302806082B06010505073002861C687474703A2F2F6C6F63616C686F73743A383038302F63612E637274302606082B06010505073001861A687474703A2F2F6C6F63616C686F73743A383038302F6F637370302D06082B060105050730018621687474703A2F2F6C6F63616C686F73743A383038302F6F6373702D6261636B7570301D0603551D0E0416041478EA1C24BC756EA767DE5CF44A03051DAD58B62F301F0603551D23041830168014DC7B208D7FEA5ED9121A13C45A99C460131D070E300D06092A864886F70D01010B050003820201007E1585A5069BA843EAEC6CD15D6791AF98F02E745DAF15CEF93F69E92BE04FC2DDF096FD85C249A3026CF780877398D0A371ECF9A2E02F91FD3C75FA20B47207E3F0DE25AA2B8E444F07B237394D4DF5F98D3745EFCE5AEFF583C6A3C9EFF384EB692F6F3650483C0F7F0309AC2A9A741E75406710DA7154E501641445781E0404AA9D36EC862DA1E36B345D4B31B3E97C2DCC39C330A7FA79149D79E7E44B83646D697B0E3E017ACFAE3B202408CD4D7B9BA60CEB0019F977AD0C75C34BE24479CB4EE81ADF0ECE6FDB17C2A0B0DF04EE198602207B235AF7F782642D0B29373F48D2E593C13290880F66EF79B0877B68464B86016506BB63FEB9D611703FFD0DD239A992C77CF17A0E92DC79045B2287EF0B6CDF60C01A99B11841AA484FFE0FB8904DF223A498B90DF88B523893E997930517E644E269BA4624EB99B087D0CB1B4D30720C3466CB6771F2B6CE573B1D134EE3C7A3BF2B65166EC04214D8C65EDE29B148DFC61214CDB9DCC57D36E3B8AC97FBA1019BBDD61BE18B80E32E06C11D3DDA962B23578106C617847819C1AAC61BDC2273D061586DE08B2FA62721F563EB6C20C0C19AC21D6557514ECEC10E09E44D0C160DA5FF99724405983FE17103D5E7F9EF3C3AD49D3B7880FE26003DBC2E1E98DA255BBFC3BD75B2AEE178BFD7565924014A23372BDD04CE79B824FABDDB55338FF9B8AD9BDB1E43F3229531820208308202040201013074305C310B3009060355040613025553310D300B06035504080C0454657374310E300C06035504070C054C6F63616C310E300C060355040A0C054D794F7267310B3009060355040B0C0243413111300F06035504030C084D79526F6F74434102140AD1000D5C4FCFA5D3F51739F2FACAE3817A281A300B0609608648016503040201A069301C06092A864886F70D010905310F170D3234303130313132303030305A302F06092A864886F70D01090431220420E21FC8BC54A9E118BA076002544A1D20CA18D9E6DF08AC308D89CE63DE7D445B301806092A864886F70D010903310B06092A864886F70D010701300D06092A864886F70D01010B050004820100399254CB4C285A24EAFAB474523813F665CEC1BA920AE2FF877A468014E7A863AB6FE2057FD4320200999285926693EC1206598074FF08F8E7366973C46D749D63CC3BF484C16888CAAA66D54450F76B78A307667F2E90C2D18B283FFE36C7FDC06D561683765A8684B22E2062D535C333C655AF70E481BB49E0F06B9B20E24E54FCA7089D18059FB6A7D08DAC30B066ABC3CE9BB3A544D45EA8F0A2864506A461B89C4AB0DD14266AA00F6AE8C7FD2445D2BF74097CC831B03EF18260DFFFE429AC52A4505D8AE4B46C6D61DAAB9DEA84C5D9D95354FE6C54317637CC5919A2E5EA6BAE9A8D457BE01D5CC98E99016D9BF78C9A636D42BF1AB6EE487226AA46"`,
|
|
321
|
+
)
|
|
322
|
+
})
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
describe('PdfEtsiCadesDetachedSignatureObject', () => {
|
|
326
|
+
it('should create ETSI CAdES signature with required properties', () => {
|
|
327
|
+
const sigObj = new PdfEtsiCadesDetachedSignatureObject({
|
|
328
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
329
|
+
certificate: rsaSigningKeys.cert,
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
expect(sigObj.content.get('Filter')?.toString()).toBe('/Adobe.PPKLite')
|
|
333
|
+
expect(sigObj.content.get('SubFilter')?.toString()).toBe(
|
|
334
|
+
'/ETSI.CAdES.detached',
|
|
335
|
+
)
|
|
336
|
+
expect(sigObj.content.get('Type')?.toString()).toBe('/Sig')
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('should include optional metadata in signature dictionary', () => {
|
|
340
|
+
const testDate = new Date('2024-01-01T12:00:00Z')
|
|
341
|
+
const sigObj = new PdfEtsiCadesDetachedSignatureObject({
|
|
342
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
343
|
+
certificate: rsaSigningKeys.cert,
|
|
344
|
+
name: 'Jake Shirley',
|
|
345
|
+
reason: 'Approval',
|
|
346
|
+
location: 'Earth',
|
|
347
|
+
contactInfo: 'test@test.com',
|
|
348
|
+
date: testDate,
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
expect(sigObj.content.get('Name')?.toString()).toContain('Jake Shirley')
|
|
352
|
+
expect(sigObj.content.get('ContactInfo')?.toString()).toContain(
|
|
353
|
+
'test@test.com',
|
|
354
|
+
)
|
|
355
|
+
expect(sigObj.content.get('M')).toBeInstanceOf(PdfDate)
|
|
356
|
+
expect(sigObj.reason).toBe('Approval')
|
|
357
|
+
expect(sigObj.location).toBe('Earth')
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('should support signature policy document', () => {
|
|
361
|
+
const policyDoc = {
|
|
362
|
+
oid: '1.2.3.4.5',
|
|
363
|
+
hash: new Uint8Array([1, 2, 3, 4]),
|
|
364
|
+
hashAlgorithm: 'SHA-256' as const,
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const sigObj = new PdfEtsiCadesDetachedSignatureObject({
|
|
368
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
369
|
+
certificate: rsaSigningKeys.cert,
|
|
370
|
+
policyDocument: policyDoc,
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
expect(sigObj.policyDocument).toEqual(policyDoc)
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('should generate valid CAdES signature', async () => {
|
|
377
|
+
const sigObj = new PdfEtsiCadesDetachedSignatureObject({
|
|
378
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
379
|
+
certificate: rsaSigningKeys.cert,
|
|
380
|
+
issuerCertificate: rsaSigningKeys.caCert,
|
|
381
|
+
date: new Date('2024-01-01T12:00:00Z'),
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
const testData = stringToBytes('Hello, PDF!')
|
|
385
|
+
const result = await sigObj.sign({ bytes: testData })
|
|
386
|
+
|
|
387
|
+
expect(result.signedBytes).toBeInstanceOf(Uint8Array)
|
|
388
|
+
expect(result.signedBytes.length).toBeGreaterThan(0)
|
|
389
|
+
// CAdES signature should start with DER sequence tag (0x30)
|
|
390
|
+
expect(result.signedBytes[0]).toBe(0x30)
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
it('should accept additional certificates', async () => {
|
|
394
|
+
const sigObj = new PdfEtsiCadesDetachedSignatureObject({
|
|
395
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
396
|
+
certificate: rsaSigningKeys.cert,
|
|
397
|
+
issuerCertificate: rsaSigningKeys.caCert,
|
|
398
|
+
additionalCertificates: [rsaSigningKeys.caCert],
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
const signed = await sigObj.sign({ bytes: stringToBytes('test') })
|
|
402
|
+
const signedData = SignedData.fromCms(signed.signedBytes)
|
|
403
|
+
|
|
404
|
+
expect(signedData.certificates).toHaveLength(2)
|
|
405
|
+
expect(signedData.certificates?.[0].toDer()).toEqual(
|
|
406
|
+
rsaSigningKeys.cert,
|
|
407
|
+
)
|
|
408
|
+
expect(signedData.certificates?.[1].toDer()).toEqual(
|
|
409
|
+
rsaSigningKeys.caCert,
|
|
410
|
+
)
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
it('should support revocation info configuration', async () => {
|
|
414
|
+
const sigObj = new PdfEtsiCadesDetachedSignatureObject({
|
|
415
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
416
|
+
certificate: rsaSigningKeys.cert,
|
|
417
|
+
revocationInfo: {
|
|
418
|
+
crls: [rsaSigningKeys.caCrl],
|
|
419
|
+
ocsps: [rsaSigningKeys.ocspResponse],
|
|
420
|
+
otherRevInfo: [],
|
|
421
|
+
},
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
const signed = await sigObj.sign({
|
|
425
|
+
bytes: stringToBytes('test'),
|
|
426
|
+
embedRevocationInfo: true,
|
|
427
|
+
})
|
|
428
|
+
const signedData = SignedData.fromCms(signed.signedBytes)
|
|
429
|
+
const revocationInfoAttribute =
|
|
430
|
+
signedData.signerInfos[0].signedAttrs?.find((x) =>
|
|
431
|
+
x.type.is(OIDs.ADOBE.REVOCATION_INFO_ARCHIVAL),
|
|
432
|
+
)
|
|
433
|
+
const revocationInfo = revocationInfoAttribute?.values[0].parseAs(
|
|
434
|
+
RevocationInfoArchival,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
expect(revocationInfo?.crls?.[0].toDer()).toEqual(rsaSigningKeys.caCrl)
|
|
438
|
+
expect(revocationInfo?.ocsps?.[0].toDer()).toEqual(
|
|
439
|
+
rsaSigningKeys.ocspResponse,
|
|
440
|
+
)
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('should support automatic revocation info fetching', async () => {
|
|
444
|
+
const sigObj = new PdfEtsiCadesDetachedSignatureObject({
|
|
445
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
446
|
+
certificate: rsaSigningKeys.cert,
|
|
447
|
+
revocationInfo: 'fetch',
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
await sigObj.sign({ bytes: stringToBytes('test') })
|
|
451
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:8080/ca.crl')
|
|
452
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:8080/ca.crt')
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('should support custom timestamp authority URL', async () => {
|
|
456
|
+
const customTSA = {
|
|
457
|
+
url: 'https://freetsa.org/tsr',
|
|
458
|
+
}
|
|
459
|
+
const sigObj = new PdfEtsiCadesDetachedSignatureObject({
|
|
460
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
461
|
+
certificate: rsaSigningKeys.cert,
|
|
462
|
+
timeStampAuthority: customTSA,
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
const signed = await sigObj.sign({ bytes: stringToBytes('test') })
|
|
466
|
+
const signedData = SignedData.fromCms(signed.signedBytes)
|
|
467
|
+
const tsaAttr = signedData.signerInfos[0].unsignedAttrs?.find((attr) =>
|
|
468
|
+
attr.type.is('1.2.840.113549.1.9.16.2.14'),
|
|
469
|
+
)
|
|
470
|
+
expect(tsaAttr).toBeDefined()
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
it('should generate valid PKCS7 signature', async () => {
|
|
474
|
+
const sigObj = new PdfEtsiCadesDetachedSignatureObject({
|
|
475
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
476
|
+
certificate: rsaSigningKeys.cert,
|
|
477
|
+
issuerCertificate: rsaSigningKeys.caCert,
|
|
478
|
+
date: new Date('2024-01-01T12:00:00Z'),
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
const testData = stringToBytes('Hello, PDF!')
|
|
482
|
+
const result = await sigObj.sign({ bytes: testData })
|
|
483
|
+
|
|
484
|
+
expect(result.signedBytes).toBeInstanceOf(Uint8Array)
|
|
485
|
+
expect(result.signedBytes.length).toBeGreaterThan(0)
|
|
486
|
+
// PKCS7 signature should start with DER sequence tag (0x30)
|
|
487
|
+
expect(result.signedBytes[0]).toBe(0x30)
|
|
488
|
+
expect(bytesToHex(result.signedBytes)).toMatchInlineSnapshot(
|
|
489
|
+
`"3082089806092A864886F70D010702A082088930820885020101310D300B0609608648016503040201300B06092A864886F70D010701A082058F3082058B30820373A00302010202140AD1000D5C4FCFA5D3F51739F2FACAE3817A281A300D06092A864886F70D01010B0500305C310B3009060355040613025553310D300B06035504080C0454657374310E300C06035504070C054C6F63616C310E300C060355040A0C054D794F7267310B3009060355040B0C0243413111300F06035504030C084D79526F6F744341301E170D3235313132343137333835345A170D3236313132343137333835345A3061310B3009060355040613025553310D300B06035504080C0454657374310E300C06035504070C054C6F63616C310E300C060355040A0C054D794F72673110300E060355040B0C075369676E696E673111300F06035504030C084A6F686E20446F6530820122300D06092A864886F70D01010105000382010F003082010A028201010091E6734DB5BC6DA8AE1833DEE522762929E6354EF53FF06B1BD576AC87FA48161F52A14BE2D622505232539B5BB47E6CF09021057230E6D62076DFD26856128378AE7FE79F62E1EBCBC9C47A2BE752794844460A31E5887A19F03A6B2569EEC91E80718F18E7E5EF78DC33E100CE2ED50BC1C358F43AC93EDA3F77E78A97E0B089A917E9A36F3734E51D1C63CFB5C2504E77FC227AAE881624033468B8BE3E1F0A5C6E2D20528CAD7DAEA7E2A6EC7A4DCF846A97174827EF48D0166720A089CA20F765479C44945E91503B3F071DF3D7EE795CB04C43876D3B52872DF135116AC52621DFEAA2691ADC84960F76EAD089B591798F67D6F96DC75483B4BB5CC7930203010001A382013E3082013A30090603551D1304023000300B0603551D0F0404030206C0301D0603551D250416301406082B0601050507030406082B06010505070303302D0603551D1F042630243022A020A01E861C687474703A2F2F6C6F63616C686F73743A383038302F63612E63726C30819106082B06010505070101048184308181302806082B06010505073002861C687474703A2F2F6C6F63616C686F73743A383038302F63612E637274302606082B06010505073001861A687474703A2F2F6C6F63616C686F73743A383038302F6F637370302D06082B060105050730018621687474703A2F2F6C6F63616C686F73743A383038302F6F6373702D6261636B7570301D0603551D0E0416041478EA1C24BC756EA767DE5CF44A03051DAD58B62F301F0603551D23041830168014DC7B208D7FEA5ED9121A13C45A99C460131D070E300D06092A864886F70D01010B050003820201007E1585A5069BA843EAEC6CD15D6791AF98F02E745DAF15CEF93F69E92BE04FC2DDF096FD85C249A3026CF780877398D0A371ECF9A2E02F91FD3C75FA20B47207E3F0DE25AA2B8E444F07B237394D4DF5F98D3745EFCE5AEFF583C6A3C9EFF384EB692F6F3650483C0F7F0309AC2A9A741E75406710DA7154E501641445781E0404AA9D36EC862DA1E36B345D4B31B3E97C2DCC39C330A7FA79149D79E7E44B83646D697B0E3E017ACFAE3B202408CD4D7B9BA60CEB0019F977AD0C75C34BE24479CB4EE81ADF0ECE6FDB17C2A0B0DF04EE198602207B235AF7F782642D0B29373F48D2E593C13290880F66EF79B0877B68464B86016506BB63FEB9D611703FFD0DD239A992C77CF17A0E92DC79045B2287EF0B6CDF60C01A99B11841AA484FFE0FB8904DF223A498B90DF88B523893E997930517E644E269BA4624EB99B087D0CB1B4D30720C3466CB6771F2B6CE573B1D134EE3C7A3BF2B65166EC04214D8C65EDE29B148DFC61214CDB9DCC57D36E3B8AC97FBA1019BBDD61BE18B80E32E06C11D3DDA962B23578106C617847819C1AAC61BDC2273D061586DE08B2FA62721F563EB6C20C0C19AC21D6557514ECEC10E09E44D0C160DA5FF99724405983FE17103D5E7F9EF3C3AD49D3B7880FE26003DBC2E1E98DA255BBFC3BD75B2AEE178BFD7565924014A23372BDD04CE79B824FABDDB55338FF9B8AD9BDB1E43F32295318202CF308202CB0201013074305C310B3009060355040613025553310D300B06035504080C0454657374310E300C06035504070C054C6F63616C310E300C060355040A0C054D794F7267310B3009060355040B0C0243413111300F06035504030C084D79526F6F74434102140AD1000D5C4FCFA5D3F51739F2FACAE3817A281A300B0609608648016503040201A082012E3081C2060B2A864886F70D010910022F3181B23081AF3081AC3081A9300B060960864801650304020104205CE7808A4A328A9D1568A6EF6B030D44B7D1C7FFA3BC25F881DA700A199C73EC30783060A45E305C310B3009060355040613025553310D300B06035504080C0454657374310E300C06035504070C054C6F63616C310E300C060355040A0C054D794F7267310B3009060355040B0C0243413111300F06035504030C084D79526F6F74434102140AD1000D5C4FCFA5D3F51739F2FACAE3817A281A301C06092A864886F70D010905310F170D3234303130313132303030305A302F06092A864886F70D01090431220420E21FC8BC54A9E118BA076002544A1D20CA18D9E6DF08AC308D89CE63DE7D445B301806092A864886F70D010903310B06092A864886F70D010701300D06092A864886F70D01010B05000482010087C1548CA434BC1F45B70E3015C1F3679FC24B1EBBB3AAF12F5A84A118B8FDB6C3CED71E1D919DCE9CC3336BD04B2A16CA7645AE533078AB927718B541840CCBAF29F26BAB815820D28ED42DA33E1C39CCB19A8FF57D5ECED1E826505D1818759183CE5DB94ABD81235288476B852EB0AC4DF9CC204E40FB5F001F63A3A070A378D0D8D42A6CA7BFAC96FCE3ED302D46CB97FC7FB76F21C09B2FEBE3623144C292B83118DF862DEB6059A4AD3157F26E9650862FD96C80A4B79C135592F6FC5269488A1A22C90B215BC150FEB75E21E276DB24D7D3037C6F5D88FF2F97F282FD482B57CEAB28D533C885D2B06C9BA05A162260F1041B45E2BEE067C4C8B43A0A"`,
|
|
490
|
+
)
|
|
491
|
+
})
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
describe('PdfAdbePkcs7Sha1SignatureObject', () => {
|
|
495
|
+
beforeAll(() => {
|
|
496
|
+
vi.stubGlobal('fetch', vi.fn(fetch))
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
it('should create Adobe PKCS7 SHA1 signature with required properties', () => {
|
|
500
|
+
const sigObj = new PdfAdbePkcs7Sha1SignatureObject({
|
|
501
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
502
|
+
certificate: rsaSigningKeys.cert,
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
expect(sigObj.content.get('Filter')?.toString()).toBe('/Adobe.PPKLite')
|
|
506
|
+
expect(sigObj.content.get('SubFilter')?.toString()).toBe(
|
|
507
|
+
'/adbe.pkcs7.sha1',
|
|
508
|
+
)
|
|
509
|
+
expect(sigObj.content.get('Type')?.toString()).toBe('/Sig')
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
it('should include optional metadata in signature dictionary', () => {
|
|
513
|
+
const testDate = new Date('2024-01-01T12:00:00Z')
|
|
514
|
+
const sigObj = new PdfAdbePkcs7Sha1SignatureObject({
|
|
515
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
516
|
+
certificate: rsaSigningKeys.cert,
|
|
517
|
+
name: 'Jake Shirley',
|
|
518
|
+
reason: 'PKCS7 SHA1 Approval',
|
|
519
|
+
location: 'Earth',
|
|
520
|
+
contactInfo: 'test@test.com',
|
|
521
|
+
date: testDate,
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
expect(sigObj.content.get('Name')?.toString()).toContain('Jake Shirley')
|
|
525
|
+
expect(sigObj.content.get('Reason')?.toString()).toContain(
|
|
526
|
+
'PKCS7 SHA1 Approval',
|
|
527
|
+
)
|
|
528
|
+
expect(sigObj.content.get('Location')?.toString()).toContain('Earth')
|
|
529
|
+
expect(sigObj.content.get('ContactInfo')?.toString()).toContain(
|
|
530
|
+
'test@test.com',
|
|
531
|
+
)
|
|
532
|
+
expect(sigObj.content.get('M')).toBeInstanceOf(PdfDate)
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
it('should accept additional certificates', async () => {
|
|
536
|
+
const sigObj = new PdfAdbePkcs7Sha1SignatureObject({
|
|
537
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
538
|
+
certificate: rsaSigningKeys.cert,
|
|
539
|
+
issuerCertificate: rsaSigningKeys.caCert,
|
|
540
|
+
additionalCertificates: [rsaSigningKeys.caCert],
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
const signed = await sigObj.sign({ bytes: stringToBytes('test') })
|
|
544
|
+
const signedData = SignedData.fromCms(signed.signedBytes)
|
|
545
|
+
|
|
546
|
+
expect(signedData.certificates).toHaveLength(2)
|
|
547
|
+
expect(signedData.certificates?.[0].toDer()).toEqual(
|
|
548
|
+
rsaSigningKeys.cert,
|
|
549
|
+
)
|
|
550
|
+
expect(signedData.certificates?.[1].toDer()).toEqual(
|
|
551
|
+
rsaSigningKeys.caCert,
|
|
552
|
+
)
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
it('should support revocation info configuration', async () => {
|
|
556
|
+
const sigObj = new PdfAdbePkcs7Sha1SignatureObject({
|
|
557
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
558
|
+
certificate: rsaSigningKeys.cert,
|
|
559
|
+
revocationInfo: {
|
|
560
|
+
crls: [rsaSigningKeys.caCrl],
|
|
561
|
+
ocsps: [rsaSigningKeys.ocspResponse],
|
|
562
|
+
otherRevInfo: [],
|
|
563
|
+
},
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
const signed = await sigObj.sign({
|
|
567
|
+
bytes: stringToBytes('test'),
|
|
568
|
+
embedRevocationInfo: true,
|
|
569
|
+
})
|
|
570
|
+
const signedData = SignedData.fromCms(signed.signedBytes)
|
|
571
|
+
const revocationInfoAttribute =
|
|
572
|
+
signedData.signerInfos[0].signedAttrs?.find((x) =>
|
|
573
|
+
x.type.is(OIDs.ADOBE.REVOCATION_INFO_ARCHIVAL),
|
|
574
|
+
)
|
|
575
|
+
const revocationInfo = revocationInfoAttribute?.values[0].parseAs(
|
|
576
|
+
RevocationInfoArchival,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
expect(revocationInfo?.crls?.[0].toDer()).toEqual(rsaSigningKeys.caCrl)
|
|
580
|
+
expect(revocationInfo?.ocsps?.[0].toDer()).toEqual(
|
|
581
|
+
rsaSigningKeys.ocspResponse,
|
|
582
|
+
)
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
it('should support automatic revocation info fetching', async () => {
|
|
586
|
+
const sigObj = new PdfAdbePkcs7Sha1SignatureObject({
|
|
587
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
588
|
+
certificate: rsaSigningKeys.cert,
|
|
589
|
+
revocationInfo: 'fetch',
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
await sigObj.sign({ bytes: stringToBytes('test') })
|
|
593
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:8080/ca.crl')
|
|
594
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:8080/ca.crt')
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
it('should support custom timestamp authority URL', async () => {
|
|
598
|
+
const customTSA = {
|
|
599
|
+
url: 'https://freetsa.org/tsr',
|
|
600
|
+
}
|
|
601
|
+
const sigObj = new PdfAdbePkcs7Sha1SignatureObject({
|
|
602
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
603
|
+
certificate: rsaSigningKeys.cert,
|
|
604
|
+
timeStampAuthority: customTSA,
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
const signed = await sigObj.sign({ bytes: stringToBytes('test') })
|
|
608
|
+
const signedData = SignedData.fromCms(signed.signedBytes)
|
|
609
|
+
const tsaAttr = signedData.signerInfos[0].unsignedAttrs?.find((attr) =>
|
|
610
|
+
attr.type.is('1.2.840.113549.1.9.16.2.14'),
|
|
611
|
+
)
|
|
612
|
+
expect(tsaAttr).toBeDefined()
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
it('should generate valid PKCS7 SHA1 signature', async () => {
|
|
616
|
+
const sigObj = new PdfAdbePkcs7Sha1SignatureObject({
|
|
617
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
618
|
+
certificate: rsaSigningKeys.cert,
|
|
619
|
+
issuerCertificate: rsaSigningKeys.caCert,
|
|
620
|
+
date: new Date('2024-01-01T12:00:00Z'),
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
const testData = stringToBytes('Hello, PDF!')
|
|
624
|
+
const result = await sigObj.sign({ bytes: testData })
|
|
625
|
+
|
|
626
|
+
expect(result.signedBytes).toBeInstanceOf(Uint8Array)
|
|
627
|
+
expect(result.signedBytes.length).toBeGreaterThan(0)
|
|
628
|
+
// PKCS7 signature should start with DER sequence tag (0x30)
|
|
629
|
+
expect(result.signedBytes[0]).toBe(0x30)
|
|
630
|
+
})
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
describe('PdfAdbePkcsX509RsaSha1SignatureObject', () => {
|
|
634
|
+
beforeAll(() => {
|
|
635
|
+
vi.stubGlobal('fetch', vi.fn(fetch))
|
|
636
|
+
})
|
|
637
|
+
|
|
638
|
+
it('should create Adobe X509 RSA SHA1 signature with required properties', () => {
|
|
639
|
+
const sigObj = new PdfAdbePkcsX509RsaSha1SignatureObject({
|
|
640
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
641
|
+
certificate: rsaSigningKeys.cert,
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
expect(sigObj.content.get('Filter')?.toString()).toBe('/Adobe.PPKLite')
|
|
645
|
+
expect(sigObj.content.get('SubFilter')?.toString()).toBe(
|
|
646
|
+
'/adbe.x509.rsa_sha1',
|
|
647
|
+
)
|
|
648
|
+
expect(sigObj.content.get('Type')?.toString()).toBe('/Sig')
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
it('should include optional metadata in signature dictionary', () => {
|
|
652
|
+
const testDate = new Date('2024-01-01T12:00:00Z')
|
|
653
|
+
const sigObj = new PdfAdbePkcsX509RsaSha1SignatureObject({
|
|
654
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
655
|
+
certificate: rsaSigningKeys.cert,
|
|
656
|
+
name: 'Jake Shirley',
|
|
657
|
+
reason: 'X509 RSA SHA1 Approval',
|
|
658
|
+
location: 'Earth',
|
|
659
|
+
contactInfo: 'test@test.com',
|
|
660
|
+
date: testDate,
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
expect(sigObj.content.get('Name')?.toString()).toContain('Jake Shirley')
|
|
664
|
+
expect(sigObj.content.get('Reason')?.toString()).toContain(
|
|
665
|
+
'X509 RSA SHA1 Approval',
|
|
666
|
+
)
|
|
667
|
+
expect(sigObj.content.get('Location')?.toString()).toContain('Earth')
|
|
668
|
+
expect(sigObj.content.get('ContactInfo')?.toString()).toContain(
|
|
669
|
+
'test@test.com',
|
|
670
|
+
)
|
|
671
|
+
expect(sigObj.content.get('M')).toBeInstanceOf(PdfDate)
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
it('should include certificate in Cert array', () => {
|
|
675
|
+
const sigObj = new PdfAdbePkcsX509RsaSha1SignatureObject({
|
|
676
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
677
|
+
certificate: rsaSigningKeys.cert,
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
const certArray = sigObj.content.get('Cert') as PdfArray
|
|
681
|
+
expect(certArray).toBeInstanceOf(PdfArray)
|
|
682
|
+
expect(certArray.items.length).toBeGreaterThanOrEqual(1)
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
it('should accept additional certificates', () => {
|
|
686
|
+
const sigObj = new PdfAdbePkcsX509RsaSha1SignatureObject({
|
|
687
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
688
|
+
certificate: rsaSigningKeys.cert,
|
|
689
|
+
additionalCertificates: [rsaSigningKeys.caCert],
|
|
690
|
+
})
|
|
691
|
+
|
|
692
|
+
const certArray = sigObj.content.get('Cert') as PdfArray
|
|
693
|
+
expect(certArray).toBeInstanceOf(PdfArray)
|
|
694
|
+
expect(certArray.items.length).toBe(2)
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
it('should support revocation info configuration', async () => {
|
|
698
|
+
const sigObj = new PdfAdbePkcsX509RsaSha1SignatureObject({
|
|
699
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
700
|
+
certificate: rsaSigningKeys.cert,
|
|
701
|
+
revocationInfo: {
|
|
702
|
+
crls: [rsaSigningKeys.caCrl],
|
|
703
|
+
ocsps: [rsaSigningKeys.ocspResponse],
|
|
704
|
+
otherRevInfo: [],
|
|
705
|
+
},
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
const signed = await sigObj.sign({ bytes: stringToBytes('test') })
|
|
709
|
+
|
|
710
|
+
expect(signed.revocationInfo?.crls?.[0]).toEqual(rsaSigningKeys.caCrl)
|
|
711
|
+
expect(signed.revocationInfo?.ocsps?.[0]).toEqual(
|
|
712
|
+
rsaSigningKeys.ocspResponse,
|
|
713
|
+
)
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
it('should support automatic revocation info fetching', async () => {
|
|
717
|
+
const sigObj = new PdfAdbePkcsX509RsaSha1SignatureObject({
|
|
718
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
719
|
+
certificate: rsaSigningKeys.cert,
|
|
720
|
+
revocationInfo: 'fetch',
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
await sigObj.sign({ bytes: stringToBytes('test') })
|
|
724
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:8080/ca.crl')
|
|
725
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:8080/ca.crt')
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
it('should generate valid X509 RSA SHA1 signature', async () => {
|
|
729
|
+
const sigObj = new PdfAdbePkcsX509RsaSha1SignatureObject({
|
|
730
|
+
privateKey: rsaSigningKeys.privateKey,
|
|
731
|
+
certificate: rsaSigningKeys.cert,
|
|
732
|
+
date: new Date('2024-01-01T12:00:00Z'),
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
const testData = stringToBytes('Hello, PDF!')
|
|
736
|
+
const result = await sigObj.sign({ bytes: testData })
|
|
737
|
+
|
|
738
|
+
expect(result.signedBytes).toBeInstanceOf(Uint8Array)
|
|
739
|
+
expect(result.signedBytes.length).toBeGreaterThan(0)
|
|
740
|
+
// OctetString starts with 0x04 tag
|
|
741
|
+
expect(result.signedBytes[0]).toBe(0x04)
|
|
742
|
+
})
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
describe('PdfEtsiRfc3161SignatureObject', () => {
|
|
746
|
+
it('should create ETSI RFC3161 signature with required properties', () => {
|
|
747
|
+
const sigObj = new PdfEtsiRfc3161SignatureObject({})
|
|
748
|
+
|
|
749
|
+
expect(sigObj.content.get('Filter')?.toString()).toBe('/Adobe.PPKLite')
|
|
750
|
+
expect(sigObj.content.get('SubFilter')?.toString()).toBe(
|
|
751
|
+
'/ETSI.RFC3161',
|
|
752
|
+
)
|
|
753
|
+
expect(sigObj.content.get('Type')?.toString()).toBe('/Sig')
|
|
754
|
+
})
|
|
755
|
+
|
|
756
|
+
it('should use default timestamp authority URL', () => {
|
|
757
|
+
const sigObj = new PdfEtsiRfc3161SignatureObject({})
|
|
758
|
+
|
|
759
|
+
expect(sigObj.timeStampAuthority.url).toBe('https://freetsa.org/tsr')
|
|
760
|
+
})
|
|
761
|
+
|
|
762
|
+
it('should accept custom timestamp authority URL', () => {
|
|
763
|
+
const customTSA = {
|
|
764
|
+
url: 'https://custom-tsa.example.com/tsr',
|
|
765
|
+
username: 'user',
|
|
766
|
+
password: 'pass',
|
|
767
|
+
}
|
|
768
|
+
const sigObj = new PdfEtsiRfc3161SignatureObject({
|
|
769
|
+
timeStampAuthority: customTSA,
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
expect(sigObj.timeStampAuthority.url).toBe(
|
|
773
|
+
'https://custom-tsa.example.com/tsr',
|
|
774
|
+
)
|
|
775
|
+
expect(sigObj.timeStampAuthority.username).toBe('user')
|
|
776
|
+
expect(sigObj.timeStampAuthority.password).toBe('pass')
|
|
777
|
+
})
|
|
778
|
+
|
|
779
|
+
it('should include optional metadata in signature dictionary', () => {
|
|
780
|
+
const sigObj = new PdfEtsiRfc3161SignatureObject({
|
|
781
|
+
name: 'Timestamp Authority',
|
|
782
|
+
reason: 'Document Timestamping',
|
|
783
|
+
location: 'Server',
|
|
784
|
+
contactInfo: 'timestamp@example.com',
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
expect(sigObj.content.get('Name')?.toString()).toContain(
|
|
788
|
+
'Timestamp Authority',
|
|
789
|
+
)
|
|
790
|
+
expect(sigObj.content.get('Reason')?.toString()).toContain(
|
|
791
|
+
'Document Timestamping',
|
|
792
|
+
)
|
|
793
|
+
expect(sigObj.content.get('Location')?.toString()).toContain('Server')
|
|
794
|
+
expect(sigObj.content.get('ContactInfo')?.toString()).toContain(
|
|
795
|
+
'timestamp@example.com',
|
|
796
|
+
)
|
|
797
|
+
})
|
|
798
|
+
|
|
799
|
+
it('should generate valid RFC3161 timestamp signature', async () => {
|
|
800
|
+
const sigObj = new PdfEtsiRfc3161SignatureObject({
|
|
801
|
+
timeStampAuthority: {
|
|
802
|
+
url: 'https://freetsa.org/tsr',
|
|
803
|
+
},
|
|
804
|
+
})
|
|
805
|
+
|
|
806
|
+
const testData = stringToBytes('Hello, PDF!')
|
|
807
|
+
const result = await sigObj.sign({ bytes: testData })
|
|
808
|
+
|
|
809
|
+
expect(result.signedBytes).toBeInstanceOf(Uint8Array)
|
|
810
|
+
expect(result.signedBytes.length).toBeGreaterThan(0)
|
|
811
|
+
// Timestamp token starts with DER sequence tag (0x30)
|
|
812
|
+
expect(result.signedBytes[0]).toBe(0x30)
|
|
813
|
+
})
|
|
814
|
+
})
|