pdf-lite 1.0.2 → 1.0.4

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 (45) hide show
  1. package/EXAMPLES.md +354 -0
  2. package/README.md +7 -3
  3. package/dist/core/index.d.ts +48 -0
  4. package/dist/core/index.js +48 -0
  5. package/dist/core/objects/pdf-array.d.ts +1 -1
  6. package/dist/core/objects/pdf-indirect-object.d.ts +1 -1
  7. package/dist/core/objects/pdf-start-xref.d.ts +1 -1
  8. package/dist/core/objects/pdf-xref-table.d.ts +1 -1
  9. package/dist/core/tokens/number-token.js +11 -6
  10. package/dist/crypto/index.d.ts +11 -0
  11. package/dist/crypto/index.js +11 -0
  12. package/dist/crypto/key-derivation/key-derivation.d.ts +0 -14
  13. package/dist/crypto/key-derivation/key-derivation.js +1 -26
  14. package/dist/crypto/key-gen/key-gen-rc4-128.js +1 -2
  15. package/dist/crypto/key-gen/key-gen-rc4-40.js +1 -2
  16. package/dist/filters/index.d.ts +7 -0
  17. package/dist/filters/index.js +7 -0
  18. package/dist/index.d.ts +5 -1
  19. package/dist/index.js +5 -1
  20. package/dist/pdf/index.d.ts +5 -4
  21. package/dist/pdf/index.js +5 -4
  22. package/dist/pdf/pdf-document.d.ts +9 -3
  23. package/dist/pdf/pdf-document.js +15 -3
  24. package/dist/security/index.d.ts +13 -0
  25. package/dist/security/index.js +13 -0
  26. package/dist/signing/index.d.ts +5 -3
  27. package/dist/signing/index.js +5 -3
  28. package/dist/signing/signatures/adbe-pkcs7-detached.d.ts +7 -0
  29. package/dist/signing/signatures/adbe-pkcs7-detached.js +36 -0
  30. package/dist/signing/signatures/adbe-pkcs7-sha1.d.ts +7 -0
  31. package/dist/signing/signatures/adbe-pkcs7-sha1.js +54 -0
  32. package/dist/signing/signatures/adbe-x509-rsa-sha1.d.ts +7 -0
  33. package/dist/signing/signatures/adbe-x509-rsa-sha1.js +77 -0
  34. package/dist/signing/signatures/base.d.ts +16 -1
  35. package/dist/signing/signatures/base.js +18 -0
  36. package/dist/signing/signatures/etsi-cades-detached.d.ts +7 -0
  37. package/dist/signing/signatures/etsi-cades-detached.js +36 -0
  38. package/dist/signing/signatures/etsi-rfc3161.d.ts +7 -0
  39. package/dist/signing/signatures/etsi-rfc3161.js +102 -0
  40. package/dist/signing/signer.d.ts +55 -0
  41. package/dist/signing/signer.js +196 -1
  42. package/dist/signing/types.d.ts +26 -0
  43. package/dist/utils/index.d.ts +17 -0
  44. package/dist/utils/index.js +17 -0
  45. package/package.json +3 -3
@@ -0,0 +1,7 @@
1
+ export * from './ascii85.js';
2
+ export * from './asciihex.js';
3
+ export * from './flate.js';
4
+ export * from './lzw.js';
5
+ export * from './pass-through.js';
6
+ export * from './runlength.js';
7
+ export * from './types.js';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  export * from './types.js';
2
- export * from './pdf/index.js';
3
2
  export * from './core/index.js';
3
+ export * from './crypto/index.js';
4
+ export * from './filters/index.js';
5
+ export * from './pdf/index.js';
6
+ export * from './security/index.js';
4
7
  export * from './signing/index.js';
8
+ export * from './utils/index.js';
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  export * from './types.js';
2
- export * from './pdf/index.js';
3
2
  export * from './core/index.js';
3
+ export * from './crypto/index.js';
4
+ export * from './filters/index.js';
5
+ export * from './pdf/index.js';
6
+ export * from './security/index.js';
4
7
  export * from './signing/index.js';
