samlesa 2.16.0 → 2.16.5

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.
@@ -0,0 +1,333 @@
1
+ /**
2
+ * @file binding-post.ts
3
+ * @author tngan
4
+ * @desc Binding-level API, declare the functions using POST binding
5
+ */
6
+ import { wording, StatusCode } from './urn.js';
7
+ import libsaml from './libsaml.js';
8
+ import utility, { get } from './utility.js';
9
+ const binding = wording.binding;
10
+ /**
11
+ * @desc Generate a base64 encoded login request
12
+ * @param {string} referenceTagXPath reference uri
13
+ * @param {object} entity object includes both idp and sp
14
+ * @param {function} customTagReplacement used when developers have their own login response template
15
+ */
16
+ function base64LoginRequest(referenceTagXPath, entity, customTagReplacement) {
17
+ const metadata = { idp: entity.idp.entityMeta, sp: entity.sp.entityMeta };
18
+ const spSetting = entity.sp.entitySetting;
19
+ let id = '';
20
+ if (metadata && metadata.idp && metadata.sp) {
21
+ const base = metadata.idp.getSingleSignOnService(binding.post);
22
+ let rawSamlRequest;
23
+ if (spSetting.loginRequestTemplate && customTagReplacement) {
24
+ const info = customTagReplacement(spSetting.loginRequestTemplate.context);
25
+ id = get(info, 'id', null);
26
+ rawSamlRequest = get(info, 'context', null);
27
+ }
28
+ else {
29
+ const nameIDFormat = spSetting.nameIDFormat;
30
+ const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
31
+ id = spSetting.generateID();
32
+ rawSamlRequest = libsaml.replaceTagsByValue(libsaml.defaultLoginRequestTemplate.context, {
33
+ ID: id,
34
+ Destination: base,
35
+ Issuer: metadata.sp.getEntityID(),
36
+ IssueInstant: new Date().toISOString(),
37
+ AssertionConsumerServiceURL: metadata.sp.getAssertionConsumerService(binding.post),
38
+ EntityID: metadata.sp.getEntityID(),
39
+ AllowCreate: spSetting.allowCreate,
40
+ NameIDFormat: selectedNameIDFormat
41
+ });
42
+ }
43
+ if (metadata.idp.isWantAuthnRequestsSigned()) {
44
+ const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
45
+ return {
46
+ id,
47
+ context: libsaml.constructSAMLSignature({
48
+ referenceTagXPath,
49
+ privateKey,
50
+ privateKeyPass,
51
+ signatureAlgorithm,
52
+ transformationAlgorithms,
53
+ rawSamlMessage: rawSamlRequest,
54
+ signingCert: metadata.sp.getX509Certificate('signing'),
55
+ signatureConfig: spSetting.signatureConfig || {
56
+ prefix: 'ds',
57
+ location: { reference: "/*[local-name(.)='AuthnRequest']/*[local-name(.)='Issuer']", action: 'after' },
58
+ }
59
+ }),
60
+ };
61
+ }
62
+ // No need to embeded XML signature
63
+ return {
64
+ id,
65
+ context: utility.base64Encode(rawSamlRequest),
66
+ };
67
+ }
68
+ throw new Error('ERR_GENERATE_POST_LOGIN_REQUEST_MISSING_METADATA');
69
+ }
70
+ /**
71
+ * @desc Generate a base64 encoded login response
72
+ * @param {object} requestInfo corresponding request, used to obtain the id
73
+ * @param {object} entity object includes both idp and sp
74
+ * @param {object} user current logged user (e.g. req.user)
75
+ * @param {function} customTagReplacement used when developers have their own login response template
76
+ * @param {boolean} encryptThenSign whether or not to encrypt then sign first (if signing). Defaults to sign-then-encrypt
77
+ * @param AttributeStatement
78
+ */
79
+ async function base64LoginResponse(requestInfo = {}, entity, user = {}, customTagReplacement, encryptThenSign = false, AttributeStatement = []) {
80
+ const idpSetting = entity.idp.entitySetting;
81
+ const spSetting = entity.sp.entitySetting;
82
+ const id = idpSetting.generateID();
83
+ const metadata = {
84
+ idp: entity.idp.entityMeta,
85
+ sp: entity.sp.entityMeta,
86
+ };
87
+ const nameIDFormat = idpSetting.nameIDFormat;
88
+ const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
89
+ if (metadata && metadata.idp && metadata.sp) {
90
+ const base = metadata.sp.getAssertionConsumerService(binding.post);
91
+ let rawSamlResponse;
92
+ const nowTime = new Date();
93
+ const spEntityID = metadata.sp.getEntityID();
94
+ const oneMinutesLaterTime = new Date(nowTime.getTime());
95
+ oneMinutesLaterTime.setMinutes(oneMinutesLaterTime.getMinutes() + 5);
96
+ const OneMinutesLater = oneMinutesLaterTime.toISOString();
97
+ const now = nowTime.toISOString();
98
+ const acl = metadata.sp.getAssertionConsumerService(binding.post);
99
+ const sessionIndex = 'session' + idpSetting.generateID(); // 这个是当前系统的会话索引,用于单点注销
100
+ const tenHoursLaterTime = new Date(nowTime.getTime());
101
+ tenHoursLaterTime.setHours(tenHoursLaterTime.getHours() + 10);
102
+ const tenHoursLater = tenHoursLaterTime.toISOString();
103
+ const tvalue = {
104
+ ID: id,
105
+ AssertionID: idpSetting.generateID(),
106
+ Destination: base,
107
+ Audience: spEntityID,
108
+ EntityID: spEntityID,
109
+ SubjectRecipient: acl,
110
+ Issuer: metadata.idp.getEntityID(),
111
+ IssueInstant: now,
112
+ AssertionConsumerServiceURL: acl,
113
+ StatusCode: StatusCode.Success,
114
+ // can be customized
115
+ ConditionsNotBefore: now,
116
+ ConditionsNotOnOrAfter: OneMinutesLater,
117
+ SubjectConfirmationDataNotOnOrAfter: OneMinutesLater,
118
+ NameIDFormat: selectedNameIDFormat,
119
+ NameID: user?.NameID || '',
120
+ InResponseTo: get(requestInfo, 'extract.request.id', ''),
121
+ AuthnStatement: `<saml:AuthnStatement AuthnInstant="${now}" SessionNotOnOrAfter="${tenHoursLater}" SessionIndex="${sessionIndex}"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement>`,
122
+ AttributeStatement: libsaml.attributeStatementBuilder(AttributeStatement),
123
+ };
124
+ if (idpSetting.loginResponseTemplate && customTagReplacement) {
125
+ const template = customTagReplacement(idpSetting.loginResponseTemplate.context);
126
+ rawSamlResponse = get(template, 'context', null);
127
+ }
128
+ else {
129
+ if (requestInfo !== null) {
130
+ tvalue.InResponseTo = requestInfo?.extract?.request?.id ?? '';
131
+ }
132
+ rawSamlResponse = libsaml.replaceTagsByValue(libsaml.defaultLoginResponseTemplate.context, tvalue);
133
+ }
134
+ const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm } = idpSetting;
135
+ const config = {
136
+ privateKey,
137
+ privateKeyPass,
138
+ signatureAlgorithm,
139
+ signingCert: metadata.idp.getX509Certificate('signing'),
140
+ isBase64Output: false,
141
+ };
142
+ // step: sign assertion ? -> encrypted ? -> sign message ?
143
+ if (metadata.sp.isWantAssertionsSigned()) {
144
+ // console.debug('sp wants assertion signed');
145
+ rawSamlResponse = libsaml.constructSAMLSignature({
146
+ ...config,
147
+ rawSamlMessage: rawSamlResponse,
148
+ transformationAlgorithms: spSetting.transformationAlgorithms,
149
+ referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']",
150
+ signatureConfig: {
151
+ prefix: 'ds',
152
+ location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']", action: 'after' },
153
+ },
154
+ });
155
+ }
156
+ // console.debug('after assertion signed', rawSamlResponse);
157
+ // SAML response must be signed sign message first, then encrypt
158
+ if (!encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
159
+ // console.debug('sign then encrypt and sign entire message');
160
+ rawSamlResponse = libsaml.constructSAMLSignature({
161
+ ...config,
162
+ rawSamlMessage: rawSamlResponse,
163
+ isMessageSigned: true,
164
+ transformationAlgorithms: spSetting.transformationAlgorithms,
165
+ signatureConfig: spSetting.signatureConfig || {
166
+ prefix: 'ds',
167
+ location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Issuer']", action: 'after' },
168
+ },
169
+ });
170
+ }
171
+ // console.debug('after message signed', rawSamlResponse);
172
+ if (idpSetting.isAssertionEncrypted) {
173
+ // console.debug('idp is configured to do encryption');
174
+ const context = await libsaml.encryptAssertion(entity.idp, entity.sp, rawSamlResponse);
175
+ if (encryptThenSign) {
176
+ //need to decode it
177
+ rawSamlResponse = utility.base64Decode(context);
178
+ }
179
+ else {
180
+ return Promise.resolve({ id, context });
181
+ }
182
+ }
183
+ //sign after encrypting
184
+ if (encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
185
+ rawSamlResponse = libsaml.constructSAMLSignature({
186
+ ...config,
187
+ rawSamlMessage: rawSamlResponse,
188
+ isMessageSigned: true,
189
+ transformationAlgorithms: spSetting.transformationAlgorithms,
190
+ signatureConfig: spSetting.signatureConfig || {
191
+ prefix: 'ds',
192
+ location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Issuer']", action: 'after' },
193
+ },
194
+ });
195
+ }
196
+ return Promise.resolve({
197
+ id,
198
+ context: utility.base64Encode(rawSamlResponse),
199
+ });
200
+ }
201
+ throw new Error('ERR_GENERATE_POST_LOGIN_RESPONSE_MISSING_METADATA');
202
+ }
203
+ /**
204
+ * @desc Generate a base64 encoded logout request
205
+ * @param {object} user current logged user (e.g. req.user)
206
+ * @param {string} referenceTagXPath reference uri
207
+ * @param {object} entity object includes both idp and sp
208
+ * @param {function} customTagReplacement used when developers have their own login response template
209
+ * @return {string} base64 encoded request
210
+ */
211
+ function base64LogoutRequest(user, referenceTagXPath, entity, customTagReplacement) {
212
+ const metadata = { init: entity.init.entityMeta, target: entity.target.entityMeta };
213
+ const initSetting = entity.init.entitySetting;
214
+ const nameIDFormat = initSetting.nameIDFormat;
215
+ const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
216
+ let id = '';
217
+ if (metadata && metadata.init && metadata.target) {
218
+ let rawSamlRequest;
219
+ if (initSetting.logoutRequestTemplate && customTagReplacement) {
220
+ const template = customTagReplacement(initSetting.logoutRequestTemplate.context);
221
+ id = get(template, 'id', null);
222
+ rawSamlRequest = get(template, 'context', null);
223
+ }
224
+ else {
225
+ id = initSetting.generateID();
226
+ const tvalue = {
227
+ ID: id,
228
+ Destination: metadata.target.getSingleLogoutService(binding.post),
229
+ Issuer: metadata.init.getEntityID(),
230
+ IssueInstant: new Date().toISOString(),
231
+ EntityID: metadata.init.getEntityID(),
232
+ NameIDFormat: selectedNameIDFormat,
233
+ NameID: user.NameID || '',
234
+ };
235
+ rawSamlRequest = libsaml.replaceTagsByValue(libsaml.defaultLogoutRequestTemplate.context, tvalue);
236
+ }
237
+ if (entity.target.entitySetting.wantLogoutRequestSigned) {
238
+ // Need to embeded XML signature
239
+ const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = initSetting;
240
+ return {
241
+ id,
242
+ context: libsaml.constructSAMLSignature({
243
+ referenceTagXPath,
244
+ privateKey,
245
+ privateKeyPass,
246
+ signatureAlgorithm,
247
+ transformationAlgorithms,
248
+ rawSamlMessage: rawSamlRequest,
249
+ signingCert: metadata.init.getX509Certificate('signing'),
250
+ signatureConfig: initSetting.signatureConfig || {
251
+ prefix: 'ds',
252
+ location: { reference: "/*[local-name(.)='LogoutRequest']/*[local-name(.)='Issuer']", action: 'after' },
253
+ }
254
+ }),
255
+ };
256
+ }
257
+ return {
258
+ id,
259
+ context: utility.base64Encode(rawSamlRequest),
260
+ };
261
+ }
262
+ throw new Error('ERR_GENERATE_POST_LOGOUT_REQUEST_MISSING_METADATA');
263
+ }
264
+ /**
265
+ * @desc Generate a base64 encoded logout response
266
+ * @param {object} requestInfo corresponding request, used to obtain the id
267
+ * @param {string} referenceTagXPath reference uri
268
+ * @param {object} entity object includes both idp and sp
269
+ * @param {function} customTagReplacement used when developers have their own login response template
270
+ */
271
+ function base64LogoutResponse(requestInfo, entity, customTagReplacement) {
272
+ const metadata = {
273
+ init: entity.init.entityMeta,
274
+ target: entity.target.entityMeta,
275
+ };
276
+ let id = '';
277
+ const initSetting = entity.init.entitySetting;
278
+ if (metadata && metadata.init && metadata.target) {
279
+ let rawSamlResponse;
280
+ if (initSetting.logoutResponseTemplate) {
281
+ const template = customTagReplacement(initSetting.logoutResponseTemplate.context);
282
+ id = template.id;
283
+ rawSamlResponse = template.context;
284
+ }
285
+ else {
286
+ id = initSetting.generateID();
287
+ const tvalue = {
288
+ ID: id,
289
+ Destination: metadata.target.getSingleLogoutService(binding.post),
290
+ EntityID: metadata.init.getEntityID(),
291
+ Issuer: metadata.init.getEntityID(),
292
+ IssueInstant: new Date().toISOString(),
293
+ StatusCode: StatusCode.Success,
294
+ InResponseTo: get(requestInfo, 'extract.request.id', '')
295
+ };
296
+ rawSamlResponse = libsaml.replaceTagsByValue(libsaml.defaultLogoutResponseTemplate.context, tvalue);
297
+ }
298
+ if (entity.target.entitySetting.wantLogoutResponseSigned) {
299
+ const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = initSetting;
300
+ return {
301
+ id,
302
+ context: libsaml.constructSAMLSignature({
303
+ isMessageSigned: true,
304
+ transformationAlgorithms: transformationAlgorithms,
305
+ privateKey,
306
+ privateKeyPass,
307
+ signatureAlgorithm,
308
+ rawSamlMessage: rawSamlResponse,
309
+ signingCert: metadata.init.getX509Certificate('signing'),
310
+ signatureConfig: {
311
+ prefix: 'ds',
312
+ location: {
313
+ reference: "/*[local-name(.)='LogoutResponse']/*[local-name(.)='Issuer']",
314
+ action: 'after'
315
+ }
316
+ }
317
+ }),
318
+ };
319
+ }
320
+ return {
321
+ id,
322
+ context: utility.base64Encode(rawSamlResponse),
323
+ };
324
+ }
325
+ throw new Error('ERR_GENERATE_POST_LOGOUT_RESPONSE_MISSING_METADATA');
326
+ }
327
+ const artifactSignBinding = {
328
+ base64LoginRequest,
329
+ base64LoginResponse,
330
+ base64LogoutRequest,
331
+ base64LogoutResponse,
332
+ };
333
+ export default artifactSignBinding;
@@ -8,6 +8,7 @@ import { namespace } from './urn.js';
8
8
  import redirectBinding from './binding-redirect.js';
