samlesa 2.18.2 → 2.18.3

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.
Files changed (43) hide show
  1. package/build/src/binding-artifact.js +1 -1
  2. package/build/src/flow.js +19 -101
  3. package/build/src/libsaml.js +87 -213
  4. package/package.json +77 -77
  5. package/types/api.d.ts +15 -0
  6. package/types/api.d.ts.map +1 -0
  7. package/types/binding-post.d.ts +48 -0
  8. package/types/binding-post.d.ts.map +1 -0
  9. package/types/binding-redirect.d.ts +54 -0
  10. package/types/binding-redirect.d.ts.map +1 -0
  11. package/types/binding-simplesign.d.ts +41 -0
  12. package/types/binding-simplesign.d.ts.map +1 -0
  13. package/types/entity-idp.d.ts +38 -0
  14. package/types/entity-idp.d.ts.map +1 -0
  15. package/types/entity-sp.d.ts +38 -0
  16. package/types/entity-sp.d.ts.map +1 -0
  17. package/types/entity.d.ts +100 -0
  18. package/types/entity.d.ts.map +1 -0
  19. package/types/extractor.d.ts +26 -0
  20. package/types/extractor.d.ts.map +1 -0
  21. package/types/flow.d.ts +7 -0
  22. package/types/flow.d.ts.map +1 -0
  23. package/types/libsaml.d.ts +208 -0
  24. package/types/libsaml.d.ts.map +1 -0
  25. package/types/metadata-idp.d.ts +25 -0
  26. package/types/metadata-idp.d.ts.map +1 -0
  27. package/types/metadata-sp.d.ts +37 -0
  28. package/types/metadata-sp.d.ts.map +1 -0
  29. package/types/metadata.d.ts +58 -0
  30. package/types/metadata.d.ts.map +1 -0
  31. package/types/src/flow.d.ts.map +1 -1
  32. package/types/src/libsaml.d.ts +12 -20
  33. package/types/src/libsaml.d.ts.map +1 -1
  34. package/types/src/utility.d.ts +1 -1
  35. package/types/src/utility.d.ts.map +1 -1
  36. package/types/types.d.ts +128 -0
  37. package/types/types.d.ts.map +1 -0
  38. package/types/urn.d.ts +195 -0
  39. package/types/urn.d.ts.map +1 -0
  40. package/types/utility.d.ts +133 -0
  41. package/types/utility.d.ts.map +1 -0
  42. package/types/validator.d.ts +4 -0
  43. package/types/validator.d.ts.map +1 -0
@@ -384,7 +384,7 @@ async function parseLoginResponseResolve(params) {
384
384
  }
385
385
  samlContent = verifiedAssertionNode1;
386
386
  // 改进的postFlow函数中关于签名验证的部分
387
- const verificationResult = libsaml.verifySignature(samlContent, verificationOptions, self);
387
+ const verificationResult = await libsaml.verifySignature(samlContent, verificationOptions, self);
388
388
  // 检查验证结果