8
+ export * from './utils/index.js';
@@ -1,4 +1,5 @@
1
- export * from './pdf-document';
2
- export * from './pdf-reader';
3
- export * from './pdf-revision';
4
- export * from './pdf-xref-lookup';
1
+ export * from './errors.js';
2
+ export * from './pdf-document.js';
3
+ export * from './pdf-reader.js';
4
+ export * from './pdf-revision.js';
5
+ export * from './pdf-xref-lookup.js';
package/dist/pdf/index.js CHANGED
@@ -1,4 +1,5 @@
1
- export * from './pdf-document';
2
- export * from './pdf-reader';
3
- export * from './pdf-revision';
4
- export * from './pdf-xref-lookup';
1
+ export * from './errors.js';
2
+ export * from './pdf-document.js';
3
+ export * from './pdf-reader.js';
4
+ export * from './pdf-revision.js';
5
+ export * from './pdf-xref-lookup.js';
@@ -11,7 +11,7 @@ import { PdfEncryptionDictionaryObject } from '../security/types';
11
11
  import { PdfTrailerEntries } from '../core/objects/pdf-trailer';
12
12
  import { PdfDocumentSecurityStoreObject } from '../signing/document-security-store';
13
13
  import { ByteArray } from '../types';
14
- import { PdfSigner } from '../signing/signer';
14
+ import { PdfDocumentVerificationResult, PdfSigner } from '../signing/signer';
15
15
  /**
16
16
  * Represents a PDF document with support for reading, writing, and modifying PDF files.
17
17
  * Handles document structure, revisions, encryption, and digital signatures.
@@ -214,9 +214,9 @@ export declare class PdfDocument extends PdfObject {
214
214
  * Sets whether the document should use incremental updates.
215
215
  * When true, locks all existing revisions to preserve original content.
216
216
  *
217
- * @param value - True to enable incremental mode, false to disable
217
+ * @param value - True to enable incremental mode, false to disable. Defaults to true.
218
218
  */
219
- setIncremental(value: boolean): void;
219
+ setIncremental(value?: boolean): void;
220
220
  /**
221
221
  * Checks if the document is in incremental mode.
222
222
  *
@@ -274,4 +274,10 @@ export declare class PdfDocument extends PdfObject {
274
274
  */
275
275
  static fromBytes(input: AsyncIterable<ByteArray> | Iterable<ByteArray>): Promise<PdfDocument>;
276
276
  isModified(): boolean;
277
+ /**
278
+ * Verifies all digital signatures in the document.
279
+ *
280
+ * @returns A promise that resolves to the verification result
281
+ */
282
+ verifySignatures(): Promise<PdfDocumentVerificationResult>;
277
283
  }
@@ -148,7 +148,11 @@ export class PdfDocument extends PdfObject {
148
148
  const revision = this.revisions.find((rev) => rev.xref.offset === lastStartXRef.offset.ref) ??
149
149
  this.revisions.find((rev) => rev.xref.offset.equals(lastStartXRef.offset.value));
150
150
  if (!revision) {
151
- throw new Error('Cannot find revision for last StartXRef');
151
+ throw new Error('Cannot find revision for last StartXRef with offset ' +
152
+ `${lastStartXRef.offset.value}. Options are: ` +
153
+ this.revisions
154
+ .map((rev) => rev.xref.offset.value)
155
+ .join(', '));
152
156
  }
153
157
  return revision;
154
158
  }