9
9
  import postBinding from './binding-post.js';
10
10
  import simpleSignBinding from './binding-simplesign.js';
11
+ import artifactSignBinding from './binding-artifact.js';
11
12
  import { flow } from './flow.js';
12
13
  /*
13
14
  * @desc interface function
@@ -56,6 +57,9 @@ export class ServiceProvider extends Entity {
56
57
  // Object context = {id, context, signature, sigAlg}
57
58
  context = simpleSignBinding.base64LoginRequest({ idp, sp: this }, customTagReplacement);
58
59
  break;
60
+ case nsBinding.artifact:
61
+ context = artifactSignBinding.base64LoginRequest("/*[local-name(.)='AuthnRequest']", { idp, sp: this }, customTagReplacement);
62
+ break;
59
63
  default:
60
64
  // Will support artifact in the next release
61
65
  throw new Error('ERR_SP_LOGIN_REQUEST_UNDEFINED_BINDING');
@@ -85,4 +89,23 @@ export class ServiceProvider extends Entity {
85
89
  request: request
86
90
  });
87
91
  }
92
+ /**
93
+ * @desc request SamlResponse by Arc id
94
+ * @param {IdentityProvider} idp object of identity provider
95
+ * @param {string} binding protocol binding
96
+ * @param {request} req request
97
+ */
98
+ artifactResolveResponse(idp, binding, request) {
99
+ const self = this;
100
+ return flow({
101
+ soap: true,
102
+ from: idp,
103
+ self: self,
104
+ checkSignature: true, // saml response must have signature
105
+ parserType: 'SAMLResponse',
106
+ type: 'login',
107
+ binding: binding,
108
+ request: request
109
+ });
110
+ }
88
111
  }
