pptx-viewer-core 1.2.5 → 1.2.6
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/CHANGELOG.md +6 -0
- package/dist/cli/index.js +0 -0
- package/dist/cli/index.mjs +0 -0
- package/dist/converter/index.js +0 -0
- package/dist/converter/index.mjs +0 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +99 -47
- package/dist/index.mjs +99 -47
- package/dist/signature-node/index.d.mts +17 -7
- package/dist/signature-node/index.d.ts +17 -7
- package/dist/signature-node/index.js +297 -209
- package/dist/signature-node/index.mjs +295 -207
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
|
|
2
2
|
import { SignedXml } from 'xml-crypto';
|
|
3
|
-
import
|
|
3
|
+
import crypto2 from 'crypto';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import tls from 'tls';
|
|
6
|
-
import
|
|
6
|
+
import forge3 from 'node-forge';
|
|
7
7
|
import http from 'http';
|
|
8
8
|
import https from 'https';
|
|
9
9
|
import fs from 'fs/promises';
|
|
@@ -206,6 +206,89 @@ function canonicalizeSignedInfoXml(signedInfoXml) {
|
|
|
206
206
|
}
|
|
207
207
|
return canonicalizeNode(signedInfoDoc.documentElement, "http://www.w3.org/2001/10/xml-exc-c14n#");
|
|
208
208
|
}
|
|
209
|
+
|
|
210
|
+
// src/signature-node/pem-utils.ts
|
|
211
|
+
function firstPemBlockForLabel(text, label) {
|
|
212
|
+
const begin = `-----BEGIN ${label}-----`;
|
|
213
|
+
const end = `-----END ${label}-----`;
|
|
214
|
+
const beginIndex = text.indexOf(begin);
|
|
215
|
+
if (beginIndex === -1) {
|
|
216
|
+
return void 0;
|
|
217
|
+
}
|
|
218
|
+
const endIndex = text.indexOf(end, beginIndex + begin.length);
|
|
219
|
+
if (endIndex === -1) {
|
|
220
|
+
return void 0;
|
|
221
|
+
}
|
|
222
|
+
return { index: beginIndex, block: text.slice(beginIndex, endIndex + end.length) };
|
|
223
|
+
}
|
|
224
|
+
function extractPemBlocks(text, label) {
|
|
225
|
+
const begin = `-----BEGIN ${label}-----`;
|
|
226
|
+
const end = `-----END ${label}-----`;
|
|
227
|
+
const blocks = [];
|
|
228
|
+
let searchFrom = 0;
|
|
229
|
+
for (; ; ) {
|
|
230
|
+
const beginIndex = text.indexOf(begin, searchFrom);
|
|
231
|
+
if (beginIndex === -1) {
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
const contentStart = beginIndex + begin.length;
|
|
235
|
+
const endIndex = text.indexOf(end, contentStart);
|
|
236
|
+
if (endIndex === -1) {
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
blocks.push(text.slice(beginIndex, endIndex + end.length));
|
|
240
|
+
searchFrom = endIndex + end.length;
|
|
241
|
+
}
|
|
242
|
+
return blocks;
|
|
243
|
+
}
|
|
244
|
+
function extractFirstPemBlock(text, labels) {
|
|
245
|
+
let best;
|
|
246
|
+
for (const label of labels) {
|
|
247
|
+
const found = firstPemBlockForLabel(text, label);
|
|
248
|
+
if (found && (!best || found.index < best.index)) {
|
|
249
|
+
best = found;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return best?.block;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/signature-node/timestamp-tag-scanner.ts
|
|
256
|
+
var MAX_TAG_WINDOW = 256;
|
|
257
|
+
var OPEN_TAG_PATTERN = /^<(?:[\w.-]+:)?(?:EncapsulatedTimeStamp|SignatureTimeStamp)\b[^>]*>/i;
|
|
258
|
+
var CLOSE_TAG_PATTERN = /^<\/(?:[\w.-]+:)?(?:EncapsulatedTimeStamp|SignatureTimeStamp)>/i;
|
|
259
|
+
function findCloseTagIndex(xml, fromIndex) {
|
|
260
|
+
let searchFrom = fromIndex;
|
|
261
|
+
for (; ; ) {
|
|
262
|
+
const ltIndex = xml.indexOf("<", searchFrom);
|
|
263
|
+
if (ltIndex === -1) {
|
|
264
|
+
return void 0;
|
|
265
|
+
}
|
|
266
|
+
const window = xml.slice(ltIndex, ltIndex + MAX_TAG_WINDOW);
|
|
267
|
+
if (CLOSE_TAG_PATTERN.test(window)) {
|
|
268
|
+
return ltIndex;
|
|
269
|
+
}
|
|
270
|
+
searchFrom = ltIndex + 1;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function findTimestampTagContent(xml) {
|
|
274
|
+
let searchFrom = 0;
|
|
275
|
+
for (; ; ) {
|
|
276
|
+
const ltIndex = xml.indexOf("<", searchFrom);
|
|
277
|
+
if (ltIndex === -1) {
|
|
278
|
+
return void 0;
|
|
279
|
+
}
|
|
280
|
+
const window = xml.slice(ltIndex, ltIndex + MAX_TAG_WINDOW);
|
|
281
|
+
const openMatch = OPEN_TAG_PATTERN.exec(window);
|
|
282
|
+
if (openMatch) {
|
|
283
|
+
const contentStart = ltIndex + openMatch[0].length;
|
|
284
|
+
const closeIndex = findCloseTagIndex(xml, contentStart);
|
|
285
|
+
return closeIndex === void 0 ? void 0 : xml.slice(contentStart, closeIndex);
|
|
286
|
+
}
|
|
287
|
+
searchFrom = ltIndex + 1;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/signature-node/pki-validation.ts
|
|
209
292
|
function certPemFromBase64(certBase64) {
|
|
210
293
|
try {
|
|
211
294
|
const der = Buffer.from(certBase64, "base64");
|
|
@@ -226,7 +309,7 @@ ${lines.join("\n")}
|
|
|
226
309
|
}
|
|
227
310
|
function certFingerprintSha256(certPem) {
|
|
228
311
|
try {
|
|
229
|
-
const cert = new
|
|
312
|
+
const cert = new crypto2.X509Certificate(certPem);
|
|
230
313
|
return cert.fingerprint256.replace(/:/g, "").toLowerCase();
|
|
231
314
|
} catch {
|
|
232
315
|
return void 0;
|
|
@@ -235,12 +318,173 @@ function certFingerprintSha256(certPem) {
|
|
|
235
318
|
function asn1Child(node, index) {
|
|
236
319
|
return Array.isArray(node.value) ? node.value[index] : void 0;
|
|
237
320
|
}
|
|
321
|
+
async function evaluateTimestampAuthority(signatureXml) {
|
|
322
|
+
try {
|
|
323
|
+
const content = findTimestampTagContent(signatureXml);
|
|
324
|
+
if (!content?.trim()) {
|
|
325
|
+
return { status: "not-present" };
|
|
326
|
+
}
|
|
327
|
+
const tokenBase64 = content.replace(/\s+/g, "");
|
|
328
|
+
const tokenDer = Buffer.from(tokenBase64, "base64");
|
|
329
|
+
if (tokenDer.length === 0) {
|
|
330
|
+
return { status: "invalid", error: "Empty timestamp token." };
|
|
331
|
+
}
|
|
332
|
+
const asn1 = forge3.asn1.fromDer(forge3.util.createBuffer(tokenDer.toString("binary")));
|
|
333
|
+
const contentType = asn1Child(asn1, 0);
|
|
334
|
+
if (!contentType) {
|
|
335
|
+
return {
|
|
336
|
+
status: "invalid",
|
|
337
|
+
error: "Malformed timestamp token structure."
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return { status: "valid" };
|
|
341
|
+
} catch (err) {
|
|
342
|
+
return {
|
|
343
|
+
status: "error",
|
|
344
|
+
error: `Timestamp evaluation failed: ${String(err)}`
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// src/signature-node/certificate-utils.ts
|
|
350
|
+
var PRIVATE_KEY_PEM_LABELS = [
|
|
351
|
+
"RSA PRIVATE KEY",
|
|
352
|
+
"EC PRIVATE KEY",
|
|
353
|
+
"ENCRYPTED PRIVATE KEY",
|
|
354
|
+
"PRIVATE KEY"
|
|
355
|
+
];
|
|
356
|
+
function certificateInfoFromBase64(certBase64) {
|
|
357
|
+
try {
|
|
358
|
+
const certificate = new crypto2.X509Certificate(Buffer.from(certBase64, "base64"));
|
|
359
|
+
return {
|
|
360
|
+
subject: certificate.subject || void 0,
|
|
361
|
+
issuer: certificate.issuer || void 0,
|
|
362
|
+
serialNumber: certificate.serialNumber || void 0,
|
|
363
|
+
validFrom: certificate.validFrom || void 0,
|
|
364
|
+
validTo: certificate.validTo || void 0
|
|
365
|
+
};
|
|
366
|
+
} catch {
|
|
367
|
+
return void 0;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function validateCertificateChain(certBase64List, additionalRootsPem) {
|
|
371
|
+
if (certBase64List.length === 0) {
|
|
372
|
+
return { status: "not-checked" };
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
const chain = certBase64List.map((value) => {
|
|
376
|
+
const der = forge3.util.decode64(value);
|
|
377
|
+
return forge3.pki.certificateFromAsn1(forge3.asn1.fromDer(der));
|
|
378
|
+
});
|
|
379
|
+
const rootPem = [...tls.rootCertificates, ...additionalRootsPem];
|
|
380
|
+
const caStore = forge3.pki.createCaStore(rootPem);
|
|
381
|
+
const verified = forge3.pki.verifyCertificateChain(caStore, chain);
|
|
382
|
+
return verified ? { status: "trusted" } : {
|
|
383
|
+
status: "untrusted",
|
|
384
|
+
error: "Certificate chain is not trusted."
|
|
385
|
+
};
|
|
386
|
+
} catch (error) {
|
|
387
|
+
return {
|
|
388
|
+
status: "untrusted",
|
|
389
|
+
error: `Certificate trust validation failed: ${String(error)}`
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function signatureAlgorithmToVerifyAlgorithm(signatureMethod) {
|
|
394
|
+
switch (signatureMethod) {
|
|
395
|
+
case "http://www.w3.org/2000/09/xmldsig#rsa-sha1":
|
|
396
|
+
return "RSA-SHA1";
|
|
397
|
+
case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256":
|
|
398
|
+
return "RSA-SHA256";
|
|
399
|
+
case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384":
|
|
400
|
+
return "RSA-SHA384";
|
|
401
|
+
case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512":
|
|
402
|
+
return "RSA-SHA512";
|
|
403
|
+
default:
|
|
404
|
+
return void 0;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function verifySignatureValue(signatureXml, certBase64List) {
|
|
408
|
+
try {
|
|
409
|
+
const parser = new DOMParser();
|
|
410
|
+
const doc = parser.parseFromString(signatureXml, "text/xml");
|
|
411
|
+
const signatureNode = doc.getElementsByTagNameNS(XMLDSIG_NS, "Signature")[0];
|
|
412
|
+
if (!signatureNode) {
|
|
413
|
+
return "not-checked";
|
|
414
|
+
}
|
|
415
|
+
const certPem = certBase64List.length > 0 ? certPemFromBase64(certBase64List[0]) : void 0;
|
|
416
|
+
if (!certPem) {
|
|
417
|
+
return "not-checked";
|
|
418
|
+
}
|
|
419
|
+
const signedInfoNode = doc.getElementsByTagNameNS(XMLDSIG_NS, "SignedInfo")[0];
|
|
420
|
+
const signatureValueNode = doc.getElementsByTagNameNS(XMLDSIG_NS, "SignatureValue")[0];
|
|
421
|
+
if (!signedInfoNode || !signatureValueNode) {
|
|
422
|
+
return "invalid";
|
|
423
|
+
}
|
|
424
|
+
const canonicalizationMethod = doc.getElementsByTagNameNS(XMLDSIG_NS, "CanonicalizationMethod").item(0)?.getAttribute("Algorithm") ?? "http://www.w3.org/2001/10/xml-exc-c14n#";
|
|
425
|
+
const signatureMethod = doc.getElementsByTagNameNS(XMLDSIG_NS, "SignatureMethod").item(0)?.getAttribute("Algorithm");
|
|
426
|
+
const verifyAlgorithm = signatureAlgorithmToVerifyAlgorithm(signatureMethod ?? void 0);
|
|
427
|
+
if (!verifyAlgorithm) {
|
|
428
|
+
return "invalid";
|
|
429
|
+
}
|
|
430
|
+
const canonicalSignedInfo = canonicalizeNode(signedInfoNode, canonicalizationMethod);
|
|
431
|
+
const signatureValueBase64 = signatureValueNode.textContent?.replace(/\s+/g, "").trim() ?? "";
|
|
432
|
+
if (signatureValueBase64.length === 0) {
|
|
433
|
+
return "invalid";
|
|
434
|
+
}
|
|
435
|
+
const verifier = crypto2.createVerify(verifyAlgorithm);
|
|
436
|
+
verifier.update(Buffer.from(canonicalSignedInfo, "utf8"));
|
|
437
|
+
verifier.end();
|
|
438
|
+
const isValid = verifier.verify(certPem, Buffer.from(signatureValueBase64, "base64"));
|
|
439
|
+
return isValid ? "verified" : "invalid";
|
|
440
|
+
} catch {
|
|
441
|
+
return "invalid";
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function loadSigningMaterialFromBuffer(certificateBuffer, certificatePath, certificatePassword) {
|
|
445
|
+
const extension = path.extname(certificatePath).toLowerCase();
|
|
446
|
+
if (extension === ".pfx" || extension === ".p12") {
|
|
447
|
+
const p12Der = forge3.util.createBuffer(Buffer.from(certificateBuffer).toString("binary"));
|
|
448
|
+
const p12Asn1 = forge3.asn1.fromDer(p12Der);
|
|
449
|
+
const p12 = forge3.pkcs12.pkcs12FromAsn1(p12Asn1, certificatePassword || "");
|
|
450
|
+
const keyBag = p12.getBags({
|
|
451
|
+
bagType: forge3.pki.oids.pkcs8ShroudedKeyBag
|
|
452
|
+
})[forge3.pki.oids.pkcs8ShroudedKeyBag];
|
|
453
|
+
const certBag = p12.getBags({ bagType: forge3.pki.oids.certBag })[forge3.pki.oids.certBag];
|
|
454
|
+
if (!keyBag || keyBag.length === 0 || !keyBag[0]?.key) {
|
|
455
|
+
throw new Error("No private key found in PKCS#12 certificate.");
|
|
456
|
+
}
|
|
457
|
+
if (!certBag || certBag.length === 0 || !certBag[0]?.cert) {
|
|
458
|
+
throw new Error("No certificate found in PKCS#12 certificate.");
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
privateKeyPem: forge3.pki.privateKeyToPem(keyBag[0].key),
|
|
462
|
+
certificatePem: forge3.pki.certificateToPem(certBag[0].cert)
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const pem = Buffer.from(certificateBuffer).toString("utf8");
|
|
466
|
+
const privateKeyPem = extractFirstPemBlock(pem, PRIVATE_KEY_PEM_LABELS);
|
|
467
|
+
const certificatePem = extractFirstPemBlock(pem, ["CERTIFICATE"]);
|
|
468
|
+
if (!privateKeyPem || !certificatePem) {
|
|
469
|
+
throw new Error("PEM certificate must contain both private key and certificate.");
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
privateKeyPem,
|
|
473
|
+
certificatePem
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function pemCertificateToBase64(pem) {
|
|
477
|
+
return pem.replace(/-----BEGIN CERTIFICATE-----/g, "").replace(/-----END CERTIFICATE-----/g, "").replace(/\s+/g, "").trim();
|
|
478
|
+
}
|
|
479
|
+
function asn1Child2(node, index) {
|
|
480
|
+
return Array.isArray(node.value) ? node.value[index] : void 0;
|
|
481
|
+
}
|
|
238
482
|
function asn1Bytes(node) {
|
|
239
483
|
return typeof node.value === "string" ? node.value : "";
|
|
240
484
|
}
|
|
241
485
|
function extractOcspUrls(certPem) {
|
|
242
486
|
try {
|
|
243
|
-
const cert = new
|
|
487
|
+
const cert = new crypto2.X509Certificate(certPem);
|
|
244
488
|
const infoAccess = cert.infoAccess;
|
|
245
489
|
if (!infoAccess) {
|
|
246
490
|
return [];
|
|
@@ -303,45 +547,45 @@ function httpPost(url, body, contentType) {
|
|
|
303
547
|
var SHA1_OID = "1.3.14.3.2.26";
|
|
304
548
|
function buildOcspRequestDer(leafPem, issuerPem) {
|
|
305
549
|
try {
|
|
306
|
-
const leaf =
|
|
307
|
-
const issuer =
|
|
308
|
-
const issuerNameDer =
|
|
309
|
-
const issuerNameHash =
|
|
310
|
-
const pubKeyDer =
|
|
311
|
-
const pubKeyAsn1 =
|
|
312
|
-
const bitString =
|
|
550
|
+
const leaf = forge3.pki.certificateFromPem(leafPem);
|
|
551
|
+
const issuer = forge3.pki.certificateFromPem(issuerPem);
|
|
552
|
+
const issuerNameDer = forge3.asn1.toDer(forge3.pki.distinguishedNameToAsn1(issuer.subject)).getBytes();
|
|
553
|
+
const issuerNameHash = forge3.md.sha1.create().update(issuerNameDer).digest().getBytes();
|
|
554
|
+
const pubKeyDer = forge3.asn1.toDer(forge3.pki.publicKeyToAsn1(issuer.publicKey)).getBytes();
|
|
555
|
+
const pubKeyAsn1 = forge3.asn1.fromDer(pubKeyDer);
|
|
556
|
+
const bitString = asn1Child2(pubKeyAsn1, 1);
|
|
313
557
|
const rawKey = bitString ? asn1Bytes(bitString).substring(1) : "";
|
|
314
|
-
const issuerKeyHash =
|
|
315
|
-
const serialBytes =
|
|
316
|
-
const request =
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
558
|
+
const issuerKeyHash = forge3.md.sha1.create().update(rawKey).digest().getBytes();
|
|
559
|
+
const serialBytes = forge3.util.hexToBytes(leaf.serialNumber);
|
|
560
|
+
const request = forge3.asn1.create(forge3.asn1.Class.UNIVERSAL, forge3.asn1.Type.SEQUENCE, true, [
|
|
561
|
+
forge3.asn1.create(forge3.asn1.Class.UNIVERSAL, forge3.asn1.Type.SEQUENCE, true, [
|
|
562
|
+
forge3.asn1.create(forge3.asn1.Class.UNIVERSAL, forge3.asn1.Type.SEQUENCE, true, [
|
|
563
|
+
forge3.asn1.create(forge3.asn1.Class.UNIVERSAL, forge3.asn1.Type.SEQUENCE, true, [
|
|
564
|
+
forge3.asn1.create(forge3.asn1.Class.UNIVERSAL, forge3.asn1.Type.SEQUENCE, true, [
|
|
565
|
+
forge3.asn1.create(forge3.asn1.Class.UNIVERSAL, forge3.asn1.Type.SEQUENCE, true, [
|
|
566
|
+
forge3.asn1.create(
|
|
567
|
+
forge3.asn1.Class.UNIVERSAL,
|
|
568
|
+
forge3.asn1.Type.OID,
|
|
325
569
|
false,
|
|
326
|
-
|
|
570
|
+
forge3.asn1.oidToDer(SHA1_OID).getBytes()
|
|
327
571
|
),
|
|
328
|
-
|
|
572
|
+
forge3.asn1.create(forge3.asn1.Class.UNIVERSAL, forge3.asn1.Type.NULL, false, "")
|
|
329
573
|
]),
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
574
|
+
forge3.asn1.create(
|
|
575
|
+
forge3.asn1.Class.UNIVERSAL,
|
|
576
|
+
forge3.asn1.Type.OCTETSTRING,
|
|
333
577
|
false,
|
|
334
578
|
issuerNameHash
|
|
335
579
|
),
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
580
|
+
forge3.asn1.create(
|
|
581
|
+
forge3.asn1.Class.UNIVERSAL,
|
|
582
|
+
forge3.asn1.Type.OCTETSTRING,
|
|
339
583
|
false,
|
|
340
584
|
issuerKeyHash
|
|
341
585
|
),
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
586
|
+
forge3.asn1.create(
|
|
587
|
+
forge3.asn1.Class.UNIVERSAL,
|
|
588
|
+
forge3.asn1.Type.INTEGER,
|
|
345
589
|
false,
|
|
346
590
|
serialBytes
|
|
347
591
|
)
|
|
@@ -350,50 +594,50 @@ function buildOcspRequestDer(leafPem, issuerPem) {
|
|
|
350
594
|
])
|
|
351
595
|
])
|
|
352
596
|
]);
|
|
353
|
-
return Buffer.from(
|
|
597
|
+
return Buffer.from(forge3.asn1.toDer(request).getBytes(), "binary");
|
|
354
598
|
} catch {
|
|
355
599
|
return void 0;
|
|
356
600
|
}
|
|
357
601
|
}
|
|
358
602
|
function parseOcspResponseStatus(data) {
|
|
359
603
|
try {
|
|
360
|
-
const asn1 =
|
|
361
|
-
const statusNode =
|
|
604
|
+
const asn1 = forge3.asn1.fromDer(forge3.util.createBuffer(data.toString("binary")));
|
|
605
|
+
const statusNode = asn1Child2(asn1, 0);
|
|
362
606
|
if (!statusNode || asn1Bytes(statusNode) !== "\0") {
|
|
363
607
|
return "error";
|
|
364
608
|
}
|
|
365
|
-
const respBytesWrapper =
|
|
609
|
+
const respBytesWrapper = asn1Child2(asn1, 1);
|
|
366
610
|
if (!respBytesWrapper) {
|
|
367
611
|
return "unknown";
|
|
368
612
|
}
|
|
369
|
-
const respBytesSeq =
|
|
613
|
+
const respBytesSeq = asn1Child2(respBytesWrapper, 0);
|
|
370
614
|
if (!respBytesSeq) {
|
|
371
615
|
return "unknown";
|
|
372
616
|
}
|
|
373
|
-
const respOctet =
|
|
617
|
+
const respOctet = asn1Child2(respBytesSeq, 1);
|
|
374
618
|
if (!respOctet) {
|
|
375
619
|
return "unknown";
|
|
376
620
|
}
|
|
377
|
-
const basicResp =
|
|
378
|
-
const tbsData =
|
|
621
|
+
const basicResp = forge3.asn1.fromDer(forge3.util.createBuffer(asn1Bytes(respOctet)));
|
|
622
|
+
const tbsData = asn1Child2(basicResp, 0);
|
|
379
623
|
if (!tbsData) {
|
|
380
624
|
return "unknown";
|
|
381
625
|
}
|
|
382
|
-
const first =
|
|
383
|
-
const responsesIdx = first?.tagClass ===
|
|
384
|
-
const responses =
|
|
626
|
+
const first = asn1Child2(tbsData, 0);
|
|
627
|
+
const responsesIdx = first?.tagClass === forge3.asn1.Class.CONTEXT_SPECIFIC && first.type === 0 ? 3 : 2;
|
|
628
|
+
const responses = asn1Child2(tbsData, responsesIdx);
|
|
385
629
|
if (!responses) {
|
|
386
630
|
return "unknown";
|
|
387
631
|
}
|
|
388
|
-
const single =
|
|
632
|
+
const single = asn1Child2(responses, 0);
|
|
389
633
|
if (!single) {
|
|
390
634
|
return "unknown";
|
|
391
635
|
}
|
|
392
|
-
const certStatus =
|
|
636
|
+
const certStatus = asn1Child2(single, 1);
|
|
393
637
|
if (!certStatus) {
|
|
394
638
|
return "unknown";
|
|
395
639
|
}
|
|
396
|
-
if (certStatus.tagClass ===
|
|
640
|
+
if (certStatus.tagClass === forge3.asn1.Class.CONTEXT_SPECIFIC) {
|
|
397
641
|
if (certStatus.type === 0) {
|
|
398
642
|
return "good";
|
|
399
643
|
}
|
|
@@ -436,164 +680,8 @@ async function evaluateCertificateRevocation(leafCertPem, issuerCertPem) {
|
|
|
436
680
|
checkedCrlUrls
|
|
437
681
|
};
|
|
438
682
|
}
|
|
439
|
-
var TIMESTAMP_TAG_REGEX = /<(?:[\w.-]+:)?(?:EncapsulatedTimeStamp|SignatureTimeStamp)[^>]*>([\s\S]*?)<\/(?:[\w.-]+:)?(?:EncapsulatedTimeStamp|SignatureTimeStamp)>/i;
|
|
440
|
-
async function evaluateTimestampAuthority(signatureXml) {
|
|
441
|
-
try {
|
|
442
|
-
const match = TIMESTAMP_TAG_REGEX.exec(signatureXml);
|
|
443
|
-
if (!match?.[1]?.trim()) {
|
|
444
|
-
return { status: "not-present" };
|
|
445
|
-
}
|
|
446
|
-
const tokenBase64 = match[1].replace(/\s+/g, "");
|
|
447
|
-
const tokenDer = Buffer.from(tokenBase64, "base64");
|
|
448
|
-
if (tokenDer.length === 0) {
|
|
449
|
-
return { status: "invalid", error: "Empty timestamp token." };
|
|
450
|
-
}
|
|
451
|
-
const asn1 = forge.asn1.fromDer(forge.util.createBuffer(tokenDer.toString("binary")));
|
|
452
|
-
const contentType = asn1Child(asn1, 0);
|
|
453
|
-
if (!contentType) {
|
|
454
|
-
return {
|
|
455
|
-
status: "invalid",
|
|
456
|
-
error: "Malformed timestamp token structure."
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
return { status: "valid" };
|
|
460
|
-
} catch (err) {
|
|
461
|
-
return {
|
|
462
|
-
status: "error",
|
|
463
|
-
error: `Timestamp evaluation failed: ${String(err)}`
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// src/signature-node/certificate-utils.ts
|
|
469
|
-
function certificateInfoFromBase64(certBase64) {
|
|
470
|
-
try {
|
|
471
|
-
const certificate = new crypto.X509Certificate(Buffer.from(certBase64, "base64"));
|
|
472
|
-
return {
|
|
473
|
-
subject: certificate.subject || void 0,
|
|
474
|
-
issuer: certificate.issuer || void 0,
|
|
475
|
-
serialNumber: certificate.serialNumber || void 0,
|
|
476
|
-
validFrom: certificate.validFrom || void 0,
|
|
477
|
-
validTo: certificate.validTo || void 0
|
|
478
|
-
};
|
|
479
|
-
} catch {
|
|
480
|
-
return void 0;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
function validateCertificateChain(certBase64List, additionalRootsPem) {
|
|
484
|
-
if (certBase64List.length === 0) {
|
|
485
|
-
return { status: "not-checked" };
|
|
486
|
-
}
|
|
487
|
-
try {
|
|
488
|
-
const chain = certBase64List.map((value) => {
|
|
489
|
-
const der = forge.util.decode64(value);
|
|
490
|
-
return forge.pki.certificateFromAsn1(forge.asn1.fromDer(der));
|
|
491
|
-
});
|
|
492
|
-
const rootPem = [...tls.rootCertificates, ...additionalRootsPem];
|
|
493
|
-
const caStore = forge.pki.createCaStore(rootPem);
|
|
494
|
-
const verified = forge.pki.verifyCertificateChain(caStore, chain);
|
|
495
|
-
return verified ? { status: "trusted" } : {
|
|
496
|
-
status: "untrusted",
|
|
497
|
-
error: "Certificate chain is not trusted."
|
|
498
|
-
};
|
|
499
|
-
} catch (error) {
|
|
500
|
-
return {
|
|
501
|
-
status: "untrusted",
|
|
502
|
-
error: `Certificate trust validation failed: ${String(error)}`
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
function signatureAlgorithmToVerifyAlgorithm(signatureMethod) {
|
|
507
|
-
switch (signatureMethod) {
|
|
508
|
-
case "http://www.w3.org/2000/09/xmldsig#rsa-sha1":
|
|
509
|
-
return "RSA-SHA1";
|
|
510
|
-
case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256":
|
|
511
|
-
return "RSA-SHA256";
|
|
512
|
-
case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384":
|
|
513
|
-
return "RSA-SHA384";
|
|
514
|
-
case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512":
|
|
515
|
-
return "RSA-SHA512";
|
|
516
|
-
default:
|
|
517
|
-
return void 0;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
function verifySignatureValue(signatureXml, certBase64List) {
|
|
521
|
-
try {
|
|
522
|
-
const parser = new DOMParser();
|
|
523
|
-
const doc = parser.parseFromString(signatureXml, "text/xml");
|
|
524
|
-
const signatureNode = doc.getElementsByTagNameNS(XMLDSIG_NS, "Signature")[0];
|
|
525
|
-
if (!signatureNode) {
|
|
526
|
-
return "not-checked";
|
|
527
|
-
}
|
|
528
|
-
const certPem = certBase64List.length > 0 ? certPemFromBase64(certBase64List[0]) : void 0;
|
|
529
|
-
if (!certPem) {
|
|
530
|
-
return "not-checked";
|
|
531
|
-
}
|
|
532
|
-
const signedInfoNode = doc.getElementsByTagNameNS(XMLDSIG_NS, "SignedInfo")[0];
|
|
533
|
-
const signatureValueNode = doc.getElementsByTagNameNS(XMLDSIG_NS, "SignatureValue")[0];
|
|
534
|
-
if (!signedInfoNode || !signatureValueNode) {
|
|
535
|
-
return "invalid";
|
|
536
|
-
}
|
|
537
|
-
const canonicalizationMethod = doc.getElementsByTagNameNS(XMLDSIG_NS, "CanonicalizationMethod").item(0)?.getAttribute("Algorithm") ?? "http://www.w3.org/2001/10/xml-exc-c14n#";
|
|
538
|
-
const signatureMethod = doc.getElementsByTagNameNS(XMLDSIG_NS, "SignatureMethod").item(0)?.getAttribute("Algorithm");
|
|
539
|
-
const verifyAlgorithm = signatureAlgorithmToVerifyAlgorithm(signatureMethod ?? void 0);
|
|
540
|
-
if (!verifyAlgorithm) {
|
|
541
|
-
return "invalid";
|
|
542
|
-
}
|
|
543
|
-
const canonicalSignedInfo = canonicalizeNode(signedInfoNode, canonicalizationMethod);
|
|
544
|
-
const signatureValueBase64 = signatureValueNode.textContent?.replace(/\s+/g, "").trim() ?? "";
|
|
545
|
-
if (signatureValueBase64.length === 0) {
|
|
546
|
-
return "invalid";
|
|
547
|
-
}
|
|
548
|
-
const verifier = crypto.createVerify(verifyAlgorithm);
|
|
549
|
-
verifier.update(Buffer.from(canonicalSignedInfo, "utf8"));
|
|
550
|
-
verifier.end();
|
|
551
|
-
const isValid = verifier.verify(certPem, Buffer.from(signatureValueBase64, "base64"));
|
|
552
|
-
return isValid ? "verified" : "invalid";
|
|
553
|
-
} catch {
|
|
554
|
-
return "invalid";
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
function loadSigningMaterialFromBuffer(certificateBuffer, certificatePath, certificatePassword) {
|
|
558
|
-
const extension = path.extname(certificatePath).toLowerCase();
|
|
559
|
-
if (extension === ".pfx" || extension === ".p12") {
|
|
560
|
-
const p12Der = forge.util.createBuffer(Buffer.from(certificateBuffer).toString("binary"));
|
|
561
|
-
const p12Asn1 = forge.asn1.fromDer(p12Der);
|
|
562
|
-
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, certificatePassword || "");
|
|
563
|
-
const keyBag = p12.getBags({
|
|
564
|
-
bagType: forge.pki.oids.pkcs8ShroudedKeyBag
|
|
565
|
-
})[forge.pki.oids.pkcs8ShroudedKeyBag];
|
|
566
|
-
const certBag = p12.getBags({ bagType: forge.pki.oids.certBag })[forge.pki.oids.certBag];
|
|
567
|
-
if (!keyBag || keyBag.length === 0 || !keyBag[0]?.key) {
|
|
568
|
-
throw new Error("No private key found in PKCS#12 certificate.");
|
|
569
|
-
}
|
|
570
|
-
if (!certBag || certBag.length === 0 || !certBag[0]?.cert) {
|
|
571
|
-
throw new Error("No certificate found in PKCS#12 certificate.");
|
|
572
|
-
}
|
|
573
|
-
return {
|
|
574
|
-
privateKeyPem: forge.pki.privateKeyToPem(keyBag[0].key),
|
|
575
|
-
certificatePem: forge.pki.certificateToPem(certBag[0].cert)
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
const pem = Buffer.from(certificateBuffer).toString("utf8");
|
|
579
|
-
const privateKeyMatch = pem.match(
|
|
580
|
-
/-----BEGIN (?:RSA |EC |ENCRYPTED )?PRIVATE KEY-----[\s\S]+?-----END (?:RSA |EC |ENCRYPTED )?PRIVATE KEY-----/m
|
|
581
|
-
);
|
|
582
|
-
const certMatch = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/m);
|
|
583
|
-
if (!privateKeyMatch || !certMatch) {
|
|
584
|
-
throw new Error("PEM certificate must contain both private key and certificate.");
|
|
585
|
-
}
|
|
586
|
-
return {
|
|
587
|
-
privateKeyPem: privateKeyMatch[0],
|
|
588
|
-
certificatePem: certMatch[0]
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
function pemCertificateToBase64(pem) {
|
|
592
|
-
return pem.replace(/-----BEGIN CERTIFICATE-----/g, "").replace(/-----END CERTIFICATE-----/g, "").replace(/\s+/g, "").trim();
|
|
593
|
-
}
|
|
594
683
|
function extractPemCertificatesFromText(text) {
|
|
595
|
-
|
|
596
|
-
return matches.map((value) => value.trim()).filter((value) => value.length > 0);
|
|
684
|
+
return extractPemBlocks(text, "CERTIFICATE").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
597
685
|
}
|
|
598
686
|
function parseBooleanEnv(envValue) {
|
|
599
687
|
if (!envValue) {
|
|
@@ -734,7 +822,7 @@ function computeDigestBase642(content, digestAlgorithmUri) {
|
|
|
734
822
|
if (!hashName) {
|
|
735
823
|
return void 0;
|
|
736
824
|
}
|
|
737
|
-
return
|
|
825
|
+
return crypto2.createHash(hashName).update(Buffer.from(content)).digest("base64");
|
|
738
826
|
}
|
|
739
827
|
async function buildReferenceChecksFromSignatureXml(zip, signatureXml) {
|
|
740
828
|
const parser = new DOMParser();
|
|
@@ -1067,7 +1155,7 @@ async function buildOfficeReferenceList(zip) {
|
|
|
1067
1155
|
continue;
|
|
1068
1156
|
}
|
|
1069
1157
|
const entryBytes = await entry.async("uint8array");
|
|
1070
|
-
const digestValue =
|
|
1158
|
+
const digestValue = crypto2.createHash("sha256").update(Buffer.from(entryBytes)).digest("base64");
|
|
1071
1159
|
references.push({
|
|
1072
1160
|
uri: `/${normalizePartPath(entryPath)}`,
|
|
1073
1161
|
digestMethod,
|
|
@@ -1083,7 +1171,7 @@ function buildOfficeSignatureXml(references, privateKeyPem, certificatePem) {
|
|
|
1083
1171
|
).join("");
|
|
1084
1172
|
const signedInfoXml = `<SignedInfo xmlns="${XMLDSIG_NS}"><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>${signedInfoReferencesXml}</SignedInfo>`;
|
|
1085
1173
|
const canonicalSignedInfo = canonicalizeSignedInfoXml(signedInfoXml);
|
|
1086
|
-
const signer =
|
|
1174
|
+
const signer = crypto2.createSign("RSA-SHA256");
|
|
1087
1175
|
signer.update(Buffer.from(canonicalSignedInfo, "utf8"));
|
|
1088
1176
|
signer.end();
|
|
1089
1177
|
const signatureValueBase64 = signer.sign(privateKeyPem).toString("base64");
|