@@ -497,9 +501,9 @@ export class PdfDocument extends PdfObject {
497
501
  * Sets whether the document should use incremental updates.
498
502
  * When true, locks all existing revisions to preserve original content.
499
503
  *
500
- * @param value - True to enable incremental mode, false to disable
504
+ * @param value - True to enable incremental mode, false to disable. Defaults to true.
501
505
  */
502
- setIncremental(value) {
506
+ setIncremental(value = true) {
503
507
  for (const revision of this.revisions) {
504
508
  revision.locked = value;
505
509
  }
@@ -685,4 +689,12 @@ export class PdfDocument extends PdfObject {
685
689
  isModified() {
686
690
  return (super.isModified() || this.revisions.some((rev) => rev.isModified()));
687
691
  }
692
+ /**
693
+ * Verifies all digital signatures in the document.
694
+ *
695
+ * @returns A promise that resolves to the verification result
696
+ */
697
+ async verifySignatures() {
698
+ return await this.signer.verify(this);
699
+ }
688
700
  }
@@ -0,0 +1,13 @@
1
+ export * from './crypt-filters/aesv2.js';
2
+ export * from './crypt-filters/aesv3.js';
3
+ export * from './crypt-filters/base.js';
4
+ export * from './crypt-filters/identity.js';
5
+ export * from './crypt-filters/v2.js';
6
+ export * from './handlers/base.js';
7
+ export * from './handlers/pubSec.js';
8
+ export * from './handlers/utils.js';
9
+ export * from './handlers/v1.js';
10
+ export * from './handlers/v2.js';
11
+ export * from './handlers/v4.js';
12
+ export * from './handlers/v5.js';
13
+ export * from './types.js';
@@ -0,0 +1,13 @@
1
+ export * from './crypt-filters/aesv2.js';
2
+ export * from './crypt-filters/aesv3.js';
3
+ export * from './crypt-filters/base.js';
4
+ export * from './crypt-filters/identity.js';
5
+ export * from './crypt-filters/v2.js';
6
+ export * from './handlers/base.js';
7
+ export * from './handlers/pubSec.js';
8
+ export * from './handlers/utils.js';
9
+ export * from './handlers/v1.js';
10
+ export * from './handlers/v2.js';
11
+ export * from './handlers/v4.js';
12
+ export * from './handlers/v5.js';
13
+ export * from './types.js';
@@ -1,3 +1,5 @@
1
- export * from './signatures';
2
- export * from './document-security-store';
3
- export * from './types';
1
+ export * from './document-security-store.js';
2
+ export * from './signatures/index.js';
3
+ export * from './signer.js';
4
+ export * from './types.js';
5
+ export * from './utils.js';
@@ -1,3 +1,5 @@
1
- export * from './signatures';
2
- export * from './document-security-store';
3
- export * from './types';
1
+ export * from './document-security-store.js';
2
+ export * from './signatures/index.js';
3
+ export * from './signer.js';
4
+ export * from './types.js';
5
+ export * from './utils.js';
@@ -54,4 +54,11 @@ export declare class PdfAdbePkcs7DetachedSignatureObject extends PdfSignatureObj
54
54
  * @returns The CMS SignedData and revocation information.
55
55
  */
56
56
  sign: PdfSignatureObject['sign'];
57
+ /**
58
+ * Verifies the signature against the provided document bytes.
59
+ *
60
+ * @param options - Verification options including the signed bytes.
61
+ * @returns The verification result.
62
+ */
63
+ verify: PdfSignatureObject['verify'];
57
64
  }
@@ -114,4 +114,40 @@ export class PdfAdbePkcs7DetachedSignatureObject extends PdfSignatureObject {
114
114
  revocationInfo,
115
115
  };
116
116
  };
117
+ /**
118
+ * Verifies the signature against the provided document bytes.
119
+ *
120
+ * @param options - Verification options including the signed bytes.
121
+ * @returns The verification result.
122
+ */
123
+ verify = async (options) => {
124
+ const { bytes, certificateValidation } = options;
125
+ try {
126
+ const signedData = SignedData.fromCms(this.signedBytes);
127
+ const certValidationOptions = certificateValidation === true
128
+ ? {}
129
+ : certificateValidation || undefined;
130
+ const result = await signedData.verify({
131
+ data: bytes,
132
+ certificateValidation: certValidationOptions,
133
+ });
134
+ if (result.valid) {
135
+ return { valid: true };
136
+ }
137
+ else {
138
+ return {
139
+ valid: false,
140
+ reasons: result.reasons,
141
+ };
142
+ }
143
+ }
144
+ catch (error) {
145
+ return {
146
+ valid: false,
147
+ reasons: [
148
+ `Failed to verify signature: ${error instanceof Error ? error.message : String(error)}`,
149
+ ],
150
+ };
151
+ }
152
+ };
117
153
  }
@@ -52,4 +52,11 @@ export declare class PdfAdbePkcs7Sha1SignatureObject extends PdfSignatureObject
52
52
  * @returns The CMS SignedData and revocation information.
53
53
  */
54
54
  sign: PdfSignatureObject['sign'];
55
+ /**
56
+ * Verifies the signature against the provided document bytes.
57
+ *
58
+ * @param options - Verification options including the signed bytes.
59
+ * @returns The verification result.
60
+ */
61
+ verify: PdfSignatureObject['verify'];
55
62
  }
