samlesa 2.17.3 → 2.18.1

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.

Potentially problematic release.


This version of samlesa might be problematic. Click here for more details.

Files changed (42) hide show
  1. package/build/src/binding-artifact.js +24 -14
  2. package/build/src/flow.js +169 -27
  3. package/build/src/libsaml.js +454 -213
  4. package/package.json +77 -77
  5. package/types/src/binding-artifact.d.ts.map +1 -1
  6. package/types/src/flow.d.ts.map +1 -1
  7. package/types/src/libsaml.d.ts +50 -1
  8. package/types/src/libsaml.d.ts.map +1 -1
  9. package/types/api.d.ts +0 -15
  10. package/types/api.d.ts.map +0 -1
  11. package/types/binding-post.d.ts +0 -48
  12. package/types/binding-post.d.ts.map +0 -1
  13. package/types/binding-redirect.d.ts +0 -54
  14. package/types/binding-redirect.d.ts.map +0 -1
  15. package/types/binding-simplesign.d.ts +0 -41
  16. package/types/binding-simplesign.d.ts.map +0 -1
  17. package/types/entity-idp.d.ts +0 -38
  18. package/types/entity-idp.d.ts.map +0 -1
  19. package/types/entity-sp.d.ts +0 -38
  20. package/types/entity-sp.d.ts.map +0 -1
  21. package/types/entity.d.ts +0 -100
  22. package/types/entity.d.ts.map +0 -1
  23. package/types/extractor.d.ts +0 -26
  24. package/types/extractor.d.ts.map +0 -1
  25. package/types/flow.d.ts +0 -7
  26. package/types/flow.d.ts.map +0 -1
  27. package/types/libsaml.d.ts +0 -208
  28. package/types/libsaml.d.ts.map +0 -1
  29. package/types/metadata-idp.d.ts +0 -25
  30. package/types/metadata-idp.d.ts.map +0 -1
  31. package/types/metadata-sp.d.ts +0 -37
  32. package/types/metadata-sp.d.ts.map +0 -1
  33. package/types/metadata.d.ts +0 -58
  34. package/types/metadata.d.ts.map +0 -1
  35. package/types/types.d.ts +0 -128
  36. package/types/types.d.ts.map +0 -1
  37. package/types/urn.d.ts +0 -195
  38. package/types/urn.d.ts.map +0 -1
  39. package/types/utility.d.ts +0 -133
  40. package/types/utility.d.ts.map +0 -1
  41. package/types/validator.d.ts +0 -4
  42. package/types/validator.d.ts.map +0 -1
@@ -3,6 +3,7 @@
3
3
  * @author tngan
4
4
  * @desc A simple library including some common functions
5
5
  */
6
+ import { X509Certificate } from 'node:crypto';
6
7
  import xml from 'xml';
7
8
  import utility, { flattenDeep, inflateString, isString } from './utility.js';
8
9
  import { algorithms, namespace, wording } from './urn.js';
@@ -73,9 +74,9 @@ const libSaml = () => {
73
74
  };
