samlesa 2.18.1 → 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.
- package/build/src/binding-artifact.js +1 -1
- package/build/src/flow.js +19 -101
- package/build/src/libsaml.js +87 -214
- package/package.json +77 -77
- package/types/api.d.ts +15 -0
- package/types/api.d.ts.map +1 -0
- package/types/binding-post.d.ts +48 -0
- package/types/binding-post.d.ts.map +1 -0
- package/types/binding-redirect.d.ts +54 -0
- package/types/binding-redirect.d.ts.map +1 -0
- package/types/binding-simplesign.d.ts +41 -0
- package/types/binding-simplesign.d.ts.map +1 -0
- package/types/entity-idp.d.ts +38 -0
- package/types/entity-idp.d.ts.map +1 -0
- package/types/entity-sp.d.ts +38 -0
- package/types/entity-sp.d.ts.map +1 -0
- package/types/entity.d.ts +100 -0
- package/types/entity.d.ts.map +1 -0
- package/types/extractor.d.ts +26 -0
- package/types/extractor.d.ts.map +1 -0
- package/types/flow.d.ts +7 -0
- package/types/flow.d.ts.map +1 -0
- package/types/libsaml.d.ts +208 -0
- package/types/libsaml.d.ts.map +1 -0
- package/types/metadata-idp.d.ts +25 -0
- package/types/metadata-idp.d.ts.map +1 -0
- package/types/metadata-sp.d.ts +37 -0
- package/types/metadata-sp.d.ts.map +1 -0
- package/types/metadata.d.ts +58 -0
- package/types/metadata.d.ts.map +1 -0
- package/types/src/flow.d.ts.map +1 -1
- package/types/src/libsaml.d.ts +12 -20
- package/types/src/libsaml.d.ts.map +1 -1
- package/types/src/utility.d.ts +1 -1
- package/types/src/utility.d.ts.map +1 -1
- package/types/types.d.ts +128 -0
- package/types/types.d.ts.map +1 -0
- package/types/urn.d.ts +195 -0
- package/types/urn.d.ts.map +1 -0
- package/types/utility.d.ts +133 -0
- package/types/utility.d.ts.map +1 -0
- package/types/validator.d.ts +4 -0
- 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(
|
|
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
|
// 检查验证结果
|
package/build/src/libsaml.js
CHANGED
|
@@ -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,11 +549,8 @@ 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
|
-
throw new Error('ERR_FAILED_TO_VERIFY_MESSAGE_SIGNATURE_AFTER_DECRYPTION');
|
|
715
554
|
let MessageSignatureStatus2 = sig.checkSignature(xml);
|
|
716
555
|
if (!MessageSignatureStatus2) {
|
|
717
556
|
throw new Error('ERR_FAILED_TO_VERIFY_MESSAGE_SIGNATURE_AFTER_DECRYPTION');
|
|
@@ -735,6 +574,11 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
735
574
|
// 处理最外层有签名但断言未加密的情况
|
|
736
575
|
else if (isMessageSigned && !encrypted) {
|
|
737
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 ?? "";
|
|
738
582
|
const sig = new SignedXml();
|
|
739
583
|
if (!opts.keyFile && !opts.metadata) {
|
|
740
584
|
throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
|
|
@@ -768,7 +612,8 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
768
612
|
sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
|
|
769
613
|
}
|
|
770
614
|
}
|
|
771
|
-
|
|
615
|
+
console.log(doc.toString());
|
|
616
|
+
sig.signatureAlgorithm = signatureAlgorithm.value;
|
|
772
617
|
// @ts-expect-error misssing Node properties are not needed
|
|
773
618
|
sig.loadSignature(signatureNode);
|
|
774
619
|
MessageSignatureStatus = sig.checkSignature(doc.toString());
|
|
@@ -779,6 +624,11 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
779
624
|
// 验证断言级签名(如果存在且未加密)
|
|
780
625
|
if (isAssertionSigned && !encrypted) {
|
|
781
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 ?? "";
|
|
782
632
|
const sig = new SignedXml();
|
|
783
633
|
if (!opts.keyFile && !opts.metadata) {
|
|
784
634
|
throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
|
|
@@ -812,6 +662,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
812
662
|
sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
|
|
813
663
|
}
|
|
814
664
|
}
|
|
665
|
+
// 检测不安全的签名算法
|
|
815
666
|
sig.signatureAlgorithm = opts.signatureAlgorithm;
|
|
816
667
|
// @ts-expect-error misssing Node properties are not needed
|
|
817
668
|
sig.loadSignature(signatureNode);
|
|
@@ -841,6 +692,12 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
841
692
|
// 解密断言
|
|
842
693
|
try {
|
|
843
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
|
+
}
|
|
844
701
|
samlContent = result[0];
|
|
845
702
|
assertionContent = result[1];
|
|
846
703
|
decrypted = true;
|
|
@@ -864,6 +721,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
864
721
|
status = (!isMessageSigned || MessageSignatureStatus) &&
|
|
865
722
|
(!isAssertionSigned || AssertionSignatureStatus) &&
|
|
866
723
|
(!encrypted || decrypted);
|
|
724
|
+
// 最终检测不安全的签名算法
|
|
867
725
|
return {
|
|
868
726
|
isMessageSigned,
|
|
869
727
|
MessageSignatureStatus,
|
|
@@ -874,7 +732,9 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
874
732
|
type, // 添加类型字段
|
|
875
733
|
status,
|
|
876
734
|
samlContent,
|
|
877
|
-
assertionContent
|
|
735
|
+
assertionContent,
|
|
736
|
+
hasUnsafeSignatureAlgorithm,
|
|
737
|
+
unsafeSignatureAlgorithm
|
|
878
738
|
};
|
|
879
739
|
},
|
|
880
740
|
verifySignatureSoap(xml, opts) {
|
|
@@ -940,6 +800,8 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
940
800
|
for (const signatureNode of selection) {
|
|
941
801
|
const sig = new SignedXml();
|
|
942
802
|
let verified = false;
|
|
803
|
+
// 检测不安全的签名算法
|
|
804
|
+
const { hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm } = checkUnsafeSignatureAlgorithm(opts.signatureAlgorithm || '');
|
|
943
805
|
sig.signatureAlgorithm = opts.signatureAlgorithm;
|
|
944
806
|
if (!opts.keyFile && !opts.metadata) {
|
|
945
807
|
throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
|
|
@@ -992,25 +854,25 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
992
854
|
// @ts-expect-error
|
|
993
855
|
const assertions = select("./*[local-name()='Assertion']", rootNode);
|
|
994
856
|
if (encryptedAssert.length === 1) {
|
|
995
|
-
return [true, encryptedAssert[0].toString(), true, false];
|
|
857
|
+
return [true, encryptedAssert[0].toString(), true, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
|
|
996
858
|
}
|
|
997
859
|
if (assertions.length === 1) {
|
|
998
|
-
return [true, assertions[0].toString(), false, false];
|
|
860
|
+
return [true, assertions[0].toString(), false, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
|
|
999
861
|
}
|
|
1000
|
-
return [true, null, false, true]; // 签名验证成功但未找到断言
|
|
862
|
+
return [true, null, false, true, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm]; // 签名验证成功但未找到断言
|
|
1001
863
|
case 'Assertion':
|
|
1002
|
-
return [true, rootNode.toString(), false, false];
|
|
864
|
+
return [true, rootNode.toString(), false, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
|
|
1003
865
|
case 'EncryptedAssertion':
|
|
1004
|
-
return [true, rootNode.toString(), true, false];
|
|
866
|
+
return [true, rootNode.toString(), true, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
|
|
1005
867
|
case 'ArtifactResolve':
|
|
1006
868
|
case 'ArtifactResponse':
|
|
1007
869
|
// 提取SOAP消息内部的实际内容
|
|
1008
|
-
return [true, rootNode.toString(), false, false];
|
|
870
|
+
return [true, rootNode.toString(), false, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
|
|
1009
871
|
default:
|
|
1010
|
-
return [true, null, false, true]; // 签名验证成功但未找到可识别的内容
|
|
872
|
+
return [true, null, false, true, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm]; // 签名验证成功但未找到可识别的内容
|
|
1011
873
|
}
|
|
1012
874
|
}
|
|
1013
|
-
return [false, null, encryptedAssertions.length > 0, false];
|
|
875
|
+
return [false, null, encryptedAssertions.length > 0, false, false, null];
|
|
1014
876
|
},
|
|
1015
877
|
/**
|
|
1016
878
|
* @desc Helper function to create the key section in metadata (abstraction for signing and encrypt use)
|
|
@@ -1240,6 +1102,8 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1240
1102
|
if (!decryptedResult) {
|
|
1241
1103
|
throw new Error('ERR_UNDEFINED_DECRYPTED_ASSERTION');
|
|
1242
1104
|
}
|
|
1105
|
+
let hasUnsafeSignatureAlgorithm = false;
|
|
1106
|
+
let unsafeSignatureAlgorithm = "";
|
|
1243
1107
|
// 解密完成后,检查解密后的断言是否还有签名需要验证
|
|
1244
1108
|
const decryptedAssertionDoc = dom.parseFromString(decryptedResult, 'application/xml');
|
|
1245
1109
|
let AssertionSignatureStatus = false;
|
|
@@ -1249,6 +1113,11 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1249
1113
|
if (assertionSignatureNode.length > 0 && opts) {
|
|
1250
1114
|
// 解密后的断言有签名,需要验证
|
|
1251
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 ?? "";
|
|
1252
1121
|
const sig = new SignedXml();
|
|
1253
1122
|
if (!opts.keyFile && !opts.metadata) {
|
|
1254
1123
|
throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
|
|
@@ -1282,6 +1151,10 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1282
1151
|
sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
|
|
1283
1152
|
}
|
|
1284
1153
|
}
|
|
1154
|
+
// 检测不安全的签名算法
|
|
1155
|
+
let checkSafeResult = checkUnsafeSignatureAlgorithm(opts.signatureAlgorithm || '');
|
|
1156
|
+
hasUnsafeSignatureAlgorithm = checkSafeResult.hasUnsafeSignatureAlgorithm;
|
|
1157
|
+
unsafeSignatureAlgorithm = checkSafeResult.unsafeSignatureAlgorithm ?? "";
|
|
1285
1158
|
sig.signatureAlgorithm = opts.signatureAlgorithm;
|
|
1286
1159
|
// @ts-expect-error misssing Node properties are not needed
|
|
1287
1160
|
sig.loadSignature(signatureNode);
|
|
@@ -1289,8 +1162,6 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1289
1162
|
const assertionDocForVerification = dom.parseFromString(decryptedResult, 'application/xml');
|
|
1290
1163
|
const assertionValid = sig.checkSignature(assertionDocForVerification.toString());
|
|
1291
1164
|
AssertionSignatureStatus = assertionValid;
|
|
1292
|
-
console.log(AssertionSignatureStatus);
|
|
1293
|
-
console.log("验证通过了====");
|
|
1294
1165
|
if (!assertionValid) {
|
|
1295
1166
|
throw new Error('ERR_FAILED_TO_VERIFY_DECRYPTED_ASSERTION_SIGNATURE');
|
|
1296
1167
|
}
|
|
@@ -1301,7 +1172,9 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1301
1172
|
doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
|
|
1302
1173
|
return [doc.toString(), decryptedResult, {
|
|
1303
1174
|
isAssertionSigned: !!(assertionSignatureNode.length > 0 && opts),
|
|
1304
|
-
AssertionSignatureStatus: AssertionSignatureStatus
|
|
1175
|
+
AssertionSignatureStatus: AssertionSignatureStatus,
|
|
1176
|
+
hasUnsafeSignatureAlgorithm,
|
|
1177
|
+
unsafeSignatureAlgorithm
|
|
1305
1178
|
}];
|
|
1306
1179
|
},
|
|
1307
1180
|
/**
|