samlesa 2.16.5 → 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.

Files changed (59) hide show
  1. package/README.md +30 -50
  2. package/build/src/binding-post.js +45 -31
  3. package/build/src/binding-redirect.js +88 -3
  4. package/build/src/binding-simplesign.js +0 -1
  5. package/build/src/entity-idp.js +1 -5
  6. package/build/src/entity-sp.js +115 -23
  7. package/build/src/extractor.js +29 -4
  8. package/build/src/flow.js +36 -103
  9. package/build/src/libsaml.js +172 -162
  10. package/build/src/metadata-sp.js +2 -0
  11. package/build/src/metadata.js +0 -2
  12. package/build/src/schema/saml-schema-ecp-2.0.xsd +1 -1
  13. package/build/src/schema/saml-schema-metadata-2.0.xsd +3 -3
  14. package/build/src/schema/saml-schema-protocol-2.0.xsd +1 -1
  15. package/build/src/schema/{env.xsd → soap-envelope.xsd} +1 -33
  16. package/build/src/schema/xml.xsd +88 -0
  17. package/build/src/schemaValidator.js +29 -12
  18. package/build/src/utility.js +12 -7
  19. package/package.json +14 -20
  20. package/types/src/api.d.ts +3 -3
  21. package/types/src/api.d.ts.map +1 -1
  22. package/types/src/binding-post.d.ts +22 -22
  23. package/types/src/binding-post.d.ts.map +1 -1
  24. package/types/src/binding-redirect.d.ts +14 -1
  25. package/types/src/binding-redirect.d.ts.map +1 -1
  26. package/types/src/binding-simplesign.d.ts.map +1 -1
  27. package/types/src/entity-idp.d.ts +3 -4
  28. package/types/src/entity-idp.d.ts.map +1 -1
  29. package/types/src/entity-sp.d.ts +44 -21
  30. package/types/src/entity-sp.d.ts.map +1 -1
  31. package/types/src/entity.d.ts.map +1 -1
  32. package/types/src/extractor.d.ts +5 -0
  33. package/types/src/extractor.d.ts.map +1 -1
  34. package/types/src/flow.d.ts.map +1 -1
  35. package/types/src/libsaml.d.ts +15 -4
  36. package/types/src/libsaml.d.ts.map +1 -1
  37. package/types/src/metadata-sp.d.ts.map +1 -1
  38. package/types/src/metadata.d.ts.map +1 -1
  39. package/types/src/schemaValidator.d.ts +1 -1
  40. package/types/src/schemaValidator.d.ts.map +1 -1
  41. package/types/src/utility.d.ts.map +1 -1
  42. package/build/index.js.map +0 -1
  43. package/build/src/api.js.map +0 -1
  44. package/build/src/binding-post.js.map +0 -1
  45. package/build/src/binding-redirect.js.map +0 -1
  46. package/build/src/binding-simplesign.js.map +0 -1
  47. package/build/src/entity-idp.js.map +0 -1
  48. package/build/src/entity-sp.js.map +0 -1
  49. package/build/src/entity.js.map +0 -1
  50. package/build/src/extractor.js.map +0 -1
  51. package/build/src/flow.js.map +0 -1
  52. package/build/src/libsaml.js.map +0 -1
  53. package/build/src/metadata-idp.js.map +0 -1
  54. package/build/src/metadata-sp.js.map +0 -1
  55. package/build/src/metadata.js.map +0 -1
  56. package/build/src/types.js.map +0 -1
  57. package/build/src/urn.js.map +0 -1
  58. package/build/src/utility.js.map +0 -1
  59. package/build/src/validator.js.map +0 -1
@@ -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',
@@ -70,19 +55,22 @@ const libSaml = () => {
70
55
  context: '<samlp:AuthnRequest 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}" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="{AssertionConsumerServiceURL}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:NameIDPolicy Format="{NameIDFormat}" AllowCreate="{AllowCreate}"/></samlp:AuthnRequest>',
71
56
  };
