samlesa 2.17.0 → 2.17.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 (65) hide show
  1. package/build/index.js +2 -1
  2. package/build/src/binding-artifact.js +330 -146
  3. package/build/src/entity-sp.js +21 -94
  4. package/build/src/extractor.js +32 -0
  5. package/build/src/flow.js +23 -112
  6. package/build/src/libsaml.js +325 -127
  7. package/build/src/libsamlSoap.js +115 -0
  8. package/build/src/schemaValidator.js +1 -5
  9. package/build/src/soap.js +123 -3
  10. package/package.json +77 -75
  11. package/types/api.d.ts +15 -0
  12. package/types/api.d.ts.map +1 -0
  13. package/types/binding-post.d.ts +48 -0
  14. package/types/binding-post.d.ts.map +1 -0
  15. package/types/binding-redirect.d.ts +54 -0
  16. package/types/binding-redirect.d.ts.map +1 -0
  17. package/types/binding-simplesign.d.ts +41 -0
  18. package/types/binding-simplesign.d.ts.map +1 -0
  19. package/types/entity-idp.d.ts +38 -0
  20. package/types/entity-idp.d.ts.map +1 -0
  21. package/types/entity-sp.d.ts +38 -0
  22. package/types/entity-sp.d.ts.map +1 -0
  23. package/types/entity.d.ts +100 -0
  24. package/types/entity.d.ts.map +1 -0
  25. package/types/extractor.d.ts +26 -0
  26. package/types/extractor.d.ts.map +1 -0
  27. package/types/flow.d.ts +7 -0
  28. package/types/flow.d.ts.map +1 -0
  29. package/types/index.d.ts +2 -1
  30. package/types/index.d.ts.map +1 -1
  31. package/types/libsaml.d.ts +208 -0
  32. package/types/libsaml.d.ts.map +1 -0
  33. package/types/metadata-idp.d.ts +25 -0
  34. package/types/metadata-idp.d.ts.map +1 -0
  35. package/types/metadata-sp.d.ts +37 -0
  36. package/types/metadata-sp.d.ts.map +1 -0
  37. package/types/metadata.d.ts +58 -0
  38. package/types/metadata.d.ts.map +1 -0
  39. package/types/src/binding-artifact.d.ts +24 -29
  40. package/types/src/binding-artifact.d.ts.map +1 -1
  41. package/types/src/binding-post.d.ts.map +1 -1
  42. package/types/src/entity-sp.d.ts +13 -24
  43. package/types/src/entity-sp.d.ts.map +1 -1
  44. package/types/src/extractor.d.ts +22 -0
  45. package/types/src/extractor.d.ts.map +1 -1
  46. package/types/src/flow.d.ts +1 -0
  47. package/types/src/flow.d.ts.map +1 -1
  48. package/types/src/libsaml.d.ts +4 -3
  49. package/types/src/libsaml.d.ts.map +1 -1
  50. package/types/src/libsamlSoap.d.ts +7 -0
  51. package/types/src/libsamlSoap.d.ts.map +1 -0
  52. package/types/src/schemaValidator.d.ts.map +1 -1
  53. package/types/src/soap.d.ts +33 -0
  54. package/types/src/soap.d.ts.map +1 -1
  55. package/types/src/validator.d.ts.map +1 -1
  56. package/types/types.d.ts +128 -0
  57. package/types/types.d.ts.map +1 -0
  58. package/types/urn.d.ts +195 -0
  59. package/types/urn.d.ts.map +1 -0
  60. package/types/utility.d.ts +133 -0
  61. package/types/utility.d.ts.map +1 -0
  62. package/types/validator.d.ts +4 -0
  63. package/types/validator.d.ts.map +1 -0
  64. package/build/src/schema/XMLSchema.dtd +0 -402
  65. package/build/src/schema/datatypes.dtd +0 -203
