scimgateway 6.2.5 → 6.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,197 @@
1
+ var utils = require('./utils');
2
+ var Parser = require('@xmldom/xmldom').DOMParser;
3
+ var xmlenc = require('xml-encryption');
4
+ var moment = require('moment');
5
+ var async = require('async');
6
+ var crypto = require('crypto');
7
+
8
+ var EncryptXml = require('./xml/encrypt');
9
+ var SignXml = require('./xml/sign');
10
+
11
+ var saml11Template = '<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" MajorVersion="1" MinorVersion="1" AssertionID="" IssueInstant="">' +
12
+ '<saml:Conditions>' +
13
+ '<saml:AudienceRestrictionCondition />' +
14
+ '</saml:Conditions>' +
15
+ '<saml:AttributeStatement>' +
16
+ '<saml:Subject>' +
17
+ '<saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" />' +
18
+ '<saml:SubjectConfirmation>' +
19
+ '<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>' +
20
+ '</saml:SubjectConfirmation>' +
21
+ '</saml:Subject>' +
22
+ '</saml:AttributeStatement>' +
23
+ '<saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:password">' +
24
+ '<saml:Subject>' +
25
+ '<saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">' +
26
+ '</saml:NameIdentifier>' +
27
+ '<saml:SubjectConfirmation>' +
28
+ '<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>' +
29
+ '</saml:SubjectConfirmation>' +
30
+ '</saml:Subject>' +
31
+ '</saml:AuthenticationStatement>' +
32
+ '</saml:Assertion>';
33
+
34
+ var newSaml11Document = utils.factoryForNode(saml11Template);
35
+
36
+ var NAMESPACE = 'urn:oasis:names:tc:SAML:1.0:assertion';
37
+
38
+ function extractSaml11Options(opts) {
39
+ return {
40
+ uid: opts.uid,
41
+ issuer: opts.issuer,
42
+ lifetimeInSeconds: opts.lifetimeInSeconds,
43
+ audiences: opts.audiences,
44
+ attributes: opts.attributes,
45
+ nameIdentifier: opts.nameIdentifier,
46
+ nameIdentifierFormat: opts.nameIdentifierFormat,
47
+ subjectConfirmationMethod: opts.subjectConfirmationMethod,
48
+ holderOfKeyProofSecret: opts.holderOfKeyProofSecret,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Creates a signed SAML 1.1 assertion from the given options.
54
+ */
55
+ exports.create = function(options, callback) {
56
+ return createAssertion(extractSaml11Options(options), {
57
+ signXml: SignXml.fromSignXmlOptions(Object.assign({
58
+ xpathToNodeBeforeSignature: "//*[local-name(.)='AuthenticationStatement']",
59
+ signatureIdAttribute: 'AssertionID'
60
+ }, options)),
61
+ encryptXml: EncryptXml.fromEncryptXmlOptions(options)
62
+ }, callback);
63
+ }
64
+
65
+ /**
66
+ * Creates an unsigned SAML 1.1 assertion from the given options.
67
+ */
68
+ exports.createUnsignedAssertion = function(options, callback) {
69
+ return createAssertion(extractSaml11Options(options), {
70
+ signXml: SignXml.unsigned,
71
+ encryptXml: EncryptXml.fromEncryptXmlOptions(options)
72
+ }, callback);
73
+ }
74
+
75
+ function createAssertion(options, strategies, callback) {
76
+ var doc;
77
+ try {
78
+ doc = newSaml11Document();
79
+ } catch(err){
80
+ return utils.reportError(err, callback);
81
+ }
82
+
83
+ doc.documentElement.setAttribute('AssertionID', '_' + (options.uid || utils.uid(32)));
84
+ if (options.issuer)
85
+ doc.documentElement.setAttribute('Issuer', options.issuer);
86
+
87
+ var now = moment.utc();
88
+ doc.documentElement.setAttribute('IssueInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
89
+ var conditions = doc.documentElement.getElementsByTagName('saml:Conditions');
90
+
91
+ if (options.lifetimeInSeconds) {
92
+ conditions[0].setAttribute('NotBefore', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
93
+ conditions[0].setAttribute('NotOnOrAfter', moment(now).add(options.lifetimeInSeconds, 'seconds').format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
94
+ }
95
+
96
+ if (options.audiences) {
97
+ var audiences = options.audiences instanceof Array ? options.audiences : [options.audiences];
98
+ audiences.forEach(function (audience) {
99
+ var element = doc.createElementNS(NAMESPACE, 'saml:Audience');
100
+ element.textContent = audience;
101
+ var audienceCondition = conditions[0].getElementsByTagNameNS(NAMESPACE, 'AudienceRestrictionCondition')[0];
102
+ audienceCondition.appendChild(element);
103
+ });
104
+ }
105
+
106
+ if (options.attributes) {
107
+ var statement = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'AttributeStatement')[0];
108
+ Object.keys(options.attributes).forEach(function(prop) {
109
+ if(typeof options.attributes[prop] === 'undefined') return;
110
+
111
+ var name = prop.indexOf('/') > -1 ? prop.substring(prop.lastIndexOf('/') + 1) : prop;
112
+ var namespace = prop.indexOf('/') > -1 ? prop.substring(0, prop.lastIndexOf('/')) : '';
113
+ var attributeElement = doc.createElementNS(NAMESPACE, 'saml:Attribute');
114
+ attributeElement.setAttribute('AttributeNamespace', namespace);
115
+ attributeElement.setAttribute('AttributeName', name);
116
+ var values = options.attributes[prop] instanceof Array ? options.attributes[prop] : [options.attributes[prop]];
117
+ values.forEach(function (value) {
118
+ var valueElement = doc.createElementNS(NAMESPACE, 'saml:AttributeValue');
119
+ valueElement.textContent = value;
120
+ attributeElement.appendChild(valueElement);
121
+ });
122
+
123
+ if (values && values.length > 0) {
124
+ statement.appendChild(attributeElement);
125
+ }
126
+ });
127
+ }
128
+
129
+ doc.getElementsByTagName('saml:AuthenticationStatement')[0]
130
+ .setAttribute('AuthenticationInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
131
+
132
+ var nameID = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'NameIdentifier')[0];
133
+
134
+ if (options.nameIdentifier) {
135
+ nameID.textContent = options.nameIdentifier;
136
+
137
+ doc.getElementsByTagName('saml:AuthenticationStatement')[0]
138
+ .getElementsByTagName('saml:NameIdentifier')[0]
139
+ .textContent = options.nameIdentifier;
140
+ }
141
+
142
+ if (options.nameIdentifierFormat) {
143
+ var nameIDs = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'NameIdentifier');
144
+ nameIDs[0].setAttribute('Format', options.nameIdentifierFormat);
145
+ nameIDs[1].setAttribute('Format', options.nameIdentifierFormat);
146
+ }
147
+
148
+ if (strategies.encryptXml === EncryptXml.unencrypted) {
149
+ var signed = strategies.signXml(doc);
150
+ return strategies.encryptXml(signed, callback);
151
+ }
152
+
153
+ // encryption is turned on,
154
+ var proofSecret;
155
+ async.waterfall([
156
+ function (cb) {
157
+ if (!options.subjectConfirmationMethod && options.subjectConfirmationMethod !== 'holder-of-key')
158
+ return cb();
159
+
160
+ crypto.randomBytes(32, function(err, randomBytes) {
161
+ proofSecret = randomBytes;
162
+ addSubjectConfirmation(strategies.encryptXml.encryptOptions, doc, options.holderOfKeyProofSecret || randomBytes, cb);
163
+ });
164
+ },
165
+ function(cb) {
166
+ strategies.signXml(doc, cb);
167
+ },
168
+ function(signed, cb) {
169
+ strategies.encryptXml(signed, cb);
170
+ }
171
+ ], function (err, result) {
172
+ if (err) return callback(err);
173
+ callback(null, result, proofSecret);
174
+ });
175
+ }
176
+
177
+ function addSubjectConfirmation(encryptOptions, doc, randomBytes, callback) {
178
+ xmlenc.encryptKeyInfo(randomBytes, encryptOptions, function(err, keyinfo) {
179
+ if (err) return callback(err);
180
+ var subjectConfirmationNodes = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'SubjectConfirmation');
181
+
182
+ for (var i=0; i<subjectConfirmationNodes.length; i++) {
183
+ var keyinfoDom;
184
+ try {
185
+ keyinfoDom = new Parser().parseFromString(keyinfo);
186
+ } catch(error){
187
+ return utils.reportError(error, callback);
188
+ }
189
+
190
+ var method = subjectConfirmationNodes[i].getElementsByTagNameNS(NAMESPACE, 'ConfirmationMethod')[0];
191
+ method.textContent = 'urn:oasis:names:tc:SAML:1.0:cm:holder-of-key';
192
+ subjectConfirmationNodes[i].appendChild(keyinfoDom.documentElement);
193
+ }
194
+
195
+ callback();
196
+ });
197
+ }
@@ -0,0 +1,205 @@
1
+ var async = require('async');
2
+ var moment = require('moment');
3
+ var xmlNameValidator = require('xml-name-validator');
4
+ var is_uri = require('valid-url').is_uri;
5
+
6
+ var EncryptXml = require('./xml/encrypt');
7
+ var SignXml = require('./xml/sign');
8
+ var utils = require('./utils');
9
+
10
+ var saml20Template = '<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="" IssueInstant="">' +
11
+ '<saml:Issuer></saml:Issuer>' +
12
+ '<saml:Subject>' +
13
+ '<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" />' +
14
+ '<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">' +
15
+ '<saml:SubjectConfirmationData />' +
16
+ '</saml:SubjectConfirmation>' +
17
+ '</saml:Subject>' +
18
+ '<saml:Conditions />' +
19
+ '<saml:AuthnStatement AuthnInstant="">' +
20
+ '<saml:AuthnContext>' +
21
+ '<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>' +
22
+ '</saml:AuthnContext>' +
23
+ '</saml:AuthnStatement>' +
24
+ '</saml:Assertion>';
25
+
26
+ var newSaml20Document = utils.factoryForNode(saml20Template);
27
+
28
+ var NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:assertion';
29
+
30
+ function getAttributeType(value){
31
+ switch(typeof value) {
32
+ case "string":
33
+ return 'xs:string';
34
+ case "boolean":
35
+ return 'xs:boolean';
36
+ case "number":
37
+ return 'xs:double';
38
+ default:
39
+ return 'xs:anyType';
40
+ }
41
+ }
42
+
43
+ function getNameFormat(name){
44
+ if (is_uri(name)){
45
+ return 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri';
46
+ }
47
+
48
+ if (xmlNameValidator.name(name).success){
49
+ return 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic';
50
+ }
51
+
52
+ return 'urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified';
53
+ }
54
+
55
+ function extractSaml20Options(opts) {
56
+ return {
57
+ uid: opts.uid,
58
+ issuer: opts.issuer,
59
+ lifetimeInSeconds: opts.lifetimeInSeconds,
60
+ audiences: opts.audiences,
61
+ recipient: opts.recipient,
62
+ inResponseTo: opts.inResponseTo,
63
+ attributes: opts.attributes,
64
+ includeAttributeNameFormat: (typeof opts.includeAttributeNameFormat !== 'undefined') ? opts.includeAttributeNameFormat : true,
65
+ typedAttributes: (typeof opts.typedAttributes !== 'undefined') ? opts.typedAttributes : true,
66
+ sessionIndex: opts.sessionIndex,
67
+ nameIdentifier: opts.nameIdentifier,
68
+ nameIdentifierFormat: opts.nameIdentifierFormat,
69
+ authnContextClassRef: opts.authnContextClassRef
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Creates a signed SAML 2.0 assertion from the given options.
75
+ */
76
+ exports.create = function createSignedAssertion(options, callback) {
77
+ return createAssertion(extractSaml20Options(options), {
78
+ signXml: SignXml.fromSignXmlOptions(Object.assign({
79
+ xpathToNodeBeforeSignature: "//*[local-name(.)='Issuer']",
80
+ signatureIdAttribute: 'ID'
81
+ }, options)),
82
+ encryptXml: EncryptXml.fromEncryptXmlOptions(options)
83
+ }, callback);
84
+ };
85
+
86
+ /**
87
+ * Creates an unsigned SAML 2.0 assertion from the given options.
88
+ */
89
+ exports.createUnsignedAssertion = function createUnsignedAssertion(options, callback) {
90
+ return createAssertion(extractSaml20Options(options), {
91
+ signXml: SignXml.unsigned,
92
+ encryptXml: EncryptXml.fromEncryptXmlOptions(options)
93
+ }, callback);
94
+ };
95
+
96
+ function createAssertion(options, strategies, callback) {
97
+ var doc = newSaml20Document();
98
+
99
+ doc.documentElement.setAttribute('ID', '_' + (options.uid || utils.uid(32)));
100
+ if (options.issuer) {
101
+ var issuer = doc.documentElement.getElementsByTagName('saml:Issuer');
102
+ issuer[0].textContent = options.issuer;
103
+ }
104
+
105
+ var now = moment.utc();
106
+ doc.documentElement.setAttribute('IssueInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
107
+ var conditions = doc.documentElement.getElementsByTagName('saml:Conditions');
108
+ var confirmationData = doc.documentElement.getElementsByTagName('saml:SubjectConfirmationData');
109
+
110
+ if (options.lifetimeInSeconds) {
111
+ conditions[0].setAttribute('NotBefore', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
112
+ conditions[0].setAttribute('NotOnOrAfter', now.clone().add(options.lifetimeInSeconds, 'seconds').format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
113
+
114
+ confirmationData[0].setAttribute('NotOnOrAfter', now.clone().add(options.lifetimeInSeconds, 'seconds').format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
115
+ }
116
+
117
+ if (options.audiences) {
118
+ var audienceRestriction = doc.createElementNS(NAMESPACE, 'saml:AudienceRestriction');
119
+ var audiences = options.audiences instanceof Array ? options.audiences : [options.audiences];
120
+ audiences.forEach(function (audience) {
121
+ var element = doc.createElementNS(NAMESPACE, 'saml:Audience');
122
+ element.textContent = audience;
123
+ audienceRestriction.appendChild(element);
124
+ });
125
+
126
+ conditions[0].appendChild(audienceRestriction);
127
+ }
128
+
129
+ if (options.recipient)
130
+ confirmationData[0].setAttribute('Recipient', options.recipient);
131
+
132
+ if (options.inResponseTo)
133
+ confirmationData[0].setAttribute('InResponseTo', options.inResponseTo);
134
+
135
+ if (options.attributes) {
136
+ var statement = doc.createElementNS(NAMESPACE, 'saml:AttributeStatement');
137
+ statement.setAttribute('xmlns:xs', 'http://www.w3.org/2001/XMLSchema');
138
+ statement.setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
139
+ doc.documentElement.appendChild(statement);
140
+ Object.keys(options.attributes).forEach(function(prop) {
141
+ if(typeof options.attributes[prop] === 'undefined') return;
142
+
143
+ var attributeElement = doc.createElementNS(NAMESPACE, 'saml:Attribute');
144
+ attributeElement.setAttribute('Name', prop);
145
+
146
+ if (options.includeAttributeNameFormat){
147
+ attributeElement.setAttribute('NameFormat', getNameFormat(prop));
148
+ }
149
+
150
+ var values = options.attributes[prop] instanceof Array ? options.attributes[prop] : [options.attributes[prop]];
151
+ values.forEach(function (value) {
152
+ if (typeof value !== 'undefined') {
153
+ var valueElement = doc.createElementNS(NAMESPACE, 'saml:AttributeValue');
154
+ valueElement.setAttribute('xsi:type', options.typedAttributes ? getAttributeType(value) : 'xs:anyType');
155
+ valueElement.textContent = value;
156
+ attributeElement.appendChild(valueElement);
157
+ }
158
+ });
159
+
160
+ if (values && values.filter(function(i){ return typeof i !== 'undefined'; }).length > 0) {
161
+ statement.appendChild(attributeElement);
162
+ }
163
+ });
164
+ }
165
+
166
+ doc.getElementsByTagName('saml:AuthnStatement')[0]
167
+ .setAttribute('AuthnInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
168
+
169
+ if (options.sessionIndex) {
170
+ doc.getElementsByTagName('saml:AuthnStatement')[0]
171
+ .setAttribute('SessionIndex', options.sessionIndex);
172
+ }
173
+
174
+ var nameID = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'NameID')[0];
175
+
176
+ if (options.nameIdentifier) {
177
+ nameID.textContent = options.nameIdentifier;
178
+ }
179
+
180
+ if (options.nameIdentifierFormat) {
181
+ nameID.setAttribute('Format', options.nameIdentifierFormat);
182
+ }
183
+
184
+ if( options.authnContextClassRef ) {
185
+ doc.getElementsByTagName('saml:AuthnContextClassRef')[0]
186
+ .textContent = options.authnContextClassRef;
187
+ }
188
+
189
+ if (strategies.encryptXml === EncryptXml.unencrypted) {
190
+ var signed = strategies.signXml(doc);
191
+ return strategies.encryptXml(signed, callback);
192
+ }
193
+
194
+ async.waterfall([
195
+ function(cb) {
196
+ strategies.signXml(doc, cb);
197
+ },
198
+ function(signed, cb) {
199
+ strategies.encryptXml(signed, cb);
200
+ }
201
+ ], function (err, result) {
202
+ if (err) return callback(err);
203
+ callback(null, result);
204
+ });
205
+ }
@@ -0,0 +1,94 @@
1
+ const Parser = require('@xmldom/xmldom').DOMParser;
2
+
3
+ exports.pemToCert = function(pem) {
4
+ const cert = /-----BEGIN CERTIFICATE-----([^-]*)-----END CERTIFICATE-----/g.exec(pem.toString());
5
+ if (cert && cert.length > 0) {
6
+ return cert[1].replace(/[\n|\r\n]/g, '');
7
+ }
8
+
9
+ return null;
10
+ };
11
+
12
+ exports.reportError = function(err, callback){
13
+ if (callback){
14
+ setImmediate(function(){
15
+ callback(err);
16
+ });
17
+ }
18
+ };
19
+
20
+ /**
21
+ * Return a unique identifier with the given `len`.
22
+ *
23
+ * utils.uid(10);
24
+ * // => "FDaS435D2z"
25
+ *
26
+ * @param {Number} len
27
+ * @return {String}
28
+ * @api private
29
+ */
30
+ exports.uid = function(len) {
31
+ const buf = []
32
+ , chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
33
+ , charlen = chars.length;
34
+
35
+ for (let i = 0; i < len; ++i) {
36
+ buf.push(chars[getRandomInt(0, charlen - 1)]);
37
+ }
38
+
39
+ return buf.join('');
40
+ };
41
+
42
+ exports.removeWhitespace = function(xml) {
43
+ return xml
44
+ .replace(/\r\n/g, '')
45
+ .replace(/\n/g, '')
46
+ .replace(/>(\s*)</g, '><') //unindent
47
+ .trim();
48
+ };
49
+
50
+ /**
51
+ * Return a random int, used by `utils.uid()`
52
+ *
53
+ * @param {Number} min
54
+ * @param {Number} max
55
+ * @return {Number}
56
+ * @api private
57
+ */
58
+
59
+ function getRandomInt(min, max) {
60
+ return Math.floor(Math.random() * (max - min + 1)) + min;
61
+ }
62
+
63
+ /**
64
+ * Returns a function that can be called to create a new Node from an inline template string.
65
+ *
66
+ * @param {string} templateString the XML template content
67
+ * @return {function(): Node}
68
+ */
69
+ exports.factoryForNode = function factoryForNode(templateString) {
70
+ const prototypeDoc = new Parser().parseFromString(templateString);
71
+
72
+ return function () {
73
+ return prototypeDoc.cloneNode(true);
74
+ };
75
+ };
76
+
77
+ /**
78
+ * Standardizes PEM content to match the spec (best effort)
79
+ *
80
+ * @param pem {Buffer} The PEM content to standardize
81
+ * @returns {Buffer} The standardized PEM. Original will be returned unmodified if the content is not PEM.
82
+ */
83
+ exports.fixPemFormatting = function (pem) {
84
+ let pemEntries = pem.toString().matchAll(/([-]{5}[^-\r\n]+[-]{5})([^-]*)([-]{5}[^-\r\n]+[-]{5})/g);
85
+ let fixedPem = ''
86
+ for (const pemParts of pemEntries) {
87
+ fixedPem = fixedPem.concat(`${pemParts[1]}\n${pemParts[2].replaceAll(/[\r\n]/g, '')}\n${pemParts[3]}\n`)
88
+ }
89
+ if (fixedPem.length === 0) {
90
+ return pem;
91
+ }
92
+
93
+ return Buffer.from(fixedPem)
94
+ }
@@ -0,0 +1,56 @@
1
+ const xmlenc = require('xml-encryption');
2
+
3
+ const utils = require('../utils');
4
+
5
+ exports.fromEncryptXmlOptions = function (options) {
6
+ if (!options.encryptionCert) {
7
+ return this.unencrypted;
8
+ } else {
9
+ const encryptOptions = {
10
+ rsa_pub: options.encryptionPublicKey,
11
+ pem: options.encryptionCert,
12
+ encryptionAlgorithm: options.encryptionAlgorithm || 'http://www.w3.org/2009/xmlenc11#aes256-gcm',
13
+ keyEncryptionAlgorithm: options.keyEncryptionAlgorithm || 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p',
14
+ disallowEncryptionWithInsecureAlgorithm: options?.disallowEncryptionWithInsecureAlgorithm !== false,
15
+ warnInsecureAlgorithm: options?.warnOnInsecureEncryptionAlgorithm !== false,
16
+ };
17
+
18
+ // expose the encryptOptions as these are needed when adding the SubjectConfirmation
19
+ return Object.assign(this.encrypted(encryptOptions), { encryptOptions: encryptOptions });
20
+ }
21
+ };
22
+
23
+ exports.unencrypted = function (xml, callback) {
24
+ if (callback) {
25
+ return setImmediate(callback, null, xml);
26
+ } else {
27
+ return xml;
28
+ }
29
+ };
30
+
31
+ exports.encrypted = function (encryptOptions) {
32
+ return function encrypt(xml, callback) {
33
+ xmlenc.encrypt(xml, encryptOptions, function (err, encrypted) {
34
+ if (err) {
35
+ // Attempt to fix errors and retry
36
+ xmlenc.encrypt(
37
+ xml,
38
+ {
39
+ ...encryptOptions,
40
+ rsa_pub: utils.fixPemFormatting(encryptOptions.rsa_pub),
41
+ pem: utils.fixPemFormatting(encryptOptions.pem),
42
+ },
43
+ function (retryErr, retryEncrypted) {
44
+ if (retryErr) {
45
+ return callback(retryErr);
46
+ }
47
+
48
+ callback(null, utils.removeWhitespace(retryEncrypted));
49
+ }
50
+ );
51
+ } else {
52
+ callback(null, utils.removeWhitespace(encrypted));
53
+ }
54
+ });
55
+ };
56
+ };
@@ -0,0 +1,104 @@
1
+ const utils = require('../utils');
2
+ const SignedXml = require('xml-crypto').SignedXml;
3
+
4
+ const algorithms = {
5
+ signature: {
6
+ 'rsa-sha256': 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
7
+ 'rsa-sha1': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
8
+ },
9
+ digest: {
10
+ 'sha256': 'http://www.w3.org/2001/04/xmlenc#sha256',
11
+ 'sha1': 'http://www.w3.org/2000/09/xmldsig#sha1'
12
+ }
13
+ };
14
+
15
+ exports.fromSignXmlOptions = function (options) {
16
+ if (!options.key)
17
+ throw new Error('Expect a private key in pem format');
18
+
19
+ if (!options.cert)
20
+ throw new Error('Expect a public key cert in pem format');
21
+
22
+ if (!options.xpathToNodeBeforeSignature)
23
+ throw new Error('xpathToNodeBeforeSignature is required')
24
+
25
+ const key = options.key;
26
+ const pem = options.cert;
27
+ const signatureAlgorithm = options.signatureAlgorithm || 'rsa-sha256';
28
+ const digestAlgorithm = options.digestAlgorithm || 'sha256';
29
+ const signatureNamespacePrefix = (function (prefix) {
30
+ // 0.10.1 added prefix, but we want to name it signatureNamespacePrefix - This is just to keep supporting prefix
31
+ return typeof prefix === 'string' ? prefix : '';
32
+ })(options.signatureNamespacePrefix || options.prefix);
33
+ const xpathToNodeBeforeSignature = options.xpathToNodeBeforeSignature;
34
+ const idAttribute = options.signatureIdAttribute;
35
+
36
+ /**
37
+ * @param {Document} doc
38
+ * @param {Function} [callback]
39
+ * @return {string}
40
+ */
41
+ return function signXmlDocument(doc, callback) {
42
+ function sign(key) {
43
+ const unsigned = exports.unsigned(doc);
44
+ const cert = utils.pemToCert(pem);
45
+
46
+ const sig = new SignedXml(null, {
47
+ signatureAlgorithm: algorithms.signature[signatureAlgorithm],
48
+ idAttribute: idAttribute
49
+ });
50
+ sig.addReference("//*[local-name(.)='Assertion']",
51
+ ["http://www.w3.org/2000/09/xmldsig#enveloped-signature", "http://www.w3.org/2001/10/xml-exc-c14n#"],
52
+ algorithms.digest[digestAlgorithm]);
53
+
54
+ sig.signingKey = key;
55
+
56
+ sig.keyInfoProvider = {
57
+ getKeyInfo: function (key, prefix) {
58
+ prefix = prefix ? prefix + ':' : prefix;
59
+ return "<" + prefix + "X509Data><" + prefix + "X509Certificate>" + cert + "</" + prefix + "X509Certificate></" + prefix + "X509Data>";
60
+ }
61
+ };
62
+
63
+ sig.computeSignature(unsigned, {
64
+ location: {reference: xpathToNodeBeforeSignature, action: 'after'},
65
+ prefix: signatureNamespacePrefix
66
+ });
67
+
68
+ return sig.getSignedXml();
69
+ }
70
+
71
+ let signed
72
+ try {
73
+ try {
74
+ signed = sign(key)
75
+ } catch (err) {
76
+ signed = sign(utils.fixPemFormatting(key))
77
+ }
78
+
79
+ if (callback) {
80
+ setImmediate(callback, null, signed);
81
+ } else {
82
+ return signed;
83
+ }
84
+ } catch (e) {
85
+ if (callback) {
86
+ setImmediate(callback, e)
87
+ }
88
+ throw e
89
+ }
90
+ };
91
+ };
92
+ /**
93
+ * @param {Document} doc
94
+ * @param {Function} [callback]
95
+ * @return {string}
96
+ */
97
+ exports.unsigned = function (doc, callback) {
98
+ const xml = utils.removeWhitespace(doc.toString());
99
+ if (callback) {
100
+ setImmediate(callback, null, xml)
101
+ } else {
102
+ return xml;
103
+ }
104
+ }
@@ -26,8 +26,7 @@
26
26
  // SOFTWARE.
27
27
  //
28
28
 
29
- // @ts-expect-error type declaration file not found
30
- import { Saml20 as saml } from 'saml'
29
+ import { Saml20 as saml } from './saml/index.js' // The `saml` entry in `package.json` must be kept — it provides the sub-dependencies (@xmldom/xmldom, moment, xml-crypto, etc.) that this local copy requires.
31
30
  import crypto from 'node:crypto'
32
31
 
33
32
  export const samlAssertionUtils = {
@@ -25,6 +25,8 @@ import * as utils from './utils.ts'
25
25
  import * as utilsScim from './utils-scim.ts'
26
26
  import * as stream from './scim-stream.js'
27
27
  export * from './helper-rest.ts'
28
+ import LicenseData from './azure-license-mapping.json'
29
+ export { LicenseData }
28
30
  // @ts-expect-error: cannot find declaration
29
31
  import hycoPkg from 'hyco-https'
30
32