72
57
  /**
73
- * @desc Default art request template
58
+ * @desc Default logout request template
74
59
  * @type {LogoutRequestTemplate}
75
60
  */
76
61
  const defaultLogoutRequestTemplate = {
77
62
  context: '<samlp:LogoutRequest 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}"><saml:Issuer>{Issuer}</saml:Issuer><saml:NameID Format="{NameIDFormat}">{NameID}</saml:NameID></samlp:LogoutRequest>',
78
63
  };
79
64
  /**
80
- * @desc Default logout request template
65
+ * @desc Default art request template
81
66
  * @type {LogoutRequestTemplate}
82
67
  */
83
68
  const defaultArtifactResolveTemplate = {
84
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>`,
85
70
  };
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>`,
73
+ };
86
74
  /**
87
75
  * @desc Default AttributeStatement template
88
76
  * @type {AttributeStatementTemplate}
@@ -123,6 +111,21 @@ const libSaml = () => {
123
111
  const defaultLogoutResponseTemplate = {
124
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>',
125
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
+ }
126
129
  function validateAndInflateSamlResponse(urlEncodedResponse) {
127
130
  // 3. 尝试DEFLATE解压(SAML规范要求使用原始DEFLATE)
128
131
  let xml = "";
@@ -130,18 +133,14 @@ const libSaml = () => {
130
133
  try { // 1. URL解码
131
134
  const base64Encoded = decodeURIComponent(urlEncodedResponse);
132
135
  // 2. Base64解码为Uint8Array
133
- const binaryStr = atob(base64Encoded);
134
- const compressedData = new Uint8Array(binaryStr.length);
135
- for (let i = 0; i < binaryStr.length; i++) {
136
- compressedData[i] = binaryStr.charCodeAt(i);
137
- }
138
- xml = inflate(compressedData, { to: 'string', raw: true });
136
+ xml = inflateString(base64Encoded);
139
137
  }
140
138
  catch (inflateError) {
141
139
  // 4. 解压失败,尝试直接解析为未压缩的XML
140
+ console.log("解压失败---------------------");
142
141
  try {
143
142
  const base64Encoded = decodeURIComponent(urlEncodedResponse);
144
- xml = Buffer.from(base64Encoded, 'base64').toString('utf-8');
143
+ xml = atob(base64Encoded);
145
144
  return { compressed: false, xml, error: null };
146
145
  }
147
146
  catch (xmlError) {
@@ -209,6 +208,7 @@ const libSaml = () => {
209
208
  createXPath,
210
209
  getQueryParamByType,
211
210
  defaultLoginRequestTemplate,
211
+ defaultArtAuthnRequestTemplate,
212
212
  defaultArtifactResolveTemplate,
213
213
  defaultLoginResponseTemplate,
214
214
  defaultAttributeStatementTemplate,
@@ -273,6 +273,9 @@ const libSaml = () => {
273
273
  };
274
274
  // 生成 XML(关闭自动声明头)
275
275
  const xmlString = xml([attributeStatement], { declaration: false });
276
+ if (xmlString.trim() === '<saml:AttributeStatement></saml:AttributeStatement>') {
277
+ return '';
278
+ }
276
279
  return xmlString.trim();
277
280
  },
278
281
  /**
@@ -320,7 +323,7 @@ const libSaml = () => {
320
323
  else {
321
324
  sig.computeSignature(rawSamlMessage);
322
325
  }
323
- return isBase64Output !== false ? utility.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
326
+ return isBase64Output ? utility.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
324
327
  },
325
328
  /**
326
329
  * @desc Verify the XML signature
@@ -333,29 +336,68 @@ const libSaml = () => {
333
336
  // tslint:disable-next-line:no-shadowed-variable
334
337
  verifySignature(xml, opts) {
335
338
  const { dom } = getContext();
336
- const doc = dom.parseFromString(xml);
339
+ const doc = dom.parseFromString(xml, 'application/xml');
337
340
  const docParser = new DOMParser();
338
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']";
339
344
  // message signature (logout response / saml response)
340
345
  const messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']";
341
346
  // assertion signature (logout response / saml response)
342
347
  const assertionSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']/*[local-name(.)='Signature']";
343
348
  // check if there is a potential malicious wrapping signature
344
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];
345
354
  // select the signature node
346
355
  let selection = [];
356
+ // @ts-expect-error misssing Node properties are not needed
347
357
  const messageSignatureNode = select(messageSignatureXpath, doc);
358
+ // @ts-expect-error misssing Node properties are not needed
348
359
  const assertionSignatureNode = select(assertionSignatureXpath, doc);
360
+ // @ts-expect-error misssing Node properties are not needed
349
361
  const wrappingElementNode = select(wrappingElementsXPath, doc);
350
- selection = selection.concat(messageSignatureNode);
351
- selection = selection.concat(assertionSignatureNode);
362
+ // @ts-expect-error misssing Node properties are not needed
363
+ const LogoutResponseSignatureElementNode = select(LogoutResponseSignatureXpath, doc);
352
364
  // try to catch potential wrapping attack
353
365
  if (wrappingElementNode.length !== 0) {
354
366
  throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
355
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);
356
377
  // guarantee to have a signature in saml response
357
378
  if (selection.length === 0) {
358
- 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
+ }
359
401
  }
360
402
  // need to refactor later on
361
403
  for (const signatureNode of selection) {
@@ -403,7 +445,6 @@ const libSaml = () => {
403
445
  }
404
446
  }
405
447
  sig.loadSignature(signatureNode);
406
- doc.removeChild(signatureNode);
407
448
  verified = sig.checkSignature(doc.toString());
408
449
  // immediately throw error when any one of the signature is failed to get verified
409
450
  if (!verified) {
@@ -416,86 +457,56 @@ const libSaml = () => {
416
457
  throw new Error('NO_SIGNATURE_REFERENCES');
417
458
  }
418
459
  const signedVerifiedXML = sig.getSignedReferences()[0];
419
- const rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement;
460
+ const rootNode = docParser.parseFromString(signedVerifiedXML, 'application/xml').documentElement;
420
461
  // process the verified signature:
421
462
  // case 1, rootSignedDoc is a response:
422
- if (rootNode.localName === 'Response') {
463
+ if (rootNode?.localName === 'Response') {
423
464
  // try getting the Xml from the first assertion
424
- const EncryptedAssertions = select("./*[local-name()='EncryptedAssertion']", rootNode);
425
- 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);
426
471
  /**第三个参数代表是否加密*/