@@ -69,7 +69,18 @@ const libSaml = () => {
69
69
  context: `<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><saml2p:ArtifactResolve xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}"><saml2:Issuer>{Issuer}</saml2:Issuer><saml2p:Artifact>{Art}</saml2p:Artifact></saml2p:ArtifactResolve></SOAP-ENV:Body></SOAP-ENV:Envelope>`,
70
70
  };
71
71
  const defaultArtAuthnRequestTemplate = {
72
- context: `<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header></SOAP-ENV:Header><samlp:ArtifactResponse xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}" InResponseTo="{InResponseTo}" Version="2.0" IssueInstant="{IssueInstant}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>{AuthnRequest}</samlp:ArtifactResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>`,
72
+ context: `<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header></SOAP-ENV:Header><SOAP-ENV:Body><samlp:ArtifactResponse xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}" InResponseTo="{InResponseTo}" Version="2.0" IssueInstant="{IssueInstant}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>{AuthnRequest}</samlp:ArtifactResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>`,
73
+ };
74
+ const defaultSoapResponseFailTemplate = {
75
+ 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
+ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
78
+ InResponseTo="{InResponseTo}" Version="2.0"
79
+ IssueInstant="{IssueInstant}">
80
+ <saml:Issuer>{Issuer}</saml:Issuer>
81
+ <samlp:Status>
82
+ <samlp:StatusCode Value="{StatusCode}"/>
83
+ </samlp:Status>{Response}</samlp:ArtifactResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>`,
73
84
  };
74
85
  /**
75
86
  * @desc Default AttributeStatement template
@@ -137,7 +148,6 @@ const libSaml = () => {
137
148
  }
138
149
  catch (inflateError) {
139
150
  // 4. 解压失败,尝试直接解析为未压缩的XML
140
- console.log("解压失败---------------------");
141
151
  try {
142
152
  const base64Encoded = decodeURIComponent(urlEncodedResponse);
143
153
  xml = atob(base64Encoded);
@@ -211,6 +221,7 @@ const libSaml = () => {
211
221
  defaultArtAuthnRequestTemplate,
212
222
  defaultArtifactResolveTemplate,
213
223
  defaultLoginResponseTemplate,
224
+ defaultSoapResponseFailTemplate,
214
225
  defaultAttributeStatementTemplate,
215
226
  defaultAttributeTemplate,
216
227
  defaultLogoutRequestTemplate,
@@ -498,55 +509,292 @@ const libSaml = () => {
498
509
  return [false, null, false, true]; // return encryptedAssert
499
510
  /* throw new Error('ERR_ZERO_SIGNATURE');*/
500
511
  },
501
- verifySignatureSoap(xml, opts) {
502
- const { dom } = getContext();
512
+ /* verifySignatureSoap(xml: string, opts: SignatureVerifierOptions & { isAssertion?: boolean }) {
513
+ const {dom} = getContext();
503
514
  const doc = dom.parseFromString(xml, 'application/xml');
504
515
  const docParser = new DOMParser();
505
- let selection = [];
516
+
517
+ let selection: any = [];
518
+
506
519
  if (opts.isAssertion) {
507
- // 断言模式下的专用逻辑
508
- const assertionSignatureXpath = "./*[local-name()='Signature']";
509
- // @ts-expect-error misssing Node properties are not needed
510
- const signatureNode = select(assertionSignatureXpath, doc.documentElement);
511
- if (signatureNode.length === 0) {
512
- throw new Error('ERR_ASSERTION_SIGNATURE_NOT_FOUND');
513
- }
514
- selection = selection.concat(signatureNode);
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) {
563
+ 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);
515
572
  }
516
- else {
517
- // 原始的SOAP响应验证逻辑
518
- const messageSignatureXpath = "/*[local-name()='Envelope']/*[local-name()='Body']" +
519
- "/*[local-name()='ArtifactResponse']/*[local-name()='Signature'] | " +
520
- "/*[local-name()='Envelope']/*[local-name()='Body']" +
521
- "/*[local-name()='ArtifactResponse']/*[local-name()='Response']/*[local-name()='Signature']";
522
- const assertionSignatureXpath = "/*[local-name()='Envelope']/*[local-name()='Body']" +
523
- "/*[local-name()='ArtifactResponse']/*[local-name()='Response']" +
524
- "/*[local-name()='Assertion']/*[local-name()='Signature'] | " +
525
- "/*[local-name()='Envelope']/*[local-name()='Body']" +
526
- "/*[local-name()='ArtifactResponse']/*[local-name()='Response']" +
527
- "/*[local-name()='EncryptedAssertion']";
528
- const wrappingElementsXPath = "/*[local-name()='Envelope']/*[local-name()='Body']" +
529
- "/*[local-name()='ArtifactResponse']/*[local-name()='Response']" +
530
- "/*[local-name()='Assertion']/*[local-name()='Subject']" +
531
- "/*[local-name()='SubjectConfirmation']" +
532
- "/*[local-name()='SubjectConfirmationData']" +
533
- "//*[local-name()='Assertion' or local-name()='Signature']";
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];
599
+ }
600
+
601
+ metadataCert = metadataCert.map(utility.normalizeCerString);
602
+
603
+ // 检查证书可用性
604
+ if (certificateNodes.length === 0 && metadataCert.length === 0) {
605
+ throw new Error('NO_SELECTED_CERTIFICATE');
606
+ }
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();
632
+ }
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');
660
+ }
661
+ }
662
+
663
+ // 处理已验证的签名
664
+ // @ts-expect-error misssing Node properties are not needed
665
+ if (rootNode.localName === 'ArtifactResponse') {
666
+ // 在 ArtifactResponse 中查找 Response
534
667
  // @ts-expect-error misssing Node properties are not needed
535
- const messageSignatureNode = select(messageSignatureXpath, doc);
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;
676
+ }
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];
693
+ }
694
+
695
+ if (assertions.length === 1) {
696
+ return [true, assertions[0].toString(), false];
697
+ }
698
+ }
699
+ // 直接处理 Response
700
+
701
+ else if (rootNode?.localName === 'Response') {
536
702
  // @ts-expect-error misssing Node properties are not needed
537
- const assertionSignatureNode = select(assertionSignatureXpath, doc);
703
+ const encryptedAssertions = select(
704
+ "./!*[local-name()='EncryptedAssertion']",
705
+ // @ts-expect-error misssing Node properties are not needed
706
+ rootNode
707
+ ) as Element[];
538
708
  // @ts-expect-error misssing Node properties are not needed
539
- const wrappingElementNode = select(wrappingElementsXPath, doc);
540
- // 检测包装攻击
541
- if (wrappingElementNode.length !== 0) {
542
- throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
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];
543
717
  }
544
- // 保证响应中至少有一个签名
545
- if (messageSignatureNode.length === 0 && assertionSignatureNode.length === 0) {
546
- throw new Error('ERR_ZERO_SIGNATURE');
718
+
719
+ if (assertions.length === 1) {
720
+ return [true, assertions[0].toString(), false];
547
721
  }
548
- selection = selection.concat(messageSignatureNode, assertionSignatureNode);
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
+ }
549
734
  }
735
+
736
+ throw new Error('ERR_ZERO_SIGNATURE');
737
+ },*/
738
+ verifySignatureSoap(xml, opts) {
739
+ const { dom } = getContext();
740
+ const doc = dom.parseFromString(xml, 'application/xml');
741
+ const docParser = new DOMParser();
742
+ // 为 SOAP 消息定义 XPath
743
+ const artifactResolveXpath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResolve']";
744
+ const artifactResponseXpath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResponse']";
745
+ // 检测 ArtifactResolve 或 ArtifactResponse 的存在
746
+ // @ts-expect-error
747
+ const artifactResolveNodes = select(artifactResolveXpath, doc);
748
+ // @ts-expect-error
749
+ const artifactResponseNodes = select(artifactResponseXpath, doc);
750
+ // 根据消息类型选择合适的 XPath
751
+ let basePath = "";
752
+ if (artifactResolveNodes.length > 0) {
753
+ basePath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResolve']";
754
+ }
755
+ else if (artifactResponseNodes.length > 0) {
756
+ basePath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResponse']";
757
+ }
758
+ else {
759
+ throw new Error('ERR_UNSUPPORTED_SOAP_MESSAGE_TYPE');
760
+ }
761
+ // 基于 SOAP 结构重新定义 XPath
762
+ const messageSignatureXpath = `${basePath}/*[local-name(.)='Signature']`;
763
+ const assertionSignatureXpath = `${basePath}/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Signature']`;
764
+ const wrappingElementsXPath = `${basePath}/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Subject']/*[local-name(.)='SubjectConfirmation']/*[local-name(.)='SubjectConfirmationData']//*[local-name(.)='Assertion' or local-name(.)='Signature']`;
765
+ const encryptedAssertionsXpath = `${basePath}/*[local-name(.)='Response']/*[local-name(.)='EncryptedAssertion']`;
766
+ // 包装攻击检测
767
+ // @ts-expect-error
768
+ const wrappingElementNode = select(wrappingElementsXPath, doc);
769
+ if (wrappingElementNode.length !== 0) {
770
+ throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
771
+ }
772
+ // @ts-expect-error
773
+ const encryptedAssertions = select(encryptedAssertionsXpath, doc);
774
+ // @ts-expect-error
775
+ const messageSignatureNode = select(messageSignatureXpath, doc);
776
+ // @ts-expect-error
777
+ const assertionSignatureNode = select(assertionSignatureXpath, doc);
778
+ let selection = [];
779
+ if (messageSignatureNode.length > 0) {
780
+ selection = selection.concat(messageSignatureNode);
781
+ }
782
+ if (assertionSignatureNode.length > 0) {
783
+ selection = selection.concat(assertionSignatureNode);
784
+ }
785
+ // 处理加密断言的情况
786
+ if (selection.length === 0) {
787
+ if (encryptedAssertions.length > 0) {
788
+ if (encryptedAssertions.length > 1) {
789
+ throw new Error('ERR_MULTIPLE_ASSERTION');
790
+ }
791
+ return [false, null, true, true];
792
+ }
793
+ }
794
+ if (selection.length === 0) {
795
+ throw new Error('ERR_ZERO_SIGNATURE');
796
+ }
797
+ // 尝试所有签名节点
550
798
  for (const signatureNode of selection) {
551
799
  const sig = new SignedXml();
552
800
  let verified = false;
@@ -555,13 +803,12 @@ const libSaml = () => {
555
803
  throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
556
804
  }
557
805
  if (opts.keyFile) {
558
- sig.publicCert = fs.readFileSync(opts.keyFile, 'utf-8');
806
+ sig.publicCert = fs.readFileSync(opts.keyFile);
559
807
  }
560
808
  if (opts.metadata) {
561
- const certificateNodes = select(".//*[local-name(.)='X509Certificate']", signatureNode);
562
- // 获取元数据中的证书
809
+ const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode);
810
+ // 证书处理逻辑
563
811
  let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
564
- // 规范化元数据证书
565
812
  if (Array.isArray(metadataCert)) {
566
813
  metadataCert = flattenDeep(metadataCert);
567
814
  }
@@ -569,108 +816,59 @@ const libSaml = () => {
569
816
  metadataCert = [metadataCert];
570
817
  }
571
818
  metadataCert = metadataCert.map(utility.normalizeCerString);
572
- // 检查证书可用性
573
- if (certificateNodes.length === 0 && metadataCert.length === 0) {
819
+ // 没有证书的情况
820
+ if (certificateNode.length === 0 && metadataCert.length === 0) {
574
821
  throw new Error('NO_SELECTED_CERTIFICATE');
575
822
  }
576
- // 响应中有证书节点
577
- if (certificateNodes.length !== 0) {
578
- // 安全获取证书数据
579
- let x509CertificateData = '';
580
- if (certificateNodes[0].firstChild) {
581
- x509CertificateData = certificateNodes[0].firstChild.data;
582
- }
583
- else if (certificateNodes[0].textContent) {
584
- x509CertificateData = certificateNodes[0].textContent;
585
- }
823
+ if (certificateNode.length !== 0) {
824
+ const x509CertificateData = certificateNode[0].firstChild.data;
586
825
  const x509Certificate = utility.normalizeCerString(x509CertificateData);
587
- // 验证证书匹配
588
- if (metadataCert.length >= 1 &&
589
- !metadataCert.find(cert => cert.trim() === x509Certificate.trim())) {
826
+ if (metadataCert.length >= 1 && !metadataCert.includes(x509Certificate)) {
590
827
  throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
591
828
  }
592
829
  sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
593
830
  }
594
831
  else {
595
- // 使用元数据中的第一个证书
596
832
  sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
597
833
  }
598
834
  }
599
- // 加载签名
600
835
  sig.loadSignature(signatureNode);
601
- // 使用原始 XML 进行验证
602
- verified = sig.checkSignature(xml);
836
+ verified = sig.checkSignature(xml); // 使用原始XML验证
603
837
  if (!verified) {
604
838
  throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
605
839
  }
606
- // 检查签名引用
607
- if (!(sig.getSignedReferences().length >= 1)) {
840
+ if (sig.getSignedReferences().length < 1) {
608
841
  throw new Error('NO_SIGNATURE_REFERENCES');
609
842
  }
610
843
  const signedVerifiedXML = sig.getSignedReferences()[0];
611
- const verifiedDoc = docParser.parseFromString(signedVerifiedXML, 'application/xml');
612
- const rootNode = verifiedDoc.documentElement;
613
- // 断言模式专用返回逻辑
614
- if (opts.isAssertion) {
615
- if (rootNode?.localName === 'Assertion') {
616
- return [true, rootNode.toString(), false];
617
- }
618
- else {
619
- throw new Error('ERR_INVALID_ASSERTION_SIGNATURE');
620
- }
621
- }
622
- // 处理已验证的签名
623
- // @ts-expect-error misssing Node properties are not needed
624
- if (rootNode.localName === 'ArtifactResponse') {
625
- // ArtifactResponse 中查找 Response
626
- // @ts-expect-error misssing Node properties are not needed
627
- const responseNodes = select("./*[local-name()='Response']",
628
- // @ts-expect-error misssing Node properties are not needed
629
- rootNode);
630
- if (responseNodes.length === 0) {
631
- continue;
632
- }
633
- const responseNode = responseNodes[0];
634
- // 在 Response 中查找断言
635
- const encryptedAssertions = select("./*[local-name()='EncryptedAssertion']", responseNode);
636
- const assertions = select("./*[local-name()='Assertion']", responseNode);
637
- if (encryptedAssertions.length === 1) {
638
- return [true, encryptedAssertions[0].toString(), true];
639
- }
640
- if (assertions.length === 1) {
641
- return [true, assertions[0].toString(), false];
642
- }
643
- }
644
- // 直接处理 Response
645
- else if (rootNode?.localName === 'Response') {
646
- // @ts-expect-error misssing Node properties are not needed
647
- const encryptedAssertions = select("./*[local-name()='EncryptedAssertion']",
648
- // @ts-expect-error misssing Node properties are not needed
649
- rootNode);
650
- // @ts-expect-error misssing Node properties are not needed
651
- const assertions = select("./*[local-name()='Assertion']",
652
- // @ts-expect-error misssing Node properties are not needed
653
- rootNode);
654
- if (encryptedAssertions.length === 1) {
655
- return [true, encryptedAssertions[0].toString(), true];
656
- }
657
- if (assertions.length === 1) {
658
- return [true, assertions[0].toString(), false];
659
- }
660
- }
661
- // 直接处理 Assertion
662
- else if (rootNode?.localName === 'Assertion') {
663
- return [true, rootNode.toString(), false];
664
- }
665
- // 直接处理 EncryptedAssertion
666
- else if (rootNode?.localName === 'EncryptedAssertion') {
667
- return [true, rootNode.toString(), true];
668
- }
669
- else {
670
- console.warn("未知的根节点类型:", rootNode?.localName);
844
+ const rootNode = docParser.parseFromString(signedVerifiedXML, 'application/xml').documentElement;
845
+ // 处理签名的内容
846
+ switch (rootNode?.localName) {
847
+ case 'Response':
848
+ // @ts-expect-error
849
+ const encryptedAssert = select("./*[local-name()='EncryptedAssertion']", rootNode);
850
+ // @ts-expect-error
851
+ const assertions = select("./*[local-name()='Assertion']", rootNode);
852
+ if (encryptedAssert.length === 1) {
853
+ return [true, encryptedAssert[0].toString(), true, false];
854
+ }
855
+ if (assertions.length === 1) {
856
+ return [true, assertions[0].toString(), false, false];
857
+ }
858
+ return [true, null, false, true]; // 签名验证成功但未找到断言
859
+ case 'Assertion':
860
+ return [true, rootNode.toString(), false, false];
861
+ case 'EncryptedAssertion':
862
+ return [true, rootNode.toString(), true, false];
863
+ case 'ArtifactResolve':
864
+ case 'ArtifactResponse':
865
+ // 提取SOAP消息内部的实际内容
866
+ return [true, rootNode.toString(), false, false];
867
+ default:
868
+ return [true, null, false, true]; // 签名验证成功但未找到可识别的内容
671
869
  }
672
870
  }
673
- throw new Error('ERR_ZERO_SIGNATURE');
871
+ return [false, null, encryptedAssertions.length > 0, false];
674
872
  },
675
873
  /**
676
874
  * @desc Helper function to create the key section in metadata (abstraction for signing and encrypt use)
@@ -0,0 +1,115 @@
1
+ import { getContext } from "./api.js";
2
+ import { select } from "xpath";
3
+ import { SignedXml } from "xml-crypto";
4
+ import fs from "fs";
5
+ import utility, { flattenDeep } from "./utility.js";
6
+ import libsaml from "./libsaml.js";
7
+ import { wording } from "./urn.js";
8
+ import { DOMParser } from '@xmldom/xmldom';
9
+ const certUse = wording.certUse;
10
+ const docParser = new DOMParser();
11
+ async function verifyAndDecryptSoapMessage(xml, opts) {
12
+ const { dom } = getContext();
13
+ const doc = dom.parseFromString(xml, 'application/xml');
14
+ const docParser = new DOMParser();
15
+ let type = '';
16
+ // 为 SOAP 消息定义 XPath
17
+ const artifactResolveXpath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResolve']";
18
+ const artifactResponseXpath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResponse']";
19
+ // 检测 ArtifactResolve 或 ArtifactResponse 的存在
20
+ // @ts-expect-error
21
+ const artifactResolveNodes = select(artifactResolveXpath, doc);
22
+ // @ts-expect-error
23
+ const artifactResponseNodes = select(artifactResponseXpath, doc);
24
+ // 根据消息类型选择合适的 XPath
25
+ let basePath = "";
26
+ if (artifactResolveNodes?.length > 0) {
27
+ type = 'artifactResolve';
28
+ basePath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResolve']";
29
+ }
30
+ else if (artifactResponseNodes?.length > 0) {
31
+ type = 'artifactResponse';
32
+ basePath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResponse']";
33
+ }
34
+ else {
35
+ throw new Error('ERR_UNSUPPORTED_SOAP_MESSAGE_TYPE');
36
+ }
37
+ // 基于 SOAP 结构重新定义 XPath
38
+ const messageSignatureXpath = `${basePath}/*[local-name(.)='Signature']`;
39
+ // @ts-expect-error
40
+ const messageSignatureNode = select(messageSignatureXpath, doc);
41
+ let selection = [];
42
+ if (messageSignatureNode?.length > 0) {
43
+ selection = selection.concat(messageSignatureNode);
44
+ }
45
+ if (selection.length === 0) {
46
+ throw new Error('ERR_ZERO_SIGNATURE');
47
+ }
48
+ return verifySignature(xml, selection, opts);
49
+ }
50
+ function verifySignature(xml, selection, opts) {
51
+ // 尝试所有签名节点
52
+ for (const signatureNode of selection) {
53
+ const sig = new SignedXml();
54
+ let verified = false;
55
+ sig.signatureAlgorithm = opts.signatureAlgorithm;
56
+ if (!opts.keyFile && !opts.metadata) {
57
+ throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
58
+ }
59
+ if (opts.keyFile) {
60
+ sig.publicCert = fs.readFileSync(opts.keyFile);
61
+ }
62
+ if (opts.metadata) {
63
+ const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode);
64
+ // 证书处理逻辑
65
+ let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
66
+ if (Array.isArray(metadataCert)) {
67
+ metadataCert = flattenDeep(metadataCert);
68
+ }
69
+ else if (typeof metadataCert === 'string') {
70
+ metadataCert = [metadataCert];
71
+ }
72
+ metadataCert = metadataCert.map(utility.normalizeCerString);
73
+ // 没有证书的情况
74
+ if (certificateNode.length === 0 && metadataCert.length === 0) {
75
+ throw new Error('NO_SELECTED_CERTIFICATE');
76
+ }
77
+ if (certificateNode.length !== 0) {
78
+ const x509CertificateData = certificateNode[0].firstChild.data;
79
+ const x509Certificate = utility.normalizeCerString(x509CertificateData);
80
+ if (metadataCert.length >= 1 && !metadataCert.includes(x509Certificate)) {
81
+ throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
82
+ }
83
+ sig.publicCert = libsaml.getKeyInfo(x509Certificate).getKey();
84
+ }
85
+ else {
86
+ sig.publicCert = libsaml.getKeyInfo(metadataCert[0]).getKey();
87
+ }
88
+ }
89
+ sig.loadSignature(signatureNode);
90
+ verified = sig.checkSignature(xml); // 使用原始XML验证
91
+ if (!verified) {
92
+ throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
93
+ }
94
+ if (sig.getSignedReferences().length < 1) {
95
+ throw new Error('NO_SIGNATURE_REFERENCES');
96
+ }
97
+ const signedVerifiedXML = sig.getSignedReferences()[0];
98
+ const rootNode = docParser.parseFromString(signedVerifiedXML, 'application/xml').documentElement;
99
+ // 处理签名的内容
100
+ switch (rootNode?.localName) {
101
+ case 'ArtifactResolve':
102
+ return [true, rootNode.toString(), false, false];
103
+ case 'ArtifactResponse':
104
+ // @ts-expect-error
105
+ const Response = select("/*[local-name()='ArtifactResponse']/*[local-name()='Response']", rootNode);
106
+ return [true, Response?.[0].toString(), false, false]; // 签名验证成功但未找到断言
107
+ default:
108
+ return [true, null, false, true]; // 签名验证成功但未找到可识别的内容
109
+ }
110
+ }
111
+ return [false, null, false, true];
112
+ }
113
+ export default {
114
+ verifyAndDecryptSoapMessage
115
+ };