@owf/eudi-jades 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,924 @@
1
+ import { base64urlDecode, base64urlEncode } from "@owf/identity-common";
2
+ import { z } from "zod";
3
+ import { parseCertificateChain } from "@owf/crypto";
4
+ //#region src/constants.ts
5
+ /**
6
+ * JAdES Constants
7
+ *
8
+ * Algorithm definitions and OIDs as per ETSI TS 119 182-1.
9
+ */
10
+ /**
11
+ * Supported signature algorithms with their hash algorithms.
12
+ */
13
+ const ALGORITHMS = {
14
+ RS256: {
15
+ hash: "SHA-256",
16
+ family: "RSA"
17
+ },
18
+ RS384: {
19
+ hash: "SHA-384",
20
+ family: "RSA"
21
+ },
22
+ RS512: {
23
+ hash: "SHA-512",
24
+ family: "RSA"
25
+ },
26
+ PS256: {
27
+ hash: "SHA-256",
28
+ family: "RSA-PSS"
29
+ },
30
+ PS384: {
31
+ hash: "SHA-384",
32
+ family: "RSA-PSS"
33
+ },
34
+ PS512: {
35
+ hash: "SHA-512",
36
+ family: "RSA-PSS"
37
+ },
38
+ ES256: {
39
+ hash: "SHA-256",
40
+ family: "ECDSA",
41
+ namedCurve: "P-256"
42
+ },
43
+ ES384: {
44
+ hash: "SHA-384",
45
+ family: "ECDSA",
46
+ namedCurve: "P-384"
47
+ },
48
+ ES512: {
49
+ hash: "SHA-512",
50
+ family: "ECDSA",
51
+ namedCurve: "P-521"
52
+ }
53
+ };
54
+ /**
55
+ * Commitment type OIDs as per RFC 5035.
56
+ */
57
+ let CommitmentOIDs = /* @__PURE__ */ function(CommitmentOIDs) {
58
+ /** Proof of origin */
59
+ CommitmentOIDs["proofOfOrigin"] = "1.2.840.113549.1.9.16.6.1";
60
+ /** Proof of receipt */
61
+ CommitmentOIDs["proofOfReceipt"] = "1.2.840.113549.1.9.16.6.2";
62
+ /** Proof of delivery */
63
+ CommitmentOIDs["proofOfDelivery"] = "1.2.840.113549.1.9.16.6.3";
64
+ /** Proof of sender */
65
+ CommitmentOIDs["proofOfSender"] = "1.2.840.113549.1.9.16.6.4";
66
+ /** Proof of approval */
67
+ CommitmentOIDs["proofOfApproval"] = "1.2.840.113549.1.9.16.6.5";
68
+ /** Proof of creation */
69
+ CommitmentOIDs["proofOfCreation"] = "1.2.840.113549.1.9.16.6.6";
70
+ return CommitmentOIDs;
71
+ }({});
72
+ /**
73
+ * JAdES baseline profiles as per ETSI TS 119 182-1.
74
+ */
75
+ let JAdESProfile = /* @__PURE__ */ function(JAdESProfile) {
76
+ /** Basic - Baseline: basic signature format */
77
+ JAdESProfile["B_B"] = "B-B";
78
+ /** Basic with Time: signatures with timestamp */
79
+ JAdESProfile["B_T"] = "B-T";
80
+ /** Basic Long-Term: signatures with validation data for long-term preservation */
81
+ JAdESProfile["B_LT"] = "B-LT";
82
+ /** Basic Long-Term with Archive timestamps */
83
+ JAdESProfile["B_LTA"] = "B-LTA";
84
+ return JAdESProfile;
85
+ }({});
86
+ /**
87
+ * Detached signature mechanism identifiers as per ETSI TS 119 182-1 Section 5.2.8.
88
+ */
89
+ const DETACHED_MECHANISM_IDS = {
90
+ httpHeaders: "http://uri.etsi.org/19182/HttpHeaders",
91
+ objectDigest: "http://uri.etsi.org/19182/ObjectIdByURIHash"
92
+ };
93
+ /**
94
+ * Critical header parameters that must be understood.
95
+ * ETSI TS 119 182-1 specifies these parameters as critical.
96
+ */
97
+ const CRITICAL_PARAMETERS = [
98
+ "x5t#o",
99
+ "sigX5ts",
100
+ "sigT",
101
+ "sigD",
102
+ "sigPl",
103
+ "sigPId",
104
+ "srCms",
105
+ "srAts",
106
+ "adoTst",
107
+ "b64"
108
+ ];
109
+ //#endregion
110
+ //#region src/jades-exception.ts
111
+ /**
112
+ * JAdES Exception
113
+ *
114
+ * Custom exception for JAdES-related errors.
115
+ */
116
+ var JAdESException = class JAdESException extends Error {
117
+ constructor(message) {
118
+ super(message);
119
+ this.name = "JAdESException";
120
+ Object.setPrototypeOf(this, JAdESException.prototype);
121
+ }
122
+ };
123
+ //#endregion
124
+ //#region src/schemas.ts
125
+ /**
126
+ * JAdES Zod Schemas
127
+ *
128
+ * Zod schemas for JAdES (JSON Advanced Electronic Signatures) as per ETSI TS 119 182-1.
129
+ * Types are derived from these schemas via z.infer<>.
130
+ *
131
+ * @see https://www.etsi.org/deliver/etsi_ts/119100_119199/11918201/01.02.01_60/ts_11918201v010201p.pdf
132
+ */
133
+ /**
134
+ * Supported signature algorithms.
135
+ */
136
+ const SignAlgSchema = z.enum([
137
+ "ES256",
138
+ "ES384",
139
+ "ES512",
140
+ "RS256",
141
+ "RS384",
142
+ "RS512",
143
+ "PS256",
144
+ "PS384",
145
+ "PS512"
146
+ ]);
147
+ /**
148
+ * X.509 Certificate Thumbprint with algorithm specification.
149
+ * ETSI TS 119 182-1 Section 5.2.2.2
150
+ */
151
+ const X5tOSchema = z.object({
152
+ digAlg: z.string().min(1),
153
+ digVal: z.string().min(1)
154
+ });
155
+ /**
156
+ * Commitment reference as per ETSI TS 119 182-1 Section 5.2.5.
157
+ */
158
+ const CommitmentReferenceSchema = z.object({
159
+ commId: z.string().min(1),
160
+ commQuals: z.array(z.object({}).passthrough()).optional()
161
+ });
162
+ /**
163
+ * Signature policy hash.
164
+ */
165
+ const SignaturePolicyHashSchema = z.object({
166
+ hashAlgo: z.string().min(1),
167
+ hashVal: z.string().min(1)
168
+ });
169
+ /**
170
+ * Signature policy descriptor.
171
+ * ETSI TS 119 182-1 Section 5.2.4
172
+ */
173
+ const SignaturePolicySchema = z.object({
174
+ sigPolicyId: z.string().optional(),
175
+ sigPolicyHash: SignaturePolicyHashSchema.optional(),
176
+ sigPolicyQualifiers: z.array(z.object({}).passthrough()).optional()
177
+ });
178
+ /**
179
+ * Issuer and serial number for signer identification.
180
+ */
181
+ const IssuerSerialSchema = z.object({
182
+ issuer: z.string().min(1),
183
+ serialNumber: z.string().min(1)
184
+ });
185
+ /**
186
+ * Signer identifier.
187
+ * ETSI TS 119 182-1 Section 5.2.3
188
+ */
189
+ const SignerIdentifierSchema = z.object({
190
+ issuerSerial: IssuerSerialSchema.optional(),
191
+ subjectKeyIdentifier: z.string().optional()
192
+ });
193
+ /**
194
+ * Detached signature descriptor.
195
+ * ETSI TS 119 182-1 Section 5.2.8
196
+ */
197
+ const SigDSchema = z.object({
198
+ mId: z.string().min(1),
199
+ pars: z.tuple([z.string(), z.string()]),
200
+ hashM: z.string().optional(),
201
+ ctM: z.string().optional()
202
+ });
203
+ /**
204
+ * Timestamp token value.
205
+ */
206
+ const TstTokenValueSchema = z.object({ val: z.string().min(1) });
207
+ /**
208
+ * Timestamp tokens container.
209
+ */
210
+ const TstTokensSchema = z.object({ tstTokens: z.array(TstTokenValueSchema).min(1) });
211
+ /**
212
+ * Signature timestamp container (for B-T profile).
213
+ */
214
+ const SigTstSchema = z.object({ sigTst: TstTokensSchema });
215
+ /**
216
+ * X.509 certificate values (for B-LT profile).
217
+ */
218
+ const XValsSchema = z.object({ xVals: z.array(z.object({ x509Cert: z.string().min(1) })) });
219
+ /**
220
+ * Revocation values (for B-LT profile).
221
+ */
222
+ const RValsSchema = z.object({ rVals: z.object({
223
+ crlVals: z.array(z.string()),
224
+ ocspVals: z.array(z.string())
225
+ }) });
226
+ /**
227
+ * Archive timestamp (for B-LTA profile).
228
+ */
229
+ const ArcTstSchema = z.object({ arcTst: TstTokensSchema.extend({ canonAlg: z.string().optional() }) });
230
+ /**
231
+ * ETSI Unsigned properties for different JAdES profiles.
232
+ */
233
+ const EtsiUSchema = z.union([
234
+ z.array(SigTstSchema),
235
+ z.tuple([
236
+ SigTstSchema,
237
+ XValsSchema,
238
+ RValsSchema
239
+ ]),
240
+ z.tuple([
241
+ SigTstSchema,
242
+ XValsSchema,
243
+ RValsSchema,
244
+ ArcTstSchema
245
+ ])
246
+ ]);
247
+ /**
248
+ * JAdES Protected Header parameters as per ETSI TS 119 182-1.
249
+ */
250
+ const ProtectedHeaderSchema = z.object({
251
+ alg: SignAlgSchema.optional(),
252
+ cty: z.string().optional(),
253
+ kid: z.string().optional(),
254
+ x5u: z.string().url().optional(),
255
+ x5c: z.array(z.string().min(1)).min(1).optional(),
256
+ "x5t#S256": z.string().min(1).optional(),
257
+ "x5t#o": X5tOSchema.optional(),
258
+ sigX5ts: z.array(X5tOSchema).min(2).optional(),
259
+ srCms: z.array(CommitmentReferenceSchema).optional(),
260
+ srAts: z.array(z.object({}).passthrough()).optional(),
261
+ sigPl: SignaturePolicySchema.optional(),
262
+ sigPId: SignerIdentifierSchema.optional(),
263
+ sigT: z.string().optional(),
264
+ sigD: SigDSchema.optional(),
265
+ b64: z.literal(false).optional(),
266
+ crit: z.array(z.string()).optional(),
267
+ iat: z.number().int().positive().optional(),
268
+ signedAt: z.number().int().positive().optional(),
269
+ jti: z.string().optional(),
270
+ typ: z.string().optional(),
271
+ adoTst: z.array(z.object({}).passthrough()).optional()
272
+ }).passthrough();
273
+ /**
274
+ * Protected header schema for signing (alg required).
275
+ */
276
+ const ProtectedHeaderForSigningSchema = ProtectedHeaderSchema.extend({ alg: SignAlgSchema }).refine((header) => {
277
+ return !!(header["x5t#S256"] || header.x5c || header["x5t#o"] || header.sigX5ts);
278
+ }, { message: "JAdES signature requires at least one certificate header: x5t#S256, x5c, x5t#o, or sigX5ts" });
279
+ /**
280
+ * JAdES Unprotected Header parameters.
281
+ */
282
+ const UnprotectedHeaderSchema = z.object({
283
+ etsiU: EtsiUSchema.optional(),
284
+ disclosures: z.array(z.string()).optional(),
285
+ kid: z.string().optional(),
286
+ kb_jwt: z.string().optional()
287
+ }).passthrough();
288
+ /**
289
+ * Signature entry in General JWS.
290
+ */
291
+ const JWSSignatureSchema = z.object({
292
+ protected: z.string().min(1),
293
+ signature: z.string().min(1),
294
+ header: UnprotectedHeaderSchema.optional()
295
+ });
296
+ /**
297
+ * General JWS structure with multiple signatures.
298
+ */
299
+ const GeneralJWSSchema = z.object({
300
+ payload: z.string(),
301
+ signatures: z.array(JWSSignatureSchema).min(1)
302
+ });
303
+ /**
304
+ * Flattened JWS structure (single signature).
305
+ */
306
+ const FlattenedJWSSchema = z.object({
307
+ protected: z.string().min(1),
308
+ payload: z.string(),
309
+ signature: z.string().min(1),
310
+ header: UnprotectedHeaderSchema.optional()
311
+ });
312
+ /**
313
+ * Compact JWS representation.
314
+ */
315
+ const CompactJWSSchema = z.object({
316
+ protected: z.string().min(1),
317
+ payload: z.string(),
318
+ signature: z.string().min(1)
319
+ });
320
+ /**
321
+ * Sign options for JAdES signing.
322
+ */
323
+ const SignOptionsSchema = z.object({
324
+ alg: SignAlgSchema,
325
+ kid: z.string().optional(),
326
+ certificates: z.array(z.string()).optional()
327
+ });
328
+ z.object({ skipSignatureValidation: z.boolean().optional() });
329
+ //#endregion
330
+ //#region src/utils.ts
331
+ /**
332
+ * JAdES Utility Functions
333
+ *
334
+ * Helper functions for certificate handling and header generation.
335
+ */
336
+ /**
337
+ * Parse PEM-encoded certificate chain and return base64 DER strings.
338
+ *
339
+ * @param pem - One or more PEM-encoded certificates
340
+ * @returns Array of base64-encoded DER certificate strings
341
+ */
342
+ function parseCerts(pem) {
343
+ return parseCertificateChain(pem);
344
+ }
345
+ /**
346
+ * Generate x5c header value from PEM certificates.
347
+ * The certificates are converted to base64-encoded DER format.
348
+ *
349
+ * ETSI TS 119 182-1 Section 5.1.8
350
+ *
351
+ * @param certs - PEM-encoded certificate(s) or array of base64 DER certs
352
+ * @returns Array of base64-encoded DER certificate strings
353
+ */
354
+ function generateX5c(certs) {
355
+ if (typeof certs === "string") return parseCertificateChain(certs);
356
+ return certs;
357
+ }
358
+ /**
359
+ * Generate x5t#S256 header value (SHA-256 thumbprint of certificate).
360
+ *
361
+ * ETSI TS 119 182-1 Section 5.1.7
362
+ *
363
+ * @param certDer - Base64-encoded DER certificate
364
+ * @returns Base64url-encoded SHA-256 thumbprint
365
+ */
366
+ async function generateX5tS256(certDer) {
367
+ const certBytes = base64ToUint8Array(certDer);
368
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", certBytes);
369
+ return uint8ArrayToBase64Url(new Uint8Array(hashBuffer));
370
+ }
371
+ /**
372
+ * Generate x5t#o header value (certificate thumbprint with specified algorithm).
373
+ *
374
+ * ETSI TS 119 182-1 Section 5.2.2.2
375
+ *
376
+ * @param certDer - Base64-encoded DER certificate
377
+ * @param algorithm - Hash algorithm ('SHA-384' or 'SHA-512')
378
+ * @returns X5tO object with algorithm identifier and digest value
379
+ */
380
+ async function generateX5tO(certDer, algorithm = "SHA-512") {
381
+ const algMap = {
382
+ "SHA-384": "S384",
383
+ "SHA-512": "S512"
384
+ };
385
+ const certBytes = base64ToUint8Array(certDer);
386
+ const hashBuffer = await globalThis.crypto.subtle.digest(algorithm, certBytes);
387
+ return {
388
+ digAlg: algMap[algorithm],
389
+ digVal: uint8ArrayToBase64Url(new Uint8Array(hashBuffer))
390
+ };
391
+ }
392
+ /**
393
+ * Generate sigX5ts header value (certificate chain thumbprints).
394
+ *
395
+ * ETSI TS 119 182-1 Section 5.2.2.3
396
+ *
397
+ * @param certsDer - Array of base64-encoded DER certificates
398
+ * @param algorithm - Hash algorithm ('SHA-384' or 'SHA-512')
399
+ * @returns Array of X5tO objects
400
+ */
401
+ async function generateSigX5ts(certsDer, algorithm = "SHA-512") {
402
+ if (certsDer.length < 2) throw new JAdESException("sigX5ts requires at least 2 certificates");
403
+ return Promise.all(certsDer.map((cert) => generateX5tO(cert, algorithm)));
404
+ }
405
+ /**
406
+ * Generate a JAdES-compliant kid from X.509 certificate.
407
+ *
408
+ * According to ETSI TS 119 182-1 Section 5.1.4, the kid should be
409
+ * the base64 encoding of DER-encoded IssuerSerial sequence.
410
+ *
411
+ * This implementation generates a SHA-256 thumbprint of the certificate
412
+ * as a simpler approach that uniquely identifies the certificate.
413
+ *
414
+ * @param certDer - Base64-encoded DER certificate
415
+ * @returns Base64url-encoded key identifier
416
+ */
417
+ async function generateKid(certDer) {
418
+ return generateX5tS256(certDer);
419
+ }
420
+ /**
421
+ * Encode an object as a base64url JSON string.
422
+ *
423
+ * @param obj - Object to encode
424
+ * @returns Base64url-encoded JSON string
425
+ */
426
+ function encodeJSON(obj) {
427
+ return base64urlEncode(JSON.stringify(obj));
428
+ }
429
+ /**
430
+ * Decode a base64url JSON string to an object.
431
+ *
432
+ * @param encoded - Base64url-encoded JSON string
433
+ * @returns Decoded object
434
+ */
435
+ function decodeJSON(encoded) {
436
+ return JSON.parse(base64urlDecode(encoded));
437
+ }
438
+ /**
439
+ * Get current ISO 8601 timestamp for sigT header.
440
+ *
441
+ * @returns ISO 8601 timestamp string
442
+ */
443
+ function getSigningTime() {
444
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
445
+ }
446
+ /**
447
+ * Validate that the protected header has at least one certificate header.
448
+ * As per ETSI TS 119 182-1 Section 5.1.7, a JAdES signature shall have
449
+ * at least one of: x5t#S256, x5c, x5t#o, sigX5ts.
450
+ *
451
+ * @param header - Protected header object
452
+ * @returns true if valid
453
+ * @throws JAdESException if no certificate header is present
454
+ */
455
+ function validateCertificateHeaders(header) {
456
+ if (!!!(header["x5t#S256"] || header.x5c || header["x5t#o"] || header.sigX5ts)) throw new JAdESException("JAdES signature requires at least one certificate header: x5t#S256, x5c, x5t#o, or sigX5ts");
457
+ return true;
458
+ }
459
+ function base64ToUint8Array(base64) {
460
+ const normalized = base64.replace(/-/g, "+").replace(/_/g, "/");
461
+ const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
462
+ const binaryString = atob(padded);
463
+ const bytes = new Uint8Array(binaryString.length);
464
+ for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
465
+ return bytes;
466
+ }
467
+ function uint8ArrayToBase64Url(bytes) {
468
+ let binary = "";
469
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
470
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
471
+ }
472
+ //#endregion
473
+ //#region src/token.ts
474
+ /**
475
+ * JAdES Token
476
+ *
477
+ * Main class for creating JAdES-compliant signatures.
478
+ * Implements ETSI TS 119 182-1 standard for JSON Advanced Electronic Signatures.
479
+ *
480
+ * @example
481
+ * ```typescript
482
+ * import { Token, parseCerts, generateX5c } from '@owf/eudi-jades'
483
+ * import { ES256 } from '@owf/crypto'
484
+ *
485
+ * const payload = { hello: 'world' }
486
+ * const token = new Token(payload)
487
+ *
488
+ * const certs = parseCerts(pemCertificate)
489
+ * token.setProtectedHeader({
490
+ * alg: 'ES256',
491
+ * x5c: generateX5c(certs),
492
+ * })
493
+ *
494
+ * const signer = await ES256.getSigner(privateKey)
495
+ * await token.sign(signer)
496
+ *
497
+ * const compactJws = token.toString()
498
+ * const generalJws = token.toJSON()
499
+ * ```
500
+ */
501
+ /**
502
+ * JAdES Token class for creating conformant JAdES signatures.
503
+ */
504
+ var Token = class {
505
+ /**
506
+ * Create a new JAdES Token.
507
+ *
508
+ * @param payload - The payload to sign. If undefined, creates a detached signature.
509
+ */
510
+ constructor(payload) {
511
+ this.protectedHeader = {};
512
+ this.unprotectedHeader = {};
513
+ this.payload = payload;
514
+ this.encodedPayload = payload === void 0 ? "" : base64urlEncode(JSON.stringify(payload));
515
+ }
516
+ /**
517
+ * Set the protected header parameters.
518
+ *
519
+ * @param header - Protected header parameters
520
+ * @returns this for method chaining
521
+ */
522
+ setProtectedHeader(header) {
523
+ if (header.alg) {
524
+ if (!SignAlgSchema.safeParse(header.alg).success) throw new JAdESException(`Invalid algorithm: ${header.alg}`);
525
+ }
526
+ const result = ProtectedHeaderSchema.safeParse(header);
527
+ if (!result.success) throw new JAdESException(`Invalid protected header: ${result.error.issues.map((e) => e.message).join(", ")}`);
528
+ this.protectedHeader = {
529
+ ...this.protectedHeader,
530
+ ...header
531
+ };
532
+ return this;
533
+ }
534
+ /**
535
+ * Set the unprotected header parameters.
536
+ *
537
+ * @param header - Unprotected header parameters
538
+ * @returns this for method chaining
539
+ */
540
+ setUnprotectedHeader(header) {
541
+ this.unprotectedHeader = {
542
+ ...this.unprotectedHeader,
543
+ ...header
544
+ };
545
+ return this;
546
+ }
547
+ /**
548
+ * Set X.509 certificate chain (x5c header).
549
+ *
550
+ * ETSI TS 119 182-1 Section 5.1.8
551
+ *
552
+ * @param certs - Array of base64-encoded DER certificates
553
+ * @returns this for method chaining
554
+ */
555
+ setX5c(certs) {
556
+ this.protectedHeader.x5c = certs;
557
+ return this;
558
+ }
559
+ /**
560
+ * Set X.509 certificate URL (x5u header).
561
+ *
562
+ * ETSI TS 119 182-1 Section 5.1.5
563
+ *
564
+ * @param uri - URI to certificate resource
565
+ * @returns this for method chaining
566
+ */
567
+ setX5u(uri) {
568
+ this.protectedHeader.x5u = uri;
569
+ return this;
570
+ }
571
+ /**
572
+ * Set X.509 certificate SHA-256 thumbprint (x5t#S256 header).
573
+ *
574
+ * ETSI TS 119 182-1 Section 5.1.7
575
+ *
576
+ * @param thumbprint - Base64url-encoded SHA-256 thumbprint
577
+ * @returns this for method chaining
578
+ */
579
+ setX5tS256(thumbprint) {
580
+ this.protectedHeader["x5t#S256"] = thumbprint;
581
+ return this;
582
+ }
583
+ /**
584
+ * Set X.509 certificate thumbprint with other algorithm (x5t#o header).
585
+ *
586
+ * ETSI TS 119 182-1 Section 5.2.2.2
587
+ *
588
+ * @param x5tO - X5tO object with algorithm and digest value
589
+ * @returns this for method chaining
590
+ */
591
+ setX5tO(x5tO) {
592
+ this.protectedHeader["x5t#o"] = x5tO;
593
+ return this;
594
+ }
595
+ /**
596
+ * Set signing time (sigT header).
597
+ *
598
+ * ETSI TS 119 182-1 Section 5.2.1
599
+ *
600
+ * @param time - ISO 8601 timestamp (defaults to current time)
601
+ * @returns this for method chaining
602
+ */
603
+ setSigningTime(time) {
604
+ this.protectedHeader.sigT = time ?? (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
605
+ return this;
606
+ }
607
+ /**
608
+ * Set signedAt timestamp in protected header.
609
+ *
610
+ * @param sec - Unix timestamp in seconds (defaults to current time)
611
+ * @returns this for method chaining
612
+ */
613
+ setSignedAt(sec) {
614
+ this.protectedHeader.signedAt = sec ?? Math.floor(Date.now() / 1e3);
615
+ return this;
616
+ }
617
+ /**
618
+ * Set issued at timestamp (iat) in protected header.
619
+ *
620
+ * @param sec - Unix timestamp in seconds (defaults to current time)
621
+ * @returns this for method chaining
622
+ */
623
+ setIssuedAt(sec) {
624
+ this.protectedHeader.iat = sec ?? Math.floor(Date.now() / 1e3);
625
+ return this;
626
+ }
627
+ /**
628
+ * Set key ID (kid header).
629
+ *
630
+ * @param kid - Key identifier
631
+ * @returns this for method chaining
632
+ */
633
+ setKid(kid) {
634
+ this.protectedHeader.kid = kid;
635
+ return this;
636
+ }
637
+ /**
638
+ * Set content type (cty header).
639
+ *
640
+ * @param cty - Content type
641
+ * @returns this for method chaining
642
+ */
643
+ setContentType(cty) {
644
+ this.protectedHeader.cty = cty;
645
+ return this;
646
+ }
647
+ /**
648
+ * Set token type (typ header).
649
+ *
650
+ * @param typ - Token type
651
+ * @returns this for method chaining
652
+ */
653
+ setType(typ) {
654
+ this.protectedHeader.typ = typ;
655
+ return this;
656
+ }
657
+ /**
658
+ * Set b64 header parameter.
659
+ *
660
+ * ETSI TS 119 182-1 Section 5.1.10
661
+ * RFC 7797 Section 3
662
+ *
663
+ * @param b64 - If true (default), payload is base64url encoded. If false, payload is unencoded.
664
+ * @returns this for method chaining
665
+ */
666
+ setB64(b64) {
667
+ if (b64) delete this.protectedHeader.b64;
668
+ else this.protectedHeader.b64 = false;
669
+ return this;
670
+ }
671
+ /**
672
+ * Configure detached signature mode.
673
+ *
674
+ * ETSI TS 119 182-1 Section 5.2.8
675
+ *
676
+ * @param sigD - Detached signature descriptor
677
+ * @returns this for method chaining
678
+ */
679
+ setDetached(sigD) {
680
+ this.protectedHeader.sigD = sigD;
681
+ this.encodedPayload = "";
682
+ if (sigD.mId === DETACHED_MECHANISM_IDS.httpHeaders) this.setB64(false);
683
+ return this;
684
+ }
685
+ /**
686
+ * Get the signing input (data to be signed).
687
+ *
688
+ * @returns The signing input string (header.payload)
689
+ */
690
+ getSigningInput() {
691
+ return `${this.getEncodedProtectedHeader()}.${this.encodedPayload}`;
692
+ }
693
+ /**
694
+ * Get the hash of the signing input for external signing.
695
+ *
696
+ * @param algorithm - Hash algorithm (defaults to algorithm from header)
697
+ * @returns Promise resolving to hash bytes
698
+ */
699
+ async getHash(algorithm) {
700
+ const alg = algorithm ?? this.getHashAlgorithm();
701
+ const signingInput = this.getSigningInput();
702
+ const encoder = new TextEncoder();
703
+ const hashBuffer = await globalThis.crypto.subtle.digest(alg, encoder.encode(signingInput));
704
+ return new Uint8Array(hashBuffer);
705
+ }
706
+ /**
707
+ * Set the signature (for external signing).
708
+ *
709
+ * @param signature - Base64url-encoded signature
710
+ * @returns this for method chaining
711
+ */
712
+ setSignature(signature) {
713
+ this.signature = signature;
714
+ return this;
715
+ }
716
+ /**
717
+ * Sign the token using the provided signer function.
718
+ *
719
+ * @param signer - Async function that signs data and returns base64url signature
720
+ * @returns Promise resolving to this for method chaining
721
+ */
722
+ async sign(signer) {
723
+ this.validateBeforeSign();
724
+ this.signature = await signer(this.getSigningInput());
725
+ return this;
726
+ }
727
+ /**
728
+ * Export to compact JWS serialization.
729
+ *
730
+ * @returns Compact JWS string (header.payload.signature)
731
+ */
732
+ toString() {
733
+ if (!this.signature) throw new JAdESException("Token not signed yet");
734
+ return `${this.getEncodedProtectedHeader()}.${this.encodedPayload}.${this.signature}`;
735
+ }
736
+ /**
737
+ * Export to General JWS JSON serialization.
738
+ *
739
+ * @returns GeneralJWS object
740
+ */
741
+ toJSON() {
742
+ if (!this.signature) throw new JAdESException("Token not signed yet");
743
+ const result = {
744
+ payload: this.encodedPayload,
745
+ signatures: [{
746
+ protected: this.getEncodedProtectedHeader(),
747
+ signature: this.signature
748
+ }]
749
+ };
750
+ if (Object.keys(this.unprotectedHeader).length > 0) result.signatures[0].header = this.unprotectedHeader;
751
+ return result;
752
+ }
753
+ /**
754
+ * Export to flattened JWS JSON serialization.
755
+ *
756
+ * @returns Flattened JWS object
757
+ */
758
+ toFlattenedJSON() {
759
+ if (!this.signature) throw new JAdESException("Token not signed yet");
760
+ const result = {
761
+ protected: this.getEncodedProtectedHeader(),
762
+ payload: this.encodedPayload,
763
+ signature: this.signature
764
+ };
765
+ if (Object.keys(this.unprotectedHeader).length > 0) result.header = this.unprotectedHeader;
766
+ return result;
767
+ }
768
+ /**
769
+ * Get the protected header object.
770
+ */
771
+ getProtectedHeader() {
772
+ return { ...this.protectedHeader };
773
+ }
774
+ /**
775
+ * Get the unprotected header object.
776
+ */
777
+ getUnprotectedHeader() {
778
+ return { ...this.unprotectedHeader };
779
+ }
780
+ /**
781
+ * Get the payload.
782
+ */
783
+ getPayload() {
784
+ return this.payload;
785
+ }
786
+ getEncodedProtectedHeader() {
787
+ return encodeJSON(this.buildFinalHeader());
788
+ }
789
+ buildFinalHeader() {
790
+ const header = { ...this.protectedHeader };
791
+ const critParams = CRITICAL_PARAMETERS.filter((param) => param in header);
792
+ if (critParams.length > 0) header.crit = critParams;
793
+ return header;
794
+ }
795
+ validateBeforeSign() {
796
+ const result = ProtectedHeaderForSigningSchema.safeParse(this.protectedHeader);
797
+ if (!result.success) throw new JAdESException(`Invalid protected header for signing: ${result.error.issues.map((e) => e.message).join(", ")}`);
798
+ }
799
+ getHashAlgorithm() {
800
+ const algMap = {
801
+ RS256: "SHA-256",
802
+ RS384: "SHA-384",
803
+ RS512: "SHA-512",
804
+ PS256: "SHA-256",
805
+ PS384: "SHA-384",
806
+ PS512: "SHA-512",
807
+ ES256: "SHA-256",
808
+ ES384: "SHA-384",
809
+ ES512: "SHA-512"
810
+ };
811
+ const alg = this.protectedHeader.alg;
812
+ if (!alg || !algMap[alg]) throw new JAdESException(`Unsupported algorithm: ${alg}`);
813
+ return algMap[alg];
814
+ }
815
+ };
816
+ //#endregion
817
+ //#region src/verifier.ts
818
+ /**
819
+ * JAdES Verifier
820
+ *
821
+ * Functions for verifying JAdES signatures.
822
+ */
823
+ /**
824
+ * Verify a JAdES signature in compact serialization.
825
+ *
826
+ * @param jws - Compact JWS string
827
+ * @param verifier - Async function that verifies signature
828
+ * @returns Promise resolving to verification result
829
+ */
830
+ async function verifyCompact(jws, verifier) {
831
+ const parts = jws.split(".");
832
+ if (parts.length !== 3) throw new JAdESException("Invalid JWS format: expected 3 parts");
833
+ const [encodedHeader, encodedPayload, signature] = parts;
834
+ const valid = await verifier(`${encodedHeader}.${encodedPayload}`, signature);
835
+ if (!valid) throw new JAdESException("Invalid signature");
836
+ const rawHeader = JSON.parse(base64urlDecode(encodedHeader));
837
+ const headerResult = ProtectedHeaderSchema.safeParse(rawHeader);
838
+ if (!headerResult.success) throw new JAdESException(`Invalid protected header: ${headerResult.error.issues.map((e) => e.message).join(", ")}`);
839
+ return {
840
+ header: headerResult.data,
841
+ payload: encodedPayload ? JSON.parse(base64urlDecode(encodedPayload)) : {},
842
+ valid
843
+ };
844
+ }
845
+ /**
846
+ * Verify a JAdES signature in General JWS JSON serialization.
847
+ *
848
+ * @param generalJws - General JWS object
849
+ * @param verifier - Async function that verifies signature
850
+ * @param signatureIndex - Index of signature to verify (default: 0)
851
+ * @returns Promise resolving to verification result
852
+ */
853
+ async function verifyGeneral(generalJws, verifier, signatureIndex = 0) {
854
+ const jwsResult = GeneralJWSSchema.safeParse(generalJws);
855
+ if (!jwsResult.success) throw new JAdESException(`Invalid General JWS structure: ${jwsResult.error.issues.map((e) => e.message).join(", ")}`);
856
+ const sig = generalJws.signatures[signatureIndex];
857
+ if (!sig) throw new JAdESException(`Signature at index ${signatureIndex} not found`);
858
+ const valid = await verifier(`${sig.protected}.${generalJws.payload}`, sig.signature);
859
+ if (!valid) throw new JAdESException("Invalid signature");
860
+ const rawHeader = JSON.parse(base64urlDecode(sig.protected));
861
+ const headerResult = ProtectedHeaderSchema.safeParse(rawHeader);
862
+ if (!headerResult.success) throw new JAdESException(`Invalid protected header: ${headerResult.error.issues.map((e) => e.message).join(", ")}`);
863
+ return {
864
+ header: headerResult.data,
865
+ payload: generalJws.payload ? JSON.parse(base64urlDecode(generalJws.payload)) : {},
866
+ unprotectedHeader: sig.header,
867
+ valid
868
+ };
869
+ }
870
+ /**
871
+ * Verify a JAdES signature (auto-detects format).
872
+ *
873
+ * @param jws - JWS string or GeneralJWS object
874
+ * @param verifier - Async function that verifies signature
875
+ * @returns Promise resolving to verification result
876
+ */
877
+ async function verify(jws, verifier) {
878
+ if (typeof jws === "string") {
879
+ try {
880
+ const parsed = JSON.parse(jws);
881
+ if (parsed.signatures && Array.isArray(parsed.signatures)) return verifyGeneral(parsed, verifier);
882
+ } catch {}
883
+ return verifyCompact(jws, verifier);
884
+ }
885
+ return verifyGeneral(jws, verifier);
886
+ }
887
+ /**
888
+ * Decode a JWS without verifying the signature.
889
+ * Use this only for inspection - always verify before trusting the content.
890
+ *
891
+ * @param jws - JWS string or GeneralJWS object
892
+ * @returns Decoded header and payload
893
+ */
894
+ function decode(jws) {
895
+ if (typeof jws === "string") {
896
+ try {
897
+ const parsed = JSON.parse(jws);
898
+ if (parsed.signatures && Array.isArray(parsed.signatures)) {
899
+ const sig = parsed.signatures[0];
900
+ return {
901
+ header: JSON.parse(base64urlDecode(sig.protected)),
902
+ payload: parsed.payload ? JSON.parse(base64urlDecode(parsed.payload)) : {},
903
+ unprotectedHeader: sig.header
904
+ };
905
+ }
906
+ } catch {}
907
+ const parts = jws.split(".");
908
+ if (parts.length !== 3) throw new JAdESException("Invalid JWS format");
909
+ return {
910
+ header: JSON.parse(base64urlDecode(parts[0])),
911
+ payload: parts[1] ? JSON.parse(base64urlDecode(parts[1])) : {}
912
+ };
913
+ }
914
+ const sig = jws.signatures[0];
915
+ return {
916
+ header: JSON.parse(base64urlDecode(sig.protected)),
917
+ payload: jws.payload ? JSON.parse(base64urlDecode(jws.payload)) : {},
918
+ unprotectedHeader: sig.header
919
+ };
920
+ }
921
+ //#endregion
922
+ export { ALGORITHMS, ArcTstSchema, CRITICAL_PARAMETERS, CommitmentOIDs, CompactJWSSchema, DETACHED_MECHANISM_IDS, EtsiUSchema, FlattenedJWSSchema, GeneralJWSSchema, JAdESException, JAdESProfile, ProtectedHeaderForSigningSchema, ProtectedHeaderSchema, RValsSchema, SigDSchema, SigTstSchema, SignAlgSchema, SignOptionsSchema, SignaturePolicySchema, SignerIdentifierSchema, Token, TstTokensSchema, UnprotectedHeaderSchema, X5tOSchema, XValsSchema, decode, decodeJSON, encodeJSON, generateKid, generateSigX5ts, generateX5c, generateX5tO, generateX5tS256, getSigningTime, parseCerts, validateCertificateHeaders, verify, verifyCompact, verifyGeneral };
923
+
924
+ //# sourceMappingURL=index.mjs.map