427
472
  // now we can process the assertion as an assertion
428
473
  if (EncryptedAssertions.length === 1) {
429
474
  /** 已加密*/
430
- return [true, EncryptedAssertions[0].toString(), true];
475
+ return [true, EncryptedAssertions[0].toString(), true, false];
431
476
  }
432
477
  if (assertions.length === 1) {
433
- return [true, assertions[0].toString(), false];
478
+ return [true, assertions[0].toString(), false, false];
434
479
  }
435
480
  }
436
- else if (rootNode.localName === 'Assertion') {
437
- return [true, rootNode.toString(), false];
481
+ else if (rootNode?.localName === 'Assertion') {
482
+ return [true, rootNode.toString(), false, false];
438
483
  }
439
- else if (rootNode.localName === 'EncryptedAssertion') {
440
- 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];
441
492
  }
442
493
  else {
443
- 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
444
495
  }
445
496
  }
446
497
  // something has gone seriously wrong if we are still here
447
- throw new Error('ERR_ZERO_SIGNATURE');
448
- // response must be signed, either entire document or assertion
449
- // default we will take the assertion section under root
450
- /* if (messageSignatureNode.length === 1) {
451
- const node = select("/!*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/!*[local-name(.)='Assertion']", doc);
452
- if (node.length === 1) {
453
- assertionNode = node[0].toString();
454
- }
455
- }
456
-
457
- if (assertionSignatureNode.length === 1) {
458
- const verifiedAssertionInfo = extract(assertionSignatureNode[0].toString(), [{
459
- key: 'refURI',
460
- localPath: ['Signature', 'SignedInfo', 'Reference'],
461
- attributes: ['URI']
462
- }]);
463
- // get the assertion supposed to be the one should be verified
464
- const desiredAssertionInfo = extract(doc.toString(), [{
465
- key: 'id',
466
- localPath: ['~Response', 'Assertion'],
467
- attributes: ['ID']
468
- }]);
469
- // 5.4.2 References
470
- // SAML assertions and protocol messages MUST supply a value for the ID attribute on the root element of
471
- // the assertion or protocol message being signed. The assertion’s or protocol message's root element may
472
- // or may not be the root element of the actual XML document containing the signed assertion or protocol
473
- // message (e.g., it might be contained within a SOAP envelope).
474
- // Signatures MUST contain a single <ds:Reference> containing a same-document reference to the ID
475
- // attribute value of the root element of the assertion or protocol message being signed. For example, if the
476
- // ID attribute value is "foo", then the URI attribute in the <ds:Reference> element MUST be "#foo".
477
- if (verifiedAssertionInfo.refURI !== `#${desiredAssertionInfo.id}`) {
478
- throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
479
- }
480
- const verifiedDoc = extract(doc.toString(), [{
481
- key: 'assertion',
482
- localPath: ['~Response', 'Assertion'],
483
- attributes: [],
484
- context: true
485
- }]);
486
- assertionNode = verifiedDoc.assertion.toString();
487
- }
488
-
489
- return [verified, assertionNode];*/
498
+ return [false, null, false, true]; // return encryptedAssert
499
+ /* throw new Error('ERR_ZERO_SIGNATURE');*/
490
500
  },