@@ -118,4 +118,58 @@ export class PdfAdbePkcs7Sha1SignatureObject extends PdfSignatureObject {
118
118
  revocationInfo,
119
119
  };
120
120
  };
121
+ /**
122
+ * Verifies the signature against the provided document bytes.
123
+ *
124
+ * @param options - Verification options including the signed bytes.
125
+ * @returns The verification result.
126
+ */
127
+ verify = async (options) => {
128
+ const { bytes, certificateValidation } = options;
129
+ try {
130
+ const signedData = SignedData.fromCms(this.signedBytes);
131
+ // For adbe.pkcs7.sha1, the signed content is the SHA-1 hash of the document
132
+ // We need to compute the SHA-1 hash of the data and compare with the embedded content
133
+ const expectedHash = await DigestAlgorithmIdentifier.digestAlgorithm('SHA-1').digest(bytes);
134
+ const certValidationOptions = certificateValidation === true
135
+ ? {}
136
+ : certificateValidation || undefined;
137
+ // Verify the signature with the hash as the data (non-detached mode)
138
+ const result = await signedData.verify({
139
+ certificateValidation: certValidationOptions,
140
+ });
141
+ if (!result.valid) {
142
+ return {
143
+ valid: false,
144
+ reasons: result.reasons,
145
+ };
146
+ }
147
+ // Additionally verify that the embedded hash matches the document hash
148
+ const embeddedContent = signedData.encapContentInfo.eContent;
149
+ if (!embeddedContent) {
150
+ return {
151
+ valid: false,
152
+ reasons: ['No embedded content in SignedData'],
153
+ };
154
+ }
155
+ // Compare the hashes
156
+ if (!this.compareArrays(embeddedContent, expectedHash)) {
157
+ return {
158
+ valid: false,
159
+ reasons: [
160
+ 'Document hash does not match embedded signature hash',
161
+ ],
162
+ };
163
+ }
164
+ return { valid: true };
165
+ }
166
+ catch (error) {
167
+ return {
168
+ valid: false,
169
+ reasons: [
170
+ `Failed to verify signature: ${error instanceof Error ? error.message : String(error)}`,
171
+ ],
172
+ };
173
+ }
174
+ };
121
175
  }
@@ -46,4 +46,11 @@ export declare class PdfAdbePkcsX509RsaSha1SignatureObject extends PdfSignatureO
46
46
  * @returns The signature bytes and revocation information.
47
47
  */
48
48
  sign: PdfSignatureObject['sign'];
49
+ /**
50
+ * Verifies the signature against the provided document bytes.
51
+ *
52
+ * @param options - Verification options including the signed bytes.
53
+ * @returns The verification result.
54
+ */
55
+ verify: PdfSignatureObject['verify'];
49
56
  }
@@ -2,7 +2,10 @@ import { PrivateKeyInfo } from 'pki-lite/keys/PrivateKeyInfo';
2
2
  import { PdfSignatureObject } from './base';
3
3
  import { OctetString } from 'pki-lite/asn1/OctetString';
4
4
  import { AlgorithmIdentifier } from 'pki-lite/algorithms/AlgorithmIdentifier';
5
+ import { Certificate } from 'pki-lite/x509/Certificate';
5
6
  import { fetchRevocationInfo } from '../utils';
