samlesa 2.16.6 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

@@ -4,10 +4,10 @@
4
4
  * @desc A simple library including some common functions
5
5
  */
6
6
  import xml from 'xml';
7
- import { createSign, createPrivateKey, createVerify } from 'node:crypto';
8
- import utility, { flattenDeep, isString } from './utility.js';
9
- import { algorithms, wording, namespace } from './urn.js';
7
+ import utility, { flattenDeep, inflateString, isString } from './utility.js';
8
+ import { algorithms, namespace, wording } from './urn.js';
10
9
  import { select } from 'xpath';
10
+ import nrsa from 'node-rsa';
11
11
  import { SignedXml } from 'xml-crypto';
12
12
  import * as xmlenc from 'xml-encryption';
13
13
  import camelCase from 'camelcase';
@@ -15,26 +15,10 @@ import { getContext } from './api.js';
15
15
  import xmlEscape from 'xml-escape';
16
16
  import * as fs from 'fs';
17
17
  import { DOMParser } from '@xmldom/xmldom';
18
- import { inflate } from 'pako';
19
18
  const signatureAlgorithms = algorithms.signature;
20
19
  const digestAlgorithms = algorithms.digest;
21
20
  const certUse = wording.certUse;
22
21
  const urlParams = wording.urlParams;