74
75
  const defaultSoapResponseFailTemplate = {
75
76
  context: `<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header></SOAP-ENV:Header>
76
- <samlp:ArtifactResponse xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
77
+ <samlp:ArtifactResponse xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
77
78
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
78
- InResponseTo="{InResponseTo}" Version="2.0"
79
+ InResponseTo="{InResponseTo}" Version="2.0"
79
80
  IssueInstant="{IssueInstant}">
80
81
  <saml:Issuer>{Issuer}</saml:Issuer>
81
82
  <samlp:Status>
@@ -336,6 +337,37 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
336
337
  }
337
338
  return isBase64Output ? utility.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
338
339
  },
340
+ // 安全的证书验证函数
341
+ validateCertificate(certificateBase64, expectedIssuer) {
342
+ try {
343
+ const cert = new X509Certificate(Buffer.from(certificateBase64, 'base64'));
344
+ // 1. 检查有效期
345
+ const now = new Date();
346
+ if (new Date(cert.validFrom) > now || new Date(cert.validTo) < now) {
347
+ throw new Error('Certificate has expired or is not yet valid');
348
+ }
349
+ // 2. 检查颁发者(如果提供)
350
+ if (expectedIssuer && !cert.subject.includes(expectedIssuer)) {
351
+ throw new Error('Certificate issuer does not match expected value');
352
+ }
353
+ // 3. 检查公钥类型(推荐 RSA 或 EC)
354
+ if (!['rsa', 'ec'].includes(cert.publicKey.type.toLowerCase())) {
355
+ throw new Error('Certificate uses unsupported public key type');
356
+ }
357
+ return {
358
+ isValid: true,
359
+ subject: cert.subject,
360
+ issuer: cert.issuer,
361
+ publicKey: cert.publicKey
362
+ };
363
+ }
364
+ catch (error) {
365
+ return {
366
+ isValid: false,
367
+ error: error.message
368
+ };
369
+ }
370
+ },
339
371
  /**
340
372
  * @desc Verify the XML signature
341
373
  * @param {string} xml xml
@@ -345,7 +377,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
345
377
  * - The second element is the cryptographically authenticated assertion node as a string, or `null` if not found.
346
378
  */
347
379
  // tslint:disable-next-line:no-shadowed-variable
348
- verifySignature(xml, opts) {
380
+ verifySignature1(xml, opts) {
349
381
  const { dom } = getContext();
350
382
  const doc = dom.parseFromString(xml, 'application/xml');
351
383
  const docParser = new DOMParser();
@@ -389,6 +421,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
389
421
  if (selection.length === 0) {
390
422
  /** 判断有没有加密如果没有加密返回 [false, null]*/
391
423
  if (encryptedAssertions.length > 0) {
424
+ console.log("走加密了====");
392
425
  if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
393
426
  return [false, null, false, true]; // we return false now
394
427
  }
@@ -399,6 +432,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
399
432
  }
400
433
  }
401
434
  if (selection.length !== 0) {
435
+ console.log("走加密了1====");
402
436
  /** 判断有没有加密如果没有加密返回 [false, null]*/
403
437
  if (logoutRequestSignature.length === 0 && LogoutResponseSignatureElementNode.length === 0 && encryptedAssertions.length > 0) {
404
438
  if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
@@ -418,10 +452,16 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
418
452
  if (!opts.keyFile && !opts.metadata) {
419
453
  throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
420
454
  }
455
+ console.log("开始1");
421
456
  if (opts.keyFile) {
457
+ console.log("开始11");
458
+ let publicCertResult = this.validateCertificate(fs.readFileSync(opts.keyFile));
459
+ console.log(publicCertResult);
460
+ console.log("结果");
422
461
  sig.publicCert = fs.readFileSync(opts.keyFile);
423
462
  }
424
463
  if (opts.metadata) {
464
+ console.log("开始111");
425
465
  const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode);
426
466
  // certificate in metadata
427
467
  let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
@@ -448,10 +488,17 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
448
488
  // to make sure the response certificate is one of those specified in metadata
449
489
  throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
450
490
  }
491
+ let publicCertResult = this.validateCertificate(x509Certificate);
492
+ console.log(publicCertResult);
493
+ console.log("结果");
451
494
  sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
452
495
  }
453
496
  else {
497
+ console.log("开始11111");
454
498
  // Select first one from metadata
499
+ let publicCertResult = this.validateCertificate(metadataCert[0]);
500
+ console.log(publicCertResult);
501
+ console.log("结果");
455
502
  sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
456
503
  }
457
504
  }
@@ -509,232 +556,327 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
509
556
  return [false, null, false, true]; // return encryptedAssert
510
557
  /* throw new Error('ERR_ZERO_SIGNATURE');*/
511
558
  },
512
- /* verifySignatureSoap(xml: string, opts: SignatureVerifierOptions & { isAssertion?: boolean }) {
513
- const {dom} = getContext();
559
+ /**
560
+ * 改进的SAML签名验证函数,支持多种签名和加密组合场景
561
+ * @param xml SAML XML内容
562
+ * @param opts 验证选项
563
+ * @param self
564
+ * @returns 验证结果对象
565
+ */
566
+ /**
567
+ * 改进的SAML签名验证函数,支持多种签名和加密组合场景
568
+ * @param xml SAML XML内容
569
+ * @param opts 验证选项
570
+ * @param self
571
+ * @returns 验证结果对象
572
+ */
573
+ verifySignature(xml, opts, self) {
574
+ const { dom } = getContext();
514
575
  const doc = dom.parseFromString(xml, 'application/xml');
515
576
  const docParser = new DOMParser();
516
-
517
- let selection: any = [];
518
-
519
- if (opts.isAssertion) {
520
- // 断言模式下的专用逻辑
521
- const assertionSignatureXpath = "./!*[local-name()='Signature']";
522
- // @ts-expect-error misssing Node properties are not needed
523
- const signatureNode = select(assertionSignatureXpath, doc.documentElement);
524
-
525
- if (signatureNode.length === 0) {
526
- throw new Error('ERR_ASSERTION_SIGNATURE_NOT_FOUND');
527
- }
528
-
529
- selection = selection.concat(signatureNode);
530
- } else {
531
- // 原始的SOAP响应验证逻辑
532
- const messageSignatureXpath =
533
- "/!*[local-name()='Envelope']/!*[local-name()='Body']" +
534
- "/!*[local-name()='ArtifactResponse']/!*[local-name()='Signature'] | " +
535
- "/!*[local-name()='Envelope']/!*[local-name()='Body']" +
536
- "/!*[local-name()='ArtifactResponse']/!*[local-name()='Response']/!*[local-name()='Signature']";
537
-
538
- const assertionSignatureXpath =
539
- "/!*[local-name()='Envelope']/!*[local-name()='Body']" +
540
- "/!*[local-name()='ArtifactResponse']/!*[local-name()='Response']" +
541
- "/!*[local-name()='Assertion']/!*[local-name()='Signature'] | " +
542
- "/!*[local-name()='Envelope']/!*[local-name()='Body']" +
543
- "/!*[local-name()='ArtifactResponse']/!*[local-name()='Response']" +
544
- "/!*[local-name()='EncryptedAssertion']";
545
-
546
- const wrappingElementsXPath =
547
- "/!*[local-name()='Envelope']/!*[local-name()='Body']" +
548
- "/!*[local-name()='ArtifactResponse']/!*[local-name()='Response']" +
549
- "/!*[local-name()='Assertion']/!*[local-name()='Subject']" +
550
- "/!*[local-name()='SubjectConfirmation']" +
551
- "/!*[local-name()='SubjectConfirmationData']" +
552
- "//!*[local-name()='Assertion' or local-name()='Signature']";
553
-
554
- // @ts-expect-error misssing Node properties are not needed
555
- const messageSignatureNode = select(messageSignatureXpath, doc);
556
- // @ts-expect-error misssing Node properties are not needed
557
- const assertionSignatureNode = select(assertionSignatureXpath, doc);
558
- // @ts-expect-error misssing Node properties are not needed
559
- const wrappingElementNode = select(wrappingElementsXPath, doc);
560
-
561
- // 检测包装攻击
562
- if (wrappingElementNode.length !== 0) {
577
+ // 定义各种XPath路径
578
+ const messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']";
579
+ const assertionSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']/*[local-name(.)='Signature']";
580
+ const wrappingElementsXPath = "/*[contains(local-name(), 'Response')]/*[local-name(.)='Assertion']/*[local-name(.)='Subject']/*[local-name(.)='SubjectConfirmation']/*[local-name(.)='SubjectConfirmationData']//*[local-name(.)='Assertion' or local-name(.)='Signature']";
581
+ const encryptedAssertionsXPath = "/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']";
582
+ // 检测包装攻击
583
+ // @ts-expect-error misssing Node properties are not needed
584
+ const wrappingElementNode = select(wrappingElementsXPath, doc);
585
+ if (wrappingElementNode.length !== 0) {
563
586
  throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
564
- }
565
-
566
- // 保证响应中至少有一个签名
567
- if (messageSignatureNode.length === 0 && assertionSignatureNode.length === 0) {
568
- throw new Error('ERR_ZERO_SIGNATURE');
569
- }
570
-
571
- selection = selection.concat(messageSignatureNode, assertionSignatureNode);
572
587
  }
573
-
574
- for (const signatureNode of selection) {
575
- const sig = new SignedXml();
576
- let verified = false;
577
-
578
- sig.signatureAlgorithm = opts.signatureAlgorithm!;
579
-
580
- if (!opts.keyFile && !opts.metadata) {
581
- throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
582
- }
583
-
584
- if (opts.keyFile) {
585
- sig.publicCert = fs.readFileSync(opts.keyFile, 'utf-8');
586
- }
587
-
588
- if (opts.metadata) {
589
- const certificateNodes = select(".//!*[local-name(.)='X509Certificate']", signatureNode) as any[];
590
-
591
- // 获取元数据中的证书
592
- let metadataCert: any = opts.metadata.getX509Certificate(certUse.signing);
593
-
594
- // 规范化元数据证书
595
- if (Array.isArray(metadataCert)) {
596
- metadataCert = flattenDeep(metadataCert);
597
- } else if (typeof metadataCert === 'string') {
598
- metadataCert = [metadataCert];
588
+ // 获取各种元素
589
+ // @ts-expect-error misssing Node properties are not needed
590
+ const messageSignatureNode = select(messageSignatureXpath, doc);
591
+ // @ts-expect-error misssing Node properties are not needed
592
+ const assertionSignatureNode = select(assertionSignatureXpath, doc);
593
+ // @ts-expect-error misssing Node properties are not needed
594
+ const encryptedAssertions = select(encryptedAssertionsXPath, doc);
595
+ // 初始化验证状态
596
+ let isMessageSigned = messageSignatureNode.length > 0;
597
+ let isAssertionSigned = assertionSignatureNode.length > 0;
598
+ let encrypted = encryptedAssertions.length > 0;
599
+ let decrypted = false;
600
+ let MessageSignatureStatus = false;
601
+ let AssertionSignatureStatus = false;
602
+ let status = false;
603
+ let samlContent = xml;
604
+ let assertionContent = null;
605
+ // 检测SAML消息类型
606
+ // 检测SAML消息类型 - 使用精确匹配避免包含关系导致的误判
607
+ const rootElementName = doc.documentElement.localName;
608
+ let type = 'Unknown';
609
+ // 使用精确字符串比较,避免包含关系的问题
610
+ switch (rootElementName) {
611
+ case 'AuthnRequest':
612
+ type = 'AuthnRequest';
613
+ break;
614
+ case 'Response':
615
+ type = 'Response';
616
+ break;
617
+ case 'LogoutRequest':
618
+ type = 'LogoutRequest';
619
+ break;
620
+ case 'LogoutResponse':
621
+ type = 'LogoutResponse';
622
+ break;
623
+ default:
624
+ // 如果不是完全匹配,尝试模糊匹配
625
+ if (rootElementName.includes('AuthnRequest')) {
626
+ type = 'AuthnRequest';
627
+ }
628
+ else if (rootElementName.includes('LogoutResponse')) {
629
+ type = 'LogoutResponse';
630
+ }
631
+ else if (rootElementName.includes('LogoutRequest')) {
632
+ type = 'LogoutRequest';
633
+ }
634
+ else if (rootElementName.includes('Response')) {
635
+ type = 'Response';
636
+ }
637
+ else {
638
+ type = 'Unknown';
639
+ }
640
+ }
641
+ // 特殊情况:带未签名断言的未签名SAML响应,应该拒绝
642
+ if (!isMessageSigned && !isAssertionSigned && !encrypted) {
643
+ return {
644
+ isMessageSigned,
645
+ MessageSignatureStatus,
646
+ isAssertionSigned,
647
+ AssertionSignatureStatus,
648
+ encrypted,
649
+ decrypted,
650
+ type, // 添加类型字段
651
+ status: false, // 明确拒绝未签名未加密的响应
652
+ samlContent,
653
+ assertionContent
654
+ };
655
+ }
656
+ // 处理最外层有签名且断言加密的情况(关键逻辑补充)
657
+ if (isMessageSigned && encrypted) {
658
+ // 1. 首先解密断言
659
+ try {
660
+ const result = this.decryptAssertionSync(self, xml, opts);
661
+ // 更新文档为解密后的版本
662
+ samlContent = result[0];
663
+ assertionContent = result[1];
664
+ // 更新验证状态
665
+ decrypted = true;
666
+ AssertionSignatureStatus = result?.[2]?.AssertionSignatureStatus || false;
667
+ isAssertionSigned = result?.[2]?.isAssertionSigned || false;
668
+ // 解密后的文档
669
+ const decryptedDoc = dom.parseFromString(samlContent, 'application/xml');
670
+ // 2. 验证最外层消息签名(使用解密后的文档)
671
+ const signatureNode = messageSignatureNode[0];
672
+ const sig = new SignedXml();
673
+ if (!opts.keyFile && !opts.metadata) {
674
+ throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
675
+ }
676
+ if (opts.keyFile) {
677
+ sig.publicCert = fs.readFileSync(opts.keyFile);
678
+ }
679
+ else if (opts.metadata) {
680
+ // @ts-expect-error misssing Node properties are not needed
681
+ const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode);
682
+ let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
683
+ if (Array.isArray(metadataCert)) {
684
+ metadataCert = flattenDeep(metadataCert);
685
+ }
686
+ else if (typeof metadataCert === 'string') {
687
+ metadataCert = [metadataCert];
688
+ }
689
+ metadataCert = metadataCert.map(utility.normalizeCerString);
690
+ if (certificateNode.length === 0 && metadataCert.length === 0) {
691
+ throw new Error('NO_SELECTED_CERTIFICATE');
692
+ }
693
+ if (certificateNode.length !== 0) {
694
+ const x509CertificateData = certificateNode[0].firstChild.data;
695
+ const x509Certificate = utility.normalizeCerString(x509CertificateData);
696
+ if (metadataCert.length >= 1 && !metadataCert.find((cert) => cert.trim() === x509Certificate.trim())) {
697
+ throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
698
+ }
699
+ sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
700
+ }
701
+ else {
702
+ sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
703
+ }
704
+ }
705
+ sig.signatureAlgorithm = opts.signatureAlgorithm;
706
+ // @ts-expect-error misssing Node properties are not needed
707
+ sig.loadSignature(signatureNode);
708
+ // 使用解密后的文档验证最外层签名.默认采用的都是采用的先签名后加密的顺序,对应sp应该先解密 然后验证签名。 如果解密后验证外层签名失败有可能是先加密后签名,此时sp应该直接验证没解密的外层签名
709
+ MessageSignatureStatus = sig.checkSignature(decryptedDoc.toString());
710
+ console.log(MessageSignatureStatus);
711
+ console.log("验证MessageSignatureStatus==========================");
712
+ if (!MessageSignatureStatus) {
713
+ /** 签名验证失败 再直接验证外层*/
714
+ throw new Error('ERR_FAILED_TO_VERIFY_MESSAGE_SIGNATURE_AFTER_DECRYPTION');
715
+ let MessageSignatureStatus2 = sig.checkSignature(xml);
716
+ if (!MessageSignatureStatus2) {
717
+ throw new Error('ERR_FAILED_TO_VERIFY_MESSAGE_SIGNATURE_AFTER_DECRYPTION');
718
+ }
719
+ else {
720
+ MessageSignatureStatus = MessageSignatureStatus2;
721
+ }
722
+ }
723
+ // 3. 验证解密后断言的签名(如果存在)
724
+ if (isAssertionSigned && AssertionSignatureStatus) {
725
+ /* console.log("断言签名验证已通过");*/
726
+ }
727
+ else if (isAssertionSigned && !AssertionSignatureStatus) {
728
+ throw new Error('ERR_FAILED_TO_VERIFY_ASSERTION_SIGNATURE_AFTER_DECRYPTION');
729
+ }
730
+ }
731
+ catch (err) {
732
+ throw err;
599
733
  }
600
-
601
- metadataCert = metadataCert.map(utility.normalizeCerString);
602
-
603
- // 检查证书可用性
604
- if (certificateNodes.length === 0 && metadataCert.length === 0) {
605
- throw new Error('NO_SELECTED_CERTIFICATE');
734
+ }
735
+ // 处理最外层有签名但断言未加密的情况
736
+ else if (isMessageSigned && !encrypted) {
737
+ const signatureNode = messageSignatureNode[0];
738
+ const sig = new SignedXml();
739
+ if (!opts.keyFile && !opts.metadata) {
740
+ throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
606
741
  }
607
-
608
- // 响应中有证书节点
609
- if (certificateNodes.length !== 0) {
610
- // 安全获取证书数据
611
- let x509CertificateData = '';
612
- if (certificateNodes[0].firstChild) {
613
- x509CertificateData = certificateNodes[0].firstChild.data;
614
- } else if (certificateNodes[0].textContent) {
615
- x509CertificateData = certificateNodes[0].textContent;
616
- }
617
-
618
- const x509Certificate = utility.normalizeCerString(x509CertificateData);
619
-
620
- // 验证证书匹配
621
- if (
622
- metadataCert.length >= 1 &&
623
- !metadataCert.find(cert => cert.trim() === x509Certificate.trim())
624
- ) {
625
- throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
626
- }
627
-
628
- sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
629
- } else {
630
- // 使用元数据中的第一个证书
631
- sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
742
+ if (opts.keyFile) {
743
+ sig.publicCert = fs.readFileSync(opts.keyFile);
632
744
  }
633
- }
634
-
635
- // 加载签名
636
- sig.loadSignature(signatureNode);
637
- // 使用原始 XML 进行验证
638
- verified = sig.checkSignature(xml);
639
-
640
- if (!verified) {
641
- throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
642
- }
643
-
644
- // 检查签名引用
645
- if (!(sig.getSignedReferences().length >= 1)) {
646
- throw new Error('NO_SIGNATURE_REFERENCES');
647
- }
648
-
649
- const signedVerifiedXML = sig.getSignedReferences()[0];
650
- const verifiedDoc = docParser.parseFromString(signedVerifiedXML, 'application/xml');
651
- const rootNode = verifiedDoc.documentElement;
652
-
653
-
654
- // 断言模式专用返回逻辑
655
- if (opts.isAssertion) {
656
- if (rootNode?.localName === 'Assertion') {
657
- return [true, rootNode.toString(), false];
658
- } else {
659
- throw new Error('ERR_INVALID_ASSERTION_SIGNATURE');
745
+ else if (opts.metadata) {
746
+ // @ts-expect-error misssing Node properties are not needed
747
+ const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode);
748
+ let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
749
+ if (Array.isArray(metadataCert)) {
750
+ metadataCert = flattenDeep(metadataCert);
751
+ }
752
+ else if (typeof metadataCert === 'string') {
753
+ metadataCert = [metadataCert];
754
+ }
755
+ metadataCert = metadataCert.map(utility.normalizeCerString);
756
+ if (certificateNode.length === 0 && metadataCert.length === 0) {
757
+ throw new Error('NO_SELECTED_CERTIFICATE');
758
+ }
759
+ if (certificateNode.length !== 0) {
760
+ const x509CertificateData = certificateNode[0].firstChild.data;
761
+ const x509Certificate = utility.normalizeCerString(x509CertificateData);
762
+ if (metadataCert.length >= 1 && !metadataCert.find((cert) => cert.trim() === x509Certificate.trim())) {
763
+ throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
764
+ }
765
+ sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
766
+ }
767
+ else {
768
+ sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
769
+ }
660
770
  }
661
- }
662
-
663
- // 处理已验证的签名
664
- // @ts-expect-error misssing Node properties are not needed
665
- if (rootNode.localName === 'ArtifactResponse') {
666
- // 在 ArtifactResponse 中查找 Response
771
+ sig.signatureAlgorithm = opts.signatureAlgorithm;
667
772
  // @ts-expect-error misssing Node properties are not needed
668
- const responseNodes = select(
669
- "./!*[local-name()='Response']",
670
- // @ts-expect-error misssing Node properties are not needed
671
- rootNode
672
- ) as Element[];
673
-
674
- if (responseNodes.length === 0) {
675
- continue;
773
+ sig.loadSignature(signatureNode);
774
+ MessageSignatureStatus = sig.checkSignature(doc.toString());
775
+ if (!MessageSignatureStatus) {
776
+ throw new Error('ERR_FAILED_TO_VERIFY_MESSAGE_SIGNATURE');
676
777
  }
677
-
678
- const responseNode = responseNodes[0];
679
-
680
- // Response 中查找断言
681
- const encryptedAssertions = select(
682
- "./!*[local-name()='EncryptedAssertion']",
683
- responseNode
684
- ) as Element[];
685
-
686
- const assertions = select(
687
- "./!*[local-name()='Assertion']",
688
- responseNode
689
- ) as Element[];
690
-
691
- if (encryptedAssertions.length === 1) {
692
- return [true, encryptedAssertions[0].toString(), true];
778
+ }
779
+ // 验证断言级签名(如果存在且未加密)
780
+ if (isAssertionSigned && !encrypted) {
781
+ const signatureNode = assertionSignatureNode[0];
782
+ const sig = new SignedXml();
783
+ if (!opts.keyFile && !opts.metadata) {
784
+ throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
693
785
  }
694
-
695
- if (assertions.length === 1) {
696
- return [true, assertions[0].toString(), false];
786
+ if (opts.keyFile) {
787
+ sig.publicCert = fs.readFileSync(opts.keyFile);
697
788
  }
698
- }
699
- // 直接处理 Response
700
-
701
- else if (rootNode?.localName === 'Response') {
789
+ else if (opts.metadata) {
790
+ // @ts-expect-error misssing Node properties are not needed
791
+ const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode);
792
+ let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
793
+ if (Array.isArray(metadataCert)) {
794
+ metadataCert = flattenDeep(metadataCert);
795
+ }
796
+ else if (typeof metadataCert === 'string') {
797
+ metadataCert = [metadataCert];
798
+ }
799
+ metadataCert = metadataCert.map(utility.normalizeCerString);
800
+ if (certificateNode.length === 0 && metadataCert.length === 0) {
801
+ throw new Error('NO_SELECTED_CERTIFICATE');
802
+ }
803
+ if (certificateNode.length !== 0) {
804
+ const x509CertificateData = certificateNode[0].firstChild.data;
805
+ const x509Certificate = utility.normalizeCerString(x509CertificateData);
806
+ if (metadataCert.length >= 1 && !metadataCert.find((cert) => cert.trim() === x509Certificate.trim())) {
807
+ throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
808
+ }
809
+ sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
810
+ }
811
+ else {
812
+ sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
813
+ }
814
+ }
815
+ sig.signatureAlgorithm = opts.signatureAlgorithm;
702
816
  // @ts-expect-error misssing Node properties are not needed
703
- const encryptedAssertions = select(
704
- "./!*[local-name()='EncryptedAssertion']",
705
- // @ts-expect-error misssing Node properties are not needed
706
- rootNode
707
- ) as Element[];
817
+ sig.loadSignature(signatureNode);
818
+ // 为断言签名验证,我们需要从根文档中获取断言部分
708
819
  // @ts-expect-error misssing Node properties are not needed
709
- const assertions = select(
710
- "./!*[local-name()='Assertion']",
711
- // @ts-expect-error misssing Node properties are not needed
712
- rootNode
713
- ) as Element[];
714
-
715
- if (encryptedAssertions.length === 1) {
716
- return [true, encryptedAssertions[0].toString(), true];
820
+ const assertionNode = select("/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']", doc)[0];
821
+ if (assertionNode) {
822
+ const assertionDoc = dom.parseFromString(assertionNode.toString(), 'application/xml');
823
+ AssertionSignatureStatus = sig.checkSignature(assertionDoc.toString());
824
+ }
825
+ else {
826
+ AssertionSignatureStatus = false;
827
+ }
828
+ if (!AssertionSignatureStatus) {
829
+ throw new Error('ERR_FAILED_TO_VERIFY_ASSERTION_SIGNATURE');
717
830
  }
718
-
719
- if (assertions.length === 1) {
720
- return [true, assertions[0].toString(), false];
831
+ }
832
+ // 处理仅加密断言的情况(无消息签名)
833
+ if (encrypted && !isMessageSigned) {
834
+ if (!encryptedAssertions || encryptedAssertions.length === 0) {
835
+ throw new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION');
836
+ }
837
+ if (encryptedAssertions.length > 1) {
838
+ throw new Error('ERR_MULTIPLE_ASSERTION');
839
+ }
840
+ const encAssertionNode = encryptedAssertions[0];
841
+ // 解密断言
842
+ try {
843
+ const result = this.decryptAssertionSync(self, xml, opts);
844
+ samlContent = result[0];
845
+ assertionContent = result[1];
846
+ decrypted = true;
847
+ AssertionSignatureStatus = result?.[2]?.AssertionSignatureStatus;
848
+ isAssertionSigned = result?.[2]?.isAssertionSigned;
849
+ }
850
+ catch (err) {
851
+ throw new Error('ERR_EXCEPTION_OF_ASSERTION_DECRYPTION');
721
852
  }
722
- }
723
- // 直接处理 Assertion
724
- else if (rootNode?.localName === 'Assertion') {
725
- return [true, rootNode.toString(), false];
726
- }
727
- // 直接处理 EncryptedAssertion
728
- else if (rootNode?.localName === 'EncryptedAssertion') {
729
- return [true, rootNode.toString(), true];
730
- } else {
731
-
732
- console.warn("未知的根节点类型:", rootNode?.localName);
733
- }
734
853
  }
735
-
736
- throw new Error('ERR_ZERO_SIGNATURE');
737
- },*/
854
+ else if (!encrypted && (isMessageSigned || isAssertionSigned)) {
855
+ // 如果没有加密但有签名,提取断言内容
856
+ // @ts-expect-error misssing Node properties are not needed
857
+ const assertions = select("/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']", doc);
858
+ if (assertions.length > 0) {
859
+ // @ts-expect-error misssing Node properties are not needed
860
+ assertionContent = assertions[0].toString();
861
+ }
862
+ }
863
+ // 检查整体状态
864
+ status = (!isMessageSigned || MessageSignatureStatus) &&
865
+ (!isAssertionSigned || AssertionSignatureStatus) &&
866
+ (!encrypted || decrypted);
867
+ return {
868
+ isMessageSigned,
869
+ MessageSignatureStatus,
870
+ isAssertionSigned,
871
+ AssertionSignatureStatus,
872
+ encrypted,
873
+ decrypted,
874
+ type, // 添加类型字段
875
+ status,
876
+ samlContent,
877
+ assertionContent
878
+ };
879
+ },
738
880
  verifySignatureSoap(xml, opts) {
739
881
  const { dom } = getContext();
740
882
  const doc = dom.parseFromString(xml, 'application/xml');
@@ -930,7 +1072,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
930
1072
  verifier.update(octetString);
931
1073
  const isValid = verifier.verify(utility.getPublicKeyPemFromCertificate(signCert), Buffer.isBuffer(signature) ? signature : Buffer.from(signature, 'base64'));
932
1074
  return isValid
933
-
1075
+
934
1076
  },*/
935
1077
  /**
936
1078
  * @desc Verifies message signature
@@ -1063,6 +1205,105 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
1063
1205
  });
1064
1206
  });
1065
1207
  },
1208
+ /**
1209
+ * 同步版本的断言解密函数
1210
+ */
1211
+ /**
1212
+ * 同步版本的断言解密函数,支持解密后验证断言签名
1213
+ */
1214
+ decryptAssertionSync(here, entireXML, opts) {
1215
+ const hereSetting = here.entitySetting;
1216
+ const { dom } = getContext();
1217
+ const doc = dom.parseFromString(entireXML, 'application/xml');
1218
+ // @ts-expect-error misssing Node properties are not needed
1219
+ const encryptedAssertions = select("/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']", doc);
1220
+ if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
1221
+ throw new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION');
1222
+ }
1223
+ if (encryptedAssertions.length > 1) {
1224
+ throw new Error('ERR_MULTIPLE_ASSERTION');
1225
+ }
1226
+ const encAssertionNode = encryptedAssertions[0];
1227
+ let decryptedResult = null;
1228
+ // 使用同步方式处理解密
1229
+ xmlenc.decrypt(encAssertionNode.toString(), {
1230
+ key: utility.readPrivateKey(hereSetting.encPrivateKey, hereSetting.encPrivateKeyPass),
1231
+ }, (err, res) => {
1232
+ if (err) {
1233
+ throw new Error('ERR_EXCEPTION_OF_ASSERTION_DECRYPTION');
1234
+ }
1235
+ if (!res) {
1236
+ throw new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION');
1237
+ }
1238
+ decryptedResult = res;
1239
+ });
1240
+ if (!decryptedResult) {
1241
+ throw new Error('ERR_UNDEFINED_DECRYPTED_ASSERTION');
1242
+ }
1243
+ // 解密完成后,检查解密后的断言是否还有签名需要验证
1244
+ const decryptedAssertionDoc = dom.parseFromString(decryptedResult, 'application/xml');
1245
+ let AssertionSignatureStatus = false;
1246
+ // 检查解密后的断言是否有签名
1247
+ // @ts-expect-error misssing Node properties are not needed
1248
+ const assertionSignatureNode = select("/*[local-name(.)='Assertion']/*[local-name(.)='Signature']", decryptedAssertionDoc);
1249
+ if (assertionSignatureNode.length > 0 && opts) {
1250
+ // 解密后的断言有签名,需要验证
1251
+ const signatureNode = assertionSignatureNode[0];
1252
+ const sig = new SignedXml();
1253
+ if (!opts.keyFile && !opts.metadata) {
1254
+ throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
1255
+ }
1256
+ if (opts.keyFile) {
1257
+ sig.publicCert = fs.readFileSync(opts.keyFile);
1258
+ }
1259
+ else if (opts.metadata) {
1260
+ // @ts-expect-error misssing Node properties are not needed
1261
+ const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode);
1262
+ let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
1263
+ if (Array.isArray(metadataCert)) {
1264
+ metadataCert = flattenDeep(metadataCert);
1265
+ }
1266
+ else if (typeof metadataCert === 'string') {
1267
+ metadataCert = [metadataCert];
1268
+ }
1269
+ metadataCert = metadataCert.map(utility.normalizeCerString);
1270
+ if (certificateNode.length === 0 && metadataCert.length === 0) {
1271
+ throw new Error('NO_SELECTED_CERTIFICATE');
1272
+ }
1273
+ if (certificateNode.length !== 0) {
1274
+ const x509CertificateData = certificateNode[0].firstChild.data;
1275
+ const x509Certificate = utility.normalizeCerString(x509CertificateData);
1276
+ if (metadataCert.length >= 1 && !metadataCert.find((cert) => cert.trim() === x509Certificate.trim())) {
1277
+ throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
1278
+ }
1279
+ sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
1280
+ }
1281
+ else {
1282
+ sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
1283
+ }
1284
+ }
1285
+ sig.signatureAlgorithm = opts.signatureAlgorithm;
1286
+ // @ts-expect-error misssing Node properties are not needed
1287
+ sig.loadSignature(signatureNode);
1288
+ // 验证解密后断言的签名
1289
+ const assertionDocForVerification = dom.parseFromString(decryptedResult, 'application/xml');
1290
+ const assertionValid = sig.checkSignature(assertionDocForVerification.toString());
1291
+ AssertionSignatureStatus = assertionValid;
1292
+ console.log(AssertionSignatureStatus);
1293
+ console.log("验证通过了====");
1294
+ if (!assertionValid) {
1295
+ throw new Error('ERR_FAILED_TO_VERIFY_DECRYPTED_ASSERTION_SIGNATURE');
1296
+ }
1297
+ }
1298
+ // 将解密后的断言替换原始文档中的加密断言
1299
+ const rawAssertionDoc = dom.parseFromString(decryptedResult, 'application/xml');
1300
+ // @ts-ignore
1301
+ doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
1302
+ return [doc.toString(), decryptedResult, {
1303
+ isAssertionSigned: !!(assertionSignatureNode.length > 0 && opts),
1304
+ AssertionSignatureStatus: AssertionSignatureStatus
1305
+ }];
1306
+ },
1066
1307
  /**
1067
1308
  * 解密 SOAP 响应中的加密断言
1068
1309
  * @param self 当前实体(SP 或 IdP)