7
+ import { PdfArray } from '../../core/objects/pdf-array';
8
+ import { PdfHexadecimal } from '../../core/objects/pdf-hexadecimal';
6
9
  /**
7
10
  * X.509 RSA-SHA1 signature object (adbe.x509.rsa_sha1).
8
11
  * Creates a raw RSA-SHA1 signature with certificates in the Cert entry.
@@ -78,4 +81,78 @@ export class PdfAdbePkcsX509RsaSha1SignatureObject extends PdfSignatureObject {
78
81
  revocationInfo,
79
82
  };
80
83
  };
84
+ /**
85
+ * Verifies the signature against the provided document bytes.
86
+ *
87
+ * @param options - Verification options including the signed bytes.
88
+ * @returns The verification result.
89
+ */
90
+ verify = async (options) => {
91
+ const { bytes } = options;
92
+ try {
93
+ const certificates = [];
94
+ const Cert = this.content.get('Cert');
95
+ if (Cert instanceof PdfArray) {
96
+ for (const certObj of Cert.items) {
97
+ const certBytes = certObj instanceof PdfHexadecimal
98
+ ? certObj.bytes
99
+ : certObj.raw;
100
+ const certificate = Certificate.fromDer(certBytes);
101
+ certificates.push(certificate);
102
+ }
103
+ }
104
+ else if (Cert instanceof PdfHexadecimal) {
105
+ const certificate = Certificate.fromDer(Cert.bytes);
106
+ certificates.push(certificate);
107
+ }
108
+ else if (Cert) {
109
+ const certificate = Certificate.fromDer(Cert.raw);
110
+ certificates.push(certificate);
111
+ }
112
+ else {
113
+ throw new Error('No Cert entry found in signature dictionary');
114
+ }
115
+ if (certificates.length === 0) {
116
+ return {
117
+ valid: false,
118
+ reasons: [
119
+ 'No certificates available for adbe.x509.rsa_sha1 verification',
120
+ ],
121
+ };
122
+ }
123
+ // Parse the signature as an OctetString
124
+ const signatureOctetString = OctetString.fromDer(this.signedBytes);
125
+ const signatureValue = signatureOctetString.bytes;
126
+ const signatureAlgorithm = AlgorithmIdentifier.signatureAlgorithm(PdfAdbePkcsX509RsaSha1SignatureObject.ALGORITHM);
127
+ for (const cert of certificates) {
128
+ const isValid = await signatureAlgorithm.verify(bytes, signatureValue, cert.tbsCertificate.subjectPublicKeyInfo);
129
+ if (isValid) {
130
+ if (options.certificateValidation === true) {
131
+ await cert.validate({
132
+ //TODO: implement default validation options
133
+ checkSignature: true,
134
+ validateChain: true,
135
+ otherCertificates: certificates,
136
+ });
137
+ }
138
+ else if (options.certificateValidation) {
139
+ await cert.validate(options.certificateValidation);
140
+ }
141
+ return { valid: true };
142
+ }
143
+ }
144
+ return {
145
+ valid: false,
146
+ reasons: ['Signature verification failed for all certificates'],
147
+ };
148
+ }
149
+ catch (error) {
150
+ return {
151
+ valid: false,
152
+ reasons: [
153
+ `Failed to verify signature: ${error instanceof Error ? error.message : String(error)}`,
154
+ ],
155
+ };
156
+ }
157
+ };
81
158
  }
@@ -1,5 +1,5 @@
1
1
  import { PdfDictionary } from '../../core/objects/pdf-dictionary';
2
- import { PdfSignatureDictionaryEntries, PdfSignatureSubType, RevocationInfo } from '../types';
2
+ import { PdfSignatureDictionaryEntries, PdfSignatureSubType, PdfSignatureVerificationOptions, PdfSignatureVerificationResult, RevocationInfo } from '../types';
3
3
  import { PdfHexadecimal } from '../../core/objects/pdf-hexadecimal';
4
4
  import { PdfIndirectObject } from '../../core/objects/pdf-indirect-object';
5
5
  import { ByteArray } from '../../types';
@@ -71,6 +71,13 @@ export declare abstract class PdfSignatureObject extends PdfIndirectObject<PdfSi
71
71
  signedBytes: ByteArray;
72
72
  revocationInfo?: RevocationInfo;
73
73
  }>;
74
+ /**
75
+ * Verifies the signature against the provided document bytes.
76
+ *
77
+ * @param options - Verification options including the signed bytes.
78
+ * @returns The verification result.
79
+ */
80
+ abstract verify(options: PdfSignatureVerificationOptions): Promise<PdfSignatureVerificationResult>;
74
81
  /**
75
82
  * Gets the signature hexadecimal content.
76
83
  *
@@ -104,4 +111,12 @@ export declare abstract class PdfSignatureObject extends PdfIndirectObject<PdfSi
104
111
  * @returns High order value to place signature near end of document.
105
112
  */
106
113
  order(): number;
114
+ /**
115
+ * Compares two byte arrays for equality.
116
+ *
117
+ * @param a - First byte array.
118
+ * @param b - Second byte array.
119
+ * @returns True if arrays are equal, false otherwise.
120
+ */
121
+ protected compareArrays(a: ByteArray, b: ByteArray): boolean;
107
122
  }
