pdf-oxide 0.3.24
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/README.md +218 -0
- package/binding.gyp +35 -0
- package/package.json +78 -0
- package/src/builders/annotation-builder.ts +367 -0
- package/src/builders/conversion-options-builder.ts +257 -0
- package/src/builders/index.ts +12 -0
- package/src/builders/metadata-builder.ts +317 -0
- package/src/builders/pdf-builder.ts +386 -0
- package/src/builders/search-options-builder.ts +151 -0
- package/src/document-editor-manager.ts +318 -0
- package/src/errors.ts +1629 -0
- package/src/form-field-manager.ts +666 -0
- package/src/hybrid-ml-manager.ts +283 -0
- package/src/index.ts +453 -0
- package/src/managers/accessibility-manager.ts +338 -0
- package/src/managers/annotation-manager.ts +439 -0
- package/src/managers/barcode-manager.ts +235 -0
- package/src/managers/batch-manager.ts +533 -0
- package/src/managers/cache-manager.ts +486 -0
- package/src/managers/compliance-manager.ts +375 -0
- package/src/managers/content-manager.ts +339 -0
- package/src/managers/document-utility-manager.ts +922 -0
- package/src/managers/dom-pdf-creator.ts +365 -0
- package/src/managers/editing-manager.ts +514 -0
- package/src/managers/enterprise-manager.ts +478 -0
- package/src/managers/extended-managers.ts +437 -0
- package/src/managers/extraction-manager.ts +583 -0
- package/src/managers/final-utilities.ts +429 -0
- package/src/managers/hybrid-ml-advanced.ts +479 -0
- package/src/managers/index.ts +239 -0
- package/src/managers/layer-manager.ts +500 -0
- package/src/managers/metadata-manager.ts +303 -0
- package/src/managers/ocr-manager.ts +756 -0
- package/src/managers/optimization-manager.ts +262 -0
- package/src/managers/outline-manager.ts +196 -0
- package/src/managers/page-manager.ts +289 -0
- package/src/managers/pattern-detection.ts +440 -0
- package/src/managers/rendering-manager.ts +863 -0
- package/src/managers/search-manager.ts +385 -0
- package/src/managers/security-manager.ts +345 -0
- package/src/managers/signature-manager.ts +1664 -0
- package/src/managers/streams.ts +618 -0
- package/src/managers/xfa-manager.ts +500 -0
- package/src/pdf-creator-manager.ts +494 -0
- package/src/properties.ts +522 -0
- package/src/result-accessors-manager.ts +867 -0
- package/src/tests/advanced-features.test.ts +414 -0
- package/src/tests/advanced.test.ts +266 -0
- package/src/tests/extended-managers.test.ts +316 -0
- package/src/tests/final-utilities.test.ts +455 -0
- package/src/tests/foundation.test.ts +315 -0
- package/src/tests/high-demand.test.ts +257 -0
- package/src/tests/specialized.test.ts +97 -0
- package/src/thumbnail-manager.ts +272 -0
- package/src/types/common.ts +142 -0
- package/src/types/document-types.ts +457 -0
- package/src/types/index.ts +6 -0
- package/src/types/manager-types.ts +284 -0
- package/src/types/native-bindings.ts +517 -0
- package/src/workers/index.ts +7 -0
- package/src/workers/pool.ts +274 -0
- package/src/workers/worker.ts +131 -0
|
@@ -0,0 +1,1664 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SignatureManager - Canonical Signature Manager (merged from 3 implementations)
|
|
3
|
+
*
|
|
4
|
+
* Consolidates:
|
|
5
|
+
* - src/signature-manager.ts SignatureManager (verification + basic field management)
|
|
6
|
+
* - src/managers/barcode-signature-rendering.ts SignaturesManager (certificate loading + signing + detail info)
|
|
7
|
+
* - src/managers/signature-creation-manager.ts SignatureCreationManager (complete signing workflow + LTV + timestamps)
|
|
8
|
+
*
|
|
9
|
+
* Provides comprehensive digital signature operations with full type safety
|
|
10
|
+
* and FFI integration.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { EventEmitter } from 'events';
|
|
14
|
+
import { promises as fs } from 'fs';
|
|
15
|
+
import { mapFfiErrorCode, SignatureException } from '../errors';
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Type Definitions (merged from all 3 sources)
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Signature algorithms
|
|
23
|
+
*/
|
|
24
|
+
export enum SignatureAlgorithm {
|
|
25
|
+
RSA2048 = 'RSA2048',
|
|
26
|
+
RSA3072 = 'RSA3072',
|
|
27
|
+
RSA4096 = 'RSA4096',
|
|
28
|
+
ECDSAP256 = 'ECDSA_P256',
|
|
29
|
+
ECDSAP384 = 'ECDSA_P384',
|
|
30
|
+
ECDSAP521 = 'ECDSA_P521',
|
|
31
|
+
RSA_SHA256 = 'RSA_SHA256',
|
|
32
|
+
RSA_SHA384 = 'RSA_SHA384',
|
|
33
|
+
RSA_SHA512 = 'RSA_SHA512',
|
|
34
|
+
ECDSA_SHA256 = 'ECDSA_SHA256',
|
|
35
|
+
ECDSA_SHA384 = 'ECDSA_SHA384',
|
|
36
|
+
ECDSA_SHA512 = 'ECDSA_SHA512',
|
|
37
|
+
ED25519 = 'ED25519',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Digest algorithms for signature
|
|
42
|
+
*/
|
|
43
|
+
export enum DigestAlgorithm {
|
|
44
|
+
SHA256 = 'SHA256',
|
|
45
|
+
SHA384 = 'SHA384',
|
|
46
|
+
SHA512 = 'SHA512',
|
|
47
|
+
SHA512_256 = 'SHA512_256',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Signature type enumeration
|
|
52
|
+
*/
|
|
53
|
+
export enum SignatureType {
|
|
54
|
+
APPROVAL = 'approval',
|
|
55
|
+
CERTIFICATION = 'certification',
|
|
56
|
+
USAGE_RIGHTS = 'usage_rights',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Certification permission level
|
|
61
|
+
*/
|
|
62
|
+
export enum CertificationPermission {
|
|
63
|
+
NO_CHANGES = 1,
|
|
64
|
+
FORM_FILLING = 2,
|
|
65
|
+
FORM_FILLING_ANNOTATIONS = 3,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Certificate format enumeration
|
|
70
|
+
*/
|
|
71
|
+
export enum CertificateFormat {
|
|
72
|
+
PFX = 'pfx',
|
|
73
|
+
PEM = 'pem',
|
|
74
|
+
DER = 'der',
|
|
75
|
+
P12 = 'p12',
|
|
76
|
+
CER = 'cer',
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Timestamp response status
|
|
81
|
+
*/
|
|
82
|
+
export enum TimestampStatus {
|
|
83
|
+
SUCCESS = 'success',
|
|
84
|
+
FAILED = 'failed',
|
|
85
|
+
TIMEOUT = 'timeout',
|
|
86
|
+
INVALID_RESPONSE = 'invalid_response',
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Interfaces from root-level SignatureManager ---
|
|
90
|
+
|
|
91
|
+
export interface DigitalSignature {
|
|
92
|
+
signatureName: string;
|
|
93
|
+
signingDate?: Date;
|
|
94
|
+
reason?: string;
|
|
95
|
+
location?: string;
|
|
96
|
+
signer?: string;
|
|
97
|
+
isCertified: boolean;
|
|
98
|
+
algorithm?: SignatureAlgorithm;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface SignatureField {
|
|
102
|
+
fieldName: string;
|
|
103
|
+
pageIndex: number;
|
|
104
|
+
isSigned: boolean;
|
|
105
|
+
signature?: DigitalSignature;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface SignatureValidationResult {
|
|
109
|
+
isValid: boolean;
|
|
110
|
+
signatures: DigitalSignature[];
|
|
111
|
+
issues: string[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface SignatureConfig {
|
|
115
|
+
algorithm?: SignatureAlgorithm;
|
|
116
|
+
digestAlgorithm?: DigestAlgorithm;
|
|
117
|
+
reason?: string;
|
|
118
|
+
location?: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- Interfaces from SignaturesManager ---
|
|
122
|
+
|
|
123
|
+
export interface Certificate {
|
|
124
|
+
subject: string;
|
|
125
|
+
issuer: string;
|
|
126
|
+
serial: string;
|
|
127
|
+
notBefore: number;
|
|
128
|
+
notAfter: number;
|
|
129
|
+
isValid: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface Signature {
|
|
133
|
+
signerName: string;
|
|
134
|
+
signingTime: number;
|
|
135
|
+
reason?: string;
|
|
136
|
+
location?: string;
|
|
137
|
+
certificate: Certificate;
|
|
138
|
+
isValid: boolean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --- Interfaces from SignatureCreationManager ---
|
|
142
|
+
|
|
143
|
+
export interface CertificateInfo {
|
|
144
|
+
readonly subject: string;
|
|
145
|
+
readonly issuer: string;
|
|
146
|
+
readonly serialNumber: string;
|
|
147
|
+
readonly validFrom: Date;
|
|
148
|
+
readonly validTo: Date;
|
|
149
|
+
readonly isValid: boolean;
|
|
150
|
+
readonly isSelfSigned: boolean;
|
|
151
|
+
readonly keyUsage?: readonly string[];
|
|
152
|
+
readonly extendedKeyUsage?: readonly string[];
|
|
153
|
+
readonly subjectAltNames?: readonly string[];
|
|
154
|
+
readonly thumbprint?: string;
|
|
155
|
+
readonly publicKeyAlgorithm?: string;
|
|
156
|
+
readonly signatureAlgorithm?: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface CertificateChain {
|
|
160
|
+
readonly certificates: readonly CertificateInfo[];
|
|
161
|
+
readonly isComplete: boolean;
|
|
162
|
+
readonly validationStatus: 'valid' | 'invalid' | 'unknown';
|
|
163
|
+
readonly validationMessages?: readonly string[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface LoadedCertificate {
|
|
167
|
+
readonly certificateId: string;
|
|
168
|
+
readonly info: CertificateInfo;
|
|
169
|
+
readonly hasPrivateKey: boolean;
|
|
170
|
+
readonly chain?: CertificateChain;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface SignatureAppearance {
|
|
174
|
+
readonly showName?: boolean;
|
|
175
|
+
readonly showDate?: boolean;
|
|
176
|
+
readonly showReason?: boolean;
|
|
177
|
+
readonly showLocation?: boolean;
|
|
178
|
+
readonly showLabels?: boolean;
|
|
179
|
+
readonly imageData?: Buffer;
|
|
180
|
+
readonly imagePath?: string;
|
|
181
|
+
readonly backgroundColor?: string;
|
|
182
|
+
readonly textColor?: string;
|
|
183
|
+
readonly borderColor?: string;
|
|
184
|
+
readonly borderWidth?: number;
|
|
185
|
+
readonly font?: string;
|
|
186
|
+
readonly fontSize?: number;
|
|
187
|
+
readonly customText?: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface SignatureFieldConfig {
|
|
191
|
+
readonly fieldName: string;
|
|
192
|
+
readonly pageIndex: number;
|
|
193
|
+
readonly x: number;
|
|
194
|
+
readonly y: number;
|
|
195
|
+
readonly width: number;
|
|
196
|
+
readonly height: number;
|
|
197
|
+
readonly appearance?: SignatureAppearance;
|
|
198
|
+
readonly tooltip?: string;
|
|
199
|
+
readonly isRequired?: boolean;
|
|
200
|
+
readonly isReadOnly?: boolean;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface SigningOptions {
|
|
204
|
+
readonly reason?: string;
|
|
205
|
+
readonly location?: string;
|
|
206
|
+
readonly contactInfo?: string;
|
|
207
|
+
readonly signatureType?: SignatureType;
|
|
208
|
+
readonly certificationPermission?: CertificationPermission;
|
|
209
|
+
readonly algorithm?: SignatureAlgorithm;
|
|
210
|
+
readonly digestAlgorithm?: DigestAlgorithm;
|
|
211
|
+
readonly appearance?: SignatureAppearance;
|
|
212
|
+
readonly embedTimestamp?: boolean;
|
|
213
|
+
readonly timestampServerUrl?: string;
|
|
214
|
+
readonly enableLtv?: boolean;
|
|
215
|
+
readonly ocspResponderUrl?: string;
|
|
216
|
+
readonly crlDistributionPoints?: readonly string[];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface TimestampConfig {
|
|
220
|
+
readonly serverUrl: string;
|
|
221
|
+
readonly username?: string;
|
|
222
|
+
readonly password?: string;
|
|
223
|
+
readonly hashAlgorithm?: DigestAlgorithm;
|
|
224
|
+
readonly timeout?: number;
|
|
225
|
+
readonly policy?: string;
|
|
226
|
+
readonly nonce?: boolean;
|
|
227
|
+
readonly certReq?: boolean;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface SigningResult {
|
|
231
|
+
readonly success: boolean;
|
|
232
|
+
readonly signatureId?: string;
|
|
233
|
+
readonly signingTime?: Date;
|
|
234
|
+
readonly timestampTime?: Date;
|
|
235
|
+
readonly error?: string;
|
|
236
|
+
readonly warnings?: readonly string[];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface TimestampResult {
|
|
240
|
+
readonly status: TimestampStatus;
|
|
241
|
+
readonly timestamp?: Date;
|
|
242
|
+
readonly serialNumber?: string;
|
|
243
|
+
readonly tsaName?: string;
|
|
244
|
+
readonly error?: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// --- Interfaces for FFI-based signing (Phase 1) ---
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Opaque handle to signing credentials loaded via FFI.
|
|
251
|
+
* Wraps a native pointer returned by pdf_credentials_from_pkcs12 or pdf_credentials_from_pem.
|
|
252
|
+
*/
|
|
253
|
+
export interface SigningCredentials {
|
|
254
|
+
/** Internal native handle - do not access directly */
|
|
255
|
+
readonly _handle: any;
|
|
256
|
+
/** Source type: 'pkcs12' or 'pem' */
|
|
257
|
+
readonly sourceType: 'pkcs12' | 'pem';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Options for FFI-based document signing operations.
|
|
262
|
+
*/
|
|
263
|
+
export interface SignOptions {
|
|
264
|
+
/** Reason for signing the document */
|
|
265
|
+
readonly reason?: string;
|
|
266
|
+
/** Location where the document was signed */
|
|
267
|
+
readonly location?: string;
|
|
268
|
+
/** Contact information for the signer */
|
|
269
|
+
readonly contact?: string;
|
|
270
|
+
/** Digest algorithm (0=SHA1, 1=SHA256, 2=SHA384, 3=SHA512). Defaults to SHA256 (1). */
|
|
271
|
+
readonly algorithm?: number;
|
|
272
|
+
/** Signature subfilter (0=PKCS7_DETACHED, 1=PKCS7_SHA1, 2=CADES_DETACHED). Defaults to PKCS7_DETACHED (0). */
|
|
273
|
+
readonly subfilter?: number;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* FFI digest algorithm constants for use with SignOptions.algorithm
|
|
278
|
+
*/
|
|
279
|
+
export enum FfiDigestAlgorithm {
|
|
280
|
+
SHA1 = 0,
|
|
281
|
+
SHA256 = 1,
|
|
282
|
+
SHA384 = 2,
|
|
283
|
+
SHA512 = 3,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* FFI signature subfilter constants for use with SignOptions.subfilter
|
|
288
|
+
*/
|
|
289
|
+
export enum FfiSignatureSubFilter {
|
|
290
|
+
PKCS7_DETACHED = 0,
|
|
291
|
+
PKCS7_SHA1 = 1,
|
|
292
|
+
CADES_DETACHED = 2,
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// =============================================================================
|
|
296
|
+
// Canonical SignatureManager
|
|
297
|
+
// =============================================================================
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Canonical Signature Manager - all signature operations in one class.
|
|
301
|
+
*
|
|
302
|
+
* Provides:
|
|
303
|
+
* - Verification (from root SignatureManager)
|
|
304
|
+
* - Certificate loading (from SignatureCreationManager)
|
|
305
|
+
* - Document signing with timestamps and LTV (from SignatureCreationManager)
|
|
306
|
+
* - Detailed signature info (from SignaturesManager)
|
|
307
|
+
*/
|
|
308
|
+
export class SignatureManager extends EventEmitter {
|
|
309
|
+
private document: any;
|
|
310
|
+
private resultCache = new Map<string, any>();
|
|
311
|
+
private maxCacheSize = 100;
|
|
312
|
+
private native: any;
|
|
313
|
+
private readonly loadedCertificates: Map<string, LoadedCertificate> = new Map();
|
|
314
|
+
private readonly createdFields: Map<string, SignatureFieldConfig> = new Map();
|
|
315
|
+
|
|
316
|
+
constructor(document: any) {
|
|
317
|
+
super();
|
|
318
|
+
if (!document) {
|
|
319
|
+
throw new Error('Document cannot be null or undefined');
|
|
320
|
+
}
|
|
321
|
+
this.document = document;
|
|
322
|
+
try {
|
|
323
|
+
this.native = require('../../index.node');
|
|
324
|
+
} catch {
|
|
325
|
+
this.native = null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ===========================================================================
|
|
330
|
+
// Verification (from root SignatureManager)
|
|
331
|
+
// ===========================================================================
|
|
332
|
+
|
|
333
|
+
async getSignatures(): Promise<DigitalSignature[]> {
|
|
334
|
+
const cacheKey = 'signatures:all';
|
|
335
|
+
if (this.resultCache.has(cacheKey)) return this.resultCache.get(cacheKey);
|
|
336
|
+
let signatures: DigitalSignature[] = [];
|
|
337
|
+
if (this.native?.get_signatures) {
|
|
338
|
+
try {
|
|
339
|
+
const signaturesJson = this.native.get_signatures() ?? [];
|
|
340
|
+
signatures = signaturesJson.length > 0 ? JSON.parse(signaturesJson[0] || '[]') : [];
|
|
341
|
+
} catch { signatures = []; }
|
|
342
|
+
}
|
|
343
|
+
this.setCached(cacheKey, signatures);
|
|
344
|
+
this.emit('signaturesRetrieved', { count: signatures.length });
|
|
345
|
+
return signatures;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async getSignatureFields(): Promise<SignatureField[]> {
|
|
349
|
+
const cacheKey = 'signatures:fields';
|
|
350
|
+
if (this.resultCache.has(cacheKey)) return this.resultCache.get(cacheKey);
|
|
351
|
+
let fields: SignatureField[] = [];
|
|
352
|
+
if (this.native?.get_signature_fields) {
|
|
353
|
+
try {
|
|
354
|
+
const fieldsJson = this.native.get_signature_fields() ?? [];
|
|
355
|
+
fields = fieldsJson.length > 0 ? JSON.parse(fieldsJson[0] || '[]') : [];
|
|
356
|
+
} catch { fields = []; }
|
|
357
|
+
}
|
|
358
|
+
this.setCached(cacheKey, fields);
|
|
359
|
+
return fields;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async verifySignatures(): Promise<SignatureValidationResult> {
|
|
363
|
+
const cacheKey = 'signatures:verification';
|
|
364
|
+
if (this.resultCache.has(cacheKey)) return this.resultCache.get(cacheKey);
|
|
365
|
+
let result: SignatureValidationResult = { isValid: true, signatures: [], issues: [] };
|
|
366
|
+
if (this.native?.verify_signatures) {
|
|
367
|
+
try { result = JSON.parse(this.native.verify_signatures()); } catch { /* defaults */ }
|
|
368
|
+
}
|
|
369
|
+
this.setCached(cacheKey, result);
|
|
370
|
+
this.emit('signaturesVerified', { isValid: result.isValid, issueCount: result.issues.length });
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async verifySignature(signatureName: string): Promise<SignatureValidationResult> {
|
|
375
|
+
const cacheKey = `signatures:verify:${signatureName}`;
|
|
376
|
+
if (this.resultCache.has(cacheKey)) return this.resultCache.get(cacheKey);
|
|
377
|
+
let result: SignatureValidationResult = { isValid: true, signatures: [], issues: [] };
|
|
378
|
+
if (this.native?.verify_signature) {
|
|
379
|
+
try { result = JSON.parse(this.native.verify_signature(signatureName)); } catch { /* defaults */ }
|
|
380
|
+
}
|
|
381
|
+
this.setCached(cacheKey, result);
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async isCertified(): Promise<boolean> {
|
|
386
|
+
const cacheKey = 'signatures:is_certified';
|
|
387
|
+
if (this.resultCache.has(cacheKey)) return this.resultCache.get(cacheKey);
|
|
388
|
+
const result = this.document?.isCertified?.() ?? this.native?.is_certified?.() ?? false;
|
|
389
|
+
this.setCached(cacheKey, result);
|
|
390
|
+
return result;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async getSignatureCount(): Promise<number> {
|
|
394
|
+
const cacheKey = 'signatures:count';
|
|
395
|
+
if (this.resultCache.has(cacheKey)) return this.resultCache.get(cacheKey);
|
|
396
|
+
const count = this.document?.getSignatureCount?.() ?? this.native?.get_signature_count?.() ?? 0;
|
|
397
|
+
this.setCached(cacheKey, count);
|
|
398
|
+
return count;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async isSigned(): Promise<boolean> {
|
|
402
|
+
return (await this.getSignatureCount()) > 0;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ===========================================================================
|
|
406
|
+
// Certificate Loading (from SignatureCreationManager)
|
|
407
|
+
// ===========================================================================
|
|
408
|
+
|
|
409
|
+
async loadCertificateFromFile(filePath: string, password?: string, format?: CertificateFormat): Promise<LoadedCertificate | null> {
|
|
410
|
+
try {
|
|
411
|
+
const certData = await fs.readFile(filePath);
|
|
412
|
+
return this.loadCertificateFromBytes(certData, password, format);
|
|
413
|
+
} catch (error) { this.emit('error', error); return null; }
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async loadCertificateFromBytes(certData: Buffer, password?: string, format?: CertificateFormat): Promise<LoadedCertificate | null> {
|
|
417
|
+
try {
|
|
418
|
+
const certId = `cert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
419
|
+
const nativeResult = await this.document?.loadCertificate?.(certData, password, format);
|
|
420
|
+
const info: CertificateInfo = nativeResult?.info ?? {
|
|
421
|
+
subject: nativeResult?.subject ?? 'Unknown', issuer: nativeResult?.issuer ?? 'Unknown',
|
|
422
|
+
serialNumber: nativeResult?.serialNumber ?? '', validFrom: new Date(nativeResult?.validFrom ?? Date.now()),
|
|
423
|
+
validTo: new Date(nativeResult?.validTo ?? Date.now() + 365 * 24 * 60 * 60 * 1000),
|
|
424
|
+
isValid: nativeResult?.isValid ?? false, isSelfSigned: nativeResult?.isSelfSigned ?? false,
|
|
425
|
+
};
|
|
426
|
+
const loadedCert: LoadedCertificate = { certificateId: certId, info, hasPrivateKey: nativeResult?.hasPrivateKey ?? true, chain: nativeResult?.chain };
|
|
427
|
+
this.loadedCertificates.set(certId, loadedCert);
|
|
428
|
+
this.emit('certificate-loaded', { certificateId: certId, subject: info.subject });
|
|
429
|
+
return loadedCert;
|
|
430
|
+
} catch (error) { this.emit('error', error); return null; }
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async loadCertificateFromPem(certificatePem: string, privateKeyPem?: string, privateKeyPassword?: string): Promise<LoadedCertificate | null> {
|
|
434
|
+
try {
|
|
435
|
+
const certId = `cert_pem_${Date.now()}`;
|
|
436
|
+
const nativeResult = await this.document?.loadCertificateFromPem?.(certificatePem, privateKeyPem, privateKeyPassword);
|
|
437
|
+
if (!nativeResult) throw new Error('Failed to load PEM certificate');
|
|
438
|
+
const info: CertificateInfo = {
|
|
439
|
+
subject: nativeResult.subject ?? 'Unknown', issuer: nativeResult.issuer ?? 'Unknown',
|
|
440
|
+
serialNumber: nativeResult.serialNumber ?? '', validFrom: new Date(nativeResult.validFrom ?? Date.now()),
|
|
441
|
+
validTo: new Date(nativeResult.validTo ?? Date.now() + 365 * 24 * 60 * 60 * 1000),
|
|
442
|
+
isValid: nativeResult.isValid ?? false, isSelfSigned: nativeResult.isSelfSigned ?? false,
|
|
443
|
+
};
|
|
444
|
+
const loadedCert: LoadedCertificate = { certificateId: certId, info, hasPrivateKey: !!privateKeyPem };
|
|
445
|
+
this.loadedCertificates.set(certId, loadedCert);
|
|
446
|
+
this.emit('certificate-loaded', { certificateId: certId, subject: info.subject });
|
|
447
|
+
return loadedCert;
|
|
448
|
+
} catch (error) { this.emit('error', error); return null; }
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async getCertificateInfo(certificateId: string): Promise<CertificateInfo | null> {
|
|
452
|
+
return this.loadedCertificates.get(certificateId)?.info ?? null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async getCertificateChain(certificateId: string): Promise<CertificateChain | null> {
|
|
456
|
+
try {
|
|
457
|
+
const cert = this.loadedCertificates.get(certificateId);
|
|
458
|
+
if (!cert) return null;
|
|
459
|
+
if (cert.chain) return cert.chain;
|
|
460
|
+
return await this.document?.getCertificateChain?.(certificateId) ?? null;
|
|
461
|
+
} catch (error) { this.emit('error', error); return null; }
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async validateCertificate(certificateId: string): Promise<{ valid: boolean; errors: string[]; warnings: string[] }> {
|
|
465
|
+
try {
|
|
466
|
+
const cert = this.loadedCertificates.get(certificateId);
|
|
467
|
+
if (!cert) return { valid: false, errors: ['Certificate not found'], warnings: [] };
|
|
468
|
+
const result = await this.document?.validateCertificate?.(certificateId);
|
|
469
|
+
return result ?? { valid: cert.info.isValid, errors: cert.info.isValid ? [] : ['Certificate validation failed'], warnings: [] };
|
|
470
|
+
} catch (error) {
|
|
471
|
+
this.emit('error', error);
|
|
472
|
+
return { valid: false, errors: [error instanceof Error ? error.message : 'Unknown error'], warnings: [] };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
getLoadedCertificates(): readonly LoadedCertificate[] {
|
|
477
|
+
return Array.from(this.loadedCertificates.values());
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
unloadCertificate(certificateId: string): boolean {
|
|
481
|
+
const deleted = this.loadedCertificates.delete(certificateId);
|
|
482
|
+
if (deleted) this.emit('certificate-unloaded', { certificateId });
|
|
483
|
+
return deleted;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ===========================================================================
|
|
487
|
+
// Signature Field Management (from SignatureCreationManager)
|
|
488
|
+
// ===========================================================================
|
|
489
|
+
|
|
490
|
+
async addSignatureField(config: SignatureFieldConfig): Promise<boolean> {
|
|
491
|
+
try {
|
|
492
|
+
const result = await this.document?.addSignatureField?.(config.fieldName, config.pageIndex, config.x, config.y, config.width, config.height, { appearance: config.appearance, tooltip: config.tooltip, isRequired: config.isRequired, isReadOnly: config.isReadOnly });
|
|
493
|
+
if (result !== false) {
|
|
494
|
+
this.createdFields.set(config.fieldName, config);
|
|
495
|
+
this.clearCachePattern('signatures:');
|
|
496
|
+
this.emit('field-added', { fieldName: config.fieldName });
|
|
497
|
+
}
|
|
498
|
+
return result !== false;
|
|
499
|
+
} catch (error) { this.emit('error', error); return false; }
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async removeSignatureField(fieldName: string): Promise<boolean> {
|
|
503
|
+
try {
|
|
504
|
+
const result = await this.document?.removeSignatureField?.(fieldName);
|
|
505
|
+
if (result) {
|
|
506
|
+
this.createdFields.delete(fieldName);
|
|
507
|
+
this.clearCachePattern('signatures:');
|
|
508
|
+
this.emit('field-removed', { fieldName });
|
|
509
|
+
}
|
|
510
|
+
return !!result;
|
|
511
|
+
} catch (error) { this.emit('error', error); return false; }
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async getSignatureFieldNames(): Promise<string[]> {
|
|
515
|
+
try { return await this.document?.getSignatureFields?.() ?? []; }
|
|
516
|
+
catch (error) { this.emit('error', error); return []; }
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
async hasSignatureField(fieldName: string): Promise<boolean> {
|
|
520
|
+
try { const fields = await this.getSignatureFieldNames(); return fields.includes(fieldName); }
|
|
521
|
+
catch (error) { this.emit('error', error); return false; }
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async updateSignatureFieldAppearance(fieldName: string, appearance: SignatureAppearance): Promise<boolean> {
|
|
525
|
+
try {
|
|
526
|
+
const result = await this.document?.updateSignatureFieldAppearance?.(fieldName, appearance);
|
|
527
|
+
this.emit('appearance-updated', { fieldName });
|
|
528
|
+
return !!result;
|
|
529
|
+
} catch (error) { this.emit('error', error); return false; }
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ===========================================================================
|
|
533
|
+
// Document Signing (from SignatureCreationManager)
|
|
534
|
+
// ===========================================================================
|
|
535
|
+
|
|
536
|
+
async signDocument(fieldName: string, certificate: LoadedCertificate | string, options?: SigningOptions): Promise<SigningResult> {
|
|
537
|
+
try {
|
|
538
|
+
const certId = typeof certificate === 'string' ? certificate : certificate.certificateId;
|
|
539
|
+
const cert = this.loadedCertificates.get(certId);
|
|
540
|
+
if (!cert) return { success: false, error: 'Certificate not found' };
|
|
541
|
+
if (!cert.hasPrivateKey) return { success: false, error: 'Certificate does not have private key' };
|
|
542
|
+
const nativeResult = await this.document?.signDocument?.(fieldName, certId, {
|
|
543
|
+
reason: options?.reason, location: options?.location, contactInfo: options?.contactInfo,
|
|
544
|
+
signatureType: options?.signatureType ?? SignatureType.APPROVAL,
|
|
545
|
+
certificationPermission: options?.certificationPermission,
|
|
546
|
+
algorithm: options?.algorithm ?? SignatureAlgorithm.RSA_SHA256,
|
|
547
|
+
digestAlgorithm: options?.digestAlgorithm ?? DigestAlgorithm.SHA256,
|
|
548
|
+
appearance: options?.appearance,
|
|
549
|
+
});
|
|
550
|
+
const signingTime = new Date();
|
|
551
|
+
let timestampTime: Date | undefined;
|
|
552
|
+
if (options?.embedTimestamp && options?.timestampServerUrl) {
|
|
553
|
+
const tsResult = await this.embedTimestamp(fieldName, { serverUrl: options.timestampServerUrl, hashAlgorithm: options.digestAlgorithm });
|
|
554
|
+
if (tsResult.status === TimestampStatus.SUCCESS) timestampTime = tsResult.timestamp;
|
|
555
|
+
}
|
|
556
|
+
if (options?.enableLtv) await this.enableLtvForSignature(fieldName, { ocspResponderUrl: options.ocspResponderUrl, crlDistributionPoints: options.crlDistributionPoints });
|
|
557
|
+
const signatureId = `sig_${fieldName}_${Date.now()}`;
|
|
558
|
+
this.clearCachePattern('signatures:');
|
|
559
|
+
this.emit('document-signed', { signatureId, fieldName, signingTime, timestampTime });
|
|
560
|
+
return { success: true, signatureId, signingTime, timestampTime, warnings: nativeResult?.warnings };
|
|
561
|
+
} catch (error) {
|
|
562
|
+
this.emit('error', error);
|
|
563
|
+
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async certifyDocument(fieldName: string, certificate: LoadedCertificate | string, permission: CertificationPermission, options?: Omit<SigningOptions, 'signatureType' | 'certificationPermission'>): Promise<SigningResult> {
|
|
568
|
+
return this.signDocument(fieldName, certificate, { ...options, signatureType: SignatureType.CERTIFICATION, certificationPermission: permission });
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async signInvisibly(certificate: LoadedCertificate | string, options?: Omit<SigningOptions, 'appearance'>): Promise<SigningResult> {
|
|
572
|
+
try {
|
|
573
|
+
const fieldName = `InvisibleSig_${Date.now()}`;
|
|
574
|
+
await this.addSignatureField({ fieldName, pageIndex: 0, x: 0, y: 0, width: 0, height: 0 });
|
|
575
|
+
return this.signDocument(fieldName, certificate, options);
|
|
576
|
+
} catch (error) { this.emit('error', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; }
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
async counterSign(certificate: LoadedCertificate | string, options?: SigningOptions): Promise<SigningResult> {
|
|
580
|
+
try {
|
|
581
|
+
const fieldName = `CounterSig_${Date.now()}`;
|
|
582
|
+
await this.addSignatureField({ fieldName, pageIndex: 0, x: 100, y: 100, width: 200, height: 50, appearance: options?.appearance });
|
|
583
|
+
return this.signDocument(fieldName, certificate, options);
|
|
584
|
+
} catch (error) { this.emit('error', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; }
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async signMultipleFields(signings: Array<{ fieldName: string; certificate: LoadedCertificate | string; options?: SigningOptions }>): Promise<SigningResult[]> {
|
|
588
|
+
const results: SigningResult[] = [];
|
|
589
|
+
for (const signing of signings) {
|
|
590
|
+
const result = await this.signDocument(signing.fieldName, signing.certificate, signing.options);
|
|
591
|
+
results.push(result);
|
|
592
|
+
if (!result.success) break;
|
|
593
|
+
}
|
|
594
|
+
return results;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
async prepareForExternalSigning(fieldName: string, options?: { estimatedSize?: number; digestAlgorithm?: DigestAlgorithm }): Promise<{ hash: Buffer; byteRange: [number, number, number, number] } | null> {
|
|
598
|
+
try {
|
|
599
|
+
const result = await this.document?.prepareForExternalSigning?.(fieldName, { estimatedSize: options?.estimatedSize ?? 8192, digestAlgorithm: options?.digestAlgorithm ?? DigestAlgorithm.SHA256 });
|
|
600
|
+
this.emit('prepared-for-external-signing', { fieldName });
|
|
601
|
+
return result ?? null;
|
|
602
|
+
} catch (error) { this.emit('error', error); return null; }
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// ===========================================================================
|
|
606
|
+
// Timestamp Operations (from SignatureCreationManager)
|
|
607
|
+
// ===========================================================================
|
|
608
|
+
|
|
609
|
+
async embedTimestamp(fieldName: string, config: TimestampConfig): Promise<TimestampResult> {
|
|
610
|
+
try {
|
|
611
|
+
const result = await this.document?.embedTimestamp?.(fieldName, { serverUrl: config.serverUrl, username: config.username, password: config.password, hashAlgorithm: config.hashAlgorithm ?? DigestAlgorithm.SHA256, timeout: config.timeout ?? 30000, policy: config.policy, nonce: config.nonce ?? true, certReq: config.certReq ?? true });
|
|
612
|
+
if (result?.success) {
|
|
613
|
+
this.emit('timestamp-embedded', { fieldName, timestamp: result.timestamp });
|
|
614
|
+
return { status: TimestampStatus.SUCCESS, timestamp: new Date(result.timestamp), serialNumber: result.serialNumber, tsaName: result.tsaName };
|
|
615
|
+
}
|
|
616
|
+
return { status: TimestampStatus.FAILED, error: result?.error ?? 'Timestamp embedding failed' };
|
|
617
|
+
} catch (error) { this.emit('error', error); return { status: TimestampStatus.FAILED, error: error instanceof Error ? error.message : 'Unknown error' }; }
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async addDocumentTimestamp(config: TimestampConfig): Promise<TimestampResult> {
|
|
621
|
+
try {
|
|
622
|
+
const fieldName = `DocTimestamp_${Date.now()}`;
|
|
623
|
+
await this.addSignatureField({ fieldName, pageIndex: 0, x: 0, y: 0, width: 0, height: 0 });
|
|
624
|
+
const result = await this.document?.addDocumentTimestamp?.(fieldName, config);
|
|
625
|
+
if (result?.success) {
|
|
626
|
+
this.emit('document-timestamp-added', { timestamp: result.timestamp });
|
|
627
|
+
return { status: TimestampStatus.SUCCESS, timestamp: new Date(result.timestamp), serialNumber: result.serialNumber, tsaName: result.tsaName };
|
|
628
|
+
}
|
|
629
|
+
return { status: TimestampStatus.FAILED, error: result?.error ?? 'Document timestamp failed' };
|
|
630
|
+
} catch (error) { this.emit('error', error); return { status: TimestampStatus.FAILED, error: error instanceof Error ? error.message : 'Unknown error' }; }
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async validateTimestamp(fieldName: string): Promise<{ valid: boolean; timestamp?: Date; errors: string[] }> {
|
|
634
|
+
try { return await this.document?.validateTimestamp?.(fieldName) ?? { valid: false, errors: ['Timestamp validation not available'] }; }
|
|
635
|
+
catch (error) { this.emit('error', error); return { valid: false, errors: [error instanceof Error ? error.message : 'Unknown error'] }; }
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async getTimestampInfo(fieldName: string): Promise<{ timestamp?: Date; tsaName?: string; serialNumber?: string; policy?: string } | null> {
|
|
639
|
+
try { return await this.document?.getTimestampInfo?.(fieldName) ?? null; }
|
|
640
|
+
catch (error) { this.emit('error', error); return null; }
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// ===========================================================================
|
|
644
|
+
// LTV Operations (from SignatureCreationManager)
|
|
645
|
+
// ===========================================================================
|
|
646
|
+
|
|
647
|
+
async enableLtvForSignature(fieldName: string, options?: { ocspResponderUrl?: string; crlDistributionPoints?: readonly string[] }): Promise<boolean> {
|
|
648
|
+
try {
|
|
649
|
+
const result = await this.document?.enableLtvForSignature?.(fieldName, { ocspResponderUrl: options?.ocspResponderUrl, crlDistributionPoints: options?.crlDistributionPoints });
|
|
650
|
+
if (result) this.emit('ltv-enabled', { fieldName });
|
|
651
|
+
return !!result;
|
|
652
|
+
} catch (error) { this.emit('error', error); return false; }
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async enableLtvForAllSignatures(options?: { ocspResponderUrl?: string; crlDistributionPoints?: readonly string[] }): Promise<number> {
|
|
656
|
+
try {
|
|
657
|
+
const fields = await this.getSignatureFieldNames();
|
|
658
|
+
let count = 0;
|
|
659
|
+
for (const field of fields) { if (await this.enableLtvForSignature(field, options)) count++; }
|
|
660
|
+
return count;
|
|
661
|
+
} catch (error) { this.emit('error', error); return 0; }
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
async addValidationInfo(fieldName: string, info: { ocspResponse?: Buffer; crl?: Buffer; certificates?: readonly Buffer[] }): Promise<boolean> {
|
|
665
|
+
try {
|
|
666
|
+
const result = await this.document?.addValidationInfo?.(fieldName, info);
|
|
667
|
+
if (result) this.emit('validation-info-added', { fieldName });
|
|
668
|
+
return !!result;
|
|
669
|
+
} catch (error) { this.emit('error', error); return false; }
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async hasLtvEnabled(fieldName: string): Promise<boolean> {
|
|
673
|
+
try { return await this.document?.hasLtvEnabled?.(fieldName) ?? false; }
|
|
674
|
+
catch (error) { this.emit('error', error); return false; }
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// ===========================================================================
|
|
678
|
+
// Signature Details (from SignaturesManager)
|
|
679
|
+
// ===========================================================================
|
|
680
|
+
|
|
681
|
+
async getSignerName(index: number): Promise<string> {
|
|
682
|
+
try { return (await this.document?.getSignerName?.(index)) || ''; } catch { return ''; }
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
async getSigningTime(index: number): Promise<number> {
|
|
686
|
+
try { return (await this.document?.getSigningTime?.(index)) || 0; } catch { return 0; }
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
async getSigningReason(index: number): Promise<string | null> {
|
|
690
|
+
try { return (await this.document?.getSigningReason?.(index)) || null; } catch { return null; }
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async getSigningLocation(index: number): Promise<string | null> {
|
|
694
|
+
try { return (await this.document?.getSigningLocation?.(index)) || null; } catch { return null; }
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
async getCertificateSubject(index: number): Promise<string> {
|
|
698
|
+
try { return (await this.document?.getCertificateSubject?.(index)) || ''; } catch { return ''; }
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async getCertificateIssuer(index: number): Promise<string> {
|
|
702
|
+
try { return (await this.document?.getCertificateIssuer?.(index)) || ''; } catch { return ''; }
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async getCertificateSerial(index: number): Promise<string> {
|
|
706
|
+
try { return (await this.document?.getCertificateSerial?.(index)) || ''; } catch { return ''; }
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async getCertificateValidity(index: number): Promise<[number, number]> {
|
|
710
|
+
try { return (await this.document?.getCertificateValidity?.(index)) || [0, 0]; } catch { return [0, 0]; }
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
async getSignatureDetails(index: number): Promise<Signature | null> {
|
|
714
|
+
try {
|
|
715
|
+
const [notBefore, notAfter] = await this.getCertificateValidity(index);
|
|
716
|
+
const certificate: Certificate = {
|
|
717
|
+
subject: await this.getCertificateSubject(index), issuer: await this.getCertificateIssuer(index),
|
|
718
|
+
serial: await this.getCertificateSerial(index), notBefore, notAfter,
|
|
719
|
+
isValid: await this.isCertificateValidByIndex(index),
|
|
720
|
+
};
|
|
721
|
+
return {
|
|
722
|
+
signerName: await this.getSignerName(index), signingTime: await this.getSigningTime(index),
|
|
723
|
+
reason: (await this.getSigningReason(index)) || undefined,
|
|
724
|
+
location: (await this.getSigningLocation(index)) || undefined,
|
|
725
|
+
certificate, isValid: true,
|
|
726
|
+
};
|
|
727
|
+
} catch (error) { this.emit('error', error); return null; }
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
async isCertificateValidByIndex(index: number): Promise<boolean> {
|
|
731
|
+
try { return (await this.document?.isCertificateValid?.(index)) || false; } catch { return false; }
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// ===========================================================================
|
|
735
|
+
// FFI-Based Document Signing (Phase 1)
|
|
736
|
+
// ===========================================================================
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Load signing credentials from a PKCS#12 (.p12/.pfx) file.
|
|
740
|
+
*
|
|
741
|
+
* Calls the native `pdf_credentials_from_pkcs12` FFI function to load
|
|
742
|
+
* a certificate and private key from a PKCS#12 container.
|
|
743
|
+
*
|
|
744
|
+
* @param filePath - Path to the .p12 or .pfx file
|
|
745
|
+
* @param password - Password to decrypt the PKCS#12 file
|
|
746
|
+
* @returns SigningCredentials handle for use with signing methods
|
|
747
|
+
* @throws SignatureException if the file cannot be loaded or the password is incorrect
|
|
748
|
+
*
|
|
749
|
+
* @example
|
|
750
|
+
* ```typescript
|
|
751
|
+
* const credentials = await sigManager.loadCredentialsPkcs12('/path/to/cert.p12', 'password');
|
|
752
|
+
* const signed = await sigManager.signWithPkcs12(pdfData, '/path/to/cert.p12', 'password');
|
|
753
|
+
* ```
|
|
754
|
+
*/
|
|
755
|
+
async loadCredentialsPkcs12(filePath: string, password: string): Promise<SigningCredentials> {
|
|
756
|
+
if (!this.native?.pdf_credentials_from_pkcs12) {
|
|
757
|
+
throw new SignatureException('Native signing not available: pdf_credentials_from_pkcs12 not found');
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const errorCode = Buffer.alloc(4);
|
|
761
|
+
const handle = this.native.pdf_credentials_from_pkcs12(filePath, password, errorCode);
|
|
762
|
+
const code = errorCode.readInt32LE(0);
|
|
763
|
+
|
|
764
|
+
if (code !== 0 || !handle) {
|
|
765
|
+
throw mapFfiErrorCode(code, `Failed to load PKCS#12 credentials from ${filePath}`);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
this.emit('credentials-loaded', { sourceType: 'pkcs12', filePath });
|
|
769
|
+
return { _handle: handle, sourceType: 'pkcs12' };
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Load signing credentials from PEM certificate and key files.
|
|
774
|
+
*
|
|
775
|
+
* Calls the native `pdf_credentials_from_pem` FFI function to load
|
|
776
|
+
* credentials from separate PEM-encoded certificate and private key files.
|
|
777
|
+
*
|
|
778
|
+
* @param certFile - Path to the PEM certificate file
|
|
779
|
+
* @param keyFile - Path to the PEM private key file
|
|
780
|
+
* @param keyPassword - Optional password for an encrypted private key
|
|
781
|
+
* @returns SigningCredentials handle for use with signing methods
|
|
782
|
+
* @throws SignatureException if the files cannot be loaded
|
|
783
|
+
*
|
|
784
|
+
* @example
|
|
785
|
+
* ```typescript
|
|
786
|
+
* const credentials = await sigManager.loadCredentialsPem('/path/to/cert.pem', '/path/to/key.pem');
|
|
787
|
+
* ```
|
|
788
|
+
*/
|
|
789
|
+
async loadCredentialsPem(certFile: string, keyFile: string, keyPassword?: string): Promise<SigningCredentials> {
|
|
790
|
+
if (!this.native?.pdf_credentials_from_pem) {
|
|
791
|
+
throw new SignatureException('Native signing not available: pdf_credentials_from_pem not found');
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const errorCode = Buffer.alloc(4);
|
|
795
|
+
const handle = this.native.pdf_credentials_from_pem(
|
|
796
|
+
certFile,
|
|
797
|
+
keyFile,
|
|
798
|
+
keyPassword ?? null,
|
|
799
|
+
errorCode,
|
|
800
|
+
);
|
|
801
|
+
const code = errorCode.readInt32LE(0);
|
|
802
|
+
|
|
803
|
+
if (code !== 0 || !handle) {
|
|
804
|
+
throw mapFfiErrorCode(code, `Failed to load PEM credentials from ${certFile} and ${keyFile}`);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
this.emit('credentials-loaded', { sourceType: 'pem', certFile, keyFile });
|
|
808
|
+
return { _handle: handle, sourceType: 'pem' };
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Free signing credentials when they are no longer needed.
|
|
813
|
+
*
|
|
814
|
+
* Calls the native `pdf_credentials_free` FFI function to release
|
|
815
|
+
* memory associated with the credentials handle.
|
|
816
|
+
*
|
|
817
|
+
* @param credentials - The credentials handle to free
|
|
818
|
+
*
|
|
819
|
+
* @example
|
|
820
|
+
* ```typescript
|
|
821
|
+
* const credentials = await sigManager.loadCredentialsPkcs12(path, password);
|
|
822
|
+
* // ... use credentials for signing ...
|
|
823
|
+
* sigManager.freeCredentials(credentials);
|
|
824
|
+
* ```
|
|
825
|
+
*/
|
|
826
|
+
freeCredentials(credentials: SigningCredentials): void {
|
|
827
|
+
if (credentials._handle && this.native?.pdf_credentials_free) {
|
|
828
|
+
this.native.pdf_credentials_free(credentials._handle);
|
|
829
|
+
this.emit('credentials-freed', { sourceType: credentials.sourceType });
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Sign a PDF document in memory using PKCS#12 credentials.
|
|
835
|
+
*
|
|
836
|
+
* Loads credentials from a PKCS#12 file, signs the PDF data, and returns
|
|
837
|
+
* the signed PDF bytes. Credentials are automatically freed after signing.
|
|
838
|
+
*
|
|
839
|
+
* @param pdfData - Buffer containing the PDF document bytes
|
|
840
|
+
* @param filePath - Path to the .p12 or .pfx certificate file
|
|
841
|
+
* @param password - Password for the PKCS#12 file
|
|
842
|
+
* @param options - Optional signing parameters (reason, location, contact, algorithm, subfilter)
|
|
843
|
+
* @returns Buffer containing the signed PDF document
|
|
844
|
+
* @throws SignatureException if credential loading or signing fails
|
|
845
|
+
*
|
|
846
|
+
* @example
|
|
847
|
+
* ```typescript
|
|
848
|
+
* const pdfData = await fs.readFile('document.pdf');
|
|
849
|
+
* const signed = await sigManager.signWithPkcs12(pdfData, 'cert.p12', 'password', {
|
|
850
|
+
* reason: 'Approval',
|
|
851
|
+
* location: 'New York',
|
|
852
|
+
* contact: 'signer@example.com',
|
|
853
|
+
* });
|
|
854
|
+
* await fs.writeFile('signed.pdf', signed);
|
|
855
|
+
* ```
|
|
856
|
+
*/
|
|
857
|
+
async signWithPkcs12(
|
|
858
|
+
pdfData: Buffer,
|
|
859
|
+
filePath: string,
|
|
860
|
+
password: string,
|
|
861
|
+
options?: SignOptions,
|
|
862
|
+
): Promise<Buffer> {
|
|
863
|
+
const credentials = await this.loadCredentialsPkcs12(filePath, password);
|
|
864
|
+
try {
|
|
865
|
+
return await this.signWithCredentials(pdfData, credentials, options);
|
|
866
|
+
} finally {
|
|
867
|
+
this.freeCredentials(credentials);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Sign a PDF document in memory using PEM credentials.
|
|
873
|
+
*
|
|
874
|
+
* Loads credentials from PEM files, signs the PDF data, and returns
|
|
875
|
+
* the signed PDF bytes. Credentials are automatically freed after signing.
|
|
876
|
+
*
|
|
877
|
+
* @param pdfData - Buffer containing the PDF document bytes
|
|
878
|
+
* @param certFile - Path to the PEM certificate file
|
|
879
|
+
* @param keyFile - Path to the PEM private key file
|
|
880
|
+
* @param options - Optional signing parameters (reason, location, contact, algorithm, subfilter)
|
|
881
|
+
* @returns Buffer containing the signed PDF document
|
|
882
|
+
* @throws SignatureException if credential loading or signing fails
|
|
883
|
+
*
|
|
884
|
+
* @example
|
|
885
|
+
* ```typescript
|
|
886
|
+
* const pdfData = await fs.readFile('document.pdf');
|
|
887
|
+
* const signed = await sigManager.signWithPem(pdfData, 'cert.pem', 'key.pem', {
|
|
888
|
+
* reason: 'Review complete',
|
|
889
|
+
* location: 'London',
|
|
890
|
+
* });
|
|
891
|
+
* await fs.writeFile('signed.pdf', signed);
|
|
892
|
+
* ```
|
|
893
|
+
*/
|
|
894
|
+
async signWithPem(
|
|
895
|
+
pdfData: Buffer,
|
|
896
|
+
certFile: string,
|
|
897
|
+
keyFile: string,
|
|
898
|
+
options?: SignOptions,
|
|
899
|
+
): Promise<Buffer> {
|
|
900
|
+
const credentials = await this.loadCredentialsPem(certFile, keyFile);
|
|
901
|
+
try {
|
|
902
|
+
return await this.signWithCredentials(pdfData, credentials, options);
|
|
903
|
+
} finally {
|
|
904
|
+
this.freeCredentials(credentials);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Sign a PDF document in memory using pre-loaded credentials.
|
|
910
|
+
*
|
|
911
|
+
* Calls the native `pdf_document_sign` FFI function to apply a digital
|
|
912
|
+
* signature to the PDF data using the provided credentials handle.
|
|
913
|
+
*
|
|
914
|
+
* @param pdfData - Buffer containing the PDF document bytes
|
|
915
|
+
* @param credentials - Pre-loaded signing credentials handle
|
|
916
|
+
* @param options - Optional signing parameters
|
|
917
|
+
* @returns Buffer containing the signed PDF document
|
|
918
|
+
* @throws SignatureException if signing fails
|
|
919
|
+
*
|
|
920
|
+
* @example
|
|
921
|
+
* ```typescript
|
|
922
|
+
* const credentials = await sigManager.loadCredentialsPkcs12(path, password);
|
|
923
|
+
* const signed = await sigManager.signWithCredentials(pdfData, credentials, {
|
|
924
|
+
* reason: 'Approved',
|
|
925
|
+
* algorithm: FfiDigestAlgorithm.SHA256,
|
|
926
|
+
* subfilter: FfiSignatureSubFilter.PKCS7_DETACHED,
|
|
927
|
+
* });
|
|
928
|
+
* sigManager.freeCredentials(credentials);
|
|
929
|
+
* ```
|
|
930
|
+
*/
|
|
931
|
+
async signWithCredentials(
|
|
932
|
+
pdfData: Buffer,
|
|
933
|
+
credentials: SigningCredentials,
|
|
934
|
+
options?: SignOptions,
|
|
935
|
+
): Promise<Buffer> {
|
|
936
|
+
if (!this.native?.pdf_document_sign) {
|
|
937
|
+
throw new SignatureException('Native signing not available: pdf_document_sign not found');
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (!credentials._handle) {
|
|
941
|
+
throw new SignatureException('Invalid credentials: handle is null');
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const errorCode = Buffer.alloc(4);
|
|
945
|
+
const outDataPtr = Buffer.alloc(8); // pointer to output data
|
|
946
|
+
const outLen = Buffer.alloc(8); // output length (size_t)
|
|
947
|
+
|
|
948
|
+
const algorithm = options?.algorithm ?? FfiDigestAlgorithm.SHA256;
|
|
949
|
+
const subfilter = options?.subfilter ?? FfiSignatureSubFilter.PKCS7_DETACHED;
|
|
950
|
+
|
|
951
|
+
const success = this.native.pdf_document_sign(
|
|
952
|
+
pdfData,
|
|
953
|
+
pdfData.length,
|
|
954
|
+
credentials._handle,
|
|
955
|
+
options?.reason ?? null,
|
|
956
|
+
options?.location ?? null,
|
|
957
|
+
options?.contact ?? null,
|
|
958
|
+
algorithm,
|
|
959
|
+
subfilter,
|
|
960
|
+
outDataPtr,
|
|
961
|
+
outLen,
|
|
962
|
+
errorCode,
|
|
963
|
+
);
|
|
964
|
+
|
|
965
|
+
const code = errorCode.readInt32LE(0);
|
|
966
|
+
|
|
967
|
+
if (!success || code !== 0) {
|
|
968
|
+
throw mapFfiErrorCode(code, 'Failed to sign PDF document');
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Read the output buffer from the native pointer
|
|
972
|
+
const resultLen = Number(outLen.readBigUInt64LE(0));
|
|
973
|
+
const resultData = this.native.pdf_read_signed_bytes(outDataPtr, resultLen);
|
|
974
|
+
|
|
975
|
+
// Free the native signed bytes buffer
|
|
976
|
+
if (this.native.pdf_signed_bytes_free) {
|
|
977
|
+
this.native.pdf_signed_bytes_free(outDataPtr, resultLen);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
this.clearCachePattern('signatures:');
|
|
981
|
+
this.emit('document-signed-ffi', {
|
|
982
|
+
reason: options?.reason,
|
|
983
|
+
location: options?.location,
|
|
984
|
+
algorithm,
|
|
985
|
+
subfilter,
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
return Buffer.from(resultData);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Sign a PDF file on disk and write the signed output to another file.
|
|
993
|
+
*
|
|
994
|
+
* Calls the native `pdf_document_sign_file` FFI function, which reads
|
|
995
|
+
* the input file, applies a digital signature, and writes the result
|
|
996
|
+
* to the output path.
|
|
997
|
+
*
|
|
998
|
+
* @param inputPath - Path to the input PDF file
|
|
999
|
+
* @param outputPath - Path to write the signed PDF file
|
|
1000
|
+
* @param credentials - Pre-loaded signing credentials handle
|
|
1001
|
+
* @param options - Optional signing parameters
|
|
1002
|
+
* @throws SignatureException if file signing fails
|
|
1003
|
+
*
|
|
1004
|
+
* @example
|
|
1005
|
+
* ```typescript
|
|
1006
|
+
* const credentials = await sigManager.loadCredentialsPkcs12('cert.p12', 'pass');
|
|
1007
|
+
* await sigManager.signFile('input.pdf', 'signed.pdf', credentials, {
|
|
1008
|
+
* reason: 'Final approval',
|
|
1009
|
+
* location: 'Berlin',
|
|
1010
|
+
* });
|
|
1011
|
+
* sigManager.freeCredentials(credentials);
|
|
1012
|
+
* ```
|
|
1013
|
+
*/
|
|
1014
|
+
async signFile(
|
|
1015
|
+
inputPath: string,
|
|
1016
|
+
outputPath: string,
|
|
1017
|
+
credentials: SigningCredentials,
|
|
1018
|
+
options?: SignOptions,
|
|
1019
|
+
): Promise<void> {
|
|
1020
|
+
if (!this.native?.pdf_document_sign_file) {
|
|
1021
|
+
throw new SignatureException('Native signing not available: pdf_document_sign_file not found');
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (!credentials._handle) {
|
|
1025
|
+
throw new SignatureException('Invalid credentials: handle is null');
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const errorCode = Buffer.alloc(4);
|
|
1029
|
+
const algorithm = options?.algorithm ?? FfiDigestAlgorithm.SHA256;
|
|
1030
|
+
const subfilter = options?.subfilter ?? FfiSignatureSubFilter.PKCS7_DETACHED;
|
|
1031
|
+
|
|
1032
|
+
const success = this.native.pdf_document_sign_file(
|
|
1033
|
+
inputPath,
|
|
1034
|
+
outputPath,
|
|
1035
|
+
credentials._handle,
|
|
1036
|
+
options?.reason ?? null,
|
|
1037
|
+
options?.location ?? null,
|
|
1038
|
+
options?.contact ?? null,
|
|
1039
|
+
algorithm,
|
|
1040
|
+
subfilter,
|
|
1041
|
+
errorCode,
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
const code = errorCode.readInt32LE(0);
|
|
1045
|
+
|
|
1046
|
+
if (!success || code !== 0) {
|
|
1047
|
+
throw mapFfiErrorCode(code, `Failed to sign PDF file ${inputPath}`);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
this.clearCachePattern('signatures:');
|
|
1051
|
+
this.emit('file-signed', { inputPath, outputPath });
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Embed Long-Term Validation (LTV) data into a signed PDF.
|
|
1056
|
+
*
|
|
1057
|
+
* Calls the native `pdf_embed_ltv_data` FFI function to add OCSP responses
|
|
1058
|
+
* and/or CRL data to the document's DSS (Document Security Store), enabling
|
|
1059
|
+
* long-term signature validation even after certificates expire.
|
|
1060
|
+
*
|
|
1061
|
+
* @param pdfData - Buffer containing the signed PDF document bytes
|
|
1062
|
+
* @param ocspData - Optional OCSP response data to embed
|
|
1063
|
+
* @param crlData - Optional CRL data to embed
|
|
1064
|
+
* @returns Buffer containing the PDF with embedded LTV data
|
|
1065
|
+
* @throws SignatureException if LTV embedding fails
|
|
1066
|
+
*
|
|
1067
|
+
* @example
|
|
1068
|
+
* ```typescript
|
|
1069
|
+
* const signedPdf = await sigManager.signWithPkcs12(pdfData, 'cert.p12', 'pass');
|
|
1070
|
+
* const ocspResponse = await fetch('http://ocsp.example.com/...').then(r => r.buffer());
|
|
1071
|
+
* const ltvPdf = await sigManager.embedLtv(signedPdf, ocspResponse);
|
|
1072
|
+
* await fs.writeFile('signed-ltv.pdf', ltvPdf);
|
|
1073
|
+
* ```
|
|
1074
|
+
*/
|
|
1075
|
+
async embedLtv(
|
|
1076
|
+
pdfData: Buffer,
|
|
1077
|
+
ocspData?: Buffer,
|
|
1078
|
+
crlData?: Buffer,
|
|
1079
|
+
): Promise<Buffer> {
|
|
1080
|
+
if (!this.native?.pdf_embed_ltv_data) {
|
|
1081
|
+
throw new SignatureException('Native LTV embedding not available: pdf_embed_ltv_data not found');
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
const errorCode = Buffer.alloc(4);
|
|
1085
|
+
const outDataPtr = Buffer.alloc(8);
|
|
1086
|
+
const outLen = Buffer.alloc(8);
|
|
1087
|
+
|
|
1088
|
+
const success = this.native.pdf_embed_ltv_data(
|
|
1089
|
+
pdfData,
|
|
1090
|
+
pdfData.length,
|
|
1091
|
+
ocspData ?? null,
|
|
1092
|
+
ocspData?.length ?? 0,
|
|
1093
|
+
crlData ?? null,
|
|
1094
|
+
crlData?.length ?? 0,
|
|
1095
|
+
outDataPtr,
|
|
1096
|
+
outLen,
|
|
1097
|
+
errorCode,
|
|
1098
|
+
);
|
|
1099
|
+
|
|
1100
|
+
const code = errorCode.readInt32LE(0);
|
|
1101
|
+
|
|
1102
|
+
if (!success || code !== 0) {
|
|
1103
|
+
throw mapFfiErrorCode(code, 'Failed to embed LTV data into PDF');
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
const resultLen = Number(outLen.readBigUInt64LE(0));
|
|
1107
|
+
const resultData = this.native.pdf_read_signed_bytes(outDataPtr, resultLen);
|
|
1108
|
+
|
|
1109
|
+
if (this.native.pdf_signed_bytes_free) {
|
|
1110
|
+
this.native.pdf_signed_bytes_free(outDataPtr, resultLen);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
this.emit('ltv-embedded-ffi', {
|
|
1114
|
+
hasOcsp: !!ocspData,
|
|
1115
|
+
hasCrl: !!crlData,
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
return Buffer.from(resultData);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Save signed PDF bytes to a file.
|
|
1123
|
+
*
|
|
1124
|
+
* Calls the native `pdf_document_save_signed` FFI function to write
|
|
1125
|
+
* signed PDF data to disk.
|
|
1126
|
+
*
|
|
1127
|
+
* @param pdfData - Buffer containing the signed PDF bytes
|
|
1128
|
+
* @param outputPath - Path to write the signed PDF file
|
|
1129
|
+
* @throws SignatureException or IoException if saving fails
|
|
1130
|
+
*
|
|
1131
|
+
* @example
|
|
1132
|
+
* ```typescript
|
|
1133
|
+
* const signed = await sigManager.signWithPkcs12(pdfData, 'cert.p12', 'pass');
|
|
1134
|
+
* await sigManager.saveSigned(signed, '/output/signed.pdf');
|
|
1135
|
+
* ```
|
|
1136
|
+
*/
|
|
1137
|
+
async saveSigned(pdfData: Buffer, outputPath: string): Promise<void> {
|
|
1138
|
+
if (!this.native?.pdf_document_save_signed) {
|
|
1139
|
+
// Fallback to Node.js file I/O if native function is not available
|
|
1140
|
+
await fs.writeFile(outputPath, pdfData);
|
|
1141
|
+
this.emit('signed-saved', { outputPath, method: 'node-fs' });
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
const errorCode = Buffer.alloc(4);
|
|
1146
|
+
const success = this.native.pdf_document_save_signed(
|
|
1147
|
+
pdfData,
|
|
1148
|
+
pdfData.length,
|
|
1149
|
+
outputPath,
|
|
1150
|
+
errorCode,
|
|
1151
|
+
);
|
|
1152
|
+
|
|
1153
|
+
const code = errorCode.readInt32LE(0);
|
|
1154
|
+
|
|
1155
|
+
if (!success || code !== 0) {
|
|
1156
|
+
throw mapFfiErrorCode(code, `Failed to save signed PDF to ${outputPath}`);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
this.emit('signed-saved', { outputPath, method: 'native-ffi' });
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// ===========================================================================
|
|
1163
|
+
// FFI-Based DER Credential Loading
|
|
1164
|
+
// ===========================================================================
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Load signing credentials from raw DER-encoded certificate and key bytes.
|
|
1168
|
+
*
|
|
1169
|
+
* Calls the native `pdf_credentials_from_der` FFI function to create
|
|
1170
|
+
* credentials from in-memory DER-encoded certificate data and an optional
|
|
1171
|
+
* private key.
|
|
1172
|
+
*
|
|
1173
|
+
* @param certData - Buffer containing DER-encoded certificate bytes
|
|
1174
|
+
* @param keyData - Optional Buffer containing DER-encoded private key bytes
|
|
1175
|
+
* @returns SigningCredentials handle for use with signing methods
|
|
1176
|
+
* @throws SignatureException if credential loading fails
|
|
1177
|
+
*
|
|
1178
|
+
* @example
|
|
1179
|
+
* ```typescript
|
|
1180
|
+
* const certDer = await fs.readFile('cert.der');
|
|
1181
|
+
* const keyDer = await fs.readFile('key.der');
|
|
1182
|
+
* const credentials = await sigManager.loadCredentialsFromDer(certDer, keyDer);
|
|
1183
|
+
* const signed = await sigManager.signWithCredentials(pdfData, credentials);
|
|
1184
|
+
* sigManager.freeCredentials(credentials);
|
|
1185
|
+
* ```
|
|
1186
|
+
*/
|
|
1187
|
+
async loadCredentialsFromDer(certData: Buffer, keyData?: Buffer): Promise<SigningCredentials> {
|
|
1188
|
+
if (!this.native?.pdf_credentials_from_der) {
|
|
1189
|
+
throw new SignatureException('Native signing not available: pdf_credentials_from_der not found');
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
if (!certData || certData.length === 0) {
|
|
1193
|
+
throw new SignatureException('Certificate data cannot be null or empty');
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const errorCode = Buffer.alloc(4);
|
|
1197
|
+
const handle = this.native.pdf_credentials_from_der(
|
|
1198
|
+
certData,
|
|
1199
|
+
certData.length,
|
|
1200
|
+
keyData ?? null,
|
|
1201
|
+
keyData?.length ?? 0,
|
|
1202
|
+
errorCode,
|
|
1203
|
+
);
|
|
1204
|
+
const code = errorCode.readInt32LE(0);
|
|
1205
|
+
|
|
1206
|
+
if (code !== 0 || !handle) {
|
|
1207
|
+
throw mapFfiErrorCode(code, 'Failed to load DER credentials');
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
this.emit('credentials-loaded', { sourceType: 'der' });
|
|
1211
|
+
return { _handle: handle, sourceType: 'pkcs12' as const }; // DER is treated as raw cert/key
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* Add a certificate chain entry to existing signing credentials.
|
|
1216
|
+
*
|
|
1217
|
+
* Calls the native `pdf_credentials_add_chain_cert` FFI function to append
|
|
1218
|
+
* an intermediate or root CA certificate to the credential's certificate chain.
|
|
1219
|
+
* This is used to build a complete certification chain for signature validation.
|
|
1220
|
+
*
|
|
1221
|
+
* @param credentials - The signing credentials handle to modify
|
|
1222
|
+
* @param certData - Buffer containing DER-encoded certificate bytes
|
|
1223
|
+
* @throws SignatureException if the chain certificate cannot be added
|
|
1224
|
+
*
|
|
1225
|
+
* @example
|
|
1226
|
+
* ```typescript
|
|
1227
|
+
* const credentials = await sigManager.loadCredentialsFromDer(certDer, keyDer);
|
|
1228
|
+
* const intermediateCa = await fs.readFile('intermediate-ca.der');
|
|
1229
|
+
* await sigManager.addChainCert(credentials, intermediateCa);
|
|
1230
|
+
* ```
|
|
1231
|
+
*/
|
|
1232
|
+
async addChainCert(credentials: SigningCredentials, certData: Buffer): Promise<void> {
|
|
1233
|
+
if (!this.native?.pdf_credentials_add_chain_cert) {
|
|
1234
|
+
throw new SignatureException('Native signing not available: pdf_credentials_add_chain_cert not found');
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
if (!credentials._handle) {
|
|
1238
|
+
throw new SignatureException('Invalid credentials: handle is null');
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
if (!certData || certData.length === 0) {
|
|
1242
|
+
throw new SignatureException('Certificate data cannot be null or empty');
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
const errorCode = Buffer.alloc(4);
|
|
1246
|
+
const success = this.native.pdf_credentials_add_chain_cert(
|
|
1247
|
+
credentials._handle,
|
|
1248
|
+
certData,
|
|
1249
|
+
certData.length,
|
|
1250
|
+
errorCode,
|
|
1251
|
+
);
|
|
1252
|
+
const code = errorCode.readInt32LE(0);
|
|
1253
|
+
|
|
1254
|
+
if (!success || code !== 0) {
|
|
1255
|
+
throw mapFfiErrorCode(code, 'Failed to add chain certificate');
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
this.emit('chain-cert-added', { sourceType: credentials.sourceType });
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Get the certificate handle from signing credentials.
|
|
1263
|
+
*
|
|
1264
|
+
* Calls the native `pdf_credentials_get_certificate` FFI function to extract
|
|
1265
|
+
* the certificate handle from a credentials object. The returned handle can be
|
|
1266
|
+
* used with certificate inspection methods like getCertificateCn, getCertificateIssuer,
|
|
1267
|
+
* and getCertificateSize.
|
|
1268
|
+
*
|
|
1269
|
+
* @param credentials - The signing credentials handle
|
|
1270
|
+
* @returns An opaque certificate handle for use with certificate inspection methods
|
|
1271
|
+
* @throws SignatureException if the certificate cannot be retrieved
|
|
1272
|
+
*
|
|
1273
|
+
* @example
|
|
1274
|
+
* ```typescript
|
|
1275
|
+
* const credentials = await sigManager.loadCredentialsPkcs12('cert.p12', 'pass');
|
|
1276
|
+
* const certHandle = await sigManager.getCertificate(credentials);
|
|
1277
|
+
* const cn = await sigManager.getCertificateCn(certHandle);
|
|
1278
|
+
* console.log(`Certificate CN: ${cn}`);
|
|
1279
|
+
* sigManager.freeCredentials(credentials);
|
|
1280
|
+
* ```
|
|
1281
|
+
*/
|
|
1282
|
+
async getCertificate(credentials: SigningCredentials): Promise<any> {
|
|
1283
|
+
if (!this.native?.pdf_credentials_get_certificate) {
|
|
1284
|
+
throw new SignatureException('Native signing not available: pdf_credentials_get_certificate not found');
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (!credentials._handle) {
|
|
1288
|
+
throw new SignatureException('Invalid credentials: handle is null');
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
const errorCode = Buffer.alloc(4);
|
|
1292
|
+
const certHandle = this.native.pdf_credentials_get_certificate(
|
|
1293
|
+
credentials._handle,
|
|
1294
|
+
errorCode,
|
|
1295
|
+
);
|
|
1296
|
+
const code = errorCode.readInt32LE(0);
|
|
1297
|
+
|
|
1298
|
+
if (code !== 0 || !certHandle) {
|
|
1299
|
+
throw mapFfiErrorCode(code, 'Failed to get certificate from credentials');
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
return certHandle;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Load a certificate from raw DER bytes for inspection.
|
|
1307
|
+
*
|
|
1308
|
+
* Calls the native `pdf_certificate_load_from_bytes` FFI function to create
|
|
1309
|
+
* a certificate handle from DER-encoded bytes. This is useful for inspecting
|
|
1310
|
+
* certificate properties without creating full signing credentials.
|
|
1311
|
+
*
|
|
1312
|
+
* @param certData - Buffer containing DER-encoded certificate bytes
|
|
1313
|
+
* @returns An opaque certificate handle for use with certificate inspection methods
|
|
1314
|
+
* @throws SignatureException if the certificate cannot be loaded
|
|
1315
|
+
*
|
|
1316
|
+
* @example
|
|
1317
|
+
* ```typescript
|
|
1318
|
+
* const certDer = await fs.readFile('cert.der');
|
|
1319
|
+
* const certHandle = await sigManager.loadCertificateFromDerBytes(certDer);
|
|
1320
|
+
* const cn = await sigManager.getCertificateCn(certHandle);
|
|
1321
|
+
* const issuer = await sigManager.getCertificateIssuerFromHandle(certHandle);
|
|
1322
|
+
* const size = await sigManager.getCertificateSize(certHandle);
|
|
1323
|
+
* ```
|
|
1324
|
+
*/
|
|
1325
|
+
async loadCertificateFromDerBytes(certData: Buffer): Promise<any> {
|
|
1326
|
+
if (!this.native?.pdf_certificate_load_from_bytes) {
|
|
1327
|
+
throw new SignatureException('Native signing not available: pdf_certificate_load_from_bytes not found');
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
if (!certData || certData.length === 0) {
|
|
1331
|
+
throw new SignatureException('Certificate data cannot be null or empty');
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
const errorCode = Buffer.alloc(4);
|
|
1335
|
+
const certHandle = this.native.pdf_certificate_load_from_bytes(
|
|
1336
|
+
certData,
|
|
1337
|
+
certData.length,
|
|
1338
|
+
errorCode,
|
|
1339
|
+
);
|
|
1340
|
+
const code = errorCode.readInt32LE(0);
|
|
1341
|
+
|
|
1342
|
+
if (code !== 0 || !certHandle) {
|
|
1343
|
+
throw mapFfiErrorCode(code, 'Failed to load certificate from bytes');
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
return certHandle;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
/**
|
|
1350
|
+
* Get the common name (CN) from a certificate handle.
|
|
1351
|
+
*
|
|
1352
|
+
* Calls the native `pdf_certificate_get_cn` FFI function to extract
|
|
1353
|
+
* the subject common name from a certificate.
|
|
1354
|
+
*
|
|
1355
|
+
* @param certHandle - An opaque certificate handle obtained from getCertificate or loadCertificateFromDerBytes
|
|
1356
|
+
* @returns The certificate common name string
|
|
1357
|
+
* @throws SignatureException if the CN cannot be retrieved
|
|
1358
|
+
*
|
|
1359
|
+
* @example
|
|
1360
|
+
* ```typescript
|
|
1361
|
+
* const credentials = await sigManager.loadCredentialsPkcs12('cert.p12', 'pass');
|
|
1362
|
+
* const certHandle = await sigManager.getCertificate(credentials);
|
|
1363
|
+
* const cn = await sigManager.getCertificateCn(certHandle);
|
|
1364
|
+
* console.log(`Signer: ${cn}`);
|
|
1365
|
+
* ```
|
|
1366
|
+
*/
|
|
1367
|
+
async getCertificateCn(certHandle: any): Promise<string> {
|
|
1368
|
+
if (!this.native?.pdf_certificate_get_cn) {
|
|
1369
|
+
throw new SignatureException('Native signing not available: pdf_certificate_get_cn not found');
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
if (!certHandle) {
|
|
1373
|
+
throw new SignatureException('Invalid certificate handle: handle is null');
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
const errorCode = Buffer.alloc(4);
|
|
1377
|
+
const resultPtr = this.native.pdf_certificate_get_cn(certHandle, errorCode);
|
|
1378
|
+
const code = errorCode.readInt32LE(0);
|
|
1379
|
+
|
|
1380
|
+
if (code !== 0 || !resultPtr) {
|
|
1381
|
+
throw mapFfiErrorCode(code, 'Failed to get certificate CN');
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
const cn = typeof resultPtr === 'string' ? resultPtr : resultPtr.toString();
|
|
1385
|
+
return cn;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* Get the issuer name from a certificate handle.
|
|
1390
|
+
*
|
|
1391
|
+
* Calls the native `pdf_certificate_get_issuer` FFI function to extract
|
|
1392
|
+
* the issuer distinguished name from a certificate.
|
|
1393
|
+
*
|
|
1394
|
+
* @param certHandle - An opaque certificate handle obtained from getCertificate or loadCertificateFromDerBytes
|
|
1395
|
+
* @returns The certificate issuer name string
|
|
1396
|
+
* @throws SignatureException if the issuer cannot be retrieved
|
|
1397
|
+
*
|
|
1398
|
+
* @example
|
|
1399
|
+
* ```typescript
|
|
1400
|
+
* const certHandle = await sigManager.loadCertificateFromDerBytes(certDer);
|
|
1401
|
+
* const issuer = await sigManager.getCertificateIssuerFromHandle(certHandle);
|
|
1402
|
+
* console.log(`Issued by: ${issuer}`);
|
|
1403
|
+
* ```
|
|
1404
|
+
*/
|
|
1405
|
+
async getCertificateIssuerFromHandle(certHandle: any): Promise<string> {
|
|
1406
|
+
if (!this.native?.pdf_certificate_get_issuer) {
|
|
1407
|
+
throw new SignatureException('Native signing not available: pdf_certificate_get_issuer not found');
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
if (!certHandle) {
|
|
1411
|
+
throw new SignatureException('Invalid certificate handle: handle is null');
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
const errorCode = Buffer.alloc(4);
|
|
1415
|
+
const resultPtr = this.native.pdf_certificate_get_issuer(certHandle, errorCode);
|
|
1416
|
+
const code = errorCode.readInt32LE(0);
|
|
1417
|
+
|
|
1418
|
+
if (code !== 0 || !resultPtr) {
|
|
1419
|
+
throw mapFfiErrorCode(code, 'Failed to get certificate issuer');
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
const issuer = typeof resultPtr === 'string' ? resultPtr : resultPtr.toString();
|
|
1423
|
+
return issuer;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
/**
|
|
1427
|
+
* Get the size in bytes of a certificate.
|
|
1428
|
+
*
|
|
1429
|
+
* Calls the native `pdf_certificate_get_size` FFI function to get the
|
|
1430
|
+
* size of the DER-encoded certificate data.
|
|
1431
|
+
*
|
|
1432
|
+
* @param certHandle - An opaque certificate handle obtained from getCertificate or loadCertificateFromDerBytes
|
|
1433
|
+
* @returns The certificate size in bytes
|
|
1434
|
+
* @throws SignatureException if the size cannot be retrieved
|
|
1435
|
+
*
|
|
1436
|
+
* @example
|
|
1437
|
+
* ```typescript
|
|
1438
|
+
* const certHandle = await sigManager.loadCertificateFromDerBytes(certDer);
|
|
1439
|
+
* const size = await sigManager.getCertificateSize(certHandle);
|
|
1440
|
+
* console.log(`Certificate size: ${size} bytes`);
|
|
1441
|
+
* ```
|
|
1442
|
+
*/
|
|
1443
|
+
async getCertificateSize(certHandle: any): Promise<number> {
|
|
1444
|
+
if (!this.native?.pdf_certificate_get_size) {
|
|
1445
|
+
throw new SignatureException('Native signing not available: pdf_certificate_get_size not found');
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
if (!certHandle) {
|
|
1449
|
+
throw new SignatureException('Invalid certificate handle: handle is null');
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
const errorCode = Buffer.alloc(4);
|
|
1453
|
+
const size = this.native.pdf_certificate_get_size(certHandle, errorCode);
|
|
1454
|
+
const code = errorCode.readInt32LE(0);
|
|
1455
|
+
|
|
1456
|
+
if (code !== 0) {
|
|
1457
|
+
throw mapFfiErrorCode(code, 'Failed to get certificate size');
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
return typeof size === 'number' ? size : Number(size);
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
/**
|
|
1464
|
+
* Free a certificate handle when it is no longer needed.
|
|
1465
|
+
*
|
|
1466
|
+
* Calls the native `pdf_certificate_free` FFI function to release
|
|
1467
|
+
* memory associated with the certificate handle.
|
|
1468
|
+
*
|
|
1469
|
+
* @param certHandle - The certificate handle to free
|
|
1470
|
+
*
|
|
1471
|
+
* @example
|
|
1472
|
+
* ```typescript
|
|
1473
|
+
* const certHandle = await sigManager.loadCertificateFromDerBytes(certDer);
|
|
1474
|
+
* const cn = await sigManager.getCertificateCn(certHandle);
|
|
1475
|
+
* sigManager.freeCertificate(certHandle);
|
|
1476
|
+
* ```
|
|
1477
|
+
*/
|
|
1478
|
+
freeCertificate(certHandle: any): void {
|
|
1479
|
+
if (certHandle && this.native?.pdf_certificate_free) {
|
|
1480
|
+
this.native.pdf_certificate_free(certHandle);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
* Add an RFC 3161 timestamp to an existing signature via a Time Stamp Authority.
|
|
1486
|
+
*
|
|
1487
|
+
* Calls the native `pdf_add_timestamp` FFI function.
|
|
1488
|
+
*
|
|
1489
|
+
* @param pdfData - Buffer containing the signed PDF document bytes
|
|
1490
|
+
* @param signatureIndex - Index of the signature to timestamp (0-based)
|
|
1491
|
+
* @param tsaUrl - URL of the Time Stamp Authority server
|
|
1492
|
+
* @returns Buffer containing the timestamped PDF bytes
|
|
1493
|
+
* @throws SignatureException if timestamping fails
|
|
1494
|
+
*/
|
|
1495
|
+
async addTimestamp(
|
|
1496
|
+
pdfData: Buffer,
|
|
1497
|
+
signatureIndex: number,
|
|
1498
|
+
tsaUrl: string,
|
|
1499
|
+
): Promise<Buffer> {
|
|
1500
|
+
if (!this.native?.pdf_add_timestamp) {
|
|
1501
|
+
throw new SignatureException('Native timestamping not available: pdf_add_timestamp not found');
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
if (!pdfData || pdfData.length === 0) {
|
|
1505
|
+
throw new SignatureException('Invalid pdfData: buffer is empty or null');
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
if (!tsaUrl || tsaUrl.length === 0) {
|
|
1509
|
+
throw new SignatureException('Invalid tsaUrl: URL is empty or null');
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
const errorCode = Buffer.alloc(4);
|
|
1513
|
+
const outDataPtr = Buffer.alloc(8);
|
|
1514
|
+
const outLen = Buffer.alloc(8);
|
|
1515
|
+
|
|
1516
|
+
const success = this.native.pdf_add_timestamp(
|
|
1517
|
+
pdfData,
|
|
1518
|
+
pdfData.length,
|
|
1519
|
+
signatureIndex,
|
|
1520
|
+
tsaUrl,
|
|
1521
|
+
outDataPtr,
|
|
1522
|
+
outLen,
|
|
1523
|
+
errorCode,
|
|
1524
|
+
);
|
|
1525
|
+
|
|
1526
|
+
const code = errorCode.readInt32LE(0);
|
|
1527
|
+
|
|
1528
|
+
if (!success || code !== 0) {
|
|
1529
|
+
throw mapFfiErrorCode(code, 'Failed to add timestamp to PDF signature');
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
const resultLen = Number(outLen.readBigUInt64LE(0));
|
|
1533
|
+
const resultData = this.native.pdf_read_signed_bytes(outDataPtr, resultLen);
|
|
1534
|
+
|
|
1535
|
+
if (this.native.pdf_signed_bytes_free) {
|
|
1536
|
+
this.native.pdf_signed_bytes_free(outDataPtr, resultLen);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
this.emit('timestamp-added', {
|
|
1540
|
+
signatureIndex,
|
|
1541
|
+
tsaUrl,
|
|
1542
|
+
});
|
|
1543
|
+
|
|
1544
|
+
return Buffer.from(resultData);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* Sign PDF data with a visible signature appearance on a specific page.
|
|
1549
|
+
*
|
|
1550
|
+
* @param pdfData - Buffer containing PDF bytes to sign
|
|
1551
|
+
* @param credentials - Pre-loaded signing credentials
|
|
1552
|
+
* @param pageNum - Page number for appearance (0-based)
|
|
1553
|
+
* @param x - X coordinate of appearance box
|
|
1554
|
+
* @param y - Y coordinate of appearance box
|
|
1555
|
+
* @param width - Width of appearance box
|
|
1556
|
+
* @param height - Height of appearance box
|
|
1557
|
+
* @param options - Optional signing parameters
|
|
1558
|
+
* @returns Buffer containing signed PDF bytes
|
|
1559
|
+
* @throws SignatureException if signing fails
|
|
1560
|
+
*/
|
|
1561
|
+
async signWithAppearance(
|
|
1562
|
+
pdfData: Buffer,
|
|
1563
|
+
credentials: SigningCredentials,
|
|
1564
|
+
pageNum: number,
|
|
1565
|
+
x: number, y: number,
|
|
1566
|
+
width: number, height: number,
|
|
1567
|
+
options?: SignOptions,
|
|
1568
|
+
): Promise<Buffer> {
|
|
1569
|
+
if (!this.native?.pdf_document_sign_with_appearance) {
|
|
1570
|
+
throw new SignatureException('Native signing not available: pdf_document_sign_with_appearance not found');
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
if (!credentials._handle) {
|
|
1574
|
+
throw new SignatureException('Invalid credentials: handle is null');
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
const errorCode = Buffer.alloc(4);
|
|
1578
|
+
const outDataPtr = Buffer.alloc(8);
|
|
1579
|
+
const outLen = Buffer.alloc(8);
|
|
1580
|
+
|
|
1581
|
+
const algorithm = options?.algorithm ?? FfiDigestAlgorithm.SHA256;
|
|
1582
|
+
|
|
1583
|
+
const success = this.native.pdf_document_sign_with_appearance(
|
|
1584
|
+
pdfData,
|
|
1585
|
+
pdfData.length,
|
|
1586
|
+
credentials._handle,
|
|
1587
|
+
pageNum,
|
|
1588
|
+
x,
|
|
1589
|
+
y,
|
|
1590
|
+
width,
|
|
1591
|
+
height,
|
|
1592
|
+
options?.reason ?? null,
|
|
1593
|
+
options?.location ?? null,
|
|
1594
|
+
options?.contact ?? null,
|
|
1595
|
+
algorithm,
|
|
1596
|
+
outDataPtr,
|
|
1597
|
+
outLen,
|
|
1598
|
+
errorCode,
|
|
1599
|
+
);
|
|
1600
|
+
|
|
1601
|
+
const code = errorCode.readInt32LE(0);
|
|
1602
|
+
|
|
1603
|
+
if (!success || code !== 0) {
|
|
1604
|
+
throw mapFfiErrorCode(code, 'Failed to sign PDF with appearance');
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
const resultLen = Number(outLen.readBigUInt64LE(0));
|
|
1608
|
+
const resultData = this.native.pdf_read_signed_bytes(outDataPtr, resultLen);
|
|
1609
|
+
|
|
1610
|
+
if (this.native.pdf_signed_bytes_free) {
|
|
1611
|
+
this.native.pdf_signed_bytes_free(outDataPtr, resultLen);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
this.clearCachePattern('signatures:');
|
|
1615
|
+
this.emit('signed-with-appearance', {
|
|
1616
|
+
pageNum,
|
|
1617
|
+
x,
|
|
1618
|
+
y,
|
|
1619
|
+
width,
|
|
1620
|
+
height,
|
|
1621
|
+
reason: options?.reason,
|
|
1622
|
+
location: options?.location,
|
|
1623
|
+
algorithm,
|
|
1624
|
+
});
|
|
1625
|
+
|
|
1626
|
+
return Buffer.from(resultData);
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// ===========================================================================
|
|
1630
|
+
// Cache
|
|
1631
|
+
// ===========================================================================
|
|
1632
|
+
|
|
1633
|
+
clearCache(): void {
|
|
1634
|
+
this.resultCache.clear();
|
|
1635
|
+
this.emit('cacheCleared');
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
getCacheStats(): Record<string, any> {
|
|
1639
|
+
return { cacheSize: this.resultCache.size, maxCacheSize: this.maxCacheSize, entries: Array.from(this.resultCache.keys()) };
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
destroy(): void {
|
|
1643
|
+
this.loadedCertificates.clear();
|
|
1644
|
+
this.createdFields.clear();
|
|
1645
|
+
this.resultCache.clear();
|
|
1646
|
+
this.removeAllListeners();
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// Private helpers
|
|
1650
|
+
private setCached(key: string, value: any): void {
|
|
1651
|
+
this.resultCache.set(key, value);
|
|
1652
|
+
if (this.resultCache.size > this.maxCacheSize) {
|
|
1653
|
+
const firstKey = this.resultCache.keys().next().value;
|
|
1654
|
+
if (firstKey !== undefined) this.resultCache.delete(firstKey);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
private clearCachePattern(prefix: string): void {
|
|
1659
|
+
const keysToDelete = Array.from(this.resultCache.keys()).filter(key => key.startsWith(prefix));
|
|
1660
|
+
keysToDelete.forEach(key => this.resultCache.delete(key));
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
export default SignatureManager;
|