samlify 2.12.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 +41 -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 +118 -151
- 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 +315 -259
- 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 +140 -85
- 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 +16 -5
- 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 +139 -90
- 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/samlify-2.11.0.tgz +0 -0
- package/src/api.ts +0 -48
- 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 -779
- 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 -259
- 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,29 +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 utility_2 = require("./utility");
|
|
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
113
|
var signatureAlgorithms = urn_1.algorithms.signature;
|
|
109
114
|
var digestAlgorithms = urn_1.algorithms.digest;
|
|
110
115
|
var certUse = urn_1.wording.certUse;
|
|
111
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
|
+
}
|
|
112
126
|
var libSaml = function () {
|
|
113
127
|
/**
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
*/
|
|
117
134
|
function getQueryParamByType(type) {
|
|
118
135
|
if ([urlParams.logoutRequest, urlParams.samlRequest].indexOf(type) !== -1) {
|
|
119
136
|
return 'SAMLRequest';
|
|
@@ -124,114 +141,187 @@ var libSaml = function () {
|
|
|
124
141
|
throw new Error('ERR_UNDEFINED_QUERY_PARAMS');
|
|
125
142
|
}
|
|
126
143
|
/**
|
|
144
|
+
* Mapping from XML-DSig signature algorithm URIs to node-rsa schemes.
|
|
127
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`).
|
|
128
151
|
*/
|
|
129
152
|
var nrsaAliasMapping = {
|
|
130
153
|
'http://www.w3.org/2000/09/xmldsig#rsa-sha1': 'pkcs1-sha1',
|
|
131
154
|
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': 'pkcs1-sha256',
|
|
132
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,
|
|
133
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
|
+
}
|
|
134
209
|
/**
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
+
*/
|
|
138
219
|
var defaultLoginRequestTemplate = {
|
|
139
|
-
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>',
|
|
140
221
|
};
|
|
141
222
|
/**
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
*/
|
|
145
231
|
var defaultLogoutRequestTemplate = {
|
|
146
|
-
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>',
|
|
147
233
|
};
|
|
148
|
-
/**
|
|
149
|
-
* @desc Default AttributeStatement template
|
|
150
|
-
* @type {AttributeStatementTemplate}
|
|
151
|
-
*/
|
|
234
|
+
/** Default AttributeStatement XML fragment template. */
|
|
152
235
|
var defaultAttributeStatementTemplate = {
|
|
153
236
|
context: '<saml:AttributeStatement>{Attributes}</saml:AttributeStatement>',
|
|
154
237
|
};
|
|
155
|
-
/**
|
|
156
|
-
* @desc Default Attribute template
|
|
157
|
-
* @type {AttributeTemplate}
|
|
158
|
-
*/
|
|
238
|
+
/** Default Attribute XML fragment template. */
|
|
159
239
|
var defaultAttributeTemplate = {
|
|
160
240
|
context: '<saml:Attribute Name="{Name}" NameFormat="{NameFormat}"><saml:AttributeValue xmlns:xs="{ValueXmlnsXs}" xmlns:xsi="{ValueXmlnsXsi}" xsi:type="{ValueXsiType}">{Value}</saml:AttributeValue></saml:Attribute>',
|
|
161
241
|
};
|
|
162
|
-
/**
|
|
163
|
-
* @desc Default login response template
|
|
164
|
-
* @type {LoginResponseTemplate}
|
|
165
|
-
*/
|
|
242
|
+
/** Default LoginResponse XML template. */
|
|
166
243
|
var defaultLoginResponseTemplate = {
|
|
167
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>',
|
|
168
245
|
attributes: [],
|
|
169
246
|
additionalTemplates: {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
247
|
+
attributeStatementTemplate: defaultAttributeStatementTemplate,
|
|
248
|
+
attributeTemplate: defaultAttributeTemplate,
|
|
249
|
+
},
|
|
173
250
|
};
|
|
174
|
-
/**
|
|
175
|
-
* @desc Default logout response template
|
|
176
|
-
* @type {LogoutResponseTemplate}
|
|
177
|
-
*/
|
|
251
|
+
/** Default LogoutResponse XML template. */
|
|
178
252
|
var defaultLogoutResponseTemplate = {
|
|
179
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>',
|
|
180
254
|
};
|
|
181
255
|
/**
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
+
*/
|
|
187
271
|
function getSigningScheme(sigAlg) {
|
|
188
|
-
if (sigAlg) {
|
|
189
|
-
|
|
190
|
-
if (!(algAlias === undefined)) {
|
|
191
|
-
return algAlias;
|
|
192
|
-
}
|
|
272
|
+
if (sigAlg === undefined) {
|
|
273
|
+
return nrsaAliasMapping[signatureAlgorithms.RSA_SHA256];
|
|
193
274
|
}
|
|
194
|
-
|
|
275
|
+
var algAlias = nrsaAliasMapping[sigAlg];
|
|
276
|
+
if (algAlias === undefined) {
|
|
277
|
+
throw new Error('ERR_UNSUPPORTED_SIGNATURE_ALGORITHM');
|
|
278
|
+
}
|
|
279
|
+
return algAlias;
|
|
195
280
|
}
|
|
196
281
|
/**
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
+
*/
|
|
202
287
|
function getDigestMethod(sigAlg) {
|
|
203
288
|
return digestAlgorithms[sigAlg];
|
|
204
289
|
}
|
|
205
290
|
/**
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
+
*/
|
|
212
298
|
function createXPath(local, isExtractAll) {
|
|
213
299
|
if ((0, utility_1.isString)(local)) {
|
|
214
300
|
var escaped = (0, utility_1.escapeXPathValue)(local);
|
|
215
|
-
return isExtractAll === true
|
|
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,15 +446,20 @@ 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;
|
|
@@ -342,30 +468,28 @@ var libSaml = function () {
|
|
|
342
468
|
var doc = dom.parseFromString(xml);
|
|
343
469
|
var contextDom = (0, api_1.getContext)().dom;
|
|
344
470
|
var docParser = contextDom;
|
|
345
|
-
//
|
|
346
|
-
// message signature (logout response / saml response)
|
|
471
|
+
// Absolute XPaths defend against signature-wrapping attacks.
|
|
347
472
|
var messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']";
|
|
348
|
-
// assertion signature (logout response / saml response)
|
|
349
473
|
var assertionSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']/*[local-name(.)='Signature']";
|
|
350
|
-
// check if there is a potential malicious wrapping signature
|
|
351
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']";
|
|
352
|
-
// select the signature node
|
|
353
475
|
var selection = [];
|
|
354
476
|
var messageSignatureNode = toNodeArray((0, xpath_1.select)(messageSignatureXpath, doc));
|
|
355
477
|
var assertionSignatureNode = toNodeArray((0, xpath_1.select)(assertionSignatureXpath, doc));
|
|
356
478
|
var wrappingElementNode = toNodeArray((0, xpath_1.select)(wrappingElementsXPath, doc));
|
|
357
479
|
selection = selection.concat(messageSignatureNode);
|
|
358
480
|
selection = selection.concat(assertionSignatureNode);
|
|
359
|
-
// try to catch potential wrapping attack
|
|
360
481
|
if (wrappingElementNode.length !== 0) {
|
|
361
482
|
throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
|
|
362
483
|
}
|
|
363
|
-
// guarantee to have a signature in saml response
|
|
364
484
|
if (selection.length === 0) {
|
|
365
|
-
return [false, null];
|
|
485
|
+
return [false, null];
|
|
366
486
|
}
|
|
367
487
|
var _loop_1 = function (signatureNode) {
|
|
368
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);
|
|
369
493
|
var verified = false;
|
|
370
494
|
sig.signatureAlgorithm = opts.signatureAlgorithm;
|
|
371
495
|
if (!opts.keyFile && !opts.metadata) {
|
|
@@ -376,79 +500,59 @@ var libSaml = function () {
|
|
|
376
500
|
}
|
|
377
501
|
if (opts.metadata) {
|
|
378
502
|
var certificateNode = toNodeArray((0, xpath_1.select)(".//*[local-name(.)='X509Certificate']", signatureNode));
|
|
379
|
-
// certificate in metadata
|
|
380
503
|
var metadataCert = opts.metadata.getX509Certificate(certUse.signing);
|
|
381
|
-
// flattens the nested array of Certificates from each KeyDescriptor
|
|
382
504
|
if (Array.isArray(metadataCert)) {
|
|
383
505
|
metadataCert = (0, utility_1.flattenDeep)(metadataCert);
|
|
384
506
|
}
|
|
385
507
|
else if (typeof metadataCert === 'string') {
|
|
386
508
|
metadataCert = [metadataCert];
|
|
387
509
|
}
|
|
388
|
-
// normalise the certificate string
|
|
389
510
|
metadataCert = metadataCert.map(utility_1.default.normalizeCerString);
|
|
390
|
-
// no certificate in node response nor metadata
|
|
391
511
|
if (certificateNode.length === 0 && metadataCert.length === 0) {
|
|
392
512
|
throw new Error('NO_SELECTED_CERTIFICATE');
|
|
393
513
|
}
|
|
394
|
-
// certificate node in response
|
|
395
514
|
if (certificateNode.length !== 0) {
|
|
396
515
|
var certEl = certificateNode[0];
|
|
397
516
|
var x509CertificateData = (_b = certEl.textContent) !== null && _b !== void 0 ? _b : '';
|
|
398
517
|
var x509Certificate_1 = utility_1.default.normalizeCerString(x509CertificateData);
|
|
399
518
|
if (metadataCert.length >= 1 &&
|
|
400
519
|
!metadataCert.find(function (cert) { return cert.trim() === x509Certificate_1.trim(); })) {
|
|
401
|
-
// keep this restriction for rolling certificate usage
|
|
402
|
-
// to make sure the response certificate is one of those specified in metadata
|
|
403
520
|
throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
|
|
404
521
|
}
|
|
405
522
|
sig.publicCert = this_1.getKeyInfo(x509Certificate_1).getKey();
|
|
406
523
|
}
|
|
407
524
|
else {
|
|
408
|
-
// Select first one from metadata
|
|
409
525
|
sig.publicCert = this_1.getKeyInfo(metadataCert[0]).getKey();
|
|
410
526
|
}
|
|
411
527
|
}
|
|
412
528
|
sig.loadSignature(signatureNode);
|
|
413
529
|
verified = sig.checkSignature(doc.toString());
|
|
414
|
-
// immediately throw error when any one of the signature is failed to get verified
|
|
415
530
|
if (!verified) {
|
|
416
531
|
return "continue";
|
|
417
|
-
// throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
|
|
418
532
|
}
|
|
419
|
-
// Require there to be at least one reference that was signed
|
|
420
533
|
if (!(sig.getSignedReferences().length >= 1)) {
|
|
421
534
|
throw new Error('NO_SIGNATURE_REFERENCES');
|
|
422
535
|
}
|
|
423
536
|
var signedVerifiedXML = sig.getSignedReferences()[0];
|
|
424
537
|
var rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement;
|
|
425
|
-
// process the verified signature:
|
|
426
|
-
// case 1, rootSignedDoc is a response:
|
|
427
538
|
if (rootNode.localName === 'Response') {
|
|
428
|
-
// try getting the Xml from the first assertion
|
|
429
539
|
var assertions = toNodeArray((0, xpath_1.select)("./*[local-name()='Assertion']", rootNode));
|
|
430
540
|
var encryptedAssertions = toNodeArray((0, xpath_1.select)("./*[local-name()='EncryptedAssertion']", rootNode));
|
|
431
|
-
// now we can process the assertion as an assertion
|
|
432
541
|
if (assertions.length === 1) {
|
|
433
542
|
return { value: [true, assertions[0].toString()] };
|
|
434
543
|
}
|
|
435
544
|
else if (encryptedAssertions.length >= 1) {
|
|
436
545
|
return { value: [true, rootNode.toString()] };
|
|
437
546
|
}
|
|
438
|
-
|
|
439
|
-
return { value: [true, null] };
|
|
440
|
-
}
|
|
547
|
+
return { value: [true, null] };
|
|
441
548
|
}
|
|
442
549
|
else if (rootNode.localName === 'Assertion') {
|
|
443
550
|
return { value: [true, rootNode.toString()] };
|
|
444
551
|
}
|
|
445
|
-
|
|
446
|
-
return { value: [true, null] };
|
|
447
|
-
}
|
|
552
|
+
return { value: [true, null] };
|
|
448
553
|
};
|
|
449
554
|
var this_1 = this;
|
|
450
555
|
try {
|
|
451
|
-
// need to refactor later on
|
|
452
556
|
for (var selection_1 = __values(selection), selection_1_1 = selection_1.next(); !selection_1_1.done; selection_1_1 = selection_1.next()) {
|
|
453
557
|
var signatureNode = selection_1_1.value;
|
|
454
558
|
var state_1 = _loop_1(signatureNode);
|
|
@@ -463,108 +567,65 @@ var libSaml = function () {
|
|
|
463
567
|
}
|
|
464
568
|
finally { if (e_1) throw e_1.error; }
|
|
465
569
|
}
|
|
466
|
-
;
|
|
467
|
-
return [false, null]; // we didn't verify anything, none of the signatures are valid
|
|
468
|
-
/*
|
|
469
|
-
// response must be signed, either entire document or assertion
|
|
470
|
-
// default we will take the assertion section under root
|
|
471
|
-
if (messageSignatureNode.length === 1) {
|
|
472
|
-
const node = select("/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']", doc);
|
|
473
|
-
if (node.length === 1) {
|
|
474
|
-
assertionNode = node[0].toString();
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
if (assertionSignatureNode.length === 1) {
|
|
479
|
-
const verifiedAssertionInfo = extract(assertionSignatureNode[0].toString(), [{
|
|
480
|
-
key: 'refURI',
|
|
481
|
-
localPath: ['Signature', 'SignedInfo', 'Reference'],
|
|
482
|
-
attributes: ['URI']
|
|
483
|
-
}]);
|
|
484
|
-
// get the assertion supposed to be the one should be verified
|
|
485
|
-
const desiredAssertionInfo = extract(doc.toString(), [{
|
|
486
|
-
key: 'id',
|
|
487
|
-
localPath: ['~Response', 'Assertion'],
|
|
488
|
-
attributes: ['ID']
|
|
489
|
-
}]);
|
|
490
|
-
// 5.4.2 References
|
|
491
|
-
// SAML assertions and protocol messages MUST supply a value for the ID attribute on the root element of
|
|
492
|
-
// the assertion or protocol message being signed. The assertion’s or protocol message's root element may
|
|
493
|
-
// or may not be the root element of the actual XML document containing the signed assertion or protocol
|
|
494
|
-
// message (e.g., it might be contained within a SOAP envelope).
|
|
495
|
-
// Signatures MUST contain a single <ds:Reference> containing a same-document reference to the ID
|
|
496
|
-
// attribute value of the root element of the assertion or protocol message being signed. For example, if the
|
|
497
|
-
// ID attribute value is "foo", then the URI attribute in the <ds:Reference> element MUST be "#foo".
|
|
498
|
-
if (verifiedAssertionInfo.refURI !== `#${desiredAssertionInfo.id}`) {
|
|
499
|
-
throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
|
|
500
|
-
}
|
|
501
|
-
const verifiedDoc = extract(doc.toString(), [{
|
|
502
|
-
key: 'assertion',
|
|
503
|
-
localPath: ['~Response', 'Assertion'],
|
|
504
|
-
attributes: [],
|
|
505
|
-
context: true
|
|
506
|
-
}]);
|
|
507
|
-
assertionNode = verifiedDoc.assertion.toString();
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
return [verified, assertionNode];*/
|
|
570
|
+
return [false, null];
|
|
511
571
|
},
|
|
512
572
|
/**
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
+
*/
|
|
518
579
|
createKeySection: function (use, certString) {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
_a['KeyDescriptor'] = [
|
|
580
|
+
return {
|
|
581
|
+
KeyDescriptor: [
|
|
522
582
|
{
|
|
523
583
|
_attr: { use: use },
|
|
524
584
|
},
|
|
525
|
-
|
|
526
|
-
|
|
585
|
+
{
|
|
586
|
+
'ds:KeyInfo': [
|
|
527
587
|
{
|
|
528
588
|
_attr: {
|
|
529
589
|
'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
|
|
530
590
|
},
|
|
531
591
|
},
|
|
532
|
-
|
|
533
|
-
|
|
592
|
+
{
|
|
593
|
+
'ds:X509Data': [{
|
|
534
594
|
'ds:X509Certificate': utility_1.default.normalizeCerString(certString),
|
|
535
595
|
}],
|
|
536
|
-
|
|
596
|
+
},
|
|
537
597
|
],
|
|
538
|
-
|
|
598
|
+
},
|
|
539
599
|
],
|
|
540
|
-
|
|
600
|
+
};
|
|
541
601
|
},
|
|
542
602
|
/**
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
+
*/
|
|
550
613
|
constructMessageSignature: function (octetString, key, passphrase, isBase64, signingAlgorithm) {
|
|
551
|
-
// Default returning base64 encoded signature
|
|
552
|
-
// Embed with node-rsa module
|
|
553
614
|
var decryptedKey = new node_rsa_1.default(utility_1.default.readPrivateKey(key, passphrase), undefined, {
|
|
554
615
|
signingScheme: getSigningScheme(signingAlgorithm),
|
|
555
616
|
});
|
|
556
617
|
var signature = decryptedKey.sign(octetString);
|
|
557
|
-
// Use private key to sign data
|
|
558
618
|
return isBase64 !== false ? signature.toString('base64') : signature;
|
|
559
619
|
},
|
|
560
620
|
/**
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
+
*/
|
|
568
629
|
verifyMessageSignature: function (metadata, octetString, signature, verifyAlgorithm) {
|
|
569
630
|
var signCert = metadata.getX509Certificate(certUse.signing);
|
|
570
631
|
var signingScheme = getSigningScheme(verifyAlgorithm);
|
|
@@ -572,10 +633,11 @@ var libSaml = function () {
|
|
|
572
633
|
return key.verify(Buffer.from(octetString), Buffer.from(signature));
|
|
573
634
|
},
|
|
574
635
|
/**
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
+
*/
|
|
579
641
|
getKeyInfo: function (x509Certificate, signatureConfig) {
|
|
580
642
|
if (signatureConfig === void 0) { signatureConfig = {}; }
|
|
581
643
|
var prefix = signatureConfig.prefix ? "".concat(signatureConfig.prefix, ":") : '';
|
|
@@ -589,14 +651,16 @@ var libSaml = function () {
|
|
|
589
651
|
};
|
|
590
652
|
},
|
|
591
653
|
/**
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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
|
+
*/
|
|
598
663
|
encryptAssertion: function (sourceEntity, targetEntity, xml) {
|
|
599
|
-
// Implement encryption after signature if it has
|
|
600
664
|
return new Promise(function (resolve, reject) {
|
|
601
665
|
if (!xml) {
|
|
602
666
|
return reject(new Error('ERR_UNDEFINED_ASSERTION'));
|
|
@@ -613,16 +677,16 @@ var libSaml = function () {
|
|
|
613
677
|
throw new Error('ERR_MULTIPLE_ASSERTION');
|
|
614
678
|
}
|
|
615
679
|
var rawAssertionNode = assertions[0];
|
|
616
|
-
// Perform encryption depends on the setting, default is false
|
|
617
680
|
if (sourceEntitySetting.isAssertionEncrypted) {
|
|
618
|
-
var
|
|
681
|
+
var encryptCert = targetEntityMetadata.getX509Certificate(certUse.encrypt);
|
|
682
|
+
var publicKeyPem = utility_1.default.getPublicKeyPemFromCertificate(encryptCert);
|
|
619
683
|
xmlenc.encrypt(rawAssertionNode.toString(), {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
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-----")),
|
|
623
686
|
encryptionAlgorithm: sourceEntitySetting.dataEncryptionAlgorithm,
|
|
624
687
|
keyEncryptionAlgorithm: sourceEntitySetting.keyEncryptionAlgorithm,
|
|
625
688
|
}, function (err, res) {
|
|
689
|
+
/* v8 ignore start */
|
|
626
690
|
if (err) {
|
|
627
691
|
console.error(err);
|
|
628
692
|
return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_ENCRYPTION'));
|
|
@@ -630,6 +694,7 @@ var libSaml = function () {
|
|
|
630
694
|
if (!res) {
|
|
631
695
|
return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
|
|
632
696
|
}
|
|
697
|
+
/* v8 ignore stop */
|
|
633
698
|
var encAssertionPrefix = sourceEntitySetting.tagPrefix.encryptedAssertion;
|
|
634
699
|
var encryptAssertionDoc = dom.parseFromString("<".concat(encAssertionPrefix, ":EncryptedAssertion xmlns:").concat(encAssertionPrefix, "=\"").concat(urn_1.namespace.names.assertion, "\">").concat(res, "</").concat(encAssertionPrefix, ":EncryptedAssertion>"));
|
|
635
700
|
doc.documentElement.replaceChild(encryptAssertionDoc.documentElement, rawAssertionNode);
|
|
@@ -637,25 +702,24 @@ var libSaml = function () {
|
|
|
637
702
|
});
|
|
638
703
|
}
|
|
639
704
|
else {
|
|
640
|
-
return resolve(utility_1.default.base64Encode(xml));
|
|
705
|
+
return resolve(utility_1.default.base64Encode(xml));
|
|
641
706
|
}
|
|
642
707
|
});
|
|
643
708
|
},
|
|
644
709
|
/**
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
+
*/
|
|
652
718
|
decryptAssertion: function (here, entireXML) {
|
|
653
719
|
return new Promise(function (resolve, reject) {
|
|
654
|
-
// Implement decryption first then check the signature
|
|
655
720
|
if (!entireXML) {
|
|
656
721
|
return reject(new Error('ERR_UNDEFINED_ASSERTION'));
|
|
657
722
|
}
|
|
658
|
-
// Perform encryption depends on the setting of where the message is sent, default is false
|
|
659
723
|
var hereSetting = here.entitySetting;
|
|
660
724
|
var dom = (0, api_1.getContext)().dom;
|
|
661
725
|
var doc = dom.parseFromString(entireXML);
|
|
@@ -670,6 +734,7 @@ var libSaml = function () {
|
|
|
670
734
|
return xmlenc.decrypt(encAssertionNode.toString(), {
|
|
671
735
|
key: utility_1.default.readPrivateKey(hereSetting.encPrivateKey, hereSetting.encPrivateKeyPass),
|
|
672
736
|
}, function (err, res) {
|
|
737
|
+
/* v8 ignore start */
|
|
673
738
|
if (err) {
|
|
674
739
|
console.error(err);
|
|
675
740
|
return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_DECRYPTION'));
|
|
@@ -677,6 +742,7 @@ var libSaml = function () {
|
|
|
677
742
|
if (!res) {
|
|
678
743
|
return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
|
|
679
744
|
}
|
|
745
|
+
/* v8 ignore stop */
|
|
680
746
|
var rawAssertionDoc = dom.parseFromString(res);
|
|
681
747
|
doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
|
|
682
748
|
return resolve([doc.toString(), res]);
|
|
@@ -684,34 +750,24 @@ var libSaml = function () {
|
|
|
684
750
|
});
|
|
685
751
|
},
|
|
686
752
|
/**
|
|
687
|
-
*
|
|
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
|
|
688
758
|
*/
|
|
689
759
|
isValidXml: function (input) {
|
|
690
760
|
return __awaiter(this, void 0, void 0, function () {
|
|
691
|
-
var validate
|
|
761
|
+
var validate;
|
|
692
762
|
return __generator(this, function (_a) {
|
|
693
763
|
switch (_a.label) {
|
|
694
764
|
case 0:
|
|
695
765
|
validate = (0, api_1.getContext)().validate;
|
|
696
|
-
/**
|
|
697
|
-
* user can write a validate function that always returns
|
|
698
|
-
* a resolved promise and skip the validator even in
|
|
699
|
-
* production, user will take the responsibility if
|
|
700
|
-
* they intend to skip the validation
|
|
701
|
-
*/
|
|
702
766
|
if (!validate) {
|
|
703
|
-
|
|
704
|
-
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)'))];
|
|
705
768
|
}
|
|
706
|
-
_a.label = 1;
|
|
707
|
-
case 1:
|
|
708
|
-
_a.trys.push([1, 3, , 4]);
|
|
709
769
|
return [4 /*yield*/, validate(input)];
|
|
710
|
-
case
|
|
711
|
-
case 3:
|
|
712
|
-
e_2 = _a.sent();
|
|
713
|
-
throw e_2;
|
|
714
|
-
case 4: return [2 /*return*/];
|
|
770
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
715
771
|
}
|
|
716
772
|
});
|
|
717
773
|
});
|