@@ -142,4 +142,22 @@ export class PdfSignatureObject extends PdfIndirectObject {
142
142
  order() {
143
143
  return PdfIndirectObject.MAX_ORDER_INDEX - 10;
144
144
  }
145
+ /**
146
+ * Compares two byte arrays for equality.
147
+ *
148
+ * @param a - First byte array.
149
+ * @param b - Second byte array.
150
+ * @returns True if arrays are equal, false otherwise.
151
+ */
152
+ compareArrays(a, b) {
153
+ if (a.length !== b.length) {
154
+ return false;
155
+ }
156
+ for (let i = 0; i < a.length; i++) {
157
+ if (a[i] !== b[i]) {
158
+ return false;
159
+ }
160
+ }
161
+ return true;
162
+ }
145
163
  }
@@ -61,4 +61,11 @@ export declare class PdfEtsiCadesDetachedSignatureObject extends PdfSignatureObj
61
61
  * @returns The CMS SignedData and revocation information.
62
62
  */
63
63
  sign: PdfSignatureObject['sign'];
64
+ /**
65
+ * Verifies the signature against the provided document bytes.
66
+ *
67
+ * @param options - Verification options including the signed bytes.
68
+ * @returns The verification result.
69
+ */
70
+ verify: PdfSignatureObject['verify'];
64
71
  }
@@ -156,4 +156,40 @@ export class PdfEtsiCadesDetachedSignatureObject extends PdfSignatureObject {
156
156
  revocationInfo,
157
157
  };
158
158
  };
159
+ /**
160
+ * Verifies the signature against the provided document bytes.
161
+ *
162
+ * @param options - Verification options including the signed bytes.
163
+ * @returns The verification result.
164
+ */
165
+ verify = async (options) => {
166
+ const { bytes, certificateValidation } = options;
167
+ try {
168
+ const signedData = SignedData.fromCms(this.signedBytes);
169
+ const certValidationOptions = certificateValidation === true
170
+ ? {}
171
+ : certificateValidation || undefined;
172
+ const result = await signedData.verify({
173
+ data: bytes,
174
+ certificateValidation: certValidationOptions,
175
+ });
176
+ if (result.valid) {
177
+ return { valid: true };
178
+ }
179
+ else {
180
+ return {
181
+ valid: false,
182
+ reasons: result.reasons,
183
+ };
184
+ }
185
+ }
186
+ catch (error) {
187
+ return {
188
+ valid: false,
189
+ reasons: [
190
+ `Failed to verify signature: ${error instanceof Error ? error.message : String(error)}`,
191
+ ],
192
+ };
193
+ }
194
+ };
159
195
  }
@@ -34,4 +34,11 @@ export declare class PdfEtsiRfc3161SignatureObject extends PdfSignatureObject {
34
34
  * @throws Error if no timestamp token is received.
35
35
  */
36
36
  sign: PdfSignatureObject['sign'];
37
+ /**
38
+ * Verifies the timestamp signature against the provided document bytes.
39
+ *
40
+ * @param options - Verification options including the signed bytes.
41
+ * @returns The verification result.
42
+ */
43
+ verify: PdfSignatureObject['verify'];
37
44
  }
@@ -1,9 +1,12 @@
1
1
  import { PdfSignatureObject } from './base';
2
2
  import { DigestAlgorithmIdentifier } from 'pki-lite/algorithms/AlgorithmIdentifier';
3
3
  import { TimeStampReq } from 'pki-lite/timestamp/TimeStampReq';
4
+ import { TSTInfo } from 'pki-lite/timestamp/TSTInfo';
4
5
  import { MessageImprint } from 'pki-lite/timestamp/MessageImprint';
5
6
  import { SignedData } from 'pki-lite/pkcs7/SignedData';
6
7
  import { fetchRevocationInfo } from '../utils';