package/build/src/flow.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import { base64Decode } from './utility.js';
2
2
  import { verifyTime } from './validator.js';
3
3
  import libsaml from './libsaml.js';
4
+ import * as uuid from 'uuid';
5
+ import { select } from 'xpath';
6
+ import { DOMParser } from '@xmldom/xmldom';
7
+ import { sendArtifactResolve } from "./soap.js";
4
8
  import { extract, loginRequestFields, loginResponseFields, logoutRequestFields, logoutResponseFields, logoutResponseStatusFields, loginResponseStatusFields } from './extractor.js';
5
9
  import { BindingNamespace, ParserType, wording, StatusCode } from './urn.js';
6
10
  const bindDict = wording.binding;
@@ -122,6 +126,237 @@ async function redirectFlow(options) {
122
126
  }
123
127
  // proceed the post flow
124
128
  async function postFlow(options) {
129
+ const { soap = false, request, from, self, parserType, checkSignature = true } = options;
130
+ const { body } = request;
131
+ const direction = libsaml.getQueryParamByType(parserType);
132
+ let encodedRequest = '';
133
+ let samlContent = '';
134
+ if (soap === false) {
135
+ encodedRequest = body[direction];
136
+ // @ts-ignore
137
+ samlContent = String(base64Decode(encodedRequest));
138
+ }
139
+ /** 增加判断是不是Soap 工件绑定*/
140
+ if (soap) {
141
+ const metadata = {
142
+ idp: from.entityMeta,
143
+ sp: self.entityMeta,
144
+ };
145
+ const spSetting = self.entitySetting;
146
+ let ID = '_' + uuid.v4();
147
+ let url = metadata.idp.getArtifactResolutionService(bindDict.soap);
148
+ let samlSoapRaw = libsaml.replaceTagsByValue(libsaml.defaultArtifactResolveTemplate.context, {
149
+ ID: request?.messageHandle,
150
+ Destination: url,
151
+ Issuer: metadata.sp.getEntityID(),
152
+ IssueInstant: new Date().toISOString(),
153
+ Art: request.Art
154
+ });
155
+ if (metadata.idp.isWantAuthnRequestsSigned()) {
156
+ const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
157
+ let signatureSoap = libsaml.constructSAMLSignature({
158
+ referenceTagXPath: "//*[local-name(.)='ArtifactResolve']",
159
+ isMessageSigned: false,
160
+ isBase64Output: false,
161
+ transformationAlgorithms: transformationAlgorithms,
162
+ privateKey,
163
+ privateKeyPass,
164
+ signatureAlgorithm,
165
+ rawSamlMessage: samlSoapRaw,
166
+ signingCert: metadata.sp.getX509Certificate('signing'),
167
+ signatureConfig: {
168
+ prefix: 'ds',
169
+ location: {
170
+ reference: "//*[local-name(.)='Issuer']",
171
+ action: 'after'
172
+ }
173
+ }
174
+ });
175
+ let data = await sendArtifactResolve(url, signatureSoap);
176
+ /* console.log(signatureSoap)
177
+ console.log("签过名的")*/
178
+ console.log(data);
179
+ console.log("keycloak数据----------------------");
180
+ samlContent = data;
181
+ }
182
+ // No need to embeded XML signature
183
+ }
184
+ const verificationOptions = {
185
+ metadata: from.entityMeta,
186
+ signatureAlgorithm: from.entitySetting.requestSignatureAlgorithm,
187
+ };
188
+ /** 断言是否加密应根据响应里面的字段判断*/
189
+ let decryptRequired = from.entitySetting.isAssertionEncrypted;
190
+ let extractorFields = [];
191
+ // validate the xml first
192
+ /* let res = await libsaml.isValidXml(samlContent).catch((error)=>{
193
+ console.log(error);
194
+ console.log("验证和结果-----------------------")
195
+ console.log("验证和结果-----------------------")
196
+ console.log("验证和结果-----------------------")
197
+ console.log("验证和结果-----------------------")
198
+ console.log("验证和结果-----------------------")
199
+ console.log("验证和结果-----------------------")
200
+ console.log("验证和结果-----------------------")
201
+ });
202
+ console.log(res);
203
+ console.log("验证和结果-----------------------")*/
204
+ if (parserType !== urlParams.samlResponse) {
205
+ extractorFields = getDefaultExtractorFields(parserType, null);
206
+ }
207
+ // check status based on different scenarios
208
+ /* await checkStatus(samlContent, parserType);*/
209
+ /**检查签名顺序 */
210
+ /* if (
211
+ checkSignature &&
212
+ from.entitySetting.messageSigningOrder === MessageSignatureOrder.ETS
213
+ ) {
214
+ console.log("===============我走的这里=========================")
215
+ const [verified, verifiedAssertionNode,isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
216
+ console.log(verified);
217
+ console.log("verified")
218
+ decryptRequired = isDecryptRequired
219
+ if (!verified) {
220
+ return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
221
+ }
222
+ if (!decryptRequired) {
223
+ extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
224
+ }
225
+ }*/
226
+ if (soap === true) {
227
+ const [verified, verifiedAssertionNode, isDecryptRequired] = libsaml.verifySignatureSoap(samlContent, verificationOptions);
228
+ decryptRequired = isDecryptRequired;
229
+ if (!verified) {
230
+ return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
231
+ }
232
+ if (!decryptRequired) {
233
+ console.log("-------------------走到了这里----------------------");
234
+ extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
235
+ }
236
+ if (parserType === 'SAMLResponse' && decryptRequired) {
237
+ // 1. 解密断言
238
+ const [decryptedSAML, decryptedAssertion] = await libsaml.decryptAssertionSoap(self, samlContent);
239
+ console.log(decryptedAssertion);
240
+ console.log("解密数据-----------------------------");
241
+ // 2. 检查解密后的断言是否包含签名
242
+ const assertionDoc = new DOMParser().parseFromString(decryptedAssertion, 'text/xml');
243
+ const assertionSignatureNodes = select("./*[local-name()='Signature']", assertionDoc.documentElement);
244
+ // 3. 如果存在签名则验证
245
+ if (assertionSignatureNodes.length > 0) {
246
+ // 3.1 创建新的验证选项(保持原配置)
247
+ const assertionVerificationOptions = {
248
+ ...verificationOptions,
249
+ isAssertion: true // 添加标识表示正在验证断言
250
+ };
251
+ // 3.2 验证断言签名
252
+ const [assertionVerified, result] = libsaml.verifySignatureSoap(decryptedAssertion, assertionVerificationOptions);
253
+ console.log(assertionVerified);
254
+ console.log(result);
255
+ console.log("验证机结果--------------");
256
+ if (!assertionVerified) {
257
+ console.error("解密后的断言签名验证失败");
258
+ return Promise.reject('ERR_FAIL_TO_VERIFY_ASSERTION_SIGNATURE');
259
+ }
260
+ if (assertionVerified) {
261
+ // @ts-ignore
262
+ samlContent = result;
263
+ extractorFields = getDefaultExtractorFields(parserType, result);
264
+ }
265
+ }
266
+ else {
267
+ samlContent = decryptedAssertion;
268
+ extractorFields = getDefaultExtractorFields(parserType, decryptedAssertion);
269
+ }
270
+ }
271
+ }
272
+ if (soap === false) {
273
+ const [verified, verifiedAssertionNode, isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
274
+ decryptRequired = isDecryptRequired;
275
+ if (!verified) {
276
+ return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
277
+ }
278
+ if (!decryptRequired) {
279
+ extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
280
+ }
281
+ if (parserType === 'SAMLResponse' && decryptRequired) {
282
+ const result = await libsaml.decryptAssertion(self, samlContent);
283
+ samlContent = result[0];
284
+ extractorFields = getDefaultExtractorFields(parserType, result[1]);
285
+ }
286
+ }
287
+ // verify the signatures (the response is signed then encrypted, then decrypt first then verify)
288
+ /* if (
289
+ checkSignature &&
290
+ from.entitySetting.messageSigningOrder === MessageSignatureOrder.STE
291
+ ) {
292
+ const [verified, verifiedAssertionNode,isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
293
+ decryptRequired = isDecryptRequired
294
+ if (verified) {
295
+ extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
296
+ } else {
297
+ return Promise.reject('ERR_FAIL_TO_VERIFY_STE_SIGNATURE');
298
+ }
299
+ }*/
300
+ const parseResult = {
301
+ samlContent: samlContent,
302
+ extract: extract(samlContent, extractorFields),
303
+ };
304
+ /**
305
+ * Validation part: validate the context of response after signature is verified and decrypted (optional)
306
+ */
307
+ const targetEntityMetadata = from.entityMeta;
308
+ const issuer = targetEntityMetadata.getEntityID();
309
+ const extractedProperties = parseResult.extract;
310
+ console.log(extractedProperties);
311
+ console.log(parseResult);
312
+ console.log("解析结果----------------------------------");
313
+ console.log("签发这-----------");
314
+ // unmatched issuer
315
+ if ((parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
316
+ && extractedProperties
317
+ && extractedProperties.issuer !== issuer) {
318
+ return Promise.reject('ERR_UNMATCH_ISSUER');
319
+ }
320
+ // invalid session time
321
+ // only run the verifyTime when `SessionNotOnOrAfter` exists
322
+ if (parserType === 'SAMLResponse'
323
+ && extractedProperties.sessionIndex.sessionNotOnOrAfter
324
+ && !verifyTime(undefined, extractedProperties.sessionIndex.sessionNotOnOrAfter, self.entitySetting.clockDrifts)) {
325
+ return Promise.reject('ERR_EXPIRED_SESSION');
326
+ }
327
+ // invalid time
328
+ // 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
329
+ if (parserType === 'SAMLResponse'
330
+ && extractedProperties.conditions
331
+ && !verifyTime(extractedProperties.conditions.notBefore, extractedProperties.conditions.notOnOrAfter, self.entitySetting.clockDrifts)) {
332
+ return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
333
+ }
334
+ //valid destination
335
+ //There is no validation of the response here. The upper-layer application
336
+ // should verify the result by itself to see if the destination is equal to the SP acs and
337
+ // whether the response.id is used to prevent replay attacks.
338
+ /*
339
+ let destination = extractedProperties?.response?.destination
340
+ let isExit = self.entitySetting?.assertionConsumerService?.filter((item) => {
341
+ return item?.Location === destination
342
+ })
343
+ if (isExit?.length === 0) {
344
+ return Promise.reject('ERR_Destination_URL');
345
+ }
346
+ if (parserType === 'SAMLResponse') {
347
+ let destination = extractedProperties?.response?.destination
348
+ let isExit = self.entitySetting?.assertionConsumerService?.filter((item: { Location: any; }) => {
349
+ return item?.Location === destination
350
+ })
351
+ if (isExit?.length === 0) {
352
+ return Promise.reject('ERR_Destination_URL');
353
+ }
354
+ }
355
+ */
356
+ return Promise.resolve(parseResult);
357
+ }
358
+ // proceed the post Artifact flow
359
+ async function postArtifactFlow(options) {
125
360
  const { request, from, self, parserType, checkSignature = true } = options;
126
361
  const { body } = request;
127
362
  const direction = libsaml.getQueryParamByType(parserType);
@@ -339,7 +574,6 @@ function checkStatus(content, parserType) {
339
574
  ? loginResponseStatusFields
340
575
  : logoutResponseStatusFields;
341
576
  const { top, second } = extract(content, fields);
342
- console.log(top, second);
343
577
  // only resolve when top-tier status code is success
344
578
  if (top === StatusCode.Success) {
345
579
  return Promise.resolve('OK');