491
501
  verifySignatureSoap(xml, opts) {
492
502
  const { dom } = getContext();
493
- const doc = dom.parseFromString(xml);
503
+ const doc = dom.parseFromString(xml, 'application/xml');
494
504
  const docParser = new DOMParser();
495
505
  let selection = [];
496
506
  if (opts.isAssertion) {
497
507
  // 断言模式下的专用逻辑
498
508
  const assertionSignatureXpath = "./*[local-name()='Signature']";
509
+ // @ts-expect-error misssing Node properties are not needed
499
510
  const signatureNode = select(assertionSignatureXpath, doc.documentElement);
500
511
  if (signatureNode.length === 0) {
501
512
  throw new Error('ERR_ASSERTION_SIGNATURE_NOT_FOUND');
@@ -520,8 +531,11 @@ const libSaml = () => {
520
531
  "/*[local-name()='SubjectConfirmation']" +
521
532
  "/*[local-name()='SubjectConfirmationData']" +
522
533
  "//*[local-name()='Assertion' or local-name()='Signature']";
534
+ // @ts-expect-error misssing Node properties are not needed
523
535
  const messageSignatureNode = select(messageSignatureXpath, doc);
536
+ // @ts-expect-error misssing Node properties are not needed
524
537
  const assertionSignatureNode = select(assertionSignatureXpath, doc);
538
+ // @ts-expect-error misssing Node properties are not needed
525
539
  const wrappingElementNode = select(wrappingElementsXPath, doc);
526
540
  // 检测包装攻击
527
541
  if (wrappingElementNode.length !== 0) {
@@ -586,9 +600,7 @@ const libSaml = () => {
586
600
  sig.loadSignature(signatureNode);
587
601
  // 使用原始 XML 进行验证
588
602
  verified = sig.checkSignature(xml);
589
- console.log("签名验证结果:", verified);
590
603
  if (!verified) {
591
- console.error("签名验证失败");
592
604
  throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
593
605
  }
594
606
  // 检查签名引用
@@ -596,12 +608,11 @@ const libSaml = () => {
596
608
  throw new Error('NO_SIGNATURE_REFERENCES');
597
609
  }
598
610
  const signedVerifiedXML = sig.getSignedReferences()[0];
599
- const verifiedDoc = docParser.parseFromString(signedVerifiedXML, 'text/xml');
611
+ const verifiedDoc = docParser.parseFromString(signedVerifiedXML, 'application/xml');
600
612
  const rootNode = verifiedDoc.documentElement;
601
- console.log("签名引用根节点:", rootNode.localName);
602
613
  // 断言模式专用返回逻辑
603
614
  if (opts.isAssertion) {
604
- if (rootNode.localName === 'Assertion') {
615
+ if (rootNode?.localName === 'Assertion') {
605
616
  return [true, rootNode.toString(), false];
606
617
  }
607
618
  else {
@@ -609,11 +620,14 @@ const libSaml = () => {
609
620
  }
610
621
  }
611
622
  // 处理已验证的签名
623
+ // @ts-expect-error misssing Node properties are not needed
612
624
  if (rootNode.localName === 'ArtifactResponse') {
613
625
  // 在 ArtifactResponse 中查找 Response
614
- 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);
615
630
  if (responseNodes.length === 0) {
616
- console.warn("ArtifactResponse 中没有找到 Response 元素");
617
631
  continue;
618
632
  }
619
633
  const responseNode = responseNodes[0];
@@ -628,9 +642,15 @@ const libSaml = () => {
628
642
  }
629
643
  }
630
644
  // 直接处理 Response
631
- else if (rootNode.localName === 'Response') {
632
- const encryptedAssertions = select("./*[local-name()='EncryptedAssertion']", rootNode);
633
- 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);
634
654
  if (encryptedAssertions.length === 1) {
635
655
  return [true, encryptedAssertions[0].toString(), true];
636
656
  }
@@ -639,15 +659,15 @@ const libSaml = () => {
639
659
  }
640
660
  }
641
661
  // 直接处理 Assertion
642
- else if (rootNode.localName === 'Assertion') {
662
+ else if (rootNode?.localName === 'Assertion') {
643
663
  return [true, rootNode.toString(), false];
644
664
  }
645
665
  // 直接处理 EncryptedAssertion
646
- else if (rootNode.localName === 'EncryptedAssertion') {
666
+ else if (rootNode?.localName === 'EncryptedAssertion') {
647
667
  return [true, rootNode.toString(), true];
648
668
  }
649
669
  else {
650
- console.warn("未知的根节点类型:", rootNode.localName);
670
+ console.warn("未知的根节点类型:", rootNode?.localName);
651
671
  }
652
672
  }
653
673
  throw new Error('ERR_ZERO_SIGNATURE');
@@ -690,59 +710,43 @@ const libSaml = () => {
690
710
  * @param signingAlgorithm - 签名算法 (默认 'rsa-sha256')
691
711
  * @returns 消息签名
692
712
  */
693
- constructMessageSignature(octetString, key, passphrase, isBase64 = true, signingAlgorithm = nrsaAliasMappingForNode[signatureAlgorithms.RSA_SHA256]) {
694
- try {
695
- // 1. 标准化输入数据
696
- const inputData = Buffer.isBuffer(octetString)
697
- ? octetString
698
- : Buffer.from(octetString, 'utf8');
699
- // 2. 创建签名器并设置算
700
- const signingAlgorithmValue = getSigningSchemeForNode(signingAlgorithm);
701
- const signer = createSign(signingAlgorithmValue);
702
- // 3. 加载私钥
703
- const privateKey = createPrivateKey({
704
- key: key,
705
- format: 'pem',
706
- passphrase: passphrase,
707
- encoding: 'utf8'
708
- });
709
- signer.write(octetString);
710
- signer.end();
711
- const signature = signer.sign(privateKey, 'base64');
712
- // 5. 处理编码输出
713
- return isBase64 ? signature.toString() : signature;
714
- }
715
- catch (error) {
716
- throw new Error(`SAML 签名失败: ${error.message}`);
717
- }
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;
718
722
  },
719
- /* constructMessageSignature(
723
+ /* verifyMessageSignature(
724
+ metadata,
720
725
  octetString: string,
721
- key: string,
722
- passphrase?: string,
723
- isBase64?: boolean,
724
- signingAlgorithm?: string
726
+ signature: string | Buffer,
727
+ verifyAlgorithm?: string
725
728
  ) {
726
- // Default returning base64 encoded signature
727
- // Embed with node-rsa module
728
- const decryptedKey = new nrsa(
729
- utility.readPrivateKey(key, passphrase),
730
- undefined,
731
- {
732
- signingScheme: getSigningScheme(signingAlgorithm),
733
- }
734
- );
735
- const signature = decryptedKey.sign(octetString);
736
- // Use private key to sign data
737
- 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
+
738
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
+ */
739
745
  verifyMessageSignature(metadata, octetString, signature, verifyAlgorithm) {
740
746
  const signCert = metadata.getX509Certificate(certUse.signing);
741
- const signingScheme = getSigningSchemeForNode(verifyAlgorithm);
742
- const verifier = createVerify(signingScheme);
743
- verifier.update(octetString);
744
- const isValid = verifier.verify(utility.getPublicKeyPemFromCertificate(signCert), Buffer.isBuffer(signature) ? signature : Buffer.from(signature, 'base64'));
745
- 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));
746
750
  },
747
751
  /**
748
752
  * @desc Get the public key in string format
@@ -777,7 +781,8 @@ const libSaml = () => {
777
781
  const sourceEntitySetting = sourceEntity.entitySetting;
778
782
  const targetEntityMetadata = targetEntity.entityMeta;
779
783
  const { dom } = getContext();
780
- const doc = dom.parseFromString(xml);
784
+ const doc = dom.parseFromString(xml, 'application/xml');
785
+ // @ts-expect-error misssing Node properties are not needed
781
786
  const assertions = select("//*[local-name(.)='Assertion']", doc);
782
787
  if (!Array.isArray(assertions) || assertions.length === 0) {
783
788
  throw new Error('ERR_NO_ASSERTION');
@@ -795,7 +800,7 @@ const libSaml = () => {
795
800
  pem: Buffer.from(`-----BEGIN CERTIFICATE-----${targetEntityMetadata.getX509Certificate(certUse.encrypt)}-----END CERTIFICATE-----`),
796
801
  encryptionAlgorithm: sourceEntitySetting.dataEncryptionAlgorithm,
797
802
  keyEncryptionAlgorithm: sourceEntitySetting.keyEncryptionAlgorithm,
798
- keyEncryptionDigest: 'SHA-512',
803
+ /* keyEncryptionDigest: 'SHA-512',*/
799
804
  disallowEncryptionWithInsecureAlgorithm: true,
800
805
  warnInsecureAlgorithm: true
801
806
  }, (err, res) => {
@@ -806,7 +811,8 @@ const libSaml = () => {
806
811
  return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
807
812
  }
808
813
  const { encryptedAssertion: encAssertionPrefix } = sourceEntitySetting.tagPrefix;
809
- 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
810
816
  doc.documentElement.replaceChild(encryptAssertionDoc.documentElement, rawAssertionNode);
811
817
  return resolve(utility.base64Encode(doc.toString()));
812
818
  });
@@ -833,7 +839,8 @@ const libSaml = () => {
833
839
  // Perform encryption depends on the setting of where the message is sent, default is false
834
840
  const hereSetting = here.entitySetting;
835
841
  const { dom } = getContext();
836
- const doc = dom.parseFromString(entireXML);
842
+ const doc = dom.parseFromString(entireXML, 'application/xml');
843
+ // @ts-expect-error misssing Node properties are not needed
837
844
  const encryptedAssertions = select("/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']", doc);
838
845
  if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
839
846
  throw new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION');
@@ -846,13 +853,13 @@ const libSaml = () => {
846
853
  key: utility.readPrivateKey(hereSetting.encPrivateKey, hereSetting.encPrivateKeyPass),
847
854
  }, (err, res) => {
848
855
  if (err) {
849
- console.error(err);
850
856
  return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_DECRYPTION'));
851
857
  }
852
858
  if (!res) {
853
859
  return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
854
860
  }
855
- const rawAssertionDoc = dom.parseFromString(res);
861
+ const rawAssertionDoc = dom.parseFromString(res, 'application/xml');
862
+ // @ts-ignore
856
863
  doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
857
864
  return resolve([doc.toString(), res]);
858
865
  });
@@ -868,11 +875,14 @@ const libSaml = () => {
868
875
  const { dom } = getContext();
869
876
  try {
870
877
  // 1. 解析 XML
871
- const doc = dom.parseFromString(entireXML);
878
+ // @ts-ignore
879
+ const doc = dom.parseFromString(entireXML, 'application/xml');
872
880
  // 2. 定位加密断言
873
881
  const encryptedAssertions = select("/*[local-name()='Envelope']/*[local-name()='Body']" +
874
882
  "/*[local-name()='ArtifactResponse']/*[local-name()='Response']" +
875
- "/*[local-name()='EncryptedAssertion']", doc);
883
+ "/*[local-name()='EncryptedAssertion']",
884
+ // @ts-ignore
885
+ doc);
876
886
  if (!encryptedAssertions || encryptedAssertions.length === 0) {
877
887
  throw new Error('ERR_ENCRYPTED_ASSERTION_NOT_FOUND');
878
888
  }
@@ -886,7 +896,6 @@ const libSaml = () => {
886
896
  const decryptedAssertion = await new Promise((resolve, reject) => {
887
897
  xmlenc.decrypt(encAssertionNode.toString(), { key: privateKey }, (err, result) => {
888
898
  if (err) {
889
- console.error('解密错误:', err);
890
899
  return reject(new Error('ERR_ASSERTION_DECRYPTION_FAILED'));
891
900
  }
892
901
  if (!result) {
@@ -896,27 +905,28 @@ const libSaml = () => {
896
905
  });
897
906
  });
898
907
  // 5. 创建解密断言的 DOM
899
- const decryptedDoc = dom.parseFromString(decryptedAssertion);
908
+ // @ts-ignore
909
+ const decryptedDoc = dom.parseFromString(decryptedAssertion, 'application/xml');
900
910
  const decryptedAssertionNode = decryptedDoc.documentElement;
901
911
  // 6. 替换加密断言为解密后的断言
902
912
  const parentNode = encAssertionNode.parentNode;
903
913
  if (!parentNode) {
904
914
  throw new Error('ERR_NO_PARENT_NODE_FOR_ENCRYPTED_ASSERTION');
905
915
  }
916
+ // @ts-ignore
906
917
  parentNode.replaceChild(decryptedAssertionNode, encAssertionNode);
907
918
  // 7. 序列化更新后的文档
908
919
  const updatedSoapXml = doc.toString();
909
920
  return [updatedSoapXml, decryptedAssertion];
910
921
  }
911
922
  catch (error) {
912
- console.error('SOAP断言解密失败:', error);
913
923
  throw new Error('ERR_SOAP_ASSERTION_DECRYPTION');
914
924
  }
915
925
  },
916
926
  /**
917
927
  * @desc Check if the xml string is valid and bounded
918
928
  */
919
- async isValidXml(input) {
929
+ async isValidXml(input, soap = false) {
920
930
  // check if global api contains the validate function
921
931
  const { validate } = getContext();
922
932
  /**
@@ -930,7 +940,7 @@ const libSaml = () => {
930
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)');
931
941
  }
932
942
  try {
933
- return await validate(input);
943
+ return await validate(input, soap);
934
944
  }
935
945
  catch (e) {
936
946
  throw e;
@@ -71,8 +71,10 @@ export class SpMetadata extends Metadata {
71
71
  });
72
72
  }
73
73
  if (isNonEmptyArray(artifactResolutionService)) {
74
+ let indexCount = 0;
74
75
  artifactResolutionService.forEach(a => {
75
76
  const attr = {
77
+ index: String(indexCount++),
76
78
  Binding: a.Binding,
77
79
  Location: a.Location,
78
80
  };