23
- /**
24
- * 算法名称映射表 (兼容 X.509 和 SAML 规范)
25
- */
26
- function mapSignAlgorithm(algorithm) {
27
- const algorithmMap = {
28
- 'rsa-sha1': 'RSA-SHA1',
29
- 'rsa-sha256': 'RSA-SHA256',
30
- 'rsa-sha384': 'RSA-SHA384',
31
- 'rsa-sha512': 'RSA-SHA512',
32
- 'ecdsa-sha256': 'ECDSA-SHA256',
33
- 'ecdsa-sha384': 'ECDSA-SHA384',
34
- 'ecdsa-sha512': 'ECDSA-SHA512'
35
- };
36
- return algorithmMap[algorithm.toLowerCase()] || algorithm;
37
- }
38
22
  const libSaml = () => {
39
23
  /**
40
24
  * @desc helper function to get back the query param for redirect binding for SLO/SSO
@@ -52,6 +36,7 @@ const libSaml = () => {
52
36
  /**
53
37
  *
54
38
  */
39
+ // 签名算法映射表
55
40
  const nrsaAliasMapping = {
56
41
  'http://www.w3.org/2000/09/xmldsig#rsa-sha1': 'pkcs1-sha1',
57
42
  'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': 'pkcs1-sha256',
@@ -126,6 +111,21 @@ const libSaml = () => {
126
111
  const defaultLogoutResponseTemplate = {
127
112
  context: '<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}" InResponseTo="{InResponseTo}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="{StatusCode}"/></samlp:Status></samlp:LogoutResponse>',
128
113
  };
114
+ /**
115
+ * @private
116
+ * @desc Get the signing scheme alias by signature algorithms, used by the node-rsa module
117
+ * @param {string} sigAlg signature algorithm
118
+ * @return {string/null} signing algorithm short-hand for the module node-rsa
119
+ */
120
+ function getSigningScheme(sigAlg) {
121
+ if (sigAlg) {
122
+ const algAlias = nrsaAliasMapping[sigAlg];
123
+ if (!(algAlias === undefined)) {
124
+ return algAlias;
125
+ }
126
+ }
127
+ return nrsaAliasMapping[signatureAlgorithms.RSA_SHA1];
128
+ }
129
129
  function validateAndInflateSamlResponse(urlEncodedResponse) {
130
130
  // 3. 尝试DEFLATE解压(SAML规范要求使用原始DEFLATE)
131
131
  let xml = "";
@@ -133,18 +133,14 @@ const libSaml = () => {
133
133
  try { // 1. URL解码
134
134
  const base64Encoded = decodeURIComponent(urlEncodedResponse);
135
135
  // 2. Base64解码为Uint8Array
136
- const binaryStr = atob(base64Encoded);
137
- const compressedData = new Uint8Array(binaryStr.length);
138
- for (let i = 0; i < binaryStr.length; i++) {
139
- compressedData[i] = binaryStr.charCodeAt(i);
140
- }
141
- xml = inflate(compressedData, { to: 'string', raw: true });
136
+ xml = inflateString(base64Encoded);
142
137
  }
143
138
  catch (inflateError) {
144
139
  // 4. 解压失败,尝试直接解析为未压缩的XML
140
+ console.log("解压失败---------------------");
145
141
  try {
146
142
  const base64Encoded = decodeURIComponent(urlEncodedResponse);
147
- xml = Buffer.from(base64Encoded, 'base64').toString('utf-8');
143
+ xml = atob(base64Encoded);
148
144
  return { compressed: false, xml, error: null };
149
145
  }
150
146
  catch (xmlError) {
@@ -277,6 +273,9 @@ const libSaml = () => {
277
273
  };
278
274
  // 生成 XML(关闭自动声明头)
279
275
  const xmlString = xml([attributeStatement], { declaration: false });
276
+ if (xmlString.trim() === '<saml:AttributeStatement></saml:AttributeStatement>') {
277
+ return '';
278
+ }
280
279
  return xmlString.trim();
281
280
  },
282
281
  /**
@@ -324,7 +323,7 @@ const libSaml = () => {
324
323
  else {
325
324
  sig.computeSignature(rawSamlMessage);
326
325
  }
327
- return isBase64Output !== false ? utility.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
326
+ return isBase64Output ? utility.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
328
327
  },
329
328
  /**
330
329
  * @desc Verify the XML signature
@@ -337,29 +336,68 @@ const libSaml = () => {
337
336
  // tslint:disable-next-line:no-shadowed-variable
338
337
  verifySignature(xml, opts) {
339
338
  const { dom } = getContext();
340
- const doc = dom.parseFromString(xml);
339
+ const doc = dom.parseFromString(xml, 'application/xml');
341
340
  const docParser = new DOMParser();
342
341
  // In order to avoid the wrapping attack, we have changed to use absolute xpath instead of naively fetching the signature element
342
+ const LogoutResponseSignatureXpath = "/*[local-name()='LogoutResponse']/*[local-name()='Signature']";
343
+ const logoutRequestSignatureXpath = "/*[local-name()='LogoutRequest']/*[local-name()='Signature']";
343
344
  // message signature (logout response / saml response)
344
345
  const messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']";
345
346
  // assertion signature (logout response / saml response)
346
347
  const assertionSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']/*[local-name(.)='Signature']";
347
348
  // check if there is a potential malicious wrapping signature
348
349
  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']";
350
+ // 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']";
351
+ // @ts-expect-error misssing Node properties are not needed
352
+ const encryptedAssertions = select("/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']", doc);
353
+ const encAssertionNode = encryptedAssertions[0];
349
354
  // select the signature node
350
355
  let selection = [];
356
+ // @ts-expect-error misssing Node properties are not needed
351
357
  const messageSignatureNode = select(messageSignatureXpath, doc);
358
+ // @ts-expect-error misssing Node properties are not needed
352
359
  const assertionSignatureNode = select(assertionSignatureXpath, doc);
360
+ // @ts-expect-error misssing Node properties are not needed
353
361
  const wrappingElementNode = select(wrappingElementsXPath, doc);
354
- selection = selection.concat(messageSignatureNode);
355
- selection = selection.concat(assertionSignatureNode);
362
+ // @ts-expect-error misssing Node properties are not needed
363
+ const LogoutResponseSignatureElementNode = select(LogoutResponseSignatureXpath, doc);
356
364
  // try to catch potential wrapping attack
357
365
  if (wrappingElementNode.length !== 0) {
358
366
  throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
359
367
  }
368
+ // 优先检测 LogoutRequest 签名
369
+ // @ts-expect-error missing Node properties
370
+ const logoutRequestSignature = select(logoutRequestSignatureXpath, doc);
371
+ if (logoutRequestSignature.length > 0) {
372
+ selection = selection.concat(logoutRequestSignature);
373
+ }
374
+ selection = selection.concat(messageSignatureNode);
375
+ selection = selection.concat(assertionSignatureNode);
376
+ selection = selection.concat(LogoutResponseSignatureElementNode);
360
377
  // guarantee to have a signature in saml response
361
378
  if (selection.length === 0) {
362
- throw new Error('ERR_ZERO_SIGNATURE');
379
+ /** 判断有没有加密如果没有加密返回 [false, null]*/
380
+ if (encryptedAssertions.length > 0) {
381
+ if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
382
+ return [false, null, false, true]; // we return false now
383
+ }
384
+ if (encryptedAssertions.length > 1) {
385
+ throw new Error('ERR_MULTIPLE_ASSERTION');
386
+ }
387
+ return [false, null, true, true]; // return encryptedAssert
388
+ }
389
+ }
390
+ if (selection.length !== 0) {
391
+ /** 判断有没有加密如果没有加密返回 [false, null]*/
392
+ if (logoutRequestSignature.length === 0 && LogoutResponseSignatureElementNode.length === 0 && encryptedAssertions.length > 0) {
393
+ if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
394
+ return [false, null, true, false]; // we return false now
395
+ }
396
+ if (encryptedAssertions.length > 1) {
397
+ throw new Error('ERR_MULTIPLE_ASSERTION');
398
+ }
399
+ return [false, null, true, false]; // return encryptedAssert
400
+ }
363
401
  }
364
402
  // need to refactor later on
365
403
  for (const signatureNode of selection) {
@@ -407,7 +445,6 @@ const libSaml = () => {
407
445
  }
408
446
  }
409
447
  sig.loadSignature(signatureNode);
410
- doc.removeChild(signatureNode);
411
448
  verified = sig.checkSignature(doc.toString());
412
449
  // immediately throw error when any one of the signature is failed to get verified
413
450
  if (!verified) {
@@ -420,86 +457,56 @@ const libSaml = () => {
420
457
  throw new Error('NO_SIGNATURE_REFERENCES');
421
458
  }
422
459
  const signedVerifiedXML = sig.getSignedReferences()[0];
423
- const rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement;
460
+ const rootNode = docParser.parseFromString(signedVerifiedXML, 'application/xml').documentElement;
424
461
  // process the verified signature:
425
462
  // case 1, rootSignedDoc is a response:
426
- if (rootNode.localName === 'Response') {
463
+ if (rootNode?.localName === 'Response') {
427
464
  // try getting the Xml from the first assertion
428
- const EncryptedAssertions = select("./*[local-name()='EncryptedAssertion']", rootNode);
429
- const assertions = select("./*[local-name()='Assertion']", rootNode);
465
+ const EncryptedAssertions = select("./*[local-name()='EncryptedAssertion']",
466
+ // @ts-expect-error misssing Node properties are not needed
467
+ rootNode);
468
+ const assertions = select("./*[local-name()='Assertion']",
469
+ // @ts-expect-error misssing Node properties are not needed
470
+ rootNode);
430
471
  /**第三个参数代表是否加密*/
431
472
  // now we can process the assertion as an assertion
432
473
  if (EncryptedAssertions.length === 1) {
433
474
  /** 已加密*/
434
- return [true, EncryptedAssertions[0].toString(), true];
475
+ return [true, EncryptedAssertions[0].toString(), true, false];
435
476
  }
436
477
  if (assertions.length === 1) {
437
- return [true, assertions[0].toString(), false];
478
+ return [true, assertions[0].toString(), false, false];
438
479
  }
439
480
  }
440
- else if (rootNode.localName === 'Assertion') {
441
- return [true, rootNode.toString(), false];
481
+ else if (rootNode?.localName === 'Assertion') {
482
+ return [true, rootNode.toString(), false, false];
442
483
  }
443
- else if (rootNode.localName === 'EncryptedAssertion') {
444
- return [true, rootNode.toString(), true];
484
+ else if (rootNode?.localName === 'EncryptedAssertion') {
485
+ return [true, rootNode.toString(), true, false];
486
+ }
487
+ else if (rootNode?.localName === 'LogoutRequest') {
488
+ return [true, rootNode.toString(), false, false];
489
+ }
490
+ else if (rootNode?.localName === 'LogoutResponse') {
491
+ return [true, rootNode.toString(), false, false];
445
492
  }
446
493
  else {
447
- return [true, null]; // signature is valid. But there is no assertion node here. It could be metadata node, hence return null
494
+ return [true, null, false, false]; // signature is valid. But there is no assertion node here. It could be metadata node, hence return null
448
495
  }
449
496
  }
450
497
  // something has gone seriously wrong if we are still here
451
- throw new Error('ERR_ZERO_SIGNATURE');
452
- // response must be signed, either entire document or assertion
453
- // default we will take the assertion section under root
454
- /* if (messageSignatureNode.length === 1) {
455
- const node = select("/!*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/!*[local-name(.)='Assertion']", doc);
456
- if (node.length === 1) {
457
- assertionNode = node[0].toString();
458
- }
459
- }
460
-
461
- if (assertionSignatureNode.length === 1) {
462
- const verifiedAssertionInfo = extract(assertionSignatureNode[0].toString(), [{
463
- key: 'refURI',
464
- localPath: ['Signature', 'SignedInfo', 'Reference'],
465
- attributes: ['URI']
466
- }]);
467
- // get the assertion supposed to be the one should be verified
468
- const desiredAssertionInfo = extract(doc.toString(), [{
469
- key: 'id',
470
- localPath: ['~Response', 'Assertion'],
471
- attributes: ['ID']
472
- }]);
473
- // 5.4.2 References
474
- // SAML assertions and protocol messages MUST supply a value for the ID attribute on the root element of
475
- // the assertion or protocol message being signed. The assertion’s or protocol message's root element may
476
- // or may not be the root element of the actual XML document containing the signed assertion or protocol
477
- // message (e.g., it might be contained within a SOAP envelope).
478
- // Signatures MUST contain a single <ds:Reference> containing a same-document reference to the ID
479
- // attribute value of the root element of the assertion or protocol message being signed. For example, if the
480
- // ID attribute value is "foo", then the URI attribute in the <ds:Reference> element MUST be "#foo".
481
- if (verifiedAssertionInfo.refURI !== `#${desiredAssertionInfo.id}`) {
482
- throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
483
- }
484
- const verifiedDoc = extract(doc.toString(), [{
485
- key: 'assertion',
486
- localPath: ['~Response', 'Assertion'],
487
- attributes: [],
488
- context: true
489
- }]);
490
- assertionNode = verifiedDoc.assertion.toString();
491
- }
492
-
493
- return [verified, assertionNode];*/
498
+ return [false, null, false, true]; // return encryptedAssert
499
+ /* throw new Error('ERR_ZERO_SIGNATURE');*/
494
500
  },
495
501
  verifySignatureSoap(xml, opts) {
496
502
  const { dom } = getContext();
497
- const doc = dom.parseFromString(xml);
503
+ const doc = dom.parseFromString(xml, 'application/xml');
498
504
  const docParser = new DOMParser();
499
505
  let selection = [];
500
506
  if (opts.isAssertion) {
501
507
  // 断言模式下的专用逻辑
502
508
  const assertionSignatureXpath = "./*[local-name()='Signature']";
509
+ // @ts-expect-error misssing Node properties are not needed
503
510
  const signatureNode = select(assertionSignatureXpath, doc.documentElement);
504
511
  if (signatureNode.length === 0) {
505
512
  throw new Error('ERR_ASSERTION_SIGNATURE_NOT_FOUND');
@@ -524,8 +531,11 @@ const libSaml = () => {
524
531
  "/*[local-name()='SubjectConfirmation']" +
525
532
  "/*[local-name()='SubjectConfirmationData']" +
526
533
  "//*[local-name()='Assertion' or local-name()='Signature']";
534
+ // @ts-expect-error misssing Node properties are not needed
527
535
  const messageSignatureNode = select(messageSignatureXpath, doc);
536
+ // @ts-expect-error misssing Node properties are not needed
528
537
  const assertionSignatureNode = select(assertionSignatureXpath, doc);
538
+ // @ts-expect-error misssing Node properties are not needed
529
539
  const wrappingElementNode = select(wrappingElementsXPath, doc);
530
540
  // 检测包装攻击
531
541
  if (wrappingElementNode.length !== 0) {
@@ -590,9 +600,7 @@ const libSaml = () => {
590
600
  sig.loadSignature(signatureNode);
591
601
  // 使用原始 XML 进行验证
592
602
  verified = sig.checkSignature(xml);
593
- console.log("签名验证结果:", verified);
594
603
  if (!verified) {
595
- console.error("签名验证失败");
596
604
  throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
597
605
  }
598
606
  // 检查签名引用
@@ -600,12 +608,11 @@ const libSaml = () => {
600
608
  throw new Error('NO_SIGNATURE_REFERENCES');
601
609
  }
602
610
  const signedVerifiedXML = sig.getSignedReferences()[0];
603
- const verifiedDoc = docParser.parseFromString(signedVerifiedXML, 'text/xml');
611
+ const verifiedDoc = docParser.parseFromString(signedVerifiedXML, 'application/xml');
604
612
  const rootNode = verifiedDoc.documentElement;
605
- console.log("签名引用根节点:", rootNode.localName);
606
613
  // 断言模式专用返回逻辑
607
614
  if (opts.isAssertion) {
608
- if (rootNode.localName === 'Assertion') {
615
+ if (rootNode?.localName === 'Assertion') {
609
616
  return [true, rootNode.toString(), false];
610
617
  }
611
618
  else {
@@ -613,11 +620,14 @@ const libSaml = () => {
613
620
  }
614
621
  }
615
622
  // 处理已验证的签名
623
+ // @ts-expect-error misssing Node properties are not needed
616
624
  if (rootNode.localName === 'ArtifactResponse') {
617
625
  // 在 ArtifactResponse 中查找 Response
618
- const responseNodes = select("./*[local-name()='Response']", rootNode);
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);
619
630
  if (responseNodes.length === 0) {
620
- console.warn("ArtifactResponse 中没有找到 Response 元素");
621
631
  continue;
622
632
  }
623
633
  const responseNode = responseNodes[0];
@@ -632,9 +642,15 @@ const libSaml = () => {
632
642
  }
633
643
  }
634
644
  // 直接处理 Response
635
- else if (rootNode.localName === 'Response') {
636
- const encryptedAssertions = select("./*[local-name()='EncryptedAssertion']", rootNode);
637
- const assertions = select("./*[local-name()='Assertion']", rootNode);
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);
638
654
  if (encryptedAssertions.length === 1) {
639
655
  return [true, encryptedAssertions[0].toString(), true];
640
656
  }
@@ -643,15 +659,15 @@ const libSaml = () => {
643
659
  }
644
660
  }
645
661
  // 直接处理 Assertion
646
- else if (rootNode.localName === 'Assertion') {
662
+ else if (rootNode?.localName === 'Assertion') {
647
663
  return [true, rootNode.toString(), false];
648
664
  }
649
665
  // 直接处理 EncryptedAssertion
650
- else if (rootNode.localName === 'EncryptedAssertion') {
666
+ else if (rootNode?.localName === 'EncryptedAssertion') {
651
667
  return [true, rootNode.toString(), true];
652
668
  }
653
669
  else {
654
- console.warn("未知的根节点类型:", rootNode.localName);
670
+ console.warn("未知的根节点类型:", rootNode?.localName);
655
671
  }
656
672
  }
657
673
  throw new Error('ERR_ZERO_SIGNATURE');
@@ -694,59 +710,43 @@ const libSaml = () => {
694
710
  * @param signingAlgorithm - 签名算法 (默认 'rsa-sha256')
695
711
  * @returns 消息签名
696
712
  */
697
- constructMessageSignature(octetString, key, passphrase, isBase64 = true, signingAlgorithm = nrsaAliasMappingForNode[signatureAlgorithms.RSA_SHA256]) {
698
- try {
699
- // 1. 标准化输入数据
700
- const inputData = Buffer.isBuffer(octetString)
701
- ? octetString
702
- : Buffer.from(octetString, 'utf8');
703
- // 2. 创建签名器并设置算
704
- const signingAlgorithmValue = getSigningSchemeForNode(signingAlgorithm);
705
- const signer = createSign(signingAlgorithmValue);
706
- // 3. 加载私钥
707
- const privateKey = createPrivateKey({
708
- key: key,
709
- format: 'pem',
710
- passphrase: passphrase,
711
- encoding: 'utf8'
712
- });
713
- signer.write(octetString);
714
- signer.end();
715
- const signature = signer.sign(privateKey, 'base64');
716
- // 5. 处理编码输出
717
- return isBase64 ? signature.toString() : signature;
718
- }
719
- catch (error) {
720
- throw new Error(`SAML 签名失败: ${error.message}`);
721
- }
713
+ constructMessageSignature(octetString, key, passphrase, isBase64, signingAlgorithm) {
714
+ // Default returning base64 encoded signature
715
+ // Embed with node-rsa module
716
+ const decryptedKey = new nrsa(utility.readPrivateKey(key, passphrase), undefined, {
717
+ signingScheme: getSigningScheme(signingAlgorithm),
718
+ });
719
+ const signature = decryptedKey.sign(octetString);
720
+ // Use private key to sign data
721
+ return isBase64 !== false ? signature.toString('base64') : signature;
722
722
  },
723
- /* constructMessageSignature(
723
+ /* verifyMessageSignature(
724
+ metadata,
724
725
  octetString: string,
725
- key: string,
726
- passphrase?: string,
727
- isBase64?: boolean,
728
- signingAlgorithm?: string
726
+ signature: string | Buffer,
727
+ verifyAlgorithm?: string
729
728
  ) {
730
- // Default returning base64 encoded signature
731
- // Embed with node-rsa module
732
- const decryptedKey = new nrsa(
733
- utility.readPrivateKey(key, passphrase),
734
- undefined,
735
- {
736
- signingScheme: getSigningScheme(signingAlgorithm),
737
- }
738
- );
739
- const signature = decryptedKey.sign(octetString);
740
- // Use private key to sign data
741
- return isBase64 !== false ? signature.toString('base64') : signature;
729
+ const signCert = metadata.getX509Certificate(certUse.signing);
730
+ const signingScheme = getSigningSchemeForNode(verifyAlgorithm);
731
+ const verifier = createVerify(signingScheme);
732
+ verifier.update(octetString);
733
+ const isValid = verifier.verify(utility.getPublicKeyPemFromCertificate(signCert), Buffer.isBuffer(signature) ? signature : Buffer.from(signature, 'base64'));
734
+ return isValid
735
+
742
736
  },*/
737
+ /**
738
+ * @desc Verifies message signature
739
+ * @param {Metadata} metadata metadata object of identity provider or service provider
740
+ * @param {string} octetString see "Bindings for the OASIS Security Assertion Markup Language (SAML V2.0)" P.17/46
741
+ * @param {string} signature context of XML signature
742
+ * @param {string} verifyAlgorithm algorithm used to verify
743
+ * @return {boolean} verification result
744
+ */
743
745
  verifyMessageSignature(metadata, octetString, signature, verifyAlgorithm) {
744
746
  const signCert = metadata.getX509Certificate(certUse.signing);
745
- const signingScheme = getSigningSchemeForNode(verifyAlgorithm);
746
- const verifier = createVerify(signingScheme);
747
- verifier.update(octetString);
748
- const isValid = verifier.verify(utility.getPublicKeyPemFromCertificate(signCert), Buffer.isBuffer(signature) ? signature : Buffer.from(signature, 'base64'));
749
- return isValid;
747
+ const signingScheme = getSigningScheme(verifyAlgorithm);
748
+ const key = new nrsa(utility.getPublicKeyPemFromCertificate(signCert), 'public', { signingScheme });
749
+ return key.verify(Buffer.from(octetString), Buffer.from(signature));
750
750
  },
751
751
  /**
752
752
  * @desc Get the public key in string format
@@ -781,7 +781,8 @@ const libSaml = () => {
781
781
  const sourceEntitySetting = sourceEntity.entitySetting;
782
782
  const targetEntityMetadata = targetEntity.entityMeta;
783
783
  const { dom } = getContext();
784
- const doc = dom.parseFromString(xml);
784
+ const doc = dom.parseFromString(xml, 'application/xml');
785
+ // @ts-expect-error misssing Node properties are not needed
785
786
  const assertions = select("//*[local-name(.)='Assertion']", doc);
786
787
  if (!Array.isArray(assertions) || assertions.length === 0) {
787
788
  throw new Error('ERR_NO_ASSERTION');
@@ -799,7 +800,7 @@ const libSaml = () => {
799
800
  pem: Buffer.from(`-----BEGIN CERTIFICATE-----${targetEntityMetadata.getX509Certificate(certUse.encrypt)}-----END CERTIFICATE-----`),
800
801
  encryptionAlgorithm: sourceEntitySetting.dataEncryptionAlgorithm,
801
802
  keyEncryptionAlgorithm: sourceEntitySetting.keyEncryptionAlgorithm,
802
- keyEncryptionDigest: 'SHA-512',
803
+ /* keyEncryptionDigest: 'SHA-512',*/
803
804
  disallowEncryptionWithInsecureAlgorithm: true,
804
805
  warnInsecureAlgorithm: true
805
806
  }, (err, res) => {
@@ -810,7 +811,8 @@ const libSaml = () => {
810
811
  return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
811
812
  }
812
813
  const { encryptedAssertion: encAssertionPrefix } = sourceEntitySetting.tagPrefix;
813
- const encryptAssertionDoc = dom.parseFromString(`<${encAssertionPrefix}:EncryptedAssertion xmlns:${encAssertionPrefix}="${namespace.names.assertion}">${res}</${encAssertionPrefix}:EncryptedAssertion>`);
814
+ const encryptAssertionDoc = dom.parseFromString(`<${encAssertionPrefix}:EncryptedAssertion xmlns:${encAssertionPrefix}="${namespace.names.assertion}">${res}</${encAssertionPrefix}:EncryptedAssertion>`, 'application/xml');
815
+ // @ts-expect-error misssing Node properties are not needed
814
816
  doc.documentElement.replaceChild(encryptAssertionDoc.documentElement, rawAssertionNode);
815
817
  return resolve(utility.base64Encode(doc.toString()));
816
818
  });
@@ -837,7 +839,8 @@ const libSaml = () => {
837
839
  // Perform encryption depends on the setting of where the message is sent, default is false
838
840
  const hereSetting = here.entitySetting;
839
841
  const { dom } = getContext();
840
- const doc = dom.parseFromString(entireXML);
842
+ const doc = dom.parseFromString(entireXML, 'application/xml');
843
+ // @ts-expect-error misssing Node properties are not needed
841
844
  const encryptedAssertions = select("/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']", doc);
842
845
  if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
843
846
  throw new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION');
@@ -850,13 +853,13 @@ const libSaml = () => {
850
853
  key: utility.readPrivateKey(hereSetting.encPrivateKey, hereSetting.encPrivateKeyPass),
851
854
  }, (err, res) => {
852
855
  if (err) {
853
- console.error(err);
854
856
  return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_DECRYPTION'));
855
857
  }
856
858
  if (!res) {
857
859
  return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
858
860
  }
859
- const rawAssertionDoc = dom.parseFromString(res);
861
+ const rawAssertionDoc = dom.parseFromString(res, 'application/xml');
862
+ // @ts-ignore
860
863
  doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
861
864
  return resolve([doc.toString(), res]);
862
865
  });
@@ -872,11 +875,14 @@ const libSaml = () => {
872
875
  const { dom } = getContext();
873
876
  try {
874
877
  // 1. 解析 XML
875
- const doc = dom.parseFromString(entireXML);
878
+ // @ts-ignore
879
+ const doc = dom.parseFromString(entireXML, 'application/xml');
876
880
  // 2. 定位加密断言
877
881
  const encryptedAssertions = select("/*[local-name()='Envelope']/*[local-name()='Body']" +
878
882
  "/*[local-name()='ArtifactResponse']/*[local-name()='Response']" +
879
- "/*[local-name()='EncryptedAssertion']", doc);
883
+ "/*[local-name()='EncryptedAssertion']",
884
+ // @ts-ignore
885
+ doc);
880
886
  if (!encryptedAssertions || encryptedAssertions.length === 0) {
881
887
  throw new Error('ERR_ENCRYPTED_ASSERTION_NOT_FOUND');
882
888
  }
@@ -890,7 +896,6 @@ const libSaml = () => {
890
896
  const decryptedAssertion = await new Promise((resolve, reject) => {
891
897
  xmlenc.decrypt(encAssertionNode.toString(), { key: privateKey }, (err, result) => {
892
898
  if (err) {
893
- console.error('解密错误:', err);
894
899
  return reject(new Error('ERR_ASSERTION_DECRYPTION_FAILED'));
895
900
  }
896
901
  if (!result) {
@@ -900,27 +905,28 @@ const libSaml = () => {
900
905
  });
901
906
  });
902
907
  // 5. 创建解密断言的 DOM
903
- const decryptedDoc = dom.parseFromString(decryptedAssertion);
908
+ // @ts-ignore
909
+ const decryptedDoc = dom.parseFromString(decryptedAssertion, 'application/xml');
904
910
  const decryptedAssertionNode = decryptedDoc.documentElement;
905
911
  // 6. 替换加密断言为解密后的断言
906
912
  const parentNode = encAssertionNode.parentNode;
907
913
  if (!parentNode) {
908
914
  throw new Error('ERR_NO_PARENT_NODE_FOR_ENCRYPTED_ASSERTION');
909
915
  }
916
+ // @ts-ignore
910
917
  parentNode.replaceChild(decryptedAssertionNode, encAssertionNode);
911
918
  // 7. 序列化更新后的文档
912
919
  const updatedSoapXml = doc.toString();
913
920
  return [updatedSoapXml, decryptedAssertion];
914
921
  }
915
922
  catch (error) {
916
- console.error('SOAP断言解密失败:', error);
917
923
  throw new Error('ERR_SOAP_ASSERTION_DECRYPTION');
918
924
  }
919
925
  },
920
926
  /**
921
927
  * @desc Check if the xml string is valid and bounded
922
928
  */
923
- async isValidXml(input) {
929
+ async isValidXml(input, soap = false) {
924
930
  // check if global api contains the validate function
925
931
  const { validate } = getContext();
926
932
  /**
@@ -934,7 +940,7 @@ const libSaml = () => {
934
940
  return Promise.reject('Your application is potentially vulnerable because no validation function found. Please read the documentation on how to setup the validator. (https://github.com/tngan/samlify#installation)');
935
941
  }
936
942
  try {
937
- return await validate(input);
943
+ return await validate(input, soap);
938
944
  }
939
945
  catch (e) {
940
946
  throw e;