samlesa 4.7.1 → 4.7.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/README.md +19 -19
- package/build/src/extractor.js +1 -0
- package/build/src/flow.js +53 -0
- package/build/src/libsaml.js +188 -221
- package/build/src/libsamlSoap.js +26 -10
- package/package.json +1 -1
- package/types/src/extractor.d.ts.map +1 -1
- package/types/src/flow.d.ts.map +1 -1
- package/types/src/libsaml.d.ts +20 -20
- package/types/src/libsaml.d.ts.map +1 -1
- package/types/src/libsamlSoap.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -154,25 +154,25 @@ const { context: samlResponse } = await idp.createLoginResponse({
|
|
|
154
154
|
});
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
-
### Artifact Binding Support
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
import { ServiceProvider, IdentityProvider } from 'samlesa';
|
|
161
|
-
|
|
162
|
-
// Create front-channel artifact login request
|
|
163
|
-
const loginRequest = sp.createLoginRequest(idp, 'artifact');
|
|
164
|
-
|
|
165
|
-
// Parse ArtifactResolve request on the SP artifact resolution endpoint
|
|
166
|
-
const artifactResolve = await sp.parseArtifactResolveRequest(idp, soapXml);
|
|
167
|
-
|
|
168
|
-
// Create ArtifactResponse with the resolved SAML message
|
|
169
|
-
const artifactResponse = await sp.createArtifactResolveResponse(idp, {
|
|
170
|
-
inResponseTo: artifactResolve.extract.request.id,
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// Parse artifact-based login response from the ACS endpoint
|
|
174
|
-
const responseResult = await sp.parseLoginResponse(idp, 'artifact', request);
|
|
175
|
-
```
|
|
157
|
+
### Artifact Binding Support
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { ServiceProvider, IdentityProvider } from 'samlesa';
|
|
161
|
+
|
|
162
|
+
// Create front-channel artifact login request
|
|
163
|
+
const loginRequest = sp.createLoginRequest(idp, 'artifact');
|
|
164
|
+
|
|
165
|
+
// Parse ArtifactResolve request on the SP artifact resolution endpoint
|
|
166
|
+
const artifactResolve = await sp.parseArtifactResolveRequest(idp, soapXml);
|
|
167
|
+
|
|
168
|
+
// Create ArtifactResponse with the resolved SAML message
|
|
169
|
+
const artifactResponse = await sp.createArtifactResolveResponse(idp, {
|
|
170
|
+
inResponseTo: artifactResolve.extract.request.id,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Parse artifact-based login response from the ACS endpoint
|
|
174
|
+
const responseResult = await sp.parseLoginResponse(idp, 'artifact', request);
|
|
175
|
+
```
|
|
176
176
|
|
|
177
177
|
---
|
|
178
178
|
|
package/build/src/extractor.js
CHANGED
|
@@ -176,6 +176,7 @@ export const logoutResponseStatusFields = [
|
|
|
176
176
|
];
|
|
177
177
|
export const loginResponseFields = assertion => [
|
|
178
178
|
{ key: 'conditions', localPath: ['Assertion', 'Conditions'], attributes: ['NotBefore', 'NotOnOrAfter'], shortcut: assertion },
|
|
179
|
+
{ key: 'audienceRestrictions', localPath: ['Assertion', 'Conditions', 'AudienceRestriction'], attributes: [], context: true, shortcut: assertion },
|
|
179
180
|
{ key: 'response', localPath: ['Response'], attributes: ['ID', 'IssueInstant', 'Destination', 'InResponseTo', 'Version'] },
|
|
180
181
|
{ key: 'responseIssuer', localPath: ['Response', 'Issuer'], attributes: [] },
|
|
181
182
|
{ key: 'audience', localPath: ['Assertion', 'Conditions', 'AudienceRestriction', 'Audience'], attributes: [], shortcut: assertion },
|
package/build/src/flow.js
CHANGED
|
@@ -101,6 +101,55 @@ function validateResponseTimes(parserType, extractedProperties, self) {
|
|
|
101
101
|
}
|
|
102
102
|
return null;
|
|
103
103
|
}
|
|
104
|
+
function normalizeStringList(value) {
|
|
105
|
+
if (Array.isArray(value)) {
|
|
106
|
+
return value.flatMap(item => normalizeStringList(item));
|
|
107
|
+
}
|
|
108
|
+
if (typeof value !== 'string') {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const normalized = value.trim();
|
|
112
|
+
return normalized ? [normalized] : [];
|
|
113
|
+
}
|
|
114
|
+
function collectAudienceValues(extractedProperties) {
|
|
115
|
+
return [
|
|
116
|
+
...normalizeStringList(extractedProperties?.audience),
|
|
117
|
+
...normalizeStringList(extractedProperties?.conditions?.audiences),
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
function collectAudienceRestrictionGroups(extractedProperties) {
|
|
121
|
+
const restrictionFragments = normalizeStringList(extractedProperties?.audienceRestrictions);
|
|
122
|
+
if (restrictionFragments.length === 0) {
|
|
123
|
+
const flattenedAudiences = collectAudienceValues(extractedProperties);
|
|
124
|
+
return flattenedAudiences.length > 0 ? [flattenedAudiences] : [];
|
|
125
|
+
}
|
|
126
|
+
return restrictionFragments.map((fragment) => {
|
|
127
|
+
const parsedRestriction = extract(fragment, [{
|
|
128
|
+
key: 'audience',
|
|
129
|
+
localPath: ['AudienceRestriction', 'Audience'],
|
|
130
|
+
attributes: [],
|
|
131
|
+
}]);
|
|
132
|
+
return normalizeStringList(parsedRestriction?.audience);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function validateAudienceRestriction(parserType, extractedProperties, self) {
|
|
136
|
+
if (parserType !== ParserType.SAMLResponse) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const expectedAudience = self?.entityMeta?.getEntityID?.();
|
|
140
|
+
if (typeof expectedAudience !== 'string' || expectedAudience.trim().length === 0) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const audienceRestrictionGroups = collectAudienceRestrictionGroups(extractedProperties);
|
|
144
|
+
if (audienceRestrictionGroups.length === 0 ||
|
|
145
|
+
audienceRestrictionGroups.some(group => group.length === 0)) {
|
|
146
|
+
return self?.entitySetting?.strictSecurity === false ? null : 'ERR_MISSING_AUDIENCE';
|
|
147
|
+
}
|
|
148
|
+
if (!audienceRestrictionGroups.every(group => group.includes(expectedAudience.trim()))) {
|
|
149
|
+
return 'ERR_UNMATCH_AUDIENCE';
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
104
153
|
function validateEndpointConstraints(parserType, extractedProperties, self, from) {
|
|
105
154
|
const requestData = extractedProperties?.request ?? {};
|
|
106
155
|
const responseData = extractedProperties?.response ?? {};
|
|
@@ -170,6 +219,10 @@ function runCommonValidation(parserType, extractedProperties, self, from) {
|
|
|
170
219
|
if (timeError) {
|
|
171
220
|
return timeError;
|
|
172
221
|
}
|
|
222
|
+
const audienceError = validateAudienceRestriction(parserType, extractedProperties, self);
|
|
223
|
+
if (audienceError) {
|
|
224
|
+
return audienceError;
|
|
225
|
+
}
|
|
173
226
|
return validateEndpointConstraints(parserType, extractedProperties, self, from);
|
|
174
227
|
}
|
|
175
228
|
// proceed the redirect binding flow
|
package/build/src/libsaml.js
CHANGED
|
@@ -28,6 +28,47 @@ const signatureAlgorithms = algorithms.signature;
|
|
|
28
28
|
const digestAlgorithms = algorithms.signatureToDigestMap;
|
|
29
29
|
const certUse = wording.certUse;
|
|
30
30
|
const urlParams = wording.urlParams;
|
|
31
|
+
function resolveSignaturePublicKeys(signatureNode, metadata) {
|
|
32
|
+
const certificateNode = toNodeArray(select(".//*[local-name(.)='X509Certificate']", signatureNode));
|
|
33
|
+
const metadataCerts = normalizeCertificates(metadata.getX509Certificate(certUse.signing));
|
|
34
|
+
if (certificateNode.length === 0 && metadataCerts.length === 0) {
|
|
35
|
+
throw new Error('NO_SELECTED_CERTIFICATE');
|
|
36
|
+
}
|
|
37
|
+
if (certificateNode.length !== 0) {
|
|
38
|
+
const x509CertificateData = certificateNode[0].firstChild?.nodeValue || '';
|
|
39
|
+
const x509Certificate = utility.normalizeCerString(x509CertificateData);
|
|
40
|
+
if (metadataCerts.length >= 1 && !metadataCerts.find((cert) => cert.trim() === x509Certificate.trim())) {
|
|
41
|
+
throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
|
|
42
|
+
}
|
|
43
|
+
return [utility.getPublicKeyPemFromCertificate(x509Certificate).toString()];
|
|
44
|
+
}
|
|
45
|
+
return metadataCerts.map((cert) => utility.getPublicKeyPemFromCertificate(cert).toString());
|
|
46
|
+
}
|
|
47
|
+
function verifyXmlSignatureWithPublicKeys(signatureNode, xmlCandidates, publicKeys, signatureAlgorithm) {
|
|
48
|
+
let lastError = null;
|
|
49
|
+
for (const publicKey of publicKeys) {
|
|
50
|
+
try {
|
|
51
|
+
const sig = new SignedXml();
|
|
52
|
+
sig.publicCert = publicKey;
|
|
53
|
+
if (signatureAlgorithm) {
|
|
54
|
+
sig.signatureAlgorithm = signatureAlgorithm;
|
|
55
|
+
}
|
|
56
|
+
sig.loadSignature(signatureNode);
|
|
57
|
+
for (const xmlCandidate of xmlCandidates) {
|
|
58
|
+
if (sig.checkSignature(xmlCandidate)) {
|
|
59
|
+
return { verified: true, sig };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
lastError = error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (lastError && publicKeys.length === 1) {
|
|
68
|
+
throw lastError;
|
|
69
|
+
}
|
|
70
|
+
return { verified: false, sig: null };
|
|
71
|
+
}
|
|
31
72
|
let conList = [
|
|
32
73
|
"http://www.w3.org/2001/10/xml-exc-c14n#",
|
|
33
74
|
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments",
|
|
@@ -49,9 +90,9 @@ const libSaml = () => {
|
|
|
49
90
|
throw new Error('ERR_UNDEFINED_QUERY_PARAMS');
|
|
50
91
|
}
|
|
51
92
|
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
93
|
+
* Detect whether an unsafe SHA1-family signature algorithm is used.
|
|
94
|
+
* @param signatureAlgorithm Signature algorithm URI.
|
|
95
|
+
* @returns {Object} Whether an unsafe algorithm is used and its name.
|
|
55
96
|
*/
|
|
56
97
|
function checkUnsafeSignatureAlgorithm(signatureAlgorithm) {
|
|
57
98
|
const unsafeAlgorithms = [
|
|
@@ -217,16 +258,16 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
217
258
|
throw new Error('ERR_UNSUPPORTED_SIGNATURE_ALGORITHM');
|
|
218
259
|
}
|
|
219
260
|
function validateAndInflateSamlResponse(urlEncodedResponse) {
|
|
220
|
-
//
|
|
261
|
+
// Try raw DEFLATE first, as required by the SAML binding.
|
|
221
262
|
let xml = "";
|
|
222
263
|
let compressed = true;
|
|
223
|
-
try { //
|
|
264
|
+
try { // URL decode.
|
|
224
265
|
const base64Encoded = decodeURIComponent(urlEncodedResponse);
|
|
225
|
-
//
|
|
266
|
+
// Decode Base64 into a Uint8Array.
|
|
226
267
|
xml = inflateString(base64Encoded);
|
|
227
268
|
}
|
|
228
269
|
catch (inflateError) {
|
|
229
|
-
//
|
|
270
|
+
// If decompression fails, try parsing as uncompressed XML.
|
|
230
271
|
try {
|
|
231
272
|
const base64Encoded = decodeURIComponent(urlEncodedResponse);
|
|
232
273
|
xml = atob(base64Encoded);
|
|
@@ -321,32 +362,32 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
321
362
|
*/
|
|
322
363
|
/** For Test */
|
|
323
364
|
attributeStatementBuilder(attributeData) {
|
|
324
|
-
//
|
|
325
|
-
//
|
|
365
|
+
// Build the XML element array.
|
|
366
|
+
// Build the XML structure.
|
|
326
367
|
const attributeStatement = {
|
|
327
368
|
'saml:AttributeStatement': [
|
|
328
|
-
//
|
|
369
|
+
// Namespace declaration on AttributeStatement.
|
|
329
370
|
{},
|
|
330
|
-
//
|
|
371
|
+
// Generate each Attribute.
|
|
331
372
|
...attributeData.map(attr => ({
|
|
332
373
|
'saml:Attribute ': [
|
|
333
|
-
// Attribute
|
|
374
|
+
// Attribute properties.
|
|
334
375
|
{
|
|
335
376
|
_attr: {
|
|
336
377
|
Name: attr.Name,
|
|
337
378
|
NameFormat: attr.NameFormat
|
|
338
379
|
}
|
|
339
380
|
},
|
|
340
|
-
//
|
|
381
|
+
// Generate each AttributeValue.
|
|
341
382
|
...attr.valueArray.map((valueObj) => ({
|
|
342
383
|
'saml:AttributeValue ': [
|
|
343
|
-
//
|
|
384
|
+
// Data type based on ValueType.
|
|
344
385
|
{
|
|
345
386
|
_attr: attr.ValueType === 1
|
|
346
387
|
? { 'xsi:type': 'xs:string' }
|
|
347
388
|
: {}
|
|
348
389
|
},
|
|
349
|
-
//
|
|
390
|
+
// Value content.
|
|
350
391
|
valueObj.value
|
|
351
392
|
]
|
|
352
393
|
}))
|
|
@@ -354,7 +395,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
354
395
|
}))
|
|
355
396
|
]
|
|
356
397
|
};
|
|
357
|
-
//
|
|
398
|
+
// Generate XML without an automatic declaration.
|
|
358
399
|
const xmlString = xml([attributeStatement], { declaration: false });
|
|
359
400
|
if (xmlString.trim() === '<saml:AttributeStatement></saml:AttributeStatement>') {
|
|
360
401
|
return '';
|
|
@@ -392,10 +433,10 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
392
433
|
sig.publicCert = this.getKeyInfo(signingCert, signatureConfig).getKey();
|
|
393
434
|
sig.getKeyInfoContent = this.getKeyInfo(signingCert, signatureConfig).getKeyInfo;
|
|
394
435
|
sig.privateKey = utility.readPrivateKey(privateKey, privateKeyPass, true);
|
|
395
|
-
//
|
|
436
|
+
// Read the target canonicalization algorithm.
|
|
396
437
|
const targetAlgo = transformationAlgorithms[1];
|
|
397
|
-
//
|
|
398
|
-
//
|
|
438
|
+
// Use targetAlgo when supported, otherwise use the default.
|
|
439
|
+
// In normal configuration targetAlgo is already in conList.
|
|
399
440
|
const selectedAlgo = conList.includes(targetAlgo)
|
|
400
441
|
? targetAlgo
|
|
401
442
|
: 'http://www.w3.org/2001/10/xml-exc-c14n#';
|
|
@@ -409,20 +450,20 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
409
450
|
}
|
|
410
451
|
return isBase64Output ? utility.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
|
|
411
452
|
},
|
|
412
|
-
//
|
|
453
|
+
// Certificate validation helper.
|
|
413
454
|
validateCertificate(certificateBase64, expectedIssuer) {
|
|
414
455
|
try {
|
|
415
456
|
const cert = new X509Certificate(Buffer.from(certificateBase64, 'base64'));
|
|
416
|
-
//
|
|
457
|
+
// Check certificate validity period.
|
|
417
458
|
const now = new Date();
|
|
418
459
|
if (new Date(cert.validFrom) > now || new Date(cert.validTo) < now) {
|
|
419
460
|
throw new Error('Certificate has expired or is not yet valid');
|
|
420
461
|
}
|
|
421
|
-
//
|
|
462
|
+
// Check issuer when an expected issuer is provided.
|
|
422
463
|
if (expectedIssuer && !cert.subject.includes(expectedIssuer)) {
|
|
423
464
|
throw new Error('Certificate issuer does not match expected value');
|
|
424
465
|
}
|
|
425
|
-
//
|
|
466
|
+
// Check public key type. RSA and EC are preferred.
|
|
426
467
|
if (!['rsa', 'ec'].includes(cert.publicKey.type.toLowerCase())) {
|
|
427
468
|
throw new Error('Certificate uses unsupported public key type');
|
|
428
469
|
}
|
|
@@ -441,31 +482,31 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
441
482
|
}
|
|
442
483
|
},
|
|
443
484
|
/**
|
|
444
|
-
*
|
|
445
|
-
* @param xml SAML XML
|
|
446
|
-
* @param opts
|
|
485
|
+
* Verify SAML signatures across message, assertion, and encryption combinations.
|
|
486
|
+
* @param xml SAML XML content.
|
|
487
|
+
* @param opts Verification options.
|
|
447
488
|
* @param self
|
|
448
|
-
* @returns
|
|
489
|
+
* @returns Verification result object.
|
|
449
490
|
*/
|
|
450
491
|
async verifySignature(xml, opts, self) {
|
|
451
|
-
const { dom } = getContext();
|
|
492
|
+
const { dom } = getContext();
|
|
452
493
|
const doc = dom.parseFromString(xml, 'application/xml');
|
|
453
494
|
const docParser = new DOMParser();
|
|
454
|
-
//
|
|
495
|
+
// Use exact SAML 2.0 XPath targets.
|
|
455
496
|
const messageSignatureXpath = "/*[local-name() = 'Response' or local-name() = 'AuthnRequest' or local-name() = 'LogoutRequest' or local-name() = 'LogoutResponse']/*[local-name() = 'Signature']";
|
|
456
497
|
const assertionSignatureXpath = "/*[local-name() = 'Response' or local-name() = 'AuthnRequest']/*[local-name() = 'Assertion']/*[local-name() = 'Signature']";
|
|
457
498
|
const wrappingElementsXPath = "/*[local-name() = 'Response']/*[local-name() = 'Assertion']/*[local-name() = 'Subject']/*[local-name() = 'SubjectConfirmation']/*[local-name() = 'SubjectConfirmationData']//*[local-name() = 'Assertion' or local-name() = 'Signature']";
|
|
458
499
|
const encryptedAssertionsXPath = "/*[local-name() = 'Response']/*[local-name() = 'EncryptedAssertion']";
|
|
459
|
-
//
|
|
500
|
+
// Detect wrapping attacks.
|
|
460
501
|
const wrappingElementNode = toNodeArray(select(wrappingElementsXPath, doc));
|
|
461
502
|
if (wrappingElementNode.length !== 0) {
|
|
462
503
|
throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
|
|
463
504
|
}
|
|
464
|
-
//
|
|
505
|
+
// Locate relevant XML elements.
|
|
465
506
|
const messageSignatureNode = toNodeArray(select(messageSignatureXpath, doc));
|
|
466
507
|
const assertionSignatureNode = toNodeArray(select(assertionSignatureXpath, doc));
|
|
467
508
|
const encryptedAssertions = toNodeArray(select(encryptedAssertionsXPath, doc));
|
|
468
|
-
//
|
|
509
|
+
// Initialize verification state.
|
|
469
510
|
let isMessageSigned = messageSignatureNode.length > 0;
|
|
470
511
|
let isAssertionSigned = assertionSignatureNode.length > 0;
|
|
471
512
|
let encrypted = encryptedAssertions.length > 0;
|
|
@@ -475,7 +516,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
475
516
|
let status = false;
|
|
476
517
|
let samlContent = xml;
|
|
477
518
|
let assertionContent = null;
|
|
478
|
-
//
|
|
519
|
+
// Detect SAML message type.
|
|
479
520
|
const rootElementName = doc.documentElement.localName;
|
|
480
521
|
let type = 'Unknown';
|
|
481
522
|
switch (rootElementName) {
|
|
@@ -510,7 +551,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
510
551
|
}
|
|
511
552
|
let hasUnsafeSignatureAlgorithm = false;
|
|
512
553
|
let unsafeSignatureAlgorithm = '';
|
|
513
|
-
//
|
|
554
|
+
// Reject unsigned SAML responses that carry unsigned assertions.
|
|
514
555
|
if (!isMessageSigned && !isAssertionSigned && !encrypted) {
|
|
515
556
|
return {
|
|
516
557
|
isMessageSigned,
|
|
@@ -527,7 +568,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
527
568
|
unsafeSignatureAlgorithm
|
|
528
569
|
};
|
|
529
570
|
}
|
|
530
|
-
//
|
|
571
|
+
// Resolve the signature algorithm from the signature node.
|
|
531
572
|
const getSignatureAlgorithm = (signatureNode) => {
|
|
532
573
|
const signatureAlgorithm = xpath.select1(".//*[local-name() = 'SignedInfo']/*[local-name() = 'SignatureMethod']/@Algorithm", signatureNode);
|
|
533
574
|
if (!signatureAlgorithm || !signatureAlgorithm.value) {
|
|
@@ -535,7 +576,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
535
576
|
}
|
|
536
577
|
return signatureAlgorithm.value;
|
|
537
578
|
};
|
|
538
|
-
//
|
|
579
|
+
// Handle outer message signature with encrypted assertion.
|
|
539
580
|
if (isMessageSigned && encrypted) {
|
|
540
581
|
try {
|
|
541
582
|
const result = await this.decryptAssertionAsync(self, xml, opts);
|
|
@@ -550,7 +591,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
550
591
|
isAssertionSigned = result?.[2]?.isAssertionSigned || false;
|
|
551
592
|
const decryptedDoc = dom.parseFromString(samlContent, 'application/xml');
|
|
552
593
|
const signatureNode = messageSignatureNode[0];
|
|
553
|
-
//
|
|
594
|
+
// Resolve the signature algorithm with an exact path.
|
|
554
595
|
const signatureAlgorithm = getSignatureAlgorithm(signatureNode);
|
|
555
596
|
const checkResult = checkUnsafeSignatureAlgorithm(signatureAlgorithm);
|
|
556
597
|
hasUnsafeSignatureAlgorithm = checkResult.hasUnsafeSignatureAlgorithm;
|
|
@@ -558,41 +599,21 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
558
599
|
if (checkResult.hasUnsafeSignatureAlgorithm && !resolveAllowLegacySha1(opts, self)) {
|
|
559
600
|
throw new Error('ERR_UNSAFE_SIGNATURE_ALGORITHM');
|
|
560
601
|
}
|
|
561
|
-
|
|
602
|
+
let publicKeys = [];
|
|
562
603
|
if (!opts.keyFile && !opts.metadata) {
|
|
563
604
|
throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
|
|
564
605
|
}
|
|
565
606
|
if (opts.keyFile) {
|
|
566
|
-
|
|
607
|
+
publicKeys = [fs.readFileSync(opts.keyFile)];
|
|
567
608
|
}
|
|
568
609
|
else if (opts.metadata) {
|
|
569
|
-
|
|
570
|
-
let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
|
|
571
|
-
metadataCert = normalizeCertificates(metadataCert);
|
|
572
|
-
if (certificateNode.length === 0 && metadataCert.length === 0) {
|
|
573
|
-
throw new Error('NO_SELECTED_CERTIFICATE');
|
|
574
|
-
}
|
|
575
|
-
if (certificateNode.length !== 0) {
|
|
576
|
-
const x509CertificateData = certificateNode[0].firstChild.data;
|
|
577
|
-
const x509Certificate = utility.normalizeCerString(x509CertificateData);
|
|
578
|
-
if (metadataCert.length >= 1 && !metadataCert.find((cert) => cert.trim() === x509Certificate.trim())) {
|
|
579
|
-
throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
|
|
580
|
-
}
|
|
581
|
-
sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
|
|
582
|
-
}
|
|
583
|
-
else {
|
|
584
|
-
sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
|
|
585
|
-
}
|
|
610
|
+
publicKeys = resolveSignaturePublicKeys(signatureNode, opts.metadata);
|
|
586
611
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
MessageSignatureStatus = sig.checkSignature(decryptedDoc.toString());
|
|
612
|
+
// Verify the outer message signature.
|
|
613
|
+
const verifyResult = verifyXmlSignatureWithPublicKeys(signatureNode, [decryptedDoc.toString(), xml], publicKeys, opts.signatureAlgorithm);
|
|
614
|
+
MessageSignatureStatus = verifyResult.verified;
|
|
591
615
|
if (!MessageSignatureStatus) {
|
|
592
|
-
|
|
593
|
-
if (!MessageSignatureStatus) {
|
|
594
|
-
throw new Error('ERR_FAILED_TO_VERIFY_MESSAGE_SIGNATURE_AFTER_DECRYPTION');
|
|
595
|
-
}
|
|
616
|
+
throw new Error('ERR_FAILED_TO_VERIFY_MESSAGE_SIGNATURE_AFTER_DECRYPTION');
|
|
596
617
|
}
|
|
597
618
|
if (isAssertionSigned && !AssertionSignatureStatus) {
|
|
598
619
|
throw new Error('ERR_FAILED_TO_VERIFY_ASSERTION_SIGNATURE_AFTER_DECRYPTION');
|
|
@@ -602,92 +623,56 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
602
623
|
throw err;
|
|
603
624
|
}
|
|
604
625
|
}
|
|
605
|
-
//
|
|
626
|
+
// Handle outer message signature without encrypted assertion.
|
|
606
627
|
else if (isMessageSigned && !encrypted) {
|
|
607
628
|
const signatureNode = messageSignatureNode[0];
|
|
608
|
-
const signatureAlgorithm = getSignatureAlgorithm(signatureNode);
|
|
629
|
+
const signatureAlgorithm = getSignatureAlgorithm(signatureNode);
|
|
609
630
|
const checkResult = checkUnsafeSignatureAlgorithm(signatureAlgorithm);
|
|
610
631
|
hasUnsafeSignatureAlgorithm = checkResult.hasUnsafeSignatureAlgorithm;
|
|
611
632
|
unsafeSignatureAlgorithm = checkResult.unsafeSignatureAlgorithm ?? "";
|
|
612
633
|
if (checkResult.hasUnsafeSignatureAlgorithm && !resolveAllowLegacySha1(opts, self)) {
|
|
613
634
|
throw new Error('ERR_UNSAFE_SIGNATURE_ALGORITHM');
|
|
614
635
|
}
|
|
615
|
-
|
|
636
|
+
let publicKeys = [];
|
|
616
637
|
if (!opts.keyFile && !opts.metadata) {
|
|
617
638
|
throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
|
|
618
639
|
}
|
|
619
640
|
if (opts.keyFile) {
|
|
620
|
-
|
|
641
|
+
publicKeys = [fs.readFileSync(opts.keyFile)];
|
|
621
642
|
}
|
|
622
643
|
else if (opts.metadata) {
|
|
623
|
-
|
|
624
|
-
let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
|
|
625
|
-
metadataCert = normalizeCertificates(metadataCert);
|
|
626
|
-
if (certificateNode.length === 0 && metadataCert.length === 0) {
|
|
627
|
-
throw new Error('NO_SELECTED_CERTIFICATE');
|
|
628
|
-
}
|
|
629
|
-
if (certificateNode.length !== 0) {
|
|
630
|
-
const x509CertificateData = certificateNode[0].firstChild.data;
|
|
631
|
-
const x509Certificate = utility.normalizeCerString(x509CertificateData);
|
|
632
|
-
if (metadataCert.length >= 1 && !metadataCert.find((cert) => cert.trim() === x509Certificate.trim())) {
|
|
633
|
-
throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
|
|
634
|
-
}
|
|
635
|
-
sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
|
|
639
|
-
}
|
|
644
|
+
publicKeys = resolveSignaturePublicKeys(signatureNode, opts.metadata);
|
|
640
645
|
}
|
|
641
|
-
|
|
642
|
-
sig.loadSignature(signatureNode);
|
|
643
|
-
MessageSignatureStatus = sig.checkSignature(doc.toString());
|
|
646
|
+
MessageSignatureStatus = verifyXmlSignatureWithPublicKeys(signatureNode, [doc.toString()], publicKeys, signatureAlgorithm).verified;
|
|
644
647
|
if (!MessageSignatureStatus) {
|
|
645
648
|
throw new Error('ERR_FAILED_TO_VERIFY_MESSAGE_SIGNATURE');
|
|
646
649
|
}
|
|
647
650
|
}
|
|
648
|
-
//
|
|
651
|
+
// Verify assertion signatures with exact algorithm resolution.
|
|
649
652
|
if (isAssertionSigned && !encrypted) {
|
|
650
653
|
const signatureNode = assertionSignatureNode[0];
|
|
651
|
-
const signatureAlgorithm = getSignatureAlgorithm(signatureNode);
|
|
654
|
+
const signatureAlgorithm = getSignatureAlgorithm(signatureNode);
|
|
652
655
|
const checkResult = checkUnsafeSignatureAlgorithm(signatureAlgorithm);
|
|
653
656
|
hasUnsafeSignatureAlgorithm = checkResult.hasUnsafeSignatureAlgorithm;
|
|
654
657
|
unsafeSignatureAlgorithm = checkResult.unsafeSignatureAlgorithm ?? "";
|
|
655
658
|
if (checkResult.hasUnsafeSignatureAlgorithm && !resolveAllowLegacySha1(opts, self)) {
|
|
656
659
|
throw new Error('ERR_UNSAFE_SIGNATURE_ALGORITHM');
|
|
657
660
|
}
|
|
658
|
-
|
|
661
|
+
let publicKeys = [];
|
|
659
662
|
if (!opts.keyFile && !opts.metadata) {
|
|
660
663
|
throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
|
|
661
664
|
}
|
|
662
665
|
if (opts.keyFile) {
|
|
663
|
-
|
|
666
|
+
publicKeys = [fs.readFileSync(opts.keyFile)];
|
|
664
667
|
}
|
|
665
668
|
else if (opts.metadata) {
|
|
666
|
-
|
|
667
|
-
let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
|
|
668
|
-
metadataCert = normalizeCertificates(metadataCert);
|
|
669
|
-
if (certificateNode.length === 0 && metadataCert.length === 0) {
|
|
670
|
-
throw new Error('NO_SELECTED_CERTIFICATE');
|
|
671
|
-
}
|
|
672
|
-
if (certificateNode.length !== 0) {
|
|
673
|
-
const x509CertificateData = certificateNode[0].firstChild.data;
|
|
674
|
-
const x509Certificate = utility.normalizeCerString(x509CertificateData);
|
|
675
|
-
if (metadataCert.length >= 1 && !metadataCert.find((cert) => cert.trim() === x509Certificate.trim())) {
|
|
676
|
-
throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
|
|
677
|
-
}
|
|
678
|
-
sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
|
|
679
|
-
}
|
|
680
|
-
else {
|
|
681
|
-
sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
|
|
682
|
-
}
|
|
669
|
+
publicKeys = resolveSignaturePublicKeys(signatureNode, opts.metadata);
|
|
683
670
|
}
|
|
684
|
-
|
|
685
|
-
sig.loadSignature(signatureNode);
|
|
686
|
-
// ✅ 优化4: 修复断言节点获取路径(精确匹配)
|
|
671
|
+
// Locate the assertion node with exact matching.
|
|
687
672
|
const assertionNode = select("/*[local-name() = 'Response' or local-name() = 'AuthnRequest']/*[local-name() = 'Assertion']", doc)[0];
|
|
688
673
|
if (assertionNode) {
|
|
689
674
|
const assertionDoc = dom.parseFromString(assertionNode.toString(), 'application/xml');
|
|
690
|
-
AssertionSignatureStatus =
|
|
675
|
+
AssertionSignatureStatus = verifyXmlSignatureWithPublicKeys(signatureNode, [assertionDoc.toString()], publicKeys, signatureAlgorithm).verified;
|
|
691
676
|
}
|
|
692
677
|
else {
|
|
693
678
|
AssertionSignatureStatus = false;
|
|
@@ -696,7 +681,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
696
681
|
throw new Error('ERR_FAILED_TO_VERIFY_ASSERTION_SIGNATURE');
|
|
697
682
|
}
|
|
698
683
|
}
|
|
699
|
-
//
|
|
684
|
+
// Handle encrypted assertion without an outer message signature.
|
|
700
685
|
if (encrypted && !isMessageSigned) {
|
|
701
686
|
if (!encryptedAssertions || encryptedAssertions.length === 0) {
|
|
702
687
|
throw new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION');
|
|
@@ -720,7 +705,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
720
705
|
throw new Error('ERR_EXCEPTION_OF_ASSERTION_DECRYPTION');
|
|
721
706
|
}
|
|
722
707
|
}
|
|
723
|
-
//
|
|
708
|
+
// Extract assertion content with exact matching.
|
|
724
709
|
else if (!encrypted && (isMessageSigned || isAssertionSigned)) {
|
|
725
710
|
const assertions = toNodeArray(select("/*[local-name() = 'Response' or local-name() = 'AuthnRequest']/*[local-name() = 'Assertion']", doc));
|
|
726
711
|
if (assertions?.length > 0) {
|
|
@@ -728,7 +713,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
728
713
|
assertionContent = assertions[0].toString();
|
|
729
714
|
}
|
|
730
715
|
}
|
|
731
|
-
//
|
|
716
|
+
// Check aggregate verification status.
|
|
732
717
|
status = (!isMessageSigned || MessageSignatureStatus) &&
|
|
733
718
|
(!isAssertionSigned || AssertionSignatureStatus) &&
|
|
734
719
|
(!encrypted || decrypted);
|
|
@@ -751,15 +736,15 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
751
736
|
const { dom } = getContext();
|
|
752
737
|
const doc = dom.parseFromString(xml, 'application/xml');
|
|
753
738
|
const docParser = new DOMParser();
|
|
754
|
-
//
|
|
739
|
+
// Define XPath expressions for SOAP messages.
|
|
755
740
|
const artifactResolveXpath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResolve']";
|
|
756
741
|
const artifactResponseXpath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResponse']";
|
|
757
|
-
//
|
|
742
|
+
// Detect ArtifactResolve or ArtifactResponse.
|
|
758
743
|
// @ts-expect-error
|
|
759
744
|
const artifactResolveNodes = toNodeArray(select(artifactResolveXpath, doc));
|
|
760
745
|
// @ts-expect-error
|
|
761
746
|
const artifactResponseNodes = toNodeArray(select(artifactResponseXpath, doc));
|
|
762
|
-
//
|
|
747
|
+
// Select the base XPath from the SOAP message type.
|
|
763
748
|
let basePath = "";
|
|
764
749
|
if (artifactResolveNodes.length > 0) {
|
|
765
750
|
basePath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResolve']";
|
|
@@ -770,12 +755,12 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
770
755
|
else {
|
|
771
756
|
throw new Error('ERR_UNSUPPORTED_SOAP_MESSAGE_TYPE');
|
|
772
757
|
}
|
|
773
|
-
//
|
|
758
|
+
// Build XPath expressions from the SOAP structure.
|
|
774
759
|
const messageSignatureXpath = `${basePath}/*[local-name(.)='Signature']`;
|
|
775
760
|
const assertionSignatureXpath = `${basePath}/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Signature']`;
|
|
776
761
|
const wrappingElementsXPath = `${basePath}/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Subject']/*[local-name(.)='SubjectConfirmation']/*[local-name(.)='SubjectConfirmationData']//*[local-name(.)='Assertion' or local-name(.)='Signature']`;
|
|
777
762
|
const encryptedAssertionsXpath = `${basePath}/*[local-name(.)='Response']/*[local-name(.)='EncryptedAssertion']`;
|
|
778
|
-
//
|
|
763
|
+
// Detect wrapping attacks.
|
|
779
764
|
// @ts-expect-error
|
|
780
765
|
const wrappingElementNode = toNodeArray(select(wrappingElementsXPath, doc));
|
|
781
766
|
if (wrappingElementNode.length !== 0) {
|
|
@@ -794,7 +779,7 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
794
779
|
if (assertionSignatureNode.length > 0) {
|
|
795
780
|
selection = selection.concat(assertionSignatureNode);
|
|
796
781
|
}
|
|
797
|
-
//
|
|
782
|
+
// Handle encrypted assertions.
|
|
798
783
|
if (selection.length === 0) {
|
|
799
784
|
if (encryptedAssertions.length > 0) {
|
|
800
785
|
if (encryptedAssertions.length > 1) {
|
|
@@ -806,54 +791,35 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
806
791
|
if (selection.length === 0) {
|
|
807
792
|
throw new Error('ERR_ZERO_SIGNATURE');
|
|
808
793
|
}
|
|
809
|
-
//
|
|
794
|
+
// Try each signature node.
|
|
810
795
|
for (const signatureNode of selection) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
// 检测不安全的签名算法
|
|
796
|
+
let publicKeys = [];
|
|
797
|
+
// Detect unsafe signature algorithms.
|
|
814
798
|
const { hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm } = checkUnsafeSignatureAlgorithm(opts.signatureAlgorithm || '');
|
|
815
799
|
if (hasUnsafeSignatureAlgorithm && !resolveAllowLegacySha1(opts)) {
|
|
816
800
|
throw new Error('ERR_UNSAFE_SIGNATURE_ALGORITHM');
|
|
817
801
|
}
|
|
818
|
-
sig.signatureAlgorithm = opts.signatureAlgorithm;
|
|
819
802
|
if (!opts.keyFile && !opts.metadata) {
|
|
820
803
|
throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
|
|
821
804
|
}
|
|
822
805
|
if (opts.keyFile) {
|
|
823
|
-
|
|
806
|
+
publicKeys = [fs.readFileSync(opts.keyFile)];
|
|
824
807
|
}
|
|
825
808
|
if (opts.metadata) {
|
|
826
|
-
|
|
827
|
-
// 证书处理逻辑
|
|
828
|
-
let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
|
|
829
|
-
metadataCert = normalizeCertificates(metadataCert);
|
|
830
|
-
// 没有证书的情况
|
|
831
|
-
if (certificateNode.length === 0 && metadataCert.length === 0) {
|
|
832
|
-
throw new Error('NO_SELECTED_CERTIFICATE');
|
|
833
|
-
}
|
|
834
|
-
if (certificateNode.length !== 0) {
|
|
835
|
-
const x509CertificateData = certificateNode[0].firstChild.data;
|
|
836
|
-
const x509Certificate = utility.normalizeCerString(x509CertificateData);
|
|
837
|
-
if (metadataCert.length >= 1 && !metadataCert.includes(x509Certificate)) {
|
|
838
|
-
throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
|
|
839
|
-
}
|
|
840
|
-
sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
|
|
841
|
-
}
|
|
842
|
-
else {
|
|
843
|
-
sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
|
|
844
|
-
}
|
|
809
|
+
publicKeys = resolveSignaturePublicKeys(signatureNode, opts.metadata);
|
|
845
810
|
}
|
|
846
|
-
|
|
847
|
-
verified =
|
|
811
|
+
const verifyResult = verifyXmlSignatureWithPublicKeys(signatureNode, [xml], publicKeys, opts.signatureAlgorithm);
|
|
812
|
+
const verified = verifyResult.verified;
|
|
813
|
+
const sig = verifyResult.sig;
|
|
848
814
|
if (!verified) {
|
|
849
815
|
throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
|
|
850
816
|
}
|
|
851
|
-
if (sig.getSignedReferences().length < 1) {
|
|
817
|
+
if (!sig || sig.getSignedReferences().length < 1) {
|
|
852
818
|
throw new Error('NO_SIGNATURE_REFERENCES');
|
|
853
819
|
}
|
|
854
820
|
const signedVerifiedXML = sig.getSignedReferences()[0];
|
|
855
821
|
const rootNode = docParser.parseFromString(signedVerifiedXML, 'application/xml').documentElement;
|
|
856
|
-
//
|
|
822
|
+
// Handle signed content.
|
|
857
823
|
switch (rootNode?.localName) {
|
|
858
824
|
case 'Response':
|
|
859
825
|
// @ts-expect-error
|
|
@@ -866,17 +832,17 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
866
832
|
if (assertions.length === 1) {
|
|
867
833
|
return [true, assertions[0].toString(), false, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
|
|
868
834
|
}
|
|
869
|
-
return [true, null, false, true, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm]; //
|
|
835
|
+
return [true, null, false, true, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm]; // Signature verified, but no assertion was found.
|
|
870
836
|
case 'Assertion':
|
|
871
837
|
return [true, rootNode.toString(), false, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
|
|
872
838
|
case 'EncryptedAssertion':
|
|
873
839
|
return [true, rootNode.toString(), true, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
|
|
874
840
|
case 'ArtifactResolve':
|
|
875
841
|
case 'ArtifactResponse':
|
|
876
|
-
//
|
|
842
|
+
// Extract the actual message inside the SOAP envelope.
|
|
877
843
|
return [true, rootNode.toString(), false, false, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm];
|
|
878
844
|
default:
|
|
879
|
-
return [true, null, false, true, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm]; //
|
|
845
|
+
return [true, null, false, true, hasUnsafeSignatureAlgorithm, unsafeSignatureAlgorithm]; // Signature verified, but no recognized content was found.
|
|
880
846
|
}
|
|
881
847
|
}
|
|
882
848
|
return [false, null, encryptedAssertions.length > 0, false, false, null];
|
|
@@ -911,18 +877,18 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
911
877
|
};
|
|
912
878
|
},
|
|
913
879
|
/**
|
|
914
|
-
* SAML
|
|
915
|
-
* @param octetString -
|
|
916
|
-
* @param key - PEM
|
|
917
|
-
* @param passphrase -
|
|
918
|
-
* @param isBase64 -
|
|
919
|
-
* @param signingAlgorithm -
|
|
920
|
-
* @returns
|
|
880
|
+
* Sign a SAML message according to the SAML V2.0 binding specification.
|
|
881
|
+
* @param octetString - Raw data to sign as an OCTET STRING.
|
|
882
|
+
* @param key - Private key in PEM format.
|
|
883
|
+
* @param passphrase - Private key passphrase, when encrypted.
|
|
884
|
+
* @param isBase64 - Whether to return base64 encoding. Defaults to true.
|
|
885
|
+
* @param signingAlgorithm - Signature algorithm. Defaults to rsa-sha256.
|
|
886
|
+
* @returns Message signature.
|
|
921
887
|
*/
|
|
922
888
|
constructMessageSignature(octetString, key, passphrase, isBase64, signingAlgorithm, securityOptions) {
|
|
923
889
|
try {
|
|
924
890
|
const algorithm = getSigningAlgorithm(signingAlgorithm ?? signatureAlgorithms.RSA_SHA256, securityOptions);
|
|
925
|
-
const privateKeyPem = utility.readPrivateKey(key, passphrase);
|
|
891
|
+
const privateKeyPem = utility.readPrivateKey(key, passphrase);
|
|
926
892
|
const signer = crypto.createSign(algorithm);
|
|
927
893
|
signer.update(octetString, 'utf8');
|
|
928
894
|
const signature = signer.sign({
|
|
@@ -945,16 +911,32 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
945
911
|
* @param {string} verifyAlgorithm algorithm used to verify
|
|
946
912
|
* @return {boolean} verification result
|
|
947
913
|
*/
|
|
948
|
-
verifyMessageSignature(metadata,
|
|
949
|
-
octetString, signature, verifyAlgorithm, securityOptions) {
|
|
914
|
+
verifyMessageSignature(metadata, octetString, signature, verifyAlgorithm, securityOptions) {
|
|
950
915
|
try {
|
|
951
|
-
const
|
|
916
|
+
const signCerts = normalizeCertificates(metadata.getX509Certificate('signing'));
|
|
952
917
|
const algorithm = getSigningAlgorithm(verifyAlgorithm, securityOptions);
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
918
|
+
if (signCerts.length === 0) {
|
|
919
|
+
throw new Error('ERR_METADATA_MISSING_SIGNING_CERT');
|
|
920
|
+
}
|
|
956
921
|
const signatureBuffer = typeof signature === 'string' ? Buffer.from(signature, 'base64') : signature;
|
|
957
|
-
|
|
922
|
+
let lastError = null;
|
|
923
|
+
for (const signCert of signCerts) {
|
|
924
|
+
try {
|
|
925
|
+
const publicKeyPem = utility.getPublicKeyPemFromCertificate(signCert);
|
|
926
|
+
const verifier = crypto.createVerify(algorithm);
|
|
927
|
+
verifier.update(octetString, 'utf8');
|
|
928
|
+
if (verifier.verify(publicKeyPem, signatureBuffer)) {
|
|
929
|
+
return true;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
catch (error) {
|
|
933
|
+
lastError = error;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
if (lastError && signCerts.length === 1) {
|
|
937
|
+
throw lastError;
|
|
938
|
+
}
|
|
939
|
+
return false;
|
|
958
940
|
}
|
|
959
941
|
catch (error) {
|
|
960
942
|
console.error('Signature verification failed:', error);
|
|
@@ -1005,7 +987,10 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1005
987
|
const rawAssertionNode = assertions[0];
|
|
1006
988
|
// Perform encryption depends on the setting, default is false
|
|
1007
989
|
if (sourceEntitySetting.isAssertionEncrypted) {
|
|
1008
|
-
let encryptPem = targetEntityMetadata.getX509Certificate(certUse.encrypt);
|
|
990
|
+
let encryptPem = normalizeCertificates(targetEntityMetadata.getX509Certificate(certUse.encrypt))[0];
|
|
991
|
+
if (!encryptPem) {
|
|
992
|
+
throw new Error('ERR_METADATA_MISSING_ENCRYPTION_CERT');
|
|
993
|
+
}
|
|
1009
994
|
const publicKeyPem = utility.getPublicKeyPemFromCertificate(encryptPem);
|
|
1010
995
|
xmlenc.encrypt(rawAssertionNode.toString(), {
|
|
1011
996
|
// use xml-encryption module
|
|
@@ -1016,9 +1001,9 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1016
1001
|
keyEncryptionDigest: sourceEntitySetting.keyEncryptionDigest,
|
|
1017
1002
|
keyEncryptionMgf1: sourceEntitySetting.keyEncryptionMgf1,
|
|
1018
1003
|
keyEncryptionOAEPParams: sourceEntitySetting.keyEncryptionOAEPParams,
|
|
1019
|
-
disallowEncryptionWithInsecureAlgorithm: sourceEntitySetting.disallowEncryptionWithInsecureAlgorithm, //
|
|
1020
|
-
disallowInsecureEncryption: sourceEntitySetting.disallowInsecureEncryption,
|
|
1021
|
-
disallowInsecureHash: sourceEntitySetting.disallowInsecureHash,
|
|
1004
|
+
disallowEncryptionWithInsecureAlgorithm: sourceEntitySetting.disallowEncryptionWithInsecureAlgorithm, // Disallow rsa-1_5 and tripledes-cbc.
|
|
1005
|
+
disallowInsecureEncryption: sourceEntitySetting.disallowInsecureEncryption, // Disallow AES-CBC encryption algorithms.
|
|
1006
|
+
disallowInsecureHash: sourceEntitySetting.disallowInsecureHash, // Disallow unsafe signature hash algorithms except MGF1.
|
|
1022
1007
|
warnInsecureAlgorithm: true
|
|
1023
1008
|
}, (err, res) => {
|
|
1024
1009
|
if (err) {
|
|
@@ -1039,10 +1024,10 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1039
1024
|
});
|
|
1040
1025
|
},
|
|
1041
1026
|
/**
|
|
1042
|
-
*
|
|
1027
|
+
* Synchronous assertion decryption helper.
|
|
1043
1028
|
*/
|
|
1044
1029
|
/**
|
|
1045
|
-
*
|
|
1030
|
+
* Synchronous assertion decryption helper with post-decryption signature validation.
|
|
1046
1031
|
*/
|
|
1047
1032
|
async decryptAssertionAsync(here, entireXML, opts) {
|
|
1048
1033
|
const hereSetting = here.entitySetting;
|
|
@@ -1056,16 +1041,16 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1056
1041
|
throw new Error('ERR_MULTIPLE_ASSERTION');
|
|
1057
1042
|
}
|
|
1058
1043
|
const encAssertionNode = encryptedAssertions[0];
|
|
1059
|
-
//
|
|
1044
|
+
// Convert the DOM node to an XML string.
|
|
1060
1045
|
const encAssertionXml = encAssertionNode.toString();
|
|
1061
|
-
//
|
|
1046
|
+
// Wrap asynchronous decryption in a Promise.
|
|
1062
1047
|
let decryptedResult;
|
|
1063
1048
|
try {
|
|
1064
1049
|
const decryptResult = await xmlenc.decrypt(encAssertionXml, {
|
|
1065
1050
|
key: utility.readPrivateKey(hereSetting.encPrivateKey, hereSetting.encPrivateKeyPass),
|
|
1066
1051
|
warnInsecureAlgorithm: true,
|
|
1067
1052
|
});
|
|
1068
|
-
// xmlenc.decrypt
|
|
1053
|
+
// xmlenc.decrypt may return Buffer, so convert it to string.
|
|
1069
1054
|
decryptedResult = typeof decryptResult === 'string' ? decryptResult : decryptResult.toString();
|
|
1070
1055
|
}
|
|
1071
1056
|
catch (err) {
|
|
@@ -1073,13 +1058,13 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1073
1058
|
}
|
|
1074
1059
|
let hasUnsafeSignatureAlgorithm = false;
|
|
1075
1060
|
let unsafeSignatureAlgorithm = "";
|
|
1076
|
-
//
|
|
1061
|
+
// After decryption, check whether the assertion still has a signature to verify.
|
|
1077
1062
|
const decryptedAssertionDoc = dom.parseFromString(decryptedResult, 'application/xml');
|
|
1078
1063
|
let AssertionSignatureStatus = false;
|
|
1079
|
-
//
|
|
1064
|
+
// Check whether the decrypted assertion has a signature.
|
|
1080
1065
|
const assertionSignatureNode = toNodeArray(select("/*[local-name(.)='Assertion']/*[local-name(.)='Signature']", decryptedAssertionDoc));
|
|
1081
1066
|
if (assertionSignatureNode.length > 0 && opts) {
|
|
1082
|
-
//
|
|
1067
|
+
// Verify the signature on the decrypted assertion.
|
|
1083
1068
|
const signatureNode = assertionSignatureNode[0];
|
|
1084
1069
|
const signatureAlgorithm = xpath.select1(".//*[local-name(.)='SignatureMethod']/@Algorithm", signatureNode);
|
|
1085
1070
|
let checkResult = checkUnsafeSignatureAlgorithm(signatureAlgorithm.value || '');
|
|
@@ -1088,50 +1073,32 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1088
1073
|
if (checkResult.hasUnsafeSignatureAlgorithm && !resolveAllowLegacySha1(opts, here)) {
|
|
1089
1074
|
throw new Error('ERR_UNSAFE_SIGNATURE_ALGORITHM');
|
|
1090
1075
|
}
|
|
1091
|
-
|
|
1076
|
+
let publicKeys = [];
|
|
1092
1077
|
if (!opts.keyFile && !opts.metadata) {
|
|
1093
1078
|
throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
|
|
1094
1079
|
}
|
|
1095
1080
|
if (opts.keyFile) {
|
|
1096
|
-
|
|
1081
|
+
publicKeys = [fs.readFileSync(opts.keyFile)];
|
|
1097
1082
|
}
|
|
1098
1083
|
else if (opts.metadata) {
|
|
1099
|
-
|
|
1100
|
-
let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
|
|
1101
|
-
metadataCert = normalizeCertificates(metadataCert);
|
|
1102
|
-
if (certificateNode.length === 0 && metadataCert.length === 0) {
|
|
1103
|
-
throw new Error('NO_SELECTED_CERTIFICATE');
|
|
1104
|
-
}
|
|
1105
|
-
if (certificateNode.length !== 0) {
|
|
1106
|
-
const x509CertificateData = certificateNode[0].firstChild.data;
|
|
1107
|
-
const x509Certificate = utility.normalizeCerString(x509CertificateData);
|
|
1108
|
-
if (metadataCert.length >= 1 && !metadataCert.find((cert) => cert.trim() === x509Certificate.trim())) {
|
|
1109
|
-
throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
|
|
1110
|
-
}
|
|
1111
|
-
sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
|
|
1112
|
-
}
|
|
1113
|
-
else {
|
|
1114
|
-
sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
|
|
1115
|
-
}
|
|
1084
|
+
publicKeys = resolveSignaturePublicKeys(signatureNode, opts.metadata);
|
|
1116
1085
|
}
|
|
1117
|
-
//
|
|
1086
|
+
// Detect unsafe signature algorithms.
|
|
1118
1087
|
let checkSafeResult = checkUnsafeSignatureAlgorithm(opts.signatureAlgorithm || '');
|
|
1119
1088
|
hasUnsafeSignatureAlgorithm = checkSafeResult.hasUnsafeSignatureAlgorithm;
|
|
1120
1089
|
unsafeSignatureAlgorithm = checkSafeResult.unsafeSignatureAlgorithm ?? "";
|
|
1121
1090
|
if (checkSafeResult.hasUnsafeSignatureAlgorithm && !resolveAllowLegacySha1(opts, here)) {
|
|
1122
1091
|
throw new Error('ERR_UNSAFE_SIGNATURE_ALGORITHM');
|
|
1123
1092
|
}
|
|
1124
|
-
|
|
1125
|
-
sig.loadSignature(signatureNode);
|
|
1126
|
-
// 验证解密后断言的签名
|
|
1093
|
+
// Verify the decrypted assertion signature.
|
|
1127
1094
|
const assertionDocForVerification = dom.parseFromString(decryptedResult, 'application/xml');
|
|
1128
|
-
const assertionValid =
|
|
1095
|
+
const assertionValid = verifyXmlSignatureWithPublicKeys(signatureNode, [assertionDocForVerification.toString()], publicKeys, opts.signatureAlgorithm).verified;
|
|
1129
1096
|
AssertionSignatureStatus = assertionValid;
|
|
1130
1097
|
if (!assertionValid) {
|
|
1131
1098
|
throw new Error('ERR_FAILED_TO_VERIFY_DECRYPTED_ASSERTION_SIGNATURE');
|
|
1132
1099
|
}
|
|
1133
1100
|
}
|
|
1134
|
-
//
|
|
1101
|
+
// Replace the encrypted assertion with the decrypted assertion.
|
|
1135
1102
|
const rawAssertionDoc = dom.parseFromString(decryptedResult, 'application/xml');
|
|
1136
1103
|
// @ts-ignore
|
|
1137
1104
|
doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
|
|
@@ -1143,19 +1110,19 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1143
1110
|
}];
|
|
1144
1111
|
},
|
|
1145
1112
|
/**
|
|
1146
|
-
*
|
|
1147
|
-
* @param self
|
|
1148
|
-
* @param entireXML
|
|
1149
|
-
* @returns
|
|
1113
|
+
* Decrypt an encrypted assertion in a SOAP response.
|
|
1114
|
+
* @param self Current entity, SP or IdP.
|
|
1115
|
+
* @param entireXML Complete SOAP XML response.
|
|
1116
|
+
* @returns Decrypted full SOAP XML and decrypted assertion XML.
|
|
1150
1117
|
*/
|
|
1151
1118
|
async decryptAssertionSoap(self, entireXML) {
|
|
1152
1119
|
let hereSetting = self.entitySetting;
|
|
1153
1120
|
const { dom } = getContext();
|
|
1154
1121
|
try {
|
|
1155
|
-
//
|
|
1122
|
+
// Parse XML.
|
|
1156
1123
|
// @ts-ignore
|
|
1157
1124
|
const doc = dom.parseFromString(entireXML, 'application/xml');
|
|
1158
|
-
//
|
|
1125
|
+
// Locate encrypted assertions.
|
|
1159
1126
|
const encryptedAssertions = select("/*[local-name()='Envelope']/*[local-name()='Body']" +
|
|
1160
1127
|
"/*[local-name()='ArtifactResponse']/*[local-name()='Response']" +
|
|
1161
1128
|
"/*[local-name()='EncryptedAssertion']",
|
|
@@ -1165,17 +1132,17 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1165
1132
|
throw new Error('ERR_ENCRYPTED_ASSERTION_NOT_FOUND');
|
|
1166
1133
|
}
|
|
1167
1134
|
if (encryptedAssertions.length > 1) {
|
|
1168
|
-
console.warn('
|
|
1135
|
+
console.warn('Multiple encrypted assertions found; only the first one will be processed');
|
|
1169
1136
|
}
|
|
1170
1137
|
const encAssertionNode = encryptedAssertions[0];
|
|
1171
|
-
//
|
|
1138
|
+
// Prepare the decryption key.
|
|
1172
1139
|
const privateKey = utility.readPrivateKey(self.entitySetting.encPrivateKey, self.entitySetting.encPrivateKeyPass);
|
|
1173
|
-
//
|
|
1140
|
+
// Decrypt the assertion.
|
|
1174
1141
|
const decryptedAssertion = await new Promise((resolve, reject) => {
|
|
1175
1142
|
xmlenc.decrypt(encAssertionNode.toString(), { key: privateKey,
|
|
1176
|
-
disallowInsecureEncryption: hereSetting.disallowInsecureEncryption,
|
|
1177
|
-
disallowDecryptionWithInsecureAlgorithm: hereSetting.disallowDecryptionWithInsecureAlgorithm,
|
|
1178
|
-
disallowInsecureHash: hereSetting.disallowInsecureHash,
|
|
1143
|
+
disallowInsecureEncryption: hereSetting.disallowInsecureEncryption, // Disallow AES-CBC encrypted XML.
|
|
1144
|
+
disallowDecryptionWithInsecureAlgorithm: hereSetting.disallowDecryptionWithInsecureAlgorithm, // Disallow rsa-1_5 and tripledes-cbc encrypted XML.
|
|
1145
|
+
disallowInsecureHash: hereSetting.disallowInsecureHash, // Disallow SHA1-family hash algorithms.
|
|
1179
1146
|
warnInsecureAlgorithm: true,
|
|
1180
1147
|
}, (err, result) => {
|
|
1181
1148
|
if (err) {
|
|
@@ -1187,18 +1154,18 @@ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="{ID}"
|
|
|
1187
1154
|
resolve(typeof result === 'string' ? result : result.toString());
|
|
1188
1155
|
});
|
|
1189
1156
|
});
|
|
1190
|
-
//
|
|
1157
|
+
// Create a DOM from the decrypted assertion.
|
|
1191
1158
|
// @ts-ignore
|
|
1192
1159
|
const decryptedDoc = dom.parseFromString(decryptedAssertion, 'application/xml');
|
|
1193
1160
|
const decryptedAssertionNode = decryptedDoc.documentElement;
|
|
1194
|
-
//
|
|
1161
|
+
// Replace the encrypted assertion with the decrypted assertion.
|
|
1195
1162
|
const parentNode = encAssertionNode.parentNode;
|
|
1196
1163
|
if (!parentNode) {
|
|
1197
1164
|
throw new Error('ERR_NO_PARENT_NODE_FOR_ENCRYPTED_ASSERTION');
|
|
1198
1165
|
}
|
|
1199
1166
|
// @ts-ignore
|
|
1200
1167
|
parentNode.replaceChild(decryptedAssertionNode, encAssertionNode);
|
|
1201
|
-
//
|
|
1168
|
+
// Serialize the updated document.
|
|
1202
1169
|
const updatedSoapXml = doc.toString();
|
|
1203
1170
|
return [updatedSoapXml, decryptedAssertion];
|
|
1204
1171
|
}
|
package/build/src/libsamlSoap.js
CHANGED
|
@@ -17,12 +17,12 @@ function toNodeArray(result) {
|
|
|
17
17
|
}
|
|
18
18
|
const certUse = wording.certUse;
|
|
19
19
|
const docParser = new DOMParser();
|
|
20
|
-
function
|
|
20
|
+
function resolvePublicCertificates(signatureNode, opts) {
|
|
21
21
|
if (!opts.keyFile && !opts.metadata) {
|
|
22
22
|
throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
|
|
23
23
|
}
|
|
24
24
|
if (opts.keyFile) {
|
|
25
|
-
return fs.readFileSync(opts.keyFile);
|
|
25
|
+
return [fs.readFileSync(opts.keyFile)];
|
|
26
26
|
}
|
|
27
27
|
const certificateNode = toNodeArray(select(".//*[local-name(.)='X509Certificate']", signatureNode));
|
|
28
28
|
const metadataCerts = normalizeCertificates(opts.metadata.getX509Certificate(certUse.signing));
|
|
@@ -35,9 +35,9 @@ function resolvePublicCertificate(signatureNode, opts) {
|
|
|
35
35
|
if (metadataCerts.length > 0 && !metadataCerts.includes(x509Certificate)) {
|
|
36
36
|
throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
|
|
37
37
|
}
|
|
38
|
-
return libsaml.getKeyInfo(x509Certificate).getKey();
|
|
38
|
+
return [libsaml.getKeyInfo(x509Certificate).getKey()];
|
|
39
39
|
}
|
|
40
|
-
return libsaml.getKeyInfo(
|
|
40
|
+
return metadataCerts.map((cert) => libsaml.getKeyInfo(cert).getKey());
|
|
41
41
|
}
|
|
42
42
|
function extractResolvedMessage(rootNode) {
|
|
43
43
|
const resolvedNodes = toNodeArray(select("./*[local-name()='Response' or local-name()='AuthnRequest' or local-name()='LogoutRequest' or local-name()='LogoutResponse']", rootNode));
|
|
@@ -48,14 +48,30 @@ function extractResolvedMessage(rootNode) {
|
|
|
48
48
|
}
|
|
49
49
|
function verifySignature(xml, signatureNodes, opts) {
|
|
50
50
|
for (const signatureNode of signatureNodes) {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
51
|
+
const publicKeys = resolvePublicCertificates(signatureNode, opts);
|
|
52
|
+
let verifiedSig = null;
|
|
53
|
+
let lastError = null;
|
|
54
|
+
for (const publicKey of publicKeys) {
|
|
55
|
+
try {
|
|
56
|
+
const sig = new SignedXml();
|
|
57
|
+
sig.publicCert = publicKey;
|
|
58
|
+
sig.loadSignature(signatureNode);
|
|
59
|
+
if (sig.checkSignature(xml)) {
|
|
60
|
+
verifiedSig = sig;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
lastError = error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!verifiedSig) {
|
|
69
|
+
if (lastError && publicKeys.length === 1) {
|
|
70
|
+
throw lastError;
|
|
71
|
+
}
|
|
56
72
|
throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
|
|
57
73
|
}
|
|
58
|
-
const signedReferences =
|
|
74
|
+
const signedReferences = verifiedSig.getSignedReferences();
|
|
59
75
|
if (signedReferences.length < 1) {
|
|
60
76
|
throw new Error('NO_SIGNATURE_REFERENCES');
|
|
61
77
|
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/extractor.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IAEnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,MAAM,MAAM,eAAe,GAAG,cAAc,EAAE,CAAC;AA4B/C,eAAO,MAAM,kBAAkB,EAAE,eAsFhC,CAAC;AAKF,eAAO,MAAM,qBAAqB,EAAE,eAsBnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAsBpC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,eAGvC,CAAC;AAEF,eAAO,MAAM,iCAAiC,EAAE,eAG/C,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,eAGxC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,KAAK,eAAe,
|
|
1
|
+
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/extractor.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IAEnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,MAAM,MAAM,eAAe,GAAG,cAAc,EAAE,CAAC;AA4B/C,eAAO,MAAM,kBAAkB,EAAE,eAsFhC,CAAC;AAKF,eAAO,MAAM,qBAAqB,EAAE,eAsBnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAsBpC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,eAGvC,CAAC;AAEF,eAAO,MAAM,iCAAiC,EAAE,eAG/C,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,eAGxC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,KAAK,eAAe,CAarE,CAAC;AAqMF,eAAO,MAAM,mBAAmB,EAAE,eAMjC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAIlC,CAAC;AAKF,eAAO,MAAM,iBAAiB,EAAE,eAiI/B,CAAC;AAOF,eAAO,MAAM,gBAAgB,EAAE,eAyL9B,CAAC;AAEF,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,OAiN/D;AASD,eAAO,MAAM,2BAA2B,EAAE,eAkZzC,CAAC;AAIF;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,OAkRrE;AAKD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,OAE5C;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,OAEzC;AAGD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,OAExC;AACD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,OAEjD;AACD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,OAE9C;AACD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,OAErD"}
|
package/types/src/flow.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../../src/flow.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,UAAU;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,GAAG,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../../src/flow.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,UAAU;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,GAAG,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AA4rBD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BhG;AAED,wBAAgB,IAAI,CAAC,OAAO,KAAA,GAAG,OAAO,CAAC,UAAU,CAAC,CA0BjD"}
|
package/types/src/libsaml.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as crypto from 'node:crypto';
|
|
2
2
|
import type { MetadataInterface } from './metadata.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* @param {Array} attributeData -
|
|
6
|
-
* @returns {string} SAML Attribute XML
|
|
4
|
+
* Generate SAML Attribute elements without an XML declaration.
|
|
5
|
+
* @param {Array} attributeData - Attribute configuration data.
|
|
6
|
+
* @returns {string} SAML Attribute XML string.
|
|
7
7
|
*/
|
|
8
8
|
export interface SignatureConstructor {
|
|
9
9
|
rawSamlMessage: string;
|
|
@@ -199,11 +199,11 @@ declare const _default: {
|
|
|
199
199
|
publicKey?: undefined;
|
|
200
200
|
};
|
|
201
201
|
/**
|
|
202
|
-
*
|
|
203
|
-
* @param xml SAML XML
|
|
204
|
-
* @param opts
|
|
202
|
+
* Verify SAML signatures across message, assertion, and encryption combinations.
|
|
203
|
+
* @param xml SAML XML content.
|
|
204
|
+
* @param opts Verification options.
|
|
205
205
|
* @param self
|
|
206
|
-
* @returns
|
|
206
|
+
* @returns Verification result object.
|
|
207
207
|
*/
|
|
208
208
|
verifySignature(xml: string, opts: SignatureVerifierOptions, self: any): Promise<any>;
|
|
209
209
|
verifySignatureSoap(xml: string, opts: SignatureVerifierOptions): (string | boolean | null)[];
|
|
@@ -215,13 +215,13 @@ declare const _default: {
|
|
|
215
215
|
*/
|
|
216
216
|
createKeySection(use: KeyUse, certString: string | Buffer): KeyComponent;
|
|
217
217
|
/**
|
|
218
|
-
* SAML
|
|
219
|
-
* @param octetString -
|
|
220
|
-
* @param key - PEM
|
|
221
|
-
* @param passphrase -
|
|
222
|
-
* @param isBase64 -
|
|
223
|
-
* @param signingAlgorithm -
|
|
224
|
-
* @returns
|
|
218
|
+
* Sign a SAML message according to the SAML V2.0 binding specification.
|
|
219
|
+
* @param octetString - Raw data to sign as an OCTET STRING.
|
|
220
|
+
* @param key - Private key in PEM format.
|
|
221
|
+
* @param passphrase - Private key passphrase, when encrypted.
|
|
222
|
+
* @param isBase64 - Whether to return base64 encoding. Defaults to true.
|
|
223
|
+
* @param signingAlgorithm - Signature algorithm. Defaults to rsa-sha256.
|
|
224
|
+
* @returns Message signature.
|
|
225
225
|
*/
|
|
226
226
|
constructMessageSignature(octetString: string, key: string, passphrase?: string, isBase64?: boolean, signingAlgorithm?: string, securityOptions?: SignatureSecurityOptions): string | Buffer;
|
|
227
227
|
/**
|
|
@@ -251,10 +251,10 @@ declare const _default: {
|
|
|
251
251
|
*/
|
|
252
252
|
encryptAssertion(sourceEntity: any, targetEntity: any, xml?: string): Promise<string>;
|
|
253
253
|
/**
|
|
254
|
-
*
|
|
254
|
+
* Synchronous assertion decryption helper.
|
|
255
255
|
*/
|
|
256
256
|
/**
|
|
257
|
-
*
|
|
257
|
+
* Synchronous assertion decryption helper with post-decryption signature validation.
|
|
258
258
|
*/
|
|
259
259
|
decryptAssertionAsync(here: any, entireXML: string, opts?: SignatureVerifierOptions): Promise<(string | {
|
|
260
260
|
isAssertionSigned: boolean;
|
|
@@ -263,10 +263,10 @@ declare const _default: {
|
|
|
263
263
|
unsafeSignatureAlgorithm: string;
|
|
264
264
|
})[]>;
|
|
265
265
|
/**
|
|
266
|
-
*
|
|
267
|
-
* @param self
|
|
268
|
-
* @param entireXML
|
|
269
|
-
* @returns
|
|
266
|
+
* Decrypt an encrypted assertion in a SOAP response.
|
|
267
|
+
* @param self Current entity, SP or IdP.
|
|
268
|
+
* @param entireXML Complete SOAP XML response.
|
|
269
|
+
* @returns Decrypted full SOAP XML and decrypted assertion XML.
|
|
270
270
|
*/
|
|
271
271
|
decryptAssertionSoap(self: any, entireXML: string): Promise<[string, string]>;
|
|
272
272
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"libsaml.d.ts","sourceRoot":"","sources":["../../src/libsaml.ts"],"names":[],"mappings":"AAQA,OAAQ,KAAK,MAAM,MAAM,aAAa,CAAA;AAItC,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"libsaml.d.ts","sourceRoot":"","sources":["../../src/libsaml.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"libsamlSoap.d.ts","sourceRoot":"","sources":["../../src/libsamlSoap.ts"],"names":[],"mappings":"AAKA,OAAgB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAiBjE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,iBAAiB,GAAG,kBAAkB,CAAC;IAC7C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;
|
|
1
|
+
{"version":3,"file":"libsamlSoap.d.ts","sourceRoot":"","sources":["../../src/libsamlSoap.ts"],"names":[],"mappings":"AAKA,OAAgB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAiBjE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,iBAAiB,GAAG,kBAAkB,CAAC;IAC7C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AA4HD,iBAAe,2BAA2B,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAoCpH;;;;AAED,wBAEE"}
|