samlesa 2.16.6 → 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 (82) hide show
  1. package/README.md +30 -50
  2. package/build/index.js +2 -1
  3. package/build/src/binding-artifact.js +330 -146
  4. package/build/src/binding-post.js +45 -31
  5. package/build/src/binding-redirect.js +0 -10
  6. package/build/src/binding-simplesign.js +0 -1
  7. package/build/src/entity-idp.js +1 -5
  8. package/build/src/entity-sp.js +21 -96
  9. package/build/src/extractor.js +48 -4
  10. package/build/src/flow.js +24 -166
  11. package/build/src/libsaml.js +468 -264
  12. package/build/src/libsamlSoap.js +115 -0
  13. package/build/src/schema/xml.xsd +88 -88
  14. package/build/src/schemaValidator.js +5 -13
  15. package/build/src/soap.js +123 -3
  16. package/build/src/utility.js +12 -7
  17. package/package.json +77 -81
  18. package/types/api.d.ts +15 -0
  19. package/types/api.d.ts.map +1 -0
  20. package/types/binding-post.d.ts +48 -0
  21. package/types/binding-post.d.ts.map +1 -0
  22. package/types/binding-redirect.d.ts +54 -0
  23. package/types/binding-redirect.d.ts.map +1 -0
  24. package/types/binding-simplesign.d.ts +41 -0
  25. package/types/binding-simplesign.d.ts.map +1 -0
  26. package/types/entity-idp.d.ts +38 -0
  27. package/types/entity-idp.d.ts.map +1 -0
  28. package/types/entity-sp.d.ts +38 -0
  29. package/types/entity-sp.d.ts.map +1 -0
  30. package/types/entity.d.ts +100 -0
  31. package/types/entity.d.ts.map +1 -0
  32. package/types/extractor.d.ts +26 -0
  33. package/types/extractor.d.ts.map +1 -0
  34. package/types/flow.d.ts +7 -0
  35. package/types/flow.d.ts.map +1 -0
  36. package/types/index.d.ts +2 -1
  37. package/types/index.d.ts.map +1 -1
  38. package/types/libsaml.d.ts +208 -0
  39. package/types/libsaml.d.ts.map +1 -0
  40. package/types/metadata-idp.d.ts +25 -0
  41. package/types/metadata-idp.d.ts.map +1 -0
  42. package/types/metadata-sp.d.ts +37 -0
  43. package/types/metadata-sp.d.ts.map +1 -0
  44. package/types/metadata.d.ts +58 -0
  45. package/types/metadata.d.ts.map +1 -0
  46. package/types/src/api.d.ts +3 -3
  47. package/types/src/api.d.ts.map +1 -1
  48. package/types/src/binding-artifact.d.ts +24 -29
  49. package/types/src/binding-artifact.d.ts.map +1 -1
  50. package/types/src/binding-post.d.ts +22 -22
  51. package/types/src/binding-post.d.ts.map +1 -1
  52. package/types/src/binding-redirect.d.ts.map +1 -1
  53. package/types/src/binding-simplesign.d.ts.map +1 -1
  54. package/types/src/entity-idp.d.ts +3 -4
  55. package/types/src/entity-idp.d.ts.map +1 -1
  56. package/types/src/entity-sp.d.ts +13 -24
  57. package/types/src/entity-sp.d.ts.map +1 -1
  58. package/types/src/entity.d.ts.map +1 -1
  59. package/types/src/extractor.d.ts +22 -0
  60. package/types/src/extractor.d.ts.map +1 -1
  61. package/types/src/flow.d.ts +1 -0
  62. package/types/src/flow.d.ts.map +1 -1
  63. package/types/src/libsaml.d.ts +16 -7
  64. package/types/src/libsaml.d.ts.map +1 -1
  65. package/types/src/libsamlSoap.d.ts +7 -0
  66. package/types/src/libsamlSoap.d.ts.map +1 -0
  67. package/types/src/schemaValidator.d.ts +1 -1
  68. package/types/src/schemaValidator.d.ts.map +1 -1
  69. package/types/src/soap.d.ts +33 -0
  70. package/types/src/soap.d.ts.map +1 -1
  71. package/types/src/utility.d.ts.map +1 -1
  72. package/types/src/validator.d.ts.map +1 -1
  73. package/types/types.d.ts +128 -0
  74. package/types/types.d.ts.map +1 -0
  75. package/types/urn.d.ts +195 -0
  76. package/types/urn.d.ts.map +1 -0
  77. package/types/utility.d.ts +133 -0
  78. package/types/utility.d.ts.map +1 -0
  79. package/types/validator.d.ts +4 -0
  80. package/types/validator.d.ts.map +1 -0
  81. package/build/src/schema/XMLSchema.dtd +0 -402
  82. package/build/src/schema/datatypes.dtd +0 -203
@@ -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',
@@ -84,7 +69,18 @@ const libSaml = () => {
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
  };