389
389
  if (!verificationResult.status) {
390
390
  // 如果验证失败,根据具体情况返回错误
package/build/src/flow.js CHANGED
@@ -151,105 +151,8 @@ async function postFlow(options) {
151
151
  // check status based on different scenarios
152
152
  await checkStatus(samlContent, parserType);
153
153
  /**检查签名顺序 */
154
- /*
155
- const [verified, verifiedAssertionNode, isDecryptRequired, noSignature] = libsaml.verifySignature(samlContent, verificationOptions);
156
- decryptRequired = isDecryptRequired
157
- if (isDecryptRequired && noSignature) {
158
-
159
- const result = await libsaml.decryptAssertion(self, samlContent);
160
- samlContent = result[0];
161
- extractorFields = getDefaultExtractorFields(parserType, result[1]);
162
- }
163
- if (!verified && !noSignature && !isDecryptRequired) {
164
-
165
- return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
166
- }
167
- if (!isDecryptRequired) {
168
-
169
- extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
170
- }
171
- if (parserType === 'SAMLResponse' && isDecryptRequired && !noSignature) {
172
- const result = await libsaml.decryptAssertion(self, samlContent);
173
- samlContent = result[0];
174
- extractorFields = getDefaultExtractorFields(parserType, result[1]);
175
- console.log("走这里来了=========")
176
- console.log(result[1])
177
- }
178
-
179
-
180
- const parseResult = {
181
- samlContent: samlContent,
182
- extract: extract(samlContent, extractorFields),
183
- };
184
- /!**
185
- * Validation part: validate the context of response after signature is verified and decrypted (optional)
186
- *!/
187
- const targetEntityMetadata = from.entityMeta;
188
- const issuer = targetEntityMetadata.getEntityID();
189
- const extractedProperties = parseResult.extract;
190
- // unmatched issuer
191
- if (
192
- (parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
193
- && extractedProperties
194
- && extractedProperties.issuer !== issuer
195
- ) {
196
- return Promise.reject('ERR_UNMATCH_ISSUER');
197
- }
198
-
199
- // invalid session time
200
- // only run the verifyTime when `SessionNotOnOrAfter` exists
201
- if (
202
- parserType === 'SAMLResponse'
203
- && extractedProperties.sessionIndex.sessionNotOnOrAfter
204
- && !verifyTime(
205
- undefined,
206
- extractedProperties.sessionIndex.sessionNotOnOrAfter,
207
- self.entitySetting.clockDrifts
208
- )
209
- ) {
210
- return Promise.reject('ERR_EXPIRED_SESSION');
211
- }
212
-
213
- // invalid time
214
- // 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
215
- if (
216
- parserType === 'SAMLResponse'
217
- && extractedProperties.conditions
218
- && !verifyTime(
219
- extractedProperties.conditions.notBefore,
220
- extractedProperties.conditions.notOnOrAfter,
221
- self.entitySetting.clockDrifts
222
- )
223
- ) {
224
- return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
225
- }
226
- //valid destination
227
- //There is no validation of the response here. The upper-layer application
228
- // should verify the result by itself to see if the destination is equal to the SP acs and
229
- // whether the response.id is used to prevent replay attacks.
230
- /!*
231
- let destination = extractedProperties?.response?.destination
232
- let isExit = self.entitySetting?.assertionConsumerService?.filter((item) => {
233
- return item?.Location === destination
234
- })
235
- if (isExit?.length === 0) {
236
- return Promise.reject('ERR_Destination_URL');
237
- }
238
- if (parserType === 'SAMLResponse') {
239
- let destination = extractedProperties?.response?.destination
240
- let isExit = self.entitySetting?.assertionConsumerService?.filter((item: { Location: any; }) => {
241
- return item?.Location === destination
242
- })
243
- if (isExit?.length === 0) {
244
- return Promise.reject('ERR_Destination_URL');
245
- }
246
- }
247
- *!/
248
-
249
-
250
- return Promise.resolve(parseResult);*/
251
154
  // 改进的postFlow函数中关于签名验证的部分
252
- const verificationResult = libsaml.verifySignature(samlContent, verificationOptions, self);
155
+ const verificationResult = await libsaml.verifySignature(samlContent, verificationOptions, self);
253
156
  /* console.log(verificationResult)
254
157
  console.log("解析对象")*/
255
158
  let resultObject = {
@@ -261,7 +164,8 @@ async function postFlow(options) {
261
164
  decrypted: true, //断言加密后断言是否解密成功,
262
165
  status: true, //是否全部通过验证,
263
166
  samlContent: 'xxx', //xxx是通过验证后 解密后的整个响应,
264
- assertionContent: 'xxx', //xxx是通过验证后 解密后的整个响应中的assertion 断言部分字符串
167
+ assertionContent: 'xxx', //xxx是通过验证后 解密后的整个响应中的assertion 断言部分字符串,
168
+ signMethod: "", //xxx是通过验证后 解密后的整个响应中的assertion 断言部分字符串,
265
169
  };
266
170
  // 检查验证结果
267
171
  if (!verificationResult.status) {
@@ -347,7 +251,21 @@ async function postFlow(options) {
347
251
  }
348
252
  }
349
253
  */
350
- return Promise.resolve(parseResult);
254
+ return Promise.resolve({
255
+ ...parseResult,
256
+ verificationResult: {
257
+ isMessageSigned: verificationResult?.isMessageSigned,
258
+ MessageSignatureStatus: verificationResult?.MessageSignatureStatus,
259
+ isAssertionSigned: verificationResult?.isAssertionSigned,
260
+ AssertionSignatureStatus: verificationResult?.AssertionSignatureStatus,
261
+ encrypted: verificationResult?.encrypted,
262
+ decrypted: verificationResult?.decrypted,
263
+ type: verificationResult?.type, // 添加类型字段
264
+ status: verificationResult?.status,
265
+ hasUnsafeSignatureAlgorithm: verificationResult?.hasUnsafeSignatureAlgorithm,
266
+ unsafeSignatureAlgorithm: verificationResult?.unsafeSignatureAlgorithm
267
+ },
268
+ });
351
269
  }
352
270
  // proceed the post Artifact flow
353
271
  async function postArtifactFlow(options) {
@@ -372,7 +290,7 @@ async function postArtifactFlow(options) {
372
290
  await checkStatus(samlContent, parserType);
373
291
  /**检查签名顺序 */
374
292
  // 改进的postFlow函数中关于签名验证的部分
375
- const verificationResult = libsaml.verifySignature(samlContent, verificationOptions, self);
293
+ const verificationResult = await libsaml.verifySignature(samlContent, verificationOptions, self);
376
294
  console.log(verificationResult);
377
295
  console.log("最终结果====");
378
296
  // 检查验证结果
@@ -7,7 +7,7 @@ import { X509Certificate } from 'node:crypto';
7
7
  import xml from 'xml';
8
8
  import utility, { flattenDeep, inflateString, isString } from './utility.js';
9
9
  import { algorithms, namespace, wording } from './urn.js';
10
- import { select } from 'xpath';
10
+ import xpath, { select } from 'xpath';
11
11
  import nrsa from 'node-rsa';
12
12
  import { SignedXml } from 'xml-crypto';
13
13
  import * as xmlenc from 'xml-encryption';
@@ -48,6 +48,25 @@ const libSaml = () => {
48
48
  'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': 'RSA-SHA256',
49
49
  'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512': 'RSA-SHA512',
50
50
  };
51
+ /**
52
+ * 检测是否使用了不安全的SHA1系列签名算法
53
+ * @param signatureAlgorithm 签名算法URI
54
+ * @returns {Object} 包含是否使用不安全算法和具体算法名称的对象
55
+ */
56
+ function checkUnsafeSignatureAlgorithm(signatureAlgorithm) {
57
+ const unsafeAlgorithms = [
58
+ 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
59
+ 'http://www.w3.org/2000/09/xmldsig#dsa-sha1',
60
+ 'http://www.w3.org/2000/09/xmldsig#hmac-sha1',
61
+ 'http://www.w3.org/2000/09/xmldsig#sha1'
62
+ ];
63
+ const isUnsafe = unsafeAlgorithms.some(alg => signatureAlgorithm.toLowerCase().includes('sha1') ||
64
+ signatureAlgorithm === 'http://www.w3.org/2000/09/xmldsig#rsa-sha1');
65
+ return {
66
+ hasUnsafeSignatureAlgorithm: isUnsafe,
67
+ unsafeSignatureAlgorithm: isUnsafe ? signatureAlgorithm : null
68
+ };
69
+ }
51
70
  /**
52
71
  * @desc Default login request template
53
72
  * @type {LoginRequestTemplate}
@@ -229,6 +248,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
229
248
  defaultLogoutResponseTemplate,
230
249
  defaultAttributeValueTemplate,
231
250
  validateAndInflateSamlResponse,
251
+ checkUnsafeSignatureAlgorithm,
232
252
  /**
233
253
  * @desc Replace the tag (e.g. {tag}) inside the raw XML
234
254
  * @param {string} rawXML raw XML string used to do keyword replacement
@@ -368,201 +388,6 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
368
388
  };
369
389
  }
370
390
  },
371
- /**
372
- * @desc Verify the XML signature
373
- * @param {string} xml xml
374
- * @param {SignatureVerifierOptions} opts cert declares the X509 certificate
375
- * @return {[boolean, string | null]} - A tuple where:
376
- * - The first element is `true` if the signature is valid, `false` otherwise.
377
- * - The second element is the cryptographically authenticated assertion node as a string, or `null` if not found.
378
- */
379
- // tslint:disable-next-line:no-shadowed-variable
380
- verifySignature1(xml, opts) {
381
- const { dom } = getContext();
382
- const doc = dom.parseFromString(xml, 'application/xml');
383
- const docParser = new DOMParser();
384
- // In order to avoid the wrapping attack, we have changed to use absolute xpath instead of naively fetching the signature element
385
- const LogoutResponseSignatureXpath = "/*[local-name()='LogoutResponse']/*[local-name()='Signature']";
386
- const logoutRequestSignatureXpath = "/*[local-name()='LogoutRequest']/*[local-name()='Signature']";
387
- // message signature (logout response / saml response)
388
- const messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']";
389
- // assertion signature (logout response / saml response)
390
- const assertionSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']/*[local-name(.)='Signature']";
391
- // check if there is a potential malicious wrapping signature
392
- 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']";
393
- // 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']";
394
- // @ts-expect-error misssing Node properties are not needed
395
- const encryptedAssertions = select("/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']", doc);
396
- const encAssertionNode = encryptedAssertions[0];
397
- // select the signature node
398
- let selection = [];
399
- // @ts-expect-error misssing Node properties are not needed
400
- const messageSignatureNode = select(messageSignatureXpath, doc);
401
- // @ts-expect-error misssing Node properties are not needed
402
- const assertionSignatureNode = select(assertionSignatureXpath, doc);
403
- // @ts-expect-error misssing Node properties are not needed
404
- const wrappingElementNode = select(wrappingElementsXPath, doc);
405
- // @ts-expect-error misssing Node properties are not needed
406
- const LogoutResponseSignatureElementNode = select(LogoutResponseSignatureXpath, doc);
407
- // try to catch potential wrapping attack
408
- if (wrappingElementNode.length !== 0) {
409
- throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
410
- }
411
- // 优先检测 LogoutRequest 签名
412
- // @ts-expect-error missing Node properties
413
- const logoutRequestSignature = select(logoutRequestSignatureXpath, doc);
414
- if (logoutRequestSignature.length > 0) {
415
- selection = selection.concat(logoutRequestSignature);
416
- }
417
- selection = selection.concat(messageSignatureNode);
418
- selection = selection.concat(assertionSignatureNode);
419
- selection = selection.concat(LogoutResponseSignatureElementNode);
420
- // guarantee to have a signature in saml response
421
- if (selection.length === 0) {
422
- /** 判断有没有加密如果没有加密返回 [false, null]*/
423
- if (encryptedAssertions.length > 0) {
424
- console.log("走加密了====");
425
- if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
426
- return [false, null, false, true]; // we return false now
427
- }
428
- if (encryptedAssertions.length > 1) {
429
- throw new Error('ERR_MULTIPLE_ASSERTION');
430
- }
431
- return [false, null, true, true]; // return encryptedAssert
432
- }
433
- }
434
- if (selection.length !== 0) {
435
- console.log("走加密了1====");
436
- /** 判断有没有加密如果没有加密返回 [false, null]*/
437
- if (logoutRequestSignature.length === 0 && LogoutResponseSignatureElementNode.length === 0 && encryptedAssertions.length > 0) {
438
- if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
439
- return [false, null, true, false]; // we return false now
440
- }
441
- if (encryptedAssertions.length > 1) {
442
- throw new Error('ERR_MULTIPLE_ASSERTION');
443
- }
444
- return [false, null, true, false]; // return encryptedAssert
445
- }
446
- }
447
- // need to refactor later on
448
- for (const signatureNode of selection) {
449
- const sig = new SignedXml();
450
- let verified = false;
451
- sig.signatureAlgorithm = opts.signatureAlgorithm;
452
- if (!opts.keyFile && !opts.metadata) {
453
- throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
454
- }
455
- console.log("开始1");
456
- if (opts.keyFile) {
457
- console.log("开始11");
458
- let publicCertResult = this.validateCertificate(fs.readFileSync(opts.keyFile));
459
- console.log(publicCertResult);
460
- console.log("结果");
461
- sig.publicCert = fs.readFileSync(opts.keyFile);
462
- }
463
- if (opts.metadata) {
464
- console.log("开始111");
465
- const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode);
466
- // certificate in metadata
467
- let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
468
- // flattens the nested array of Certificates from each KeyDescriptor
469
- if (Array.isArray(metadataCert)) {
470
- metadataCert = flattenDeep(metadataCert);
471
- }
472
- else if (typeof metadataCert === 'string') {
473
- metadataCert = [metadataCert];
474
- }
475
- // normalise the certificate string
476
- metadataCert = metadataCert.map(utility.normalizeCerString);
477
- // no certificate in node response nor metadata
478
- if (certificateNode.length === 0 && metadataCert.length === 0) {
479
- throw new Error('NO_SELECTED_CERTIFICATE');
480
- }
481
- // certificate node in response
482
- if (certificateNode.length !== 0) {
483
- const x509CertificateData = certificateNode[0].firstChild.data;
484
- const x509Certificate = utility.normalizeCerString(x509CertificateData);
485
- if (metadataCert.length >= 1 &&
486
- !metadataCert.find(cert => cert.trim() === x509Certificate.trim())) {
487
- // keep this restriction for rolling certificate usage
488
- // to make sure the response certificate is one of those specified in metadata
489
- throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
490
- }
491
- let publicCertResult = this.validateCertificate(x509Certificate);
492
- console.log(publicCertResult);
493
- console.log("结果");
494
- sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
495
- }
496
- else {
497
- console.log("开始11111");
498
- // Select first one from metadata
499
- let publicCertResult = this.validateCertificate(metadataCert[0]);
500
- console.log(publicCertResult);
501
- console.log("结果");
502
- sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
503
- }
504
- }
505
- sig.loadSignature(signatureNode);
506
- verified = sig.checkSignature(doc.toString());
507
- // immediately throw error when any one of the signature is failed to get verified
508
- if (!verified) {
509
- throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
510
- }
511
- // attempt is made to get the signed Reference as a string();
512
- // note, we don't have access to the actual signedReferences API unfortunately
513
- // mainly a sanity check here for SAML. (Although ours would still be secure, if multiple references are used)
514
- if (!(sig.getSignedReferences().length >= 1)) {
515
- throw new Error('NO_SIGNATURE_REFERENCES');
516
- }
517
- const signedVerifiedXML = sig.getSignedReferences()[0];
518
- const rootNode = docParser.parseFromString(signedVerifiedXML, 'application/xml').documentElement;
519
- // process the verified signature:
520
- // case 1, rootSignedDoc is a response:
521
- if (rootNode?.localName === 'Response') {
522
- // try getting the Xml from the first assertion
523
- const EncryptedAssertions = select("./*[local-name()='EncryptedAssertion']",
524
- // @ts-expect-error misssing Node properties are not needed
525
- rootNode);
526
- const assertions = select("./*[local-name()='Assertion']",
527
- // @ts-expect-error misssing Node properties are not needed
528
- rootNode);
529
- /**第三个参数代表是否加密*/
530
- // now we can process the assertion as an assertion
531
- if (EncryptedAssertions.length === 1) {
532
- /** 已加密*/
533
- return [true, EncryptedAssertions[0].toString(), true, false];
534
- }
535
- if (assertions.length === 1) {
536
- return [true, assertions[0].toString(), false, false];
537
- }
538
- }
539
- else if (rootNode?.localName === 'Assertion') {
540
- return [true, rootNode.toString(), false, false];
541
- }
542
- else if (rootNode?.localName === 'EncryptedAssertion') {
543
- return [true, rootNode.toString(), true, false];
544
- }
545
- else if (rootNode?.localName === 'LogoutRequest') {
546
- return [true, rootNode.toString(), false, false];
547
- }
548
- else if (rootNode?.localName === 'LogoutResponse') {
549
- return [true, rootNode.toString(), false, false];
550
- }
551
- else {
552
- return [true, null, false, false]; // signature is valid. But there is no assertion node here. It could be metadata node, hence return null
553
- }
554
- }
555
- // something has gone seriously wrong if we are still here
556
- return [false, null, false, true]; // return encryptedAssert
557
- /* throw new Error('ERR_ZERO_SIGNATURE');*/
558
- },
559
- /**
560
- * 改进的SAML签名验证函数,支持多种签名和加密组合场景
561
- * @param xml SAML XML内容
562
- * @param opts 验证选项
563
- * @param self
564
- * @returns 验证结果对象
565
- */
566
391
  /**
567
392
  * 改进的SAML签名验证函数,支持多种签名和加密组合场景
568
393
  * @param xml SAML XML内容
@@ -570,7 +395,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
570
395
  * @param self
571
396
  * @returns 验证结果对象
572
397
  */
573
- verifySignature(xml, opts, self) {
398
+ async verifySignature(xml, opts, self) {
574
399
  const { dom } = getContext();
575
400
  const doc = dom.parseFromString(xml, 'application/xml');
576
401
  const docParser = new DOMParser();
@@ -638,6 +463,10 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
638
463
  type = 'Unknown';
639
464
  }
640
465
  }
466
+ let hasUnsafeSignatureAlgorithm = false;
467
+ let unsafeSignatureAlgorithm = '';
468
+ console.log({ hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm });
469
+ console.log("检查结果=====");
641
470
  // 特殊情况:带未签名断言的未签名SAML响应,应该拒绝
642
471
  if (!isMessageSigned && !isAssertionSigned && !encrypted) {
643
472
  return {
@@ -650,7 +479,9 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
650
479
  type, // 添加类型字段
651
480
  status: false, // 明确拒绝未签名未加密的响应
652
481
  samlContent,
653
- assertionContent
482
+ assertionContent,
483
+ hasUnsafeSignatureAlgorithm,
484
+ unsafeSignatureAlgorithm
654
485
  };
655
486
  }
656
487
  // 处理最外层有签名且断言加密的情况(关键逻辑补充)
@@ -660,6 +491,12 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
660
491
  const result = this.decryptAssertionSync(self, xml, opts);
661
492
  // 更新文档为解密后的版本
662
493
  samlContent = result[0];
494
+ let res = await this.isValidXml(samlContent).catch((error) => {
495
+ return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
496
+ });
497
+ if (res !== true) {
498
+ return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
499
+ }
663
500
  assertionContent = result[1];
664
501
  // 更新验证状态
665
502
  decrypted = true;
@@ -669,6 +506,11 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
669
506
  const decryptedDoc = dom.parseFromString(samlContent, 'application/xml');
670
507
  // 2. 验证最外层消息签名(使用解密后的文档)
671
508
  const signatureNode = messageSignatureNode[0];
509
+ // @ts-expect-error misssing Node properties are not needed
510
+ const signatureAlgorithm = xpath.select1(".//*[local-name(.)='SignatureMethod']/@Algorithm", signatureNode);
511
+ let checkResult = checkUnsafeSignatureAlgorithm(signatureAlgorithm.value || '');
512
+ hasUnsafeSignatureAlgorithm = checkResult.hasUnsafeSignatureAlgorithm;
513
+ unsafeSignatureAlgorithm = checkResult.unsafeSignatureAlgorithm ?? "";
672
514
  const sig = new SignedXml();
673
515
  if (!opts.keyFile && !opts.metadata) {
674
516
  throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
@@ -707,8 +549,6 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
707
549
  sig.loadSignature(signatureNode);
708
550
  // 使用解密后的文档验证最外层签名.默认采用的都是采用的先签名后加密的顺序,对应sp应该先解密 然后验证签名。 如果解密后验证外层签名失败有可能是先加密后签名,此时sp应该直接验证没解密的外层签名
709
551
  MessageSignatureStatus = sig.checkSignature(decryptedDoc.toString());
710
- console.log(MessageSignatureStatus);
711
- console.log("验证MessageSignatureStatus==========================");
712
552
  if (!MessageSignatureStatus) {
713
553
  /** 签名验证失败 再直接验证外层*/
714
554
  let MessageSignatureStatus2 = sig.checkSignature(xml);
@@ -734,6 +574,11 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
734
574
  // 处理最外层有签名但断言未加密的情况
735
575
  else if (isMessageSigned && !encrypted) {
736
576
  const signatureNode = messageSignatureNode[0];
577
+ // @ts-expect-error misssing Node properties are not needed
578
+ const signatureAlgorithm = xpath.select1(".//*[local-name(.)='SignatureMethod']/@Algorithm", signatureNode);
579
+ let checkResult = checkUnsafeSignatureAlgorithm(signatureAlgorithm.value || '');
580
+ hasUnsafeSignatureAlgorithm = checkResult.hasUnsafeSignatureAlgorithm;
581
+ unsafeSignatureAlgorithm = checkResult.unsafeSignatureAlgorithm ?? "";
737
582
  const sig = new SignedXml();
738
583
  if (!opts.keyFile && !opts.metadata) {
739
584
  throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
@@ -767,7 +612,8 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
767
612
  sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
768
613
  }
769
614
  }
770
- sig.signatureAlgorithm = opts.signatureAlgorithm;
615
+ console.log(doc.toString());
616
+ sig.signatureAlgorithm = signatureAlgorithm.value;
771
617
  // @ts-expect-error misssing Node properties are not needed
772
618
  sig.loadSignature(signatureNode);
773
619
  MessageSignatureStatus = sig.checkSignature(doc.toString());
@@ -778,6 +624,11 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
778
624
  // 验证断言级签名(如果存在且未加密)
779
625
  if (isAssertionSigned && !encrypted) {
780
626
  const signatureNode = assertionSignatureNode[0];
627
+ // @ts-expect-error misssing Node properties are not needed
628
+ const signatureAlgorithm = xpath.select1(".//*[local-name(.)='SignatureMethod']/@Algorithm", signatureNode);
629
+ let checkResult = checkUnsafeSignatureAlgorithm(signatureAlgorithm.value || '');
630
+ hasUnsafeSignatureAlgorithm = checkResult.hasUnsafeSignatureAlgorithm;
631
+ unsafeSignatureAlgorithm = checkResult.unsafeSignatureAlgorithm ?? "";
781
632
  const sig = new SignedXml();
782
633
  if (!opts.keyFile && !opts.metadata) {
783
634
  throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
@@ -811,6 +662,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
811
662
  sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
812
663
  }
813
664
  }
665
+ // 检测不安全的签名算法
814
666
  sig.signatureAlgorithm = opts.signatureAlgorithm;
815
667
  // @ts-expect-error misssing Node properties are not needed
816
668
  sig.loadSignature(signatureNode);
@@ -840,6 +692,12 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
840
692
  // 解密断言
841
693
  try {
842
694
  const result = this.decryptAssertionSync(self, xml, opts);
695
+ let res = await this.isValidXml(samlContent).catch((error) => {
696
+ return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
697
+ });
698
+ if (res !== true) {
699
+ return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
700
+ }
843
701
  samlContent = result[0];
844
702
  assertionContent = result[1];
845
703
  decrypted = true;
@@ -863,6 +721,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
863
721
  status = (!isMessageSigned || MessageSignatureStatus) &&
864
722
  (!isAssertionSigned || AssertionSignatureStatus) &&
865
723
  (!encrypted || decrypted);
724
+ // 最终检测不安全的签名算法
866
725
  return {
867
726
  isMessageSigned,
868
727
  MessageSignatureStatus,
@@ -873,7 +732,9 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
873
732
  type, // 添加类型字段
874
733
  status,
875
734
  samlContent,
876
- assertionContent
735
+ assertionContent,
736
+ hasUnsafeSignatureAlgorithm,
737
+ unsafeSignatureAlgorithm
877
738
  };
878
739
  },
879
740
  verifySignatureSoap(xml, opts) {
@@ -939,6 +800,8 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
939
800
  for (const signatureNode of selection) {
940
801
  const sig = new SignedXml();
941
802
  let verified = false;
803
+ // 检测不安全的签名算法
804
+ const { hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm } = checkUnsafeSignatureAlgorithm(opts.signatureAlgorithm || '');
942
805
  sig.signatureAlgorithm = opts.signatureAlgorithm;
943
806
  if (!opts.keyFile && !opts.metadata) {
944
807
  throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
@@ -991,25 +854,25 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
991
854
  // @ts-expect-error
992
855
  const assertions = select("./*[local-name()='Assertion']", rootNode);
993
856
  if (encryptedAssert.length === 1) {
994
- return [true, encryptedAssert[0].toString(), true, false];
857
+ return [true, encryptedAssert[0].toString(), true, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
995
858
  }
996
859
  if (assertions.length === 1) {
997
- return [true, assertions[0].toString(), false, false];
860
+ return [true, assertions[0].toString(), false, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
998
861
  }
999
- return [true, null, false, true]; // 签名验证成功但未找到断言
862
+ return [true, null, false, true, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm]; // 签名验证成功但未找到断言
1000
863
  case 'Assertion':
1001
- return [true, rootNode.toString(), false, false];
864
+ return [true, rootNode.toString(), false, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
1002
865
  case 'EncryptedAssertion':
1003
- return [true, rootNode.toString(), true, false];
866
+ return [true, rootNode.toString(), true, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
1004
867
  case 'ArtifactResolve':
1005
868
  case 'ArtifactResponse':
1006
869
  // 提取SOAP消息内部的实际内容
1007
- return [true, rootNode.toString(), false, false];
870
+ return [true, rootNode.toString(), false, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
1008
871
  default:
1009
- return [true, null, false, true]; // 签名验证成功但未找到可识别的内容
872
+ return [true, null, false, true, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm]; // 签名验证成功但未找到可识别的内容
1010
873
  }
1011
874
  }
1012
- return [false, null, encryptedAssertions.length > 0, false];
875
+ return [false, null, encryptedAssertions.length > 0, false, false, null];
1013
876
  },
1014
877
  /**
1015
878
  * @desc Helper function to create the key section in metadata (abstraction for signing and encrypt use)
@@ -1239,6 +1102,8 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
1239
1102
  if (!decryptedResult) {
1240
1103
  throw new Error('ERR_UNDEFINED_DECRYPTED_ASSERTION');
1241
1104
  }
1105
+ let hasUnsafeSignatureAlgorithm = false;
1106
+ let unsafeSignatureAlgorithm = "";
1242
1107
  // 解密完成后,检查解密后的断言是否还有签名需要验证
1243
1108
  const decryptedAssertionDoc = dom.parseFromString(decryptedResult, 'application/xml');
1244
1109
  let AssertionSignatureStatus = false;
@@ -1248,6 +1113,11 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
1248
1113
  if (assertionSignatureNode.length > 0 && opts) {
1249
1114
  // 解密后的断言有签名,需要验证
1250
1115
  const signatureNode = assertionSignatureNode[0];
1116
+ // @ts-expect-error misssing Node properties are not needed
1117
+ const signatureAlgorithm = xpath.select1(".//*[local-name(.)='SignatureMethod']/@Algorithm", signatureNode);
1118
+ let checkResult = checkUnsafeSignatureAlgorithm(signatureAlgorithm.value || '');
1119
+ hasUnsafeSignatureAlgorithm = checkResult.hasUnsafeSignatureAlgorithm;
1120
+ unsafeSignatureAlgorithm = checkResult.unsafeSignatureAlgorithm ?? "";
1251
1121
  const sig = new SignedXml();
1252
1122
  if (!opts.keyFile && !opts.metadata) {
1253
1123
  throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
@@ -1281,6 +1151,10 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
1281
1151
  sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
1282
1152
  }
1283
1153
  }
1154
+ // 检测不安全的签名算法
1155
+ let checkSafeResult = checkUnsafeSignatureAlgorithm(opts.signatureAlgorithm || '');
1156
+ hasUnsafeSignatureAlgorithm = checkSafeResult.hasUnsafeSignatureAlgorithm;
1157
+ unsafeSignatureAlgorithm = checkSafeResult.unsafeSignatureAlgorithm ?? "";
1284
1158
  sig.signatureAlgorithm = opts.signatureAlgorithm;
1285
1159
  // @ts-expect-error misssing Node properties are not needed
1286
1160
  sig.loadSignature(signatureNode);
@@ -1288,8 +1162,6 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
1288
1162
  const assertionDocForVerification = dom.parseFromString(decryptedResult, 'application/xml');
1289
1163
  const assertionValid = sig.checkSignature(assertionDocForVerification.toString());
1290
1164
  AssertionSignatureStatus = assertionValid;
1291
- console.log(AssertionSignatureStatus);
1292
- console.log("验证通过了====");
1293
1165
  if (!assertionValid) {
1294
1166
  throw new Error('ERR_FAILED_TO_VERIFY_DECRYPTED_ASSERTION_SIGNATURE');
1295
1167
  }
@@ -1300,7 +1172,9 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
1300
1172
  doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
1301
1173
  return [doc.toString(), decryptedResult, {
1302
1174
  isAssertionSigned: !!(assertionSignatureNode.length > 0 && opts),
1303
- AssertionSignatureStatus: AssertionSignatureStatus
1175
+ AssertionSignatureStatus: AssertionSignatureStatus,
1176
+ hasUnsafeSignatureAlgorithm,
1177
+ unsafeSignatureAlgorithm
1304
1178
  }];
1305
1179
  },
1306
1180
  /**