8
+ import { Certificate } from 'pki-lite/x509/Certificate';
9
+ import { OIDs } from 'pki-lite/core/OIDs';
7
10
  /**
8
11
  * RFC 3161 timestamp signature object (ETSI.RFC3161).
9
12
  * Creates document timestamps using a Time Stamp Authority (TSA).
@@ -67,4 +70,103 @@ export class PdfEtsiRfc3161SignatureObject extends PdfSignatureObject {
67
70
  revocationInfo,
68
71
  };
69
72
  };
73
+ /**
74
+ * Verifies the timestamp signature against the provided document bytes.
75
+ *
76
+ * @param options - Verification options including the signed bytes.
77
+ * @returns The verification result.
78
+ */
79
+ verify = async (options) => {
80
+ const { bytes, certificateValidation } = options;
81
+ const digestAlgorithm = DigestAlgorithmIdentifier.digestAlgorithm('SHA-512');
82
+ const expectedMessageImprint = new MessageImprint({
83
+ hashAlgorithm: digestAlgorithm,
84
+ hashedMessage: await digestAlgorithm.digest(bytes),
85
+ });
86
+ try {
87
+ const signedData = SignedData.fromCms(this.signedBytes);
88
+ // Extract TSTInfo from the signed data's encapsulated content
89
+ // The eContentType should be id-ct-TSTInfo (1.2.840.113549.1.9.16.1.4)
90
+ const encapContentInfo = signedData.encapContentInfo;
91
+ if (encapContentInfo.eContentType.toString() !== OIDs.PKCS7.TST_INFO) {
92
+ return {
93
+ valid: false,
94
+ reasons: [
95
+ `Invalid content type: expected id-ct-TSTInfo (${OIDs.PKCS7.TST_INFO}), got ${encapContentInfo.eContentType.toString()}`,
96
+ ],
97
+ };
98
+ }
99
+ if (!encapContentInfo.eContent) {
100
+ return {
101
+ valid: false,
102
+ reasons: ['No TSTInfo content found in timestamp token'],
103
+ };
104
+ }
105
+ // Parse the TSTInfo from the encapsulated content
106
+ const tstInfo = TSTInfo.fromDer(encapContentInfo.eContent);
107
+ // Verify that the messageImprint in TSTInfo matches what we computed
108
+ const tstMessageImprint = tstInfo.messageImprint;
109
+ if (tstMessageImprint.hashAlgorithm.algorithm.toString() !==
110
+ expectedMessageImprint.hashAlgorithm.algorithm.toString()) {
111
+ return {
112
+ valid: false,
113
+ reasons: [
114
+ `Hash algorithm mismatch: TSTInfo uses ${tstMessageImprint.hashAlgorithm.algorithm.toString()}, expected ${expectedMessageImprint.hashAlgorithm.algorithm.toString()}`,
115
+ ],
116
+ };
117
+ }
118
+ const tstHash = tstMessageImprint.hashedMessage;
119
+ const expectedHash = expectedMessageImprint.hashedMessage;
120
+ if (tstHash.length !== expectedHash.length) {
121
+ return {
122
+ valid: false,
123
+ reasons: [
124
+ `Hash length mismatch: TSTInfo has ${tstHash.length} bytes, expected ${expectedHash.length} bytes`,
125
+ ],
126
+ };
127
+ }
128
+ for (let i = 0; i < tstHash.length; i++) {
129
+ if (tstHash[i] !== expectedHash[i]) {
130
+ return {
131
+ valid: false,
132
+ reasons: [
133
+ 'Message imprint mismatch: the timestamp does not match the document',
134
+ ],
135
+ };
136
+ }
137
+ }
138
+ // Verify the signature on the SignedData
139
+ const certValidationOptions = certificateValidation === true
140
+ ? {}
141
+ : certificateValidation || undefined;
142
+ if (options.certificateValidation) {
143
+ const certificates = signedData.certificates ?? [];
144
+ for (const cert of certificates) {
145
+ if (!(cert instanceof Certificate)) {
146
+ //TODO: support other cert types
147
+ continue;
148
+ }
149
+ if (!(await cert.validate(certValidationOptions))) {
150
+ return {
151
+ valid: false,
152
+ reasons: [
153
+ 'Certificate validation failed for timestamp signer',
154
+ ],
155
+ };
156
+ }
157
+ }
158
+ }
159
+ return {
160
+ valid: true,
161
+ };
162
+ }
163
+ catch (error) {
164
+ return {
165
+ valid: false,
166
+ reasons: [
167
+ `Failed to verify signature: ${error instanceof Error ? error.message : String(error)}`,
168
+ ],
169
+ };
170
+ }
171
+ };
70
172
  }