86
71
  const defaultArtAuthnRequestTemplate = {
87
- 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>`,
88
84
  };
89
85
  /**
90
86
  * @desc Default AttributeStatement template
@@ -126,6 +122,21 @@ const libSaml = () => {
126
122
  const defaultLogoutResponseTemplate = {
127
123
  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
124
  };
125
+ /**
126
+ * @private
127
+ * @desc Get the signing scheme alias by signature algorithms, used by the node-rsa module
128
+ * @param {string} sigAlg signature algorithm
129
+ * @return {string/null} signing algorithm short-hand for the module node-rsa
130
+ */
131
+ function getSigningScheme(sigAlg) {
132
+ if (sigAlg) {
133
+ const algAlias = nrsaAliasMapping[sigAlg];
134
+ if (!(algAlias === undefined)) {
135
+ return algAlias;
136
+ }
137
+ }
138
+ return nrsaAliasMapping[signatureAlgorithms.RSA_SHA1];
139
+ }
129
140
  function validateAndInflateSamlResponse(urlEncodedResponse) {
130
141
  // 3. 尝试DEFLATE解压(SAML规范要求使用原始DEFLATE)
131
142
  let xml = "";
@@ -133,18 +144,13 @@ const libSaml = () => {
133
144
  try { // 1. URL解码
134
145
  const base64Encoded = decodeURIComponent(urlEncodedResponse);
135
146
  // 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 });
147
+ xml = inflateString(base64Encoded);
142
148
  }
143
149
  catch (inflateError) {
144
150
  // 4. 解压失败,尝试直接解析为未压缩的XML
145
151
  try {
146
152
  const base64Encoded = decodeURIComponent(urlEncodedResponse);
147
- xml = Buffer.from(base64Encoded, 'base64').toString('utf-8');
153
+ xml = atob(base64Encoded);
148
154
  return { compressed: false, xml, error: null };
149
155
  }
150
156
  catch (xmlError) {
@@ -215,6 +221,7 @@ const libSaml = () => {
215
221
  defaultArtAuthnRequestTemplate,
216
222
  defaultArtifactResolveTemplate,
217
223
  defaultLoginResponseTemplate,
224
+ defaultSoapResponseFailTemplate,
218
225
  defaultAttributeStatementTemplate,
219
226
  defaultAttributeTemplate,
220
227
  defaultLogoutRequestTemplate,
@@ -277,6 +284,9 @@ const libSaml = () => {
277
284
  };
278
285
  // 生成 XML(关闭自动声明头)
279
286
  const xmlString = xml([attributeStatement], { declaration: false });
287
+ if (xmlString.trim() === '<saml:AttributeStatement></saml:AttributeStatement>') {
288
+ return '';
289
+ }
280
290
  return xmlString.trim();
281
291
  },
282
292
  /**
@@ -324,7 +334,7 @@ const libSaml = () => {
324
334
  else {
325
335
  sig.computeSignature(rawSamlMessage);
326
336
  }
327
- return isBase64Output !== false ? utility.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
337
+ return isBase64Output ? utility.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
328
338
  },
329
339
  /**
330
340
  * @desc Verify the XML signature
@@ -337,29 +347,68 @@ const libSaml = () => {
337
347
  // tslint:disable-next-line:no-shadowed-variable
338
348
  verifySignature(xml, opts) {
339
349
  const { dom } = getContext();
340
- const doc = dom.parseFromString(xml);
350
+ const doc = dom.parseFromString(xml, 'application/xml');
341
351
  const docParser = new DOMParser();
342
352
  // In order to avoid the wrapping attack, we have changed to use absolute xpath instead of naively fetching the signature element
353
+ const LogoutResponseSignatureXpath = "/*[local-name()='LogoutResponse']/*[local-name()='Signature']";
354
+ const logoutRequestSignatureXpath = "/*[local-name()='LogoutRequest']/*[local-name()='Signature']";
343
355
  // message signature (logout response / saml response)
344
356
  const messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']";
345
357
  // assertion signature (logout response / saml response)
346
358
  const assertionSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']/*[local-name(.)='Signature']";
347
359
  // check if there is a potential malicious wrapping signature
348
360
  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']";
361
+ // 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']";
362
+ // @ts-expect-error misssing Node properties are not needed
363
+ const encryptedAssertions = select("/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']", doc);
364
+ const encAssertionNode = encryptedAssertions[0];
349
365
  // select the signature node
350
366
  let selection = [];
367
+ // @ts-expect-error misssing Node properties are not needed
351
368
  const messageSignatureNode = select(messageSignatureXpath, doc);
369
+ // @ts-expect-error misssing Node properties are not needed
352
370
  const assertionSignatureNode = select(assertionSignatureXpath, doc);
371
+ // @ts-expect-error misssing Node properties are not needed
353
372
  const wrappingElementNode = select(wrappingElementsXPath, doc);
354
- selection = selection.concat(messageSignatureNode);
355
- selection = selection.concat(assertionSignatureNode);
373
+ // @ts-expect-error misssing Node properties are not needed
374
+ const LogoutResponseSignatureElementNode = select(LogoutResponseSignatureXpath, doc);
356
375
  // try to catch potential wrapping attack
357
376
  if (wrappingElementNode.length !== 0) {
358
377
  throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
359
378
  }
379
+ // 优先检测 LogoutRequest 签名
380
+ // @ts-expect-error missing Node properties
381
+ const logoutRequestSignature = select(logoutRequestSignatureXpath, doc);
382
+ if (logoutRequestSignature.length > 0) {
383
+ selection = selection.concat(logoutRequestSignature);
384
+ }
385
+ selection = selection.concat(messageSignatureNode);
386
+ selection = selection.concat(assertionSignatureNode);
387
+ selection = selection.concat(LogoutResponseSignatureElementNode);
360
388
  // guarantee to have a signature in saml response
361
389
  if (selection.length === 0) {
362
- throw new Error('ERR_ZERO_SIGNATURE');
390
+ /** 判断有没有加密如果没有加密返回 [false, null]*/
391
+ if (encryptedAssertions.length > 0) {
392
+ if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
393
+ return [false, null, false, true]; // we return false now
394
+ }
395
+ if (encryptedAssertions.length > 1) {
396
+ throw new Error('ERR_MULTIPLE_ASSERTION');
397
+ }
398
+ return [false, null, true, true]; // return encryptedAssert
399
+ }
400
+ }
401
+ if (selection.length !== 0) {
402
+ /** 判断有没有加密如果没有加密返回 [false, null]*/
403
+ if (logoutRequestSignature.length === 0 && LogoutResponseSignatureElementNode.length === 0 && encryptedAssertions.length > 0) {
404
+ if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
405
+ return [false, null, true, false]; // we return false now
406
+ }
407
+ if (encryptedAssertions.length > 1) {
408
+ throw new Error('ERR_MULTIPLE_ASSERTION');
409
+ }
410
+ return [false, null, true, false]; // return encryptedAssert
411
+ }
363
412
  }
364
413
  // need to refactor later on
365
414
  for (const signatureNode of selection) {
@@ -407,7 +456,6 @@ const libSaml = () => {
407
456
  }
408
457
  }
409
458
  sig.loadSignature(signatureNode);
410
- doc.removeChild(signatureNode);
411
459
  verified = sig.checkSignature(doc.toString());
412
460
  // immediately throw error when any one of the signature is failed to get verified
413
461
  if (!verified) {
@@ -420,123 +468,333 @@ const libSaml = () => {
420
468
  throw new Error('NO_SIGNATURE_REFERENCES');
421
469
  }
422
470
  const signedVerifiedXML = sig.getSignedReferences()[0];
423
- const rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement;
471
+ const rootNode = docParser.parseFromString(signedVerifiedXML, 'application/xml').documentElement;
424
472
  // process the verified signature:
425
473
  // case 1, rootSignedDoc is a response:
426
- if (rootNode.localName === 'Response') {
474
+ if (rootNode?.localName === 'Response') {
427
475
  // try getting the Xml from the first assertion
428
- const EncryptedAssertions = select("./*[local-name()='EncryptedAssertion']", rootNode);
429
- const assertions = select("./*[local-name()='Assertion']", rootNode);
476
+ const EncryptedAssertions = select("./*[local-name()='EncryptedAssertion']",
477
+ // @ts-expect-error misssing Node properties are not needed
478
+ rootNode);
479
+ const assertions = select("./*[local-name()='Assertion']",
480
+ // @ts-expect-error misssing Node properties are not needed
481
+ rootNode);
430
482
  /**第三个参数代表是否加密*/
431
483
  // now we can process the assertion as an assertion
432
484
  if (EncryptedAssertions.length === 1) {
433
485
  /** 已加密*/
434
- return [true, EncryptedAssertions[0].toString(), true];
486
+ return [true, EncryptedAssertions[0].toString(), true, false];
435
487
  }
436
488
  if (assertions.length === 1) {
437
- return [true, assertions[0].toString(), false];
489
+ return [true, assertions[0].toString(), false, false];
438
490
  }
439
491
  }
440
- else if (rootNode.localName === 'Assertion') {
441
- return [true, rootNode.toString(), false];
492
+ else if (rootNode?.localName === 'Assertion') {
493
+ return [true, rootNode.toString(), false, false];
494
+ }
495
+ else if (rootNode?.localName === 'EncryptedAssertion') {
496
+ return [true, rootNode.toString(), true, false];
497
+ }
498
+ else if (rootNode?.localName === 'LogoutRequest') {
499
+ return [true, rootNode.toString(), false, false];
442
500
  }
443
- else if (rootNode.localName === 'EncryptedAssertion') {
444
- return [true, rootNode.toString(), true];
501
+ else if (rootNode?.localName === 'LogoutResponse') {
502
+ return [true, rootNode.toString(), false, false];
445
503
  }
446
504
  else {
447
- return [true, null]; // signature is valid. But there is no assertion node here. It could be metadata node, hence return null
505
+ return [true, null, false, false]; // signature is valid. But there is no assertion node here. It could be metadata node, hence return null
448
506
  }
449
507
  }
450
508
  // 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
- }
509
+ return [false, null, false, true]; // return encryptedAssert
510
+ /* throw new Error('ERR_ZERO_SIGNATURE');*/
511
+ },
512
+ /* verifySignatureSoap(xml: string, opts: SignatureVerifierOptions & { isAssertion?: boolean }) {
513
+ const {dom} = getContext();
514
+ const doc = dom.parseFromString(xml, 'application/xml');
515
+ const docParser = new DOMParser();
516
+
517
+ let selection: any = [];
518
+
519
+ if (opts.isAssertion) {
520
+ // 断言模式下的专用逻辑
521
+ const assertionSignatureXpath = "./!*[local-name()='Signature']";
522
+ // @ts-expect-error misssing Node properties are not needed
523
+ const signatureNode = select(assertionSignatureXpath, doc.documentElement);
524
+
525
+ if (signatureNode.length === 0) {
526
+ throw new Error('ERR_ASSERTION_SIGNATURE_NOT_FOUND');
527
+ }
528
+
529
+ selection = selection.concat(signatureNode);
530
+ } else {
531
+ // 原始的SOAP响应验证逻辑
532
+ const messageSignatureXpath =
533
+ "/!*[local-name()='Envelope']/!*[local-name()='Body']" +
534
+ "/!*[local-name()='ArtifactResponse']/!*[local-name()='Signature'] | " +
535
+ "/!*[local-name()='Envelope']/!*[local-name()='Body']" +
536
+ "/!*[local-name()='ArtifactResponse']/!*[local-name()='Response']/!*[local-name()='Signature']";
537
+
538
+ const assertionSignatureXpath =
539
+ "/!*[local-name()='Envelope']/!*[local-name()='Body']" +
540
+ "/!*[local-name()='ArtifactResponse']/!*[local-name()='Response']" +
541
+ "/!*[local-name()='Assertion']/!*[local-name()='Signature'] | " +
542
+ "/!*[local-name()='Envelope']/!*[local-name()='Body']" +
543
+ "/!*[local-name()='ArtifactResponse']/!*[local-name()='Response']" +
544
+ "/!*[local-name()='EncryptedAssertion']";
545
+
546
+ const wrappingElementsXPath =
547
+ "/!*[local-name()='Envelope']/!*[local-name()='Body']" +
548
+ "/!*[local-name()='ArtifactResponse']/!*[local-name()='Response']" +
549
+ "/!*[local-name()='Assertion']/!*[local-name()='Subject']" +
550
+ "/!*[local-name()='SubjectConfirmation']" +
551
+ "/!*[local-name()='SubjectConfirmationData']" +
552
+ "//!*[local-name()='Assertion' or local-name()='Signature']";
553
+
554
+ // @ts-expect-error misssing Node properties are not needed
555
+ const messageSignatureNode = select(messageSignatureXpath, doc);
556
+ // @ts-expect-error misssing Node properties are not needed
557
+ const assertionSignatureNode = select(assertionSignatureXpath, doc);
558
+ // @ts-expect-error misssing Node properties are not needed
559
+ const wrappingElementNode = select(wrappingElementsXPath, doc);
560
+
561
+ // 检测包装攻击
562
+ if (wrappingElementNode.length !== 0) {
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);
572
+ }
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;
459
616
  }
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();
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');
491
626
  }
492
-
493
- return [verified, assertionNode];*/
494
- },
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
667
+ // @ts-expect-error misssing Node properties are not needed
668
+ const responseNodes = select(
669
+ "./!*[local-name()='Response']",
670
+ // @ts-expect-error misssing Node properties are not needed
671
+ rootNode
672
+ ) as Element[];
673
+
674
+ if (responseNodes.length === 0) {
675
+ continue;
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') {
702
+ // @ts-expect-error misssing Node properties are not needed
703
+ const encryptedAssertions = select(
704
+ "./!*[local-name()='EncryptedAssertion']",
705
+ // @ts-expect-error misssing Node properties are not needed
706
+ rootNode
707
+ ) as Element[];
708
+ // @ts-expect-error misssing Node properties are not needed
709
+ const assertions = select(
710
+ "./!*[local-name()='Assertion']",
711
+ // @ts-expect-error misssing Node properties are not needed
712
+ rootNode
713
+ ) as Element[];
714
+
715
+ if (encryptedAssertions.length === 1) {
716
+ return [true, encryptedAssertions[0].toString(), true];
717
+ }
718
+
719
+ if (assertions.length === 1) {
720
+ return [true, assertions[0].toString(), false];
721
+ }
722
+ }
723
+ // 直接处理 Assertion
724
+ else if (rootNode?.localName === 'Assertion') {
725
+ return [true, rootNode.toString(), false];
726
+ }
727
+ // 直接处理 EncryptedAssertion
728
+ else if (rootNode?.localName === 'EncryptedAssertion') {
729
+ return [true, rootNode.toString(), true];
730
+ } else {
731
+
732
+ console.warn("未知的根节点类型:", rootNode?.localName);
733
+ }
734
+ }
735
+
736
+ throw new Error('ERR_ZERO_SIGNATURE');
737
+ },*/
495
738
  verifySignatureSoap(xml, opts) {
496
739
  const { dom } = getContext();
497
- const doc = dom.parseFromString(xml);
740
+ const doc = dom.parseFromString(xml, 'application/xml');
498
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);
499
778
  let selection = [];
500
- if (opts.isAssertion) {
501
- // 断言模式下的专用逻辑
502
- const assertionSignatureXpath = "./*[local-name()='Signature']";
503
- const signatureNode = select(assertionSignatureXpath, doc.documentElement);
504
- if (signatureNode.length === 0) {
505
- throw new Error('ERR_ASSERTION_SIGNATURE_NOT_FOUND');
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];
506
792
  }
507
- selection = selection.concat(signatureNode);
508
793
  }
509
- else {
510
- // 原始的SOAP响应验证逻辑
511
- const messageSignatureXpath = "/*[local-name()='Envelope']/*[local-name()='Body']" +
512
- "/*[local-name()='ArtifactResponse']/*[local-name()='Signature'] | " +
513
- "/*[local-name()='Envelope']/*[local-name()='Body']" +
514
- "/*[local-name()='ArtifactResponse']/*[local-name()='Response']/*[local-name()='Signature']";
515
- const assertionSignatureXpath = "/*[local-name()='Envelope']/*[local-name()='Body']" +
516
- "/*[local-name()='ArtifactResponse']/*[local-name()='Response']" +
517
- "/*[local-name()='Assertion']/*[local-name()='Signature'] | " +
518
- "/*[local-name()='Envelope']/*[local-name()='Body']" +
519
- "/*[local-name()='ArtifactResponse']/*[local-name()='Response']" +
520
- "/*[local-name()='EncryptedAssertion']";
521
- const wrappingElementsXPath = "/*[local-name()='Envelope']/*[local-name()='Body']" +
522
- "/*[local-name()='ArtifactResponse']/*[local-name()='Response']" +
523
- "/*[local-name()='Assertion']/*[local-name()='Subject']" +
524
- "/*[local-name()='SubjectConfirmation']" +
525
- "/*[local-name()='SubjectConfirmationData']" +
526
- "//*[local-name()='Assertion' or local-name()='Signature']";
527
- const messageSignatureNode = select(messageSignatureXpath, doc);
528
- const assertionSignatureNode = select(assertionSignatureXpath, doc);
529
- const wrappingElementNode = select(wrappingElementsXPath, doc);
530
- // 检测包装攻击
531
- if (wrappingElementNode.length !== 0) {
532
- throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
533
- }
534
- // 保证响应中至少有一个签名
535
- if (messageSignatureNode.length === 0 && assertionSignatureNode.length === 0) {
536
- throw new Error('ERR_ZERO_SIGNATURE');
537
- }
538
- selection = selection.concat(messageSignatureNode, assertionSignatureNode);
794
+ if (selection.length === 0) {
795
+ throw new Error('ERR_ZERO_SIGNATURE');
539
796
  }
797
+ // 尝试所有签名节点
540
798
  for (const signatureNode of selection) {
541
799
  const sig = new SignedXml();
542
800
  let verified = false;
@@ -545,13 +803,12 @@ const libSaml = () => {
545
803
  throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
546
804
  }
547
805
  if (opts.keyFile) {
548
- sig.publicCert = fs.readFileSync(opts.keyFile, 'utf-8');
806
+ sig.publicCert = fs.readFileSync(opts.keyFile);
549
807
  }
550
808
  if (opts.metadata) {
551
- const certificateNodes = select(".//*[local-name(.)='X509Certificate']", signatureNode);
552
- // 获取元数据中的证书
809
+ const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode);
810
+ // 证书处理逻辑
553
811
  let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
554
- // 规范化元数据证书
555
812
  if (Array.isArray(metadataCert)) {
556
813
  metadataCert = flattenDeep(metadataCert);
557
814
  }
@@ -559,102 +816,59 @@ const libSaml = () => {
559
816
  metadataCert = [metadataCert];
560
817
  }
561
818
  metadataCert = metadataCert.map(utility.normalizeCerString);
562
- // 检查证书可用性
563
- if (certificateNodes.length === 0 && metadataCert.length === 0) {
819
+ // 没有证书的情况
820
+ if (certificateNode.length === 0 && metadataCert.length === 0) {
564
821
  throw new Error('NO_SELECTED_CERTIFICATE');
565
822
  }
566
- // 响应中有证书节点
567
- if (certificateNodes.length !== 0) {
568
- // 安全获取证书数据
569
- let x509CertificateData = '';
570
- if (certificateNodes[0].firstChild) {
571
- x509CertificateData = certificateNodes[0].firstChild.data;
572
- }
573
- else if (certificateNodes[0].textContent) {
574
- x509CertificateData = certificateNodes[0].textContent;
575
- }
823
+ if (certificateNode.length !== 0) {
824
+ const x509CertificateData = certificateNode[0].firstChild.data;
576
825
  const x509Certificate = utility.normalizeCerString(x509CertificateData);
577
- // 验证证书匹配
578
- if (metadataCert.length >= 1 &&
579
- !metadataCert.find(cert => cert.trim() === x509Certificate.trim())) {
826
+ if (metadataCert.length >= 1 && !metadataCert.includes(x509Certificate)) {
580
827
  throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
581
828
  }
582
829
  sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
583
830
  }
584
831
  else {
585
- // 使用元数据中的第一个证书
586
832
  sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
587
833
  }
588
834
  }
589
- // 加载签名
590
835
  sig.loadSignature(signatureNode);
591
- // 使用原始 XML 进行验证
592
- verified = sig.checkSignature(xml);
593
- console.log("签名验证结果:", verified);
836
+ verified = sig.checkSignature(xml); // 使用原始XML验证
594
837
  if (!verified) {
595
- console.error("签名验证失败");
596
838
  throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
597
839
  }
598
- // 检查签名引用
599
- if (!(sig.getSignedReferences().length >= 1)) {
840
+ if (sig.getSignedReferences().length < 1) {
600
841
  throw new Error('NO_SIGNATURE_REFERENCES');
601
842
  }
602
843
  const signedVerifiedXML = sig.getSignedReferences()[0];
603
- const verifiedDoc = docParser.parseFromString(signedVerifiedXML, 'text/xml');
604
- const rootNode = verifiedDoc.documentElement;
605
- console.log("签名引用根节点:", rootNode.localName);
606
- // 断言模式专用返回逻辑
607
- if (opts.isAssertion) {
608
- if (rootNode.localName === 'Assertion') {
609
- return [true, rootNode.toString(), false];
610
- }
611
- else {
612
- throw new Error('ERR_INVALID_ASSERTION_SIGNATURE');
613
- }
614
- }
615
- // 处理已验证的签名
616
- if (rootNode.localName === 'ArtifactResponse') {
617
- // ArtifactResponse 中查找 Response
618
- const responseNodes = select("./*[local-name()='Response']", rootNode);
619
- if (responseNodes.length === 0) {
620
- console.warn("ArtifactResponse 中没有找到 Response 元素");
621
- continue;
622
- }
623
- const responseNode = responseNodes[0];
624
- // 在 Response 中查找断言
625
- const encryptedAssertions = select("./*[local-name()='EncryptedAssertion']", responseNode);
626
- const assertions = select("./*[local-name()='Assertion']", responseNode);
627
- if (encryptedAssertions.length === 1) {
628
- return [true, encryptedAssertions[0].toString(), true];
629
- }
630
- if (assertions.length === 1) {
631
- return [true, assertions[0].toString(), false];
632
- }
633
- }
634
- // 直接处理 Response
635
- else if (rootNode.localName === 'Response') {
636
- const encryptedAssertions = select("./*[local-name()='EncryptedAssertion']", rootNode);
637
- const assertions = select("./*[local-name()='Assertion']", rootNode);
638
- if (encryptedAssertions.length === 1) {
639
- return [true, encryptedAssertions[0].toString(), true];
640
- }
641
- if (assertions.length === 1) {
642
- return [true, assertions[0].toString(), false];
643
- }
644
- }
645
- // 直接处理 Assertion
646
- else if (rootNode.localName === 'Assertion') {
647
- return [true, rootNode.toString(), false];
648
- }
649
- // 直接处理 EncryptedAssertion
650
- else if (rootNode.localName === 'EncryptedAssertion') {
651
- return [true, rootNode.toString(), true];
652
- }
653
- else {
654
- 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]; // 签名验证成功但未找到可识别的内容
655
869
  }
656
870
  }
657
- throw new Error('ERR_ZERO_SIGNATURE');
871
+ return [false, null, encryptedAssertions.length > 0, false];
658
872
  },
659
873
  /**
660
874
  * @desc Helper function to create the key section in metadata (abstraction for signing and encrypt use)
@@ -694,59 +908,43 @@ const libSaml = () => {
694
908
  * @param signingAlgorithm - 签名算法 (默认 'rsa-sha256')
695
909
  * @returns 消息签名
696
910
  */
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
- }
911
+ constructMessageSignature(octetString, key, passphrase, isBase64, signingAlgorithm) {
912
+ // Default returning base64 encoded signature
913
+ // Embed with node-rsa module
914
+ const decryptedKey = new nrsa(utility.readPrivateKey(key, passphrase), undefined, {
915
+ signingScheme: getSigningScheme(signingAlgorithm),
916
+ });
917
+ const signature = decryptedKey.sign(octetString);
918
+ // Use private key to sign data
919
+ return isBase64 !== false ? signature.toString('base64') : signature;
722
920
  },
723
- /* constructMessageSignature(
921
+ /* verifyMessageSignature(
922
+ metadata,
724
923
  octetString: string,
725
- key: string,
726
- passphrase?: string,
727
- isBase64?: boolean,
728
- signingAlgorithm?: string
924
+ signature: string | Buffer,
925
+ verifyAlgorithm?: string
729
926
  ) {
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;
927
+ const signCert = metadata.getX509Certificate(certUse.signing);
928
+ const signingScheme = getSigningSchemeForNode(verifyAlgorithm);
929
+ const verifier = createVerify(signingScheme);
930
+ verifier.update(octetString);
931
+ const isValid = verifier.verify(utility.getPublicKeyPemFromCertificate(signCert), Buffer.isBuffer(signature) ? signature : Buffer.from(signature, 'base64'));
932
+ return isValid
933
+
742
934
  },*/
935
+ /**
936
+ * @desc Verifies message signature
937
+ * @param {Metadata} metadata metadata object of identity provider or service provider
938
+ * @param {string} octetString see "Bindings for the OASIS Security Assertion Markup Language (SAML V2.0)" P.17/46
939
+ * @param {string} signature context of XML signature
940
+ * @param {string} verifyAlgorithm algorithm used to verify
941
+ * @return {boolean} verification result
942
+ */
743
943
  verifyMessageSignature(metadata, octetString, signature, verifyAlgorithm) {
744
944
  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;
945
+ const signingScheme = getSigningScheme(verifyAlgorithm);
946
+ const key = new nrsa(utility.getPublicKeyPemFromCertificate(signCert), 'public', { signingScheme });
947
+ return key.verify(Buffer.from(octetString), Buffer.from(signature));
750
948
  },
751
949
  /**
752
950
  * @desc Get the public key in string format
@@ -781,7 +979,8 @@ const libSaml = () => {
781
979
  const sourceEntitySetting = sourceEntity.entitySetting;
782
980
  const targetEntityMetadata = targetEntity.entityMeta;
783
981
  const { dom } = getContext();
784
- const doc = dom.parseFromString(xml);
982
+ const doc = dom.parseFromString(xml, 'application/xml');
983
+ // @ts-expect-error misssing Node properties are not needed
785
984
  const assertions = select("//*[local-name(.)='Assertion']", doc);
786
985
  if (!Array.isArray(assertions) || assertions.length === 0) {
787
986
  throw new Error('ERR_NO_ASSERTION');
@@ -799,7 +998,7 @@ const libSaml = () => {
799
998
  pem: Buffer.from(`-----BEGIN CERTIFICATE-----${targetEntityMetadata.getX509Certificate(certUse.encrypt)}-----END CERTIFICATE-----`),
800
999
  encryptionAlgorithm: sourceEntitySetting.dataEncryptionAlgorithm,
801
1000
  keyEncryptionAlgorithm: sourceEntitySetting.keyEncryptionAlgorithm,
802
- keyEncryptionDigest: 'SHA-512',
1001
+ /* keyEncryptionDigest: 'SHA-512',*/
803
1002
  disallowEncryptionWithInsecureAlgorithm: true,
804
1003
  warnInsecureAlgorithm: true
805
1004
  }, (err, res) => {
@@ -810,7 +1009,8 @@ const libSaml = () => {
810
1009
  return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
811
1010
  }
812
1011
  const { encryptedAssertion: encAssertionPrefix } = sourceEntitySetting.tagPrefix;
813
- const encryptAssertionDoc = dom.parseFromString(`<${encAssertionPrefix}:EncryptedAssertion xmlns:${encAssertionPrefix}="${namespace.names.assertion}">${res}</${encAssertionPrefix}:EncryptedAssertion>`);
1012
+ const encryptAssertionDoc = dom.parseFromString(`<${encAssertionPrefix}:EncryptedAssertion xmlns:${encAssertionPrefix}="${namespace.names.assertion}">${res}</${encAssertionPrefix}:EncryptedAssertion>`, 'application/xml');
1013
+ // @ts-expect-error misssing Node properties are not needed
814
1014
  doc.documentElement.replaceChild(encryptAssertionDoc.documentElement, rawAssertionNode);
815
1015
  return resolve(utility.base64Encode(doc.toString()));
816
1016
  });
@@ -837,7 +1037,8 @@ const libSaml = () => {
837
1037
  // Perform encryption depends on the setting of where the message is sent, default is false
838
1038
  const hereSetting = here.entitySetting;
839
1039
  const { dom } = getContext();
840
- const doc = dom.parseFromString(entireXML);
1040
+ const doc = dom.parseFromString(entireXML, 'application/xml');
1041
+ // @ts-expect-error misssing Node properties are not needed
841
1042
  const encryptedAssertions = select("/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']", doc);
842
1043
  if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
843
1044
  throw new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION');
@@ -850,13 +1051,13 @@ const libSaml = () => {
850
1051
  key: utility.readPrivateKey(hereSetting.encPrivateKey, hereSetting.encPrivateKeyPass),
851
1052
  }, (err, res) => {
852
1053
  if (err) {
853
- console.error(err);
854
1054
  return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_DECRYPTION'));
855
1055
  }
856
1056
  if (!res) {
857
1057
  return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
858
1058
  }
859
- const rawAssertionDoc = dom.parseFromString(res);
1059
+ const rawAssertionDoc = dom.parseFromString(res, 'application/xml');
1060
+ // @ts-ignore
860
1061
  doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
861
1062
  return resolve([doc.toString(), res]);
862
1063
  });
@@ -872,11 +1073,14 @@ const libSaml = () => {
872
1073
  const { dom } = getContext();
873
1074
  try {
874
1075
  // 1. 解析 XML
875
- const doc = dom.parseFromString(entireXML);
1076
+ // @ts-ignore
1077
+ const doc = dom.parseFromString(entireXML, 'application/xml');
876
1078
  // 2. 定位加密断言
877
1079
  const encryptedAssertions = select("/*[local-name()='Envelope']/*[local-name()='Body']" +
878
1080
  "/*[local-name()='ArtifactResponse']/*[local-name()='Response']" +
879
- "/*[local-name()='EncryptedAssertion']", doc);
1081
+ "/*[local-name()='EncryptedAssertion']",
1082
+ // @ts-ignore
1083
+ doc);
880
1084
  if (!encryptedAssertions || encryptedAssertions.length === 0) {
881
1085
  throw new Error('ERR_ENCRYPTED_ASSERTION_NOT_FOUND');
882
1086
  }
@@ -890,7 +1094,6 @@ const libSaml = () => {
890
1094
  const decryptedAssertion = await new Promise((resolve, reject) => {
891
1095
  xmlenc.decrypt(encAssertionNode.toString(), { key: privateKey }, (err, result) => {
892
1096
  if (err) {
893
- console.error('解密错误:', err);
894
1097
  return reject(new Error('ERR_ASSERTION_DECRYPTION_FAILED'));
895
1098
  }
896
1099
  if (!result) {
@@ -900,27 +1103,28 @@ const libSaml = () => {
900
1103
  });
901
1104
  });
902
1105
  // 5. 创建解密断言的 DOM
903
- const decryptedDoc = dom.parseFromString(decryptedAssertion);
1106
+ // @ts-ignore
1107
+ const decryptedDoc = dom.parseFromString(decryptedAssertion, 'application/xml');
904
1108
  const decryptedAssertionNode = decryptedDoc.documentElement;
905
1109
  // 6. 替换加密断言为解密后的断言
906
1110
  const parentNode = encAssertionNode.parentNode;
907
1111
  if (!parentNode) {
908
1112
  throw new Error('ERR_NO_PARENT_NODE_FOR_ENCRYPTED_ASSERTION');
909
1113
  }
1114
+ // @ts-ignore
910
1115
  parentNode.replaceChild(decryptedAssertionNode, encAssertionNode);
911
1116
  // 7. 序列化更新后的文档
912
1117
  const updatedSoapXml = doc.toString();
913
1118
  return [updatedSoapXml, decryptedAssertion];
914
1119
  }
915
1120
  catch (error) {
916
- console.error('SOAP断言解密失败:', error);
917
1121
  throw new Error('ERR_SOAP_ASSERTION_DECRYPTION');
918
1122
  }
919
1123
  },
920
1124
  /**
921
1125
  * @desc Check if the xml string is valid and bounded
922
1126
  */
923
- async isValidXml(input) {
1127
+ async isValidXml(input, soap = false) {
924
1128
  // check if global api contains the validate function
925
1129
  const { validate } = getContext();
926
1130
  /**
@@ -934,7 +1138,7 @@ const libSaml = () => {
934
1138
  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
1139
  }
936
1140
  try {
937
- return await validate(input);
1141
+ return await validate(input, soap);
938
1142
  }
939
1143
  catch (e) {
940
1144
  throw e;