samlify 2.11.0 → 2.13.0
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 +1 -1
- package/build/src/api.js +52 -3
- package/build/src/api.js.map +1 -1
- package/build/src/binding-post.js +236 -182
- package/build/src/binding-post.js.map +1 -1
- package/build/src/binding-redirect.js +303 -215
- package/build/src/binding-redirect.js.map +1 -1
- package/build/src/binding-simplesign.js +285 -137
- package/build/src/binding-simplesign.js.map +1 -1
- package/build/src/entity-idp.js +130 -47
- package/build/src/entity-idp.js.map +1 -1
- package/build/src/entity-sp.js +81 -39
- package/build/src/entity-sp.js.map +1 -1
- package/build/src/entity.js +100 -62
- package/build/src/entity.js.map +1 -1
- package/build/src/extractor.js +119 -155
- package/build/src/extractor.js.map +1 -1
- package/build/src/flow.js +100 -96
- package/build/src/flow.js.map +1 -1
- package/build/src/libsaml.js +318 -261
- package/build/src/libsaml.js.map +1 -1
- package/build/src/metadata-idp.js +60 -30
- package/build/src/metadata-idp.js.map +1 -1
- package/build/src/metadata-sp.js +51 -41
- package/build/src/metadata-sp.js.map +1 -1
- package/build/src/metadata.js +47 -43
- package/build/src/metadata.js.map +1 -1
- package/build/src/options.js +73 -0
- package/build/src/options.js.map +1 -0
- package/build/src/urn.js +28 -1
- package/build/src/urn.js.map +1 -1
- package/build/src/utility.js +165 -83
- package/build/src/utility.js.map +1 -1
- package/build/src/validator.js +27 -10
- package/build/src/validator.js.map +1 -1
- package/package.json +17 -7
- package/types/src/api.d.ts +33 -3
- package/types/src/binding-post.d.ts +67 -34
- package/types/src/binding-redirect.d.ts +58 -31
- package/types/src/binding-simplesign.d.ts +77 -21
- package/types/src/entity-idp.d.ts +40 -31
- package/types/src/entity-sp.d.ts +37 -27
- package/types/src/entity.d.ts +71 -77
- package/types/src/extractor.d.ts +31 -22
- package/types/src/flow.d.ts +24 -2
- package/types/src/libsaml.d.ts +172 -118
- package/types/src/metadata-idp.d.ts +27 -11
- package/types/src/metadata-sp.d.ts +29 -19
- package/types/src/metadata.d.ts +59 -34
- package/types/src/options.d.ts +37 -0
- package/types/src/types.d.ts +250 -24
- package/types/src/urn.d.ts +7 -0
- package/types/src/utility.d.ts +144 -89
- package/types/src/validator.d.ts +21 -0
- package/.circleci/config.yml +0 -98
- package/.editorconfig +0 -19
- package/.github/FUNDING.yml +0 -1
- package/.github/workflows/deploy-docs.yml +0 -56
- package/.pre-commit.sh +0 -15
- package/.snyk +0 -4
- package/Makefile +0 -25
- package/index.ts +0 -28
- package/src/api.ts +0 -36
- package/src/binding-post.ts +0 -336
- package/src/binding-redirect.ts +0 -335
- package/src/binding-simplesign.ts +0 -231
- package/src/entity-idp.ts +0 -145
- package/src/entity-sp.ts +0 -114
- package/src/entity.ts +0 -243
- package/src/extractor.ts +0 -399
- package/src/flow.ts +0 -469
- package/src/libsaml.ts +0 -777
- package/src/metadata-idp.ts +0 -146
- package/src/metadata-sp.ts +0 -203
- package/src/metadata.ts +0 -166
- package/src/types.ts +0 -127
- package/src/urn.ts +0 -210
- package/src/utility.ts +0 -231
- package/src/validator.ts +0 -44
- package/tsconfig.json +0 -41
- package/tslint.json +0 -35
- package/types.d.ts +0 -2
- package/vitest.config.ts +0 -12
package/build/src/libsaml.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* @file
|
|
4
|
-
* @author tngan
|
|
5
|
-
* @desc
|
|
6
|
-
|
|
3
|
+
* @file libsaml.ts
|
|
4
|
+
* @author tngan
|
|
5
|
+
* @desc SAML primitives: templates, XML signing/verification, assertion
|
|
6
|
+
* encryption/decryption, and XPath helpers used by the higher-level flows.
|
|
7
|
+
*/
|
|
8
|
+
var __assign = (this && this.__assign) || function () {
|
|
9
|
+
__assign = Object.assign || function(t) {
|
|
10
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
11
|
+
s = arguments[i];
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
}
|
|
15
|
+
return t;
|
|
16
|
+
};
|
|
17
|
+
return __assign.apply(this, arguments);
|
|
18
|
+
};
|
|
7
19
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
20
|
if (k2 === undefined) k2 = k;
|
|
9
21
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -91,30 +103,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
91
103
|
var utility_1 = __importStar(require("./utility"));
|
|
92
104
|
var urn_1 = require("./urn");
|
|
93
105
|
var xpath_1 = require("xpath");
|
|
94
|
-
function toNodeArray(result) {
|
|
95
|
-
if (Array.isArray(result))
|
|
96
|
-
return result;
|
|
97
|
-
if (result != null && typeof result === 'object' && 'nodeType' in result)
|
|
98
|
-
return [result];
|
|
99
|
-
return [];
|
|
100
|
-
}
|
|
101
106
|
var node_rsa_1 = __importDefault(require("node-rsa"));
|
|
102
107
|
var xml_crypto_1 = require("xml-crypto");
|
|
103
108
|
var xmlenc = __importStar(require("@authenio/xml-encryption"));
|
|
104
|
-
var camelcase_1 = __importDefault(require("camelcase"));
|
|
105
109
|
var api_1 = require("./api");
|
|
106
110
|
var xml_escape_1 = __importDefault(require("xml-escape"));
|
|
111
|
+
var crypto = __importStar(require("crypto"));
|
|
107
112
|
var fs = __importStar(require("fs"));
|
|
108
|
-
var xmldom_1 = require("@xmldom/xmldom");
|
|
109
113
|
var signatureAlgorithms = urn_1.algorithms.signature;
|
|
110
114
|
var digestAlgorithms = urn_1.algorithms.digest;
|
|
111
115
|
var certUse = urn_1.wording.certUse;
|
|
112
116
|
var urlParams = urn_1.wording.urlParams;
|
|
117
|
+
/** Coerce the heterogeneous return of `xpath.select` into a Node array. */
|
|
118
|
+
function toNodeArray(result) {
|
|
119
|
+
if (Array.isArray(result))
|
|
120
|
+
return result;
|
|
121
|
+
if (result != null && typeof result === 'object' && 'nodeType' in result) {
|
|
122
|
+
return [result];
|
|
123
|
+
}
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
113
126
|
var libSaml = function () {
|
|
114
127
|
/**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
128
|
+
* Map a SAML URL parameter type onto its canonical query-string key
|
|
129
|
+
* (`SAMLRequest` or `SAMLResponse`).
|
|
130
|
+
*
|
|
131
|
+
* @param type SAML URL parameter name
|
|
132
|
+
* @returns `SAMLRequest` or `SAMLResponse`
|
|
133
|
+
*/
|
|
118
134
|
function getQueryParamByType(type) {
|
|
119
135
|
if ([urlParams.logoutRequest, urlParams.samlRequest].indexOf(type) !== -1) {
|
|
120
136
|
return 'SAMLRequest';
|
|
@@ -125,113 +141,187 @@ var libSaml = function () {
|
|
|
125
141
|
throw new Error('ERR_UNDEFINED_QUERY_PARAMS');
|
|
126
142
|
}
|
|
127
143
|
/**
|
|
144
|
+
* Mapping from XML-DSig signature algorithm URIs to node-rsa schemes.
|
|
128
145
|
*
|
|
146
|
+
* The PSS entry covers the redirect-binding detached signature path:
|
|
147
|
+
* `node-rsa` accepts `pss-sha256` directly as a `signingScheme` value.
|
|
148
|
+
* The `xmldsig-more#sha256-rsa-MGF1` URI (W3C Note, 2007-05) is listed
|
|
149
|
+
* in `xmldsig-core §6.4.2` as the recommended successor to PKCS#1 v1.5
|
|
150
|
+
* RSA-SHA256 (`saml-sec-consider §6.5`).
|
|
129
151
|
*/
|
|
130
152
|
var nrsaAliasMapping = {
|
|
131
153
|
'http://www.w3.org/2000/09/xmldsig#rsa-sha1': 'pkcs1-sha1',
|
|
132
154
|
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': 'pkcs1-sha256',
|
|
133
155
|
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512': 'pkcs1-sha512',
|
|
156
|
+
'http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1': 'pss-sha256',
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* RSASSA-PSS plugin class for `xml-crypto`'s `SignedXml.SignatureAlgorithms`
|
|
160
|
+
* registry (`xmldsig-core §6.4.2`).
|
|
161
|
+
*
|
|
162
|
+
* **Temporary shim.** xml-crypto already implements PSS-SHA256 on its
|
|
163
|
+
* master branch (PR node-saml/xml-crypto#488, merged 2025-10-17), but
|
|
164
|
+
* the latest published npm version (6.1.2, 2024-08) predates that
|
|
165
|
+
* commit. Once xml-crypto cuts a release containing #488, delete this
|
|
166
|
+
* class and the `registerPssAlgorithms` helper below — the URI alone
|
|
167
|
+
* in `algorithms.signature` is sufficient and the constructor will
|
|
168
|
+
* seed `SignatureAlgorithms` with the upstream implementation.
|
|
169
|
+
*
|
|
170
|
+
* `RSA_PKCS1_PSS_PADDING` with `RSA_PSS_SALTLEN_DIGEST` matches the
|
|
171
|
+
* MGF1+SHA-256 convention referenced by the `sha256-rsa-MGF1` URI
|
|
172
|
+
* (xmldsig-more, W3C Note 2007-05).
|
|
173
|
+
*/
|
|
174
|
+
var pssPaddingOptions = {
|
|
175
|
+
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
176
|
+
saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST,
|
|
134
177
|
};
|
|
178
|
+
var RsaSha256Mgf1 = /** @class */ (function () {
|
|
179
|
+
function RsaSha256Mgf1() {
|
|
180
|
+
this.getSignature = function (signedInfo, privateKey) {
|
|
181
|
+
var signOpts = {
|
|
182
|
+
key: privateKey,
|
|
183
|
+
padding: pssPaddingOptions.padding,
|
|
184
|
+
saltLength: pssPaddingOptions.saltLength,
|
|
185
|
+
};
|
|
186
|
+
return crypto.sign('RSA-SHA256', Buffer.from(signedInfo), signOpts).toString('base64');
|
|
187
|
+
};
|
|
188
|
+
this.verifySignature = function (material, key, signatureValue) {
|
|
189
|
+
var verifyOpts = {
|
|
190
|
+
key: key,
|
|
191
|
+
padding: pssPaddingOptions.padding,
|
|
192
|
+
saltLength: pssPaddingOptions.saltLength,
|
|
193
|
+
};
|
|
194
|
+
return crypto.verify('RSA-SHA256', Buffer.from(material), verifyOpts, Buffer.from(signatureValue, 'base64'));
|
|
195
|
+
};
|
|
196
|
+
this.getAlgorithmName = function () { return 'http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1'; };
|
|
197
|
+
}
|
|
198
|
+
return RsaSha256Mgf1;
|
|
199
|
+
}());
|
|
200
|
+
/**
|
|
201
|
+
* Register the RSASSA-PSS SHA-256 signature class on a fresh `SignedXml`
|
|
202
|
+
* instance so the algorithm-agility surface from `xmldsig-core §6.4` is
|
|
203
|
+
* available for both signing and verification. PKCS#1 v1.5 entries are
|
|
204
|
+
* preserved untouched.
|
|
205
|
+
*/
|
|
206
|
+
function registerPssAlgorithms(sig) {
|
|
207
|
+
sig.SignatureAlgorithms = __assign(__assign({}, sig.SignatureAlgorithms), { 'http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1': RsaSha256Mgf1 });
|
|
208
|
+
}
|
|
135
209
|
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
210
|
+
* Default AuthnRequest XML template.
|
|
211
|
+
*
|
|
212
|
+
* Per saml-core §3.4.1, `ProtocolBinding`, `AssertionConsumerServiceURL`,
|
|
213
|
+
* and `AssertionConsumerServiceIndex` are all `use="optional"` and
|
|
214
|
+
* mutually exclusive — when the index is set, neither URL nor
|
|
215
|
+
* ProtocolBinding may be present. All three are placeholders here so
|
|
216
|
+
* that `replaceTagsByValue` drops whichever the caller leaves
|
|
217
|
+
* undefined (closes #437).
|
|
218
|
+
*/
|
|
139
219
|
var defaultLoginRequestTemplate = {
|
|
140
|
-
context: '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}" ProtocolBinding="
|
|
220
|
+
context: '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" ForceAuthn="{ForceAuthn}" IssueInstant="{IssueInstant}" Destination="{Destination}" ProtocolBinding="{ProtocolBinding}" AssertionConsumerServiceURL="{AssertionConsumerServiceURL}" AssertionConsumerServiceIndex="{AssertionConsumerServiceIndex}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:NameIDPolicy Format="{NameIDFormat}" AllowCreate="{AllowCreate}"/></samlp:AuthnRequest>',
|
|
141
221
|
};
|
|
142
222
|
/**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
223
|
+
* Default LogoutRequest XML template.
|
|
224
|
+
*
|
|
225
|
+
* The optional `<samlp:SessionIndex>` element (saml-core §3.7.1) is
|
|
226
|
+
* included with a placeholder body. When the caller leaves
|
|
227
|
+
* `user.sessionIndex` undefined, `replaceTagsByValue` drops the whole
|
|
228
|
+
* element from the rendered XML, matching the schema's
|
|
229
|
+
* `minOccurs="0"` declaration.
|
|
230
|
+
*/
|
|
146
231
|
var defaultLogoutRequestTemplate = {
|
|
147
|
-
context: '<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}"><saml:Issuer>{Issuer}</saml:Issuer><saml:NameID Format="{NameIDFormat}">{NameID}</saml:NameID></samlp:LogoutRequest>',
|
|
232
|
+
context: '<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}"><saml:Issuer>{Issuer}</saml:Issuer><saml:NameID Format="{NameIDFormat}">{NameID}</saml:NameID><samlp:SessionIndex>{SessionIndex}</samlp:SessionIndex></samlp:LogoutRequest>',
|
|
148
233
|
};
|
|
149
|
-
/**
|
|
150
|
-
* @desc Default AttributeStatement template
|
|
151
|
-
* @type {AttributeStatementTemplate}
|
|
152
|
-
*/
|
|
234
|
+
/** Default AttributeStatement XML fragment template. */
|
|
153
235
|
var defaultAttributeStatementTemplate = {
|
|
154
236
|
context: '<saml:AttributeStatement>{Attributes}</saml:AttributeStatement>',
|
|
155
237
|
};
|
|
156
|
-
/**
|
|
157
|
-
* @desc Default Attribute template
|
|
158
|
-
* @type {AttributeTemplate}
|
|
159
|
-
*/
|
|
238
|
+
/** Default Attribute XML fragment template. */
|
|
160
239
|
var defaultAttributeTemplate = {
|
|
161
240
|
context: '<saml:Attribute Name="{Name}" NameFormat="{NameFormat}"><saml:AttributeValue xmlns:xs="{ValueXmlnsXs}" xmlns:xsi="{ValueXmlnsXsi}" xsi:type="{ValueXsiType}">{Value}</saml:AttributeValue></saml:Attribute>',
|
|
162
241
|
};
|
|
163
|
-
/**
|
|
164
|
-
* @desc Default login response template
|
|
165
|
-
* @type {LoginResponseTemplate}
|
|
166
|
-
*/
|
|
242
|
+
/** Default LoginResponse XML template. */
|
|
167
243
|
var defaultLoginResponseTemplate = {
|
|
168
244
|
context: '<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}" InResponseTo="{InResponseTo}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="{StatusCode}"/></samlp:Status><saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{AssertionID}" Version="2.0" IssueInstant="{IssueInstant}"><saml:Issuer>{Issuer}</saml:Issuer><saml:Subject><saml:NameID Format="{NameIDFormat}">{NameID}</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="{SubjectConfirmationDataNotOnOrAfter}" Recipient="{SubjectRecipient}" InResponseTo="{InResponseTo}"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="{ConditionsNotBefore}" NotOnOrAfter="{ConditionsNotOnOrAfter}"><saml:AudienceRestriction><saml:Audience>{Audience}</saml:Audience></saml:AudienceRestriction></saml:Conditions>{AuthnStatement}{AttributeStatement}</saml:Assertion></samlp:Response>',
|
|
169
245
|
attributes: [],
|
|
170
246
|
additionalTemplates: {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
247
|
+
attributeStatementTemplate: defaultAttributeStatementTemplate,
|
|
248
|
+
attributeTemplate: defaultAttributeTemplate,
|
|
249
|
+
},
|
|
174
250
|
};
|
|
175
|
-
/**
|
|
176
|
-
* @desc Default logout response template
|
|
177
|
-
* @type {LogoutResponseTemplate}
|
|
178
|
-
*/
|
|
251
|
+
/** Default LogoutResponse XML template. */
|
|
179
252
|
var defaultLogoutResponseTemplate = {
|
|
180
253
|
context: '<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}" InResponseTo="{InResponseTo}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="{StatusCode}"/></samlp:Status></samlp:LogoutResponse>',
|
|
181
254
|
};
|
|
182
255
|
/**
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
256
|
+
* Map a SAML signature algorithm URI to its node-rsa signing scheme.
|
|
257
|
+
*
|
|
258
|
+
* - When `sigAlg` is omitted, the default RSA-SHA256 scheme is used
|
|
259
|
+
* (per `saml-bindings §3.4.4.1` recommendation).
|
|
260
|
+
* - When `sigAlg` is supplied but does not match a known URI, the
|
|
261
|
+
* function throws. Silently downgrading to RSA-SHA1 (the previous
|
|
262
|
+
* behaviour) was a verification-time vulnerability: an attacker
|
|
263
|
+
* could supply an unknown `SigAlg` query parameter to coerce
|
|
264
|
+
* verification onto SHA-1, which is collision-broken
|
|
265
|
+
* (`saml-sec-consider §6.5`, `xmldsig-core §6.4`).
|
|
266
|
+
*
|
|
267
|
+
* @param sigAlg signature algorithm URI
|
|
268
|
+
* @returns node-rsa signing scheme string
|
|
269
|
+
* @throws when `sigAlg` is supplied and does not match a supported URI
|
|
270
|
+
*/
|
|
188
271
|
function getSigningScheme(sigAlg) {
|
|
189
|
-
if (sigAlg) {
|
|
190
|
-
|
|
191
|
-
if (!(algAlias === undefined)) {
|
|
192
|
-
return algAlias;
|
|
193
|
-
}
|
|
272
|
+
if (sigAlg === undefined) {
|
|
273
|
+
return nrsaAliasMapping[signatureAlgorithms.RSA_SHA256];
|
|
194
274
|
}
|
|
195
|
-
|
|
275
|
+
var algAlias = nrsaAliasMapping[sigAlg];
|
|
276
|
+
if (algAlias === undefined) {
|
|
277
|
+
throw new Error('ERR_UNSUPPORTED_SIGNATURE_ALGORITHM');
|
|
278
|
+
}
|
|
279
|
+
return algAlias;
|
|
196
280
|
}
|
|
197
281
|
/**
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
282
|
+
* Return the companion digest URI for a given signature algorithm URI.
|
|
283
|
+
*
|
|
284
|
+
* @param sigAlg signature algorithm URI
|
|
285
|
+
* @returns digest algorithm URI or undefined when unsupported
|
|
286
|
+
*/
|
|
203
287
|
function getDigestMethod(sigAlg) {
|
|
204
288
|
return digestAlgorithms[sigAlg];
|
|
205
289
|
}
|
|
206
290
|
/**
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
291
|
+
* Build an XPath expression that matches either a named element or one of
|
|
292
|
+
* its attributes.
|
|
293
|
+
*
|
|
294
|
+
* @param local element name, or `{ name, attr }` for an attribute selector
|
|
295
|
+
* @param isExtractAll when true the element selector resolves to its text()
|
|
296
|
+
* @returns XPath expression
|
|
297
|
+
*/
|
|
213
298
|
function createXPath(local, isExtractAll) {
|
|
214
299
|
if ((0, utility_1.isString)(local)) {
|
|
215
|
-
|
|
300
|
+
var escaped = (0, utility_1.escapeXPathValue)(local);
|
|
301
|
+
return isExtractAll === true
|
|
302
|
+
? '//*[local-name(.)=' + escaped + ']/text()'
|
|
303
|
+
: '//*[local-name(.)=' + escaped + ']';
|
|
216
304
|
}
|
|
217
|
-
|
|
305
|
+
var _a = local, name = _a.name, attr = _a.attr;
|
|
306
|
+
return '//*[local-name(.)=' + (0, utility_1.escapeXPathValue)(name) + ']/@' + attr;
|
|
218
307
|
}
|
|
219
308
|
/**
|
|
220
|
-
*
|
|
221
|
-
* @desc Tag normalization
|
|
222
|
-
* @param {string} prefix prefix of the tag
|
|
223
|
-
* @param {content} content normalize it to capitalized camel case
|
|
224
|
-
* @return {string}
|
|
309
|
+
* Capitalise a content string after camel-casing and optionally prefix it.
|
|
225
310
|
*/
|
|
226
311
|
function tagging(prefix, content) {
|
|
227
|
-
var camelContent = (0,
|
|
312
|
+
var camelContent = (0, utility_1.camelCase)(content);
|
|
228
313
|
return prefix + camelContent.charAt(0).toUpperCase() + camelContent.slice(1);
|
|
229
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Replacer for {@link replaceTagsByValue}. Always XML-escapes the
|
|
317
|
+
* replacement text, in both attribute and element-text contexts, to
|
|
318
|
+
* prevent SAML attribute/element injection through user-controlled
|
|
319
|
+
* template values.
|
|
320
|
+
*/
|
|
230
321
|
function escapeTag(replacement) {
|
|
231
322
|
return function (_match, quote) {
|
|
232
|
-
var text =
|
|
233
|
-
|
|
234
|
-
return quote ? "".concat(quote).concat((0, xml_escape_1.default)(text)) : text;
|
|
323
|
+
var text = replacement === null || replacement === undefined ? '' : String(replacement);
|
|
324
|
+
return quote ? "".concat(quote).concat((0, xml_escape_1.default)(text)) : (0, xml_escape_1.default)(text);
|
|
235
325
|
};
|
|
236
326
|
}
|
|
237
327
|
return {
|
|
@@ -244,24 +334,55 @@ var libSaml = function () {
|
|
|
244
334
|
defaultLogoutRequestTemplate: defaultLogoutRequestTemplate,
|
|
245
335
|
defaultLogoutResponseTemplate: defaultLogoutResponseTemplate,
|
|
246
336
|
/**
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
337
|
+
* Substitute `{Tag}` placeholders inside an XML template with the given
|
|
338
|
+
* replacement map. Replacement text is XML-escaped in both attribute and
|
|
339
|
+
* element-text positions to prevent SAML element/attribute injection.
|
|
340
|
+
*
|
|
341
|
+
* When a tag's value is `null` or `undefined`:
|
|
342
|
+
* - in **attribute position** (`name="{Tag}"`) the entire attribute is
|
|
343
|
+
* omitted from the rendered XML, per `saml-core §3.4.1` (every
|
|
344
|
+
* `<AuthnRequest>` attribute is `use="optional"`); previously samlify
|
|
345
|
+
* emitted `name=""` or the literal `name="undefined"`, which is
|
|
346
|
+
* invalid for typed attributes (e.g. `AssertionConsumerServiceURL`
|
|
347
|
+
* declared as `xs:anyURI`).
|
|
348
|
+
* - in **element-only-body position** (`<X>{Tag}</X>` or
|
|
349
|
+
* `<X attr="...">{Tag}</X>`) the entire element is dropped — per
|
|
350
|
+
* `saml-core §3.7.1` for `<samlp:SessionIndex>` and any other
|
|
351
|
+
* `minOccurs="0"` element where the body is solely a placeholder.
|
|
352
|
+
* - in **mixed-text position** (`<X>prefix{Tag}suffix</X>`) the
|
|
353
|
+
* placeholder is replaced with the empty string (legacy behaviour).
|
|
354
|
+
*
|
|
355
|
+
* @param rawXML template with `{Tag}` placeholders
|
|
356
|
+
* @param tagValues replacement map keyed by tag name
|
|
357
|
+
* @returns XML with placeholders resolved
|
|
358
|
+
*/
|
|
252
359
|
replaceTagsByValue: function (rawXML, tagValues) {
|
|
253
360
|
Object.keys(tagValues).forEach(function (t) {
|
|
254
|
-
|
|
361
|
+
var value = tagValues[t];
|
|
362
|
+
if (value === null || value === undefined) {
|
|
363
|
+
// Drop the entire `\s+name="{Tag}"` (or single-quoted) attribute.
|
|
364
|
+
rawXML = rawXML.replace(new RegExp("\\s+[A-Za-z_:][\\w:.-]*=(\"|')\\{".concat(t, "\\}\\1"), 'g'), '');
|
|
365
|
+
// Drop the entire `<X ...>{Tag}</X>` element when the placeholder
|
|
366
|
+
// is the only body content (allows optional elements to be omitted
|
|
367
|
+
// rather than rendered empty).
|
|
368
|
+
rawXML = rawXML.replace(new RegExp("<([A-Za-z_:][\\w:.-]*)((?:\\s+[^>]*)?)>\\{".concat(t, "\\}</\\1>"), 'g'), '');
|
|
369
|
+
// Replace any remaining `{Tag}` occurrences with empty string.
|
|
370
|
+
rawXML = rawXML.replace(new RegExp("\\{".concat(t, "\\}"), 'g'), '');
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
rawXML = rawXML.replace(new RegExp("(\"?)\\{".concat(t, "\\}"), 'g'), escapeTag(value));
|
|
255
374
|
});
|
|
256
375
|
return rawXML;
|
|
257
376
|
},
|
|
258
377
|
/**
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
378
|
+
* Build a serialized `<AttributeStatement>` from attribute descriptors
|
|
379
|
+
* by applying the attribute and statement templates.
|
|
380
|
+
*
|
|
381
|
+
* @param attributes attribute descriptors (name, format, value)
|
|
382
|
+
* @param attributeTemplate per-attribute template
|
|
383
|
+
* @param attributeStatementTemplate wrapping statement template
|
|
384
|
+
* @returns serialized XML fragment
|
|
385
|
+
*/
|
|
265
386
|
attributeStatementBuilder: function (attributes, attributeTemplate, attributeStatementTemplate) {
|
|
266
387
|
if (attributeTemplate === void 0) { attributeTemplate = defaultAttributeTemplate; }
|
|
267
388
|
if (attributeStatementTemplate === void 0) { attributeStatementTemplate = defaultAttributeStatementTemplate; }
|
|
@@ -281,37 +402,37 @@ var libSaml = function () {
|
|
|
281
402
|
return attributeStatementTemplate.context.replace('{Attributes}', attr);
|
|
282
403
|
},
|
|
283
404
|
/**
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
* @param {string[]} transformationAlgorithms canonicalization and transformation Algorithms
|
|
292
|
-
* @return {string} base64 encoded string
|
|
293
|
-
*/
|
|
405
|
+
* Compute an XML-DSig signature over the supplied SAML message. Can
|
|
406
|
+
* sign the message root (`isMessageSigned`), a referenced subtree
|
|
407
|
+
* (`referenceTagXPath`), or both.
|
|
408
|
+
*
|
|
409
|
+
* @param opts signature inputs and layout options
|
|
410
|
+
* @returns base64 (default) or raw signed XML string
|
|
411
|
+
*/
|
|
294
412
|
constructSAMLSignature: function (opts) {
|
|
295
413
|
var rawSamlMessage = opts.rawSamlMessage, referenceTagXPath = opts.referenceTagXPath, privateKey = opts.privateKey, privateKeyPass = opts.privateKeyPass, _a = opts.signatureAlgorithm, signatureAlgorithm = _a === void 0 ? signatureAlgorithms.RSA_SHA256 : _a, _b = opts.transformationAlgorithms, transformationAlgorithms = _b === void 0 ? [
|
|
296
414
|
'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
|
|
297
415
|
'http://www.w3.org/2001/10/xml-exc-c14n#',
|
|
298
416
|
] : _b, signingCert = opts.signingCert, signatureConfig = opts.signatureConfig, _c = opts.isBase64Output, isBase64Output = _c === void 0 ? true : _c, _d = opts.isMessageSigned, isMessageSigned = _d === void 0 ? false : _d;
|
|
299
417
|
var sig = new xml_crypto_1.SignedXml();
|
|
300
|
-
//
|
|
418
|
+
// xmldsig-core §6.4.2 — make PSS variants available alongside the
|
|
419
|
+
// PKCS#1 v1.5 set built into xml-crypto v6.x. No-op for callers
|
|
420
|
+
// staying on RSA-SHA*; required when `signatureAlgorithm` is one of
|
|
421
|
+
// the `xmldsig-more 2007-05` PSS URIs.
|
|
422
|
+
registerPssAlgorithms(sig);
|
|
301
423
|
var digestAlgorithm = getDigestMethod(signatureAlgorithm);
|
|
302
424
|
if (referenceTagXPath) {
|
|
303
425
|
sig.addReference({
|
|
304
426
|
xpath: referenceTagXPath,
|
|
305
427
|
transforms: transformationAlgorithms,
|
|
306
|
-
digestAlgorithm: digestAlgorithm
|
|
428
|
+
digestAlgorithm: digestAlgorithm,
|
|
307
429
|
});
|
|
308
430
|
}
|
|
309
431
|
if (isMessageSigned) {
|
|
310
432
|
sig.addReference({
|
|
311
|
-
// reference to the root node
|
|
312
433
|
xpath: '/*',
|
|
313
434
|
transforms: transformationAlgorithms,
|
|
314
|
-
digestAlgorithm: digestAlgorithm
|
|
435
|
+
digestAlgorithm: digestAlgorithm,
|
|
315
436
|
});
|
|
316
437
|
}
|
|
317
438
|
sig.signatureAlgorithm = signatureAlgorithm;
|
|
@@ -325,46 +446,50 @@ var libSaml = function () {
|
|
|
325
446
|
else {
|
|
326
447
|
sig.computeSignature(rawSamlMessage);
|
|
327
448
|
}
|
|
328
|
-
return isBase64Output !== false
|
|
449
|
+
return isBase64Output !== false
|
|
450
|
+
? utility_1.default.base64Encode(sig.getSignedXml())
|
|
451
|
+
: sig.getSignedXml();
|
|
329
452
|
},
|
|
330
453
|
/**
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
454
|
+
* Verify an XML-DSig signature on a SAML payload and, on success, return
|
|
455
|
+
* the cryptographically authenticated assertion node.
|
|
456
|
+
*
|
|
457
|
+
* Defends against classic wrapping attacks by rejecting assertions that
|
|
458
|
+
* appear inside a `SubjectConfirmationData` subtree.
|
|
459
|
+
*
|
|
460
|
+
* @param xml SAML message XML
|
|
461
|
+
* @param opts metadata or key file plus signature algorithm
|
|
462
|
+
* @returns tuple `[verified, authenticatedAssertion | null]`
|
|
337
463
|
*/
|
|
338
464
|
verifySignature: function (xml, opts) {
|
|
339
465
|
var e_1, _a;
|
|
340
466
|
var _b;
|
|
341
467
|
var dom = (0, api_1.getContext)().dom;
|
|
342
468
|
var doc = dom.parseFromString(xml);
|
|
343
|
-
var
|
|
344
|
-
|
|
345
|
-
//
|
|
469
|
+
var contextDom = (0, api_1.getContext)().dom;
|
|
470
|
+
var docParser = contextDom;
|
|
471
|
+
// Absolute XPaths defend against signature-wrapping attacks.
|
|
346
472
|
var messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']";
|
|
347
|
-
// assertion signature (logout response / saml response)
|
|
348
473
|
var assertionSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']/*[local-name(.)='Signature']";
|
|
349
|
-
// check if there is a potential malicious wrapping signature
|
|
350
474
|
var wrappingElementsXPath = "/*[contains(local-name(), 'Response')]/*[local-name(.)='Assertion']/*[local-name(.)='Subject']/*[local-name(.)='SubjectConfirmation']/*[local-name(.)='SubjectConfirmationData']//*[local-name(.)='Assertion' or local-name(.)='Signature']";
|
|
351
|
-
// select the signature node
|
|
352
475
|
var selection = [];
|
|
353
476
|
var messageSignatureNode = toNodeArray((0, xpath_1.select)(messageSignatureXpath, doc));
|
|
354
477
|
var assertionSignatureNode = toNodeArray((0, xpath_1.select)(assertionSignatureXpath, doc));
|
|
355
478
|
var wrappingElementNode = toNodeArray((0, xpath_1.select)(wrappingElementsXPath, doc));
|
|
356
479
|
selection = selection.concat(messageSignatureNode);
|
|
357
480
|
selection = selection.concat(assertionSignatureNode);
|
|
358
|
-
// try to catch potential wrapping attack
|
|
359
481
|
if (wrappingElementNode.length !== 0) {
|
|
360
482
|
throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
|
|
361
483
|
}
|
|
362
|
-
// guarantee to have a signature in saml response
|
|
363
484
|
if (selection.length === 0) {
|
|
364
|
-
return [false, null];
|
|
485
|
+
return [false, null];
|
|
365
486
|
}
|
|
366
487
|
var _loop_1 = function (signatureNode) {
|
|
367
488
|
var sig = new xml_crypto_1.SignedXml();
|
|
489
|
+
// Register PSS plugins on the verifier instance so the algorithm
|
|
490
|
+
// declared inside the signed XML can resolve to a SignatureAlgorithm
|
|
491
|
+
// class (xmldsig-core §6.4.2; see `registerPssAlgorithms`).
|
|
492
|
+
registerPssAlgorithms(sig);
|
|
368
493
|
var verified = false;
|
|
369
494
|
sig.signatureAlgorithm = opts.signatureAlgorithm;
|
|
370
495
|
if (!opts.keyFile && !opts.metadata) {
|
|
@@ -375,79 +500,59 @@ var libSaml = function () {
|
|
|
375
500
|
}
|
|
376
501
|
if (opts.metadata) {
|
|
377
502
|
var certificateNode = toNodeArray((0, xpath_1.select)(".//*[local-name(.)='X509Certificate']", signatureNode));
|
|
378
|
-
// certificate in metadata
|
|
379
503
|
var metadataCert = opts.metadata.getX509Certificate(certUse.signing);
|
|
380
|
-
// flattens the nested array of Certificates from each KeyDescriptor
|
|
381
504
|
if (Array.isArray(metadataCert)) {
|
|
382
505
|
metadataCert = (0, utility_1.flattenDeep)(metadataCert);
|
|
383
506
|
}
|
|
384
507
|
else if (typeof metadataCert === 'string') {
|
|
385
508
|
metadataCert = [metadataCert];
|
|
386
509
|
}
|
|
387
|
-
// normalise the certificate string
|
|
388
510
|
metadataCert = metadataCert.map(utility_1.default.normalizeCerString);
|
|
389
|
-
// no certificate in node response nor metadata
|
|
390
511
|
if (certificateNode.length === 0 && metadataCert.length === 0) {
|
|
391
512
|
throw new Error('NO_SELECTED_CERTIFICATE');
|
|
392
513
|
}
|
|
393
|
-
// certificate node in response
|
|
394
514
|
if (certificateNode.length !== 0) {
|
|
395
515
|
var certEl = certificateNode[0];
|
|
396
516
|
var x509CertificateData = (_b = certEl.textContent) !== null && _b !== void 0 ? _b : '';
|
|
397
517
|
var x509Certificate_1 = utility_1.default.normalizeCerString(x509CertificateData);
|
|
398
518
|
if (metadataCert.length >= 1 &&
|
|
399
519
|
!metadataCert.find(function (cert) { return cert.trim() === x509Certificate_1.trim(); })) {
|
|
400
|
-
// keep this restriction for rolling certificate usage
|
|
401
|
-
// to make sure the response certificate is one of those specified in metadata
|
|
402
520
|
throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
|
|
403
521
|
}
|
|
404
522
|
sig.publicCert = this_1.getKeyInfo(x509Certificate_1).getKey();
|
|
405
523
|
}
|
|
406
524
|
else {
|
|
407
|
-
// Select first one from metadata
|
|
408
525
|
sig.publicCert = this_1.getKeyInfo(metadataCert[0]).getKey();
|
|
409
526
|
}
|
|
410
527
|
}
|
|
411
528
|
sig.loadSignature(signatureNode);
|
|
412
529
|
verified = sig.checkSignature(doc.toString());
|
|
413
|
-
// immediately throw error when any one of the signature is failed to get verified
|
|
414
530
|
if (!verified) {
|
|
415
531
|
return "continue";
|
|
416
|
-
// throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
|
|
417
532
|
}
|
|
418
|
-
// Require there to be at least one reference that was signed
|
|
419
533
|
if (!(sig.getSignedReferences().length >= 1)) {
|
|
420
534
|
throw new Error('NO_SIGNATURE_REFERENCES');
|
|
421
535
|
}
|
|
422
536
|
var signedVerifiedXML = sig.getSignedReferences()[0];
|
|
423
537
|
var rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement;
|
|
424
|
-
// process the verified signature:
|
|
425
|
-
// case 1, rootSignedDoc is a response:
|
|
426
538
|
if (rootNode.localName === 'Response') {
|
|
427
|
-
// try getting the Xml from the first assertion
|
|
428
539
|
var assertions = toNodeArray((0, xpath_1.select)("./*[local-name()='Assertion']", rootNode));
|
|
429
540
|
var encryptedAssertions = toNodeArray((0, xpath_1.select)("./*[local-name()='EncryptedAssertion']", rootNode));
|
|
430
|
-
// now we can process the assertion as an assertion
|
|
431
541
|
if (assertions.length === 1) {
|
|
432
542
|
return { value: [true, assertions[0].toString()] };
|
|
433
543
|
}
|
|
434
544
|
else if (encryptedAssertions.length >= 1) {
|
|
435
545
|
return { value: [true, rootNode.toString()] };
|
|
436
546
|
}
|
|
437
|
-
|
|
438
|
-
return { value: [true, null] };
|
|
439
|
-
}
|
|
547
|
+
return { value: [true, null] };
|
|
440
548
|
}
|
|
441
549
|
else if (rootNode.localName === 'Assertion') {
|
|
442
550
|
return { value: [true, rootNode.toString()] };
|
|
443
551
|
}
|
|
444
|
-
|
|
445
|
-
return { value: [true, null] };
|
|
446
|
-
}
|
|
552
|
+
return { value: [true, null] };
|
|
447
553
|
};
|
|
448
554
|
var this_1 = this;
|
|
449
555
|
try {
|
|
450
|
-
// need to refactor later on
|
|
451
556
|
for (var selection_1 = __values(selection), selection_1_1 = selection_1.next(); !selection_1_1.done; selection_1_1 = selection_1.next()) {
|
|
452
557
|
var signatureNode = selection_1_1.value;
|
|
453
558
|
var state_1 = _loop_1(signatureNode);
|
|
@@ -462,108 +567,65 @@ var libSaml = function () {
|
|
|
462
567
|
}
|
|
463
568
|
finally { if (e_1) throw e_1.error; }
|
|
464
569
|
}
|
|
465
|
-
;
|
|
466
|
-
return [false, null]; // we didn't verify anything, none of the signatures are valid
|
|
467
|
-
/*
|
|
468
|
-
// response must be signed, either entire document or assertion
|
|
469
|
-
// default we will take the assertion section under root
|
|
470
|
-
if (messageSignatureNode.length === 1) {
|
|
471
|
-
const node = select("/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']", doc);
|
|
472
|
-
if (node.length === 1) {
|
|
473
|
-
assertionNode = node[0].toString();
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (assertionSignatureNode.length === 1) {
|
|
478
|
-
const verifiedAssertionInfo = extract(assertionSignatureNode[0].toString(), [{
|
|
479
|
-
key: 'refURI',
|
|
480
|
-
localPath: ['Signature', 'SignedInfo', 'Reference'],
|
|
481
|
-
attributes: ['URI']
|
|
482
|
-
}]);
|
|
483
|
-
// get the assertion supposed to be the one should be verified
|
|
484
|
-
const desiredAssertionInfo = extract(doc.toString(), [{
|
|
485
|
-
key: 'id',
|
|
486
|
-
localPath: ['~Response', 'Assertion'],
|
|
487
|
-
attributes: ['ID']
|
|
488
|
-
}]);
|
|
489
|
-
// 5.4.2 References
|
|
490
|
-
// SAML assertions and protocol messages MUST supply a value for the ID attribute on the root element of
|
|
491
|
-
// the assertion or protocol message being signed. The assertion’s or protocol message's root element may
|
|
492
|
-
// or may not be the root element of the actual XML document containing the signed assertion or protocol
|
|
493
|
-
// message (e.g., it might be contained within a SOAP envelope).
|
|
494
|
-
// Signatures MUST contain a single <ds:Reference> containing a same-document reference to the ID
|
|
495
|
-
// attribute value of the root element of the assertion or protocol message being signed. For example, if the
|
|
496
|
-
// ID attribute value is "foo", then the URI attribute in the <ds:Reference> element MUST be "#foo".
|
|
497
|
-
if (verifiedAssertionInfo.refURI !== `#${desiredAssertionInfo.id}`) {
|
|
498
|
-
throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
|
|
499
|
-
}
|
|
500
|
-
const verifiedDoc = extract(doc.toString(), [{
|
|
501
|
-
key: 'assertion',
|
|
502
|
-
localPath: ['~Response', 'Assertion'],
|
|
503
|
-
attributes: [],
|
|
504
|
-
context: true
|
|
505
|
-
}]);
|
|
506
|
-
assertionNode = verifiedDoc.assertion.toString();
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
return [verified, assertionNode];*/
|
|
570
|
+
return [false, null];
|
|
510
571
|
},
|
|
511
572
|
/**
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
573
|
+
* Build the metadata `<KeyDescriptor>` fragment for a certificate use.
|
|
574
|
+
*
|
|
575
|
+
* @param use `signing` or `encryption`
|
|
576
|
+
* @param certString PEM certificate body or Buffer
|
|
577
|
+
* @returns element tree consumable by the `xml` module
|
|
578
|
+
*/
|
|
517
579
|
createKeySection: function (use, certString) {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
_a['KeyDescriptor'] = [
|
|
580
|
+
return {
|
|
581
|
+
KeyDescriptor: [
|
|
521
582
|
{
|
|
522
583
|
_attr: { use: use },
|
|
523
584
|
},
|
|
524
|
-
|
|
525
|
-
|
|
585
|
+
{
|
|
586
|
+
'ds:KeyInfo': [
|
|
526
587
|
{
|
|
527
588
|
_attr: {
|
|
528
589
|
'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
|
|
529
590
|
},
|
|
530
591
|
},
|
|
531
|
-
|
|
532
|
-
|
|
592
|
+
{
|
|
593
|
+
'ds:X509Data': [{
|
|
533
594
|
'ds:X509Certificate': utility_1.default.normalizeCerString(certString),
|
|
534
595
|
}],
|
|
535
|
-
|
|
596
|
+
},
|
|
536
597
|
],
|
|
537
|
-
|
|
598
|
+
},
|
|
538
599
|
],
|
|
539
|
-
|
|
600
|
+
};
|
|
540
601
|
},
|
|
541
602
|
/**
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
603
|
+
* Produce a detached RSA signature over a SAML redirect-binding octet
|
|
604
|
+
* string. See SAML bindings spec §3.4.4.1.
|
|
605
|
+
*
|
|
606
|
+
* @param octetString canonical query-string to sign
|
|
607
|
+
* @param key PEM private key
|
|
608
|
+
* @param passphrase optional passphrase for the key
|
|
609
|
+
* @param isBase64 when true (default), base64-encode the signature
|
|
610
|
+
* @param signingAlgorithm signature algorithm URI
|
|
611
|
+
* @returns base64 string (default) or raw Buffer signature
|
|
612
|
+
*/
|
|
549
613
|
constructMessageSignature: function (octetString, key, passphrase, isBase64, signingAlgorithm) {
|
|
550
|
-
// Default returning base64 encoded signature
|
|
551
|
-
// Embed with node-rsa module
|
|
552
614
|
var decryptedKey = new node_rsa_1.default(utility_1.default.readPrivateKey(key, passphrase), undefined, {
|
|
553
615
|
signingScheme: getSigningScheme(signingAlgorithm),
|
|
554
616
|
});
|
|
555
617
|
var signature = decryptedKey.sign(octetString);
|
|
556
|
-
// Use private key to sign data
|
|
557
618
|
return isBase64 !== false ? signature.toString('base64') : signature;
|
|
558
619
|
},
|
|
559
620
|
/**
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
621
|
+
* Verify a detached RSA signature over a redirect-binding octet string.
|
|
622
|
+
*
|
|
623
|
+
* @param metadata peer metadata carrying the signing certificate
|
|
624
|
+
* @param octetString canonical query-string that was signed
|
|
625
|
+
* @param signature signature bytes
|
|
626
|
+
* @param verifyAlgorithm signature algorithm URI (optional)
|
|
627
|
+
* @returns true when the signature verifies
|
|
628
|
+
*/
|
|
567
629
|
verifyMessageSignature: function (metadata, octetString, signature, verifyAlgorithm) {
|
|
568
630
|
var signCert = metadata.getX509Certificate(certUse.signing);
|
|
569
631
|
var signingScheme = getSigningScheme(verifyAlgorithm);
|
|
@@ -571,10 +633,11 @@ var libSaml = function () {
|
|
|
571
633
|
return key.verify(Buffer.from(octetString), Buffer.from(signature));
|
|
572
634
|
},
|
|
573
635
|
/**
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
636
|
+
* Build the KeyInfo XML fragment and PEM public key for a certificate.
|
|
637
|
+
*
|
|
638
|
+
* @param x509Certificate certificate body (no PEM wrappers)
|
|
639
|
+
* @param signatureConfig optional prefix/location for the KeyInfo element
|
|
640
|
+
*/
|
|
578
641
|
getKeyInfo: function (x509Certificate, signatureConfig) {
|
|
579
642
|
if (signatureConfig === void 0) { signatureConfig = {}; }
|
|
580
643
|
var prefix = signatureConfig.prefix ? "".concat(signatureConfig.prefix, ":") : '';
|
|
@@ -588,14 +651,16 @@ var libSaml = function () {
|
|
|
588
651
|
};
|
|
589
652
|
},
|
|
590
653
|
/**
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
654
|
+
* Encrypt the `<Assertion>` inside a SAML response using the target
|
|
655
|
+
* entity's encryption certificate. Returns the base64-encoded XML
|
|
656
|
+
* containing the `<EncryptedAssertion>` element in place of the plaintext.
|
|
657
|
+
*
|
|
658
|
+
* @param sourceEntity entity initiating the encryption (its settings drive the algorithms)
|
|
659
|
+
* @param targetEntity entity whose certificate is used
|
|
660
|
+
* @param xml response XML containing a single `<Assertion>`
|
|
661
|
+
* @returns promise resolving to base64-encoded XML
|
|
662
|
+
*/
|
|
597
663
|
encryptAssertion: function (sourceEntity, targetEntity, xml) {
|
|
598
|
-
// Implement encryption after signature if it has
|
|
599
664
|
return new Promise(function (resolve, reject) {
|
|
600
665
|
if (!xml) {
|
|
601
666
|
return reject(new Error('ERR_UNDEFINED_ASSERTION'));
|
|
@@ -612,16 +677,16 @@ var libSaml = function () {
|
|
|
612
677
|
throw new Error('ERR_MULTIPLE_ASSERTION');
|
|
613
678
|
}
|
|
614
679
|
var rawAssertionNode = assertions[0];
|
|
615
|
-
// Perform encryption depends on the setting, default is false
|
|
616
680
|
if (sourceEntitySetting.isAssertionEncrypted) {
|
|
617
|
-
var
|
|
681
|
+
var encryptCert = targetEntityMetadata.getX509Certificate(certUse.encrypt);
|
|
682
|
+
var publicKeyPem = utility_1.default.getPublicKeyPemFromCertificate(encryptCert);
|
|
618
683
|
xmlenc.encrypt(rawAssertionNode.toString(), {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
pem: Buffer.from("-----BEGIN CERTIFICATE-----".concat(targetEntityMetadata.getX509Certificate(certUse.encrypt), "-----END CERTIFICATE-----")),
|
|
684
|
+
rsa_pub: Buffer.from(publicKeyPem),
|
|
685
|
+
pem: Buffer.from("-----BEGIN CERTIFICATE-----".concat(encryptCert, "-----END CERTIFICATE-----")),
|
|
622
686
|
encryptionAlgorithm: sourceEntitySetting.dataEncryptionAlgorithm,
|
|
623
687
|
keyEncryptionAlgorithm: sourceEntitySetting.keyEncryptionAlgorithm,
|
|
624
688
|
}, function (err, res) {
|
|
689
|
+
/* v8 ignore start */
|
|
625
690
|
if (err) {
|
|
626
691
|
console.error(err);
|
|
627
692
|
return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_ENCRYPTION'));
|
|
@@ -629,6 +694,7 @@ var libSaml = function () {
|
|
|
629
694
|
if (!res) {
|
|
630
695
|
return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
|
|
631
696
|
}
|
|
697
|
+
/* v8 ignore stop */
|
|
632
698
|
var encAssertionPrefix = sourceEntitySetting.tagPrefix.encryptedAssertion;
|
|
633
699
|
var encryptAssertionDoc = dom.parseFromString("<".concat(encAssertionPrefix, ":EncryptedAssertion xmlns:").concat(encAssertionPrefix, "=\"").concat(urn_1.namespace.names.assertion, "\">").concat(res, "</").concat(encAssertionPrefix, ":EncryptedAssertion>"));
|
|
634
700
|
doc.documentElement.replaceChild(encryptAssertionDoc.documentElement, rawAssertionNode);
|
|
@@ -636,25 +702,24 @@ var libSaml = function () {
|
|
|
636
702
|
});
|
|
637
703
|
}
|
|
638
704
|
else {
|
|
639
|
-
return resolve(utility_1.default.base64Encode(xml));
|
|
705
|
+
return resolve(utility_1.default.base64Encode(xml));
|
|
640
706
|
}
|
|
641
707
|
});
|
|
642
708
|
},
|
|
643
709
|
/**
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
710
|
+
* Decrypt the `<EncryptedAssertion>` inside a SAML response using the
|
|
711
|
+
* local entity's private key. Returns both the decrypted document XML
|
|
712
|
+
* and the raw assertion fragment for downstream extraction.
|
|
713
|
+
*
|
|
714
|
+
* @param here local entity performing decryption
|
|
715
|
+
* @param entireXML SAML response XML containing `<EncryptedAssertion>`
|
|
716
|
+
* @returns tuple `[decryptedDocumentXml, rawAssertionXml]`
|
|
717
|
+
*/
|
|
651
718
|
decryptAssertion: function (here, entireXML) {
|
|
652
719
|
return new Promise(function (resolve, reject) {
|
|
653
|
-
// Implement decryption first then check the signature
|
|
654
720
|
if (!entireXML) {
|
|
655
721
|
return reject(new Error('ERR_UNDEFINED_ASSERTION'));
|
|
656
722
|
}
|
|
657
|
-
// Perform encryption depends on the setting of where the message is sent, default is false
|
|
658
723
|
var hereSetting = here.entitySetting;
|
|
659
724
|
var dom = (0, api_1.getContext)().dom;
|
|
660
725
|
var doc = dom.parseFromString(entireXML);
|
|
@@ -669,6 +734,7 @@ var libSaml = function () {
|
|
|
669
734
|
return xmlenc.decrypt(encAssertionNode.toString(), {
|
|
670
735
|
key: utility_1.default.readPrivateKey(hereSetting.encPrivateKey, hereSetting.encPrivateKeyPass),
|
|
671
736
|
}, function (err, res) {
|
|
737
|
+
/* v8 ignore start */
|
|
672
738
|
if (err) {
|
|
673
739
|
console.error(err);
|
|
674
740
|
return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_DECRYPTION'));
|
|
@@ -676,6 +742,7 @@ var libSaml = function () {
|
|
|
676
742
|
if (!res) {
|
|
677
743
|
return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
|
|
678
744
|
}
|
|
745
|
+
/* v8 ignore stop */
|
|
679
746
|
var rawAssertionDoc = dom.parseFromString(res);
|
|
680
747
|
doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
|
|
681
748
|
return resolve([doc.toString(), res]);
|
|
@@ -683,34 +750,24 @@ var libSaml = function () {
|
|
|
683
750
|
});
|
|
684
751
|
},
|
|
685
752
|
/**
|
|
686
|
-
*
|
|
753
|
+
* Validate the SAML XML against the registered schema validator. Throws
|
|
754
|
+
* when no validator has been configured via {@link setSchemaValidator}
|
|
755
|
+
* so consumers can't silently ship without schema checks.
|
|
756
|
+
*
|
|
757
|
+
* @param input SAML XML string
|
|
687
758
|
*/
|
|
688
759
|
isValidXml: function (input) {
|
|
689
760
|
return __awaiter(this, void 0, void 0, function () {
|
|
690
|
-
var validate
|
|
761
|
+
var validate;
|
|
691
762
|
return __generator(this, function (_a) {
|
|
692
763
|
switch (_a.label) {
|
|
693
764
|
case 0:
|
|
694
765
|
validate = (0, api_1.getContext)().validate;
|
|
695
|
-
/**
|
|
696
|
-
* user can write a validate function that always returns
|
|
697
|
-
* a resolved promise and skip the validator even in
|
|
698
|
-
* production, user will take the responsibility if
|
|
699
|
-
* they intend to skip the validation
|
|
700
|
-
*/
|
|
701
766
|
if (!validate) {
|
|
702
|
-
|
|
703
|
-
return [2 /*return*/, Promise.reject('Your application is potentially vulnerable because no validation function found. Please read the documentation on how to setup the validator. (https://github.com/tngan/samlify#installation)')];
|
|
767
|
+
return [2 /*return*/, Promise.reject(new Error('Your application is potentially vulnerable because no validation function found. Please read the documentation on how to setup the validator. (https://github.com/tngan/samlify#installation)'))];
|
|
704
768
|
}
|
|
705
|
-
_a.label = 1;
|
|
706
|
-
case 1:
|
|
707
|
-
_a.trys.push([1, 3, , 4]);
|
|
708
769
|
return [4 /*yield*/, validate(input)];
|
|
709
|
-
case
|
|
710
|
-
case 3:
|
|
711
|
-
e_2 = _a.sent();
|
|
712
|
-
throw e_2;
|
|
713
|
-
case 4: return [2 /*return*/];
|
|
770
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
714
771
|
}
|
|
715
772
|
});
|
|
716
773
|
});
|