Skip to content

Commit 31283f1

Browse files
committed
feat: moving AES-128-CBC AES-256-CBC to the list of insecure algorithms
BREAKING CHANGE: CBC algorithms are now marked insecure
1 parent 6348314 commit 31283f1

4 files changed

Lines changed: 128 additions & 21 deletions

File tree

README.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,32 @@ Currently the library supports:
7878
* http://www.w3.org/2001/04/xmlenc#rsa-1_5 (Insecure Algorithm)
7979

8080
* EncryptedData using:
81-
* http://www.w3.org/2001/04/xmlenc#aes128-cbc
82-
* http://www.w3.org/2001/04/xmlenc#aes256-cbc
81+
* http://www.w3.org/2001/04/xmlenc#aes128-cbc (Insecure Algorithm)
82+
* http://www.w3.org/2001/04/xmlenc#aes256-cbc (Insecure Algorithm)
8383
* http://www.w3.org/2009/xmlenc11#aes128-gcm
8484
* http://www.w3.org/2009/xmlenc11#aes256-gcm
8585
* http://www.w3.org/2001/04/xmlenc#tripledes-cbc (Insecure Algorithm)
8686

87-
Insecure Algorithms can be disabled via `disallowEncryptionWithInsecureAlgorithm`/`disallowDecryptionWithInsecureAlgorithm` flags when encrypting/decrypting. This flag is off by default in 0.x versions.
87+
Insecure Algorithms can be used via `disallowEncryptionWithInsecureAlgorithm`/`disallowDecryptionWithInsecureAlgorithm` flags when encrypting/decrypting by setting them to false. In version 4.0 onwards, these flags are true by default (forbidding insecure algorithms).
8888

89-
A warning will be piped to `stderr` using console.warn() by default when the aforementioned algorithms are used. This can be disabled via the `warnInsecureAlgorithm` flag.
89+
A warning will be piped to `stderr` using console.warn() by default when the aforementioned algorithms are used and above mentioned flags are false. This can be disabled via the `warnInsecureAlgorithm` flag.
90+
91+
We recommend usage of AES-256-GCM (Galois/Counter Mode) for the strongest security posture and to align with current industry best practices.
92+
93+
Note that `xml-encryption` versions prior to 4.0 supported AES-128-CBC and AES-256-CBC as secure algorithms. In version 4.0 onwards, these are treated as insecure because they use the Cipher Block Chaining (CBC) mode of encryption, which does not provide integrity guarantees. To continue using AES128-CBC and AES256-CBC, enable support for insecure algorithms via `disallowEncryptionWithInsecureAlgorithm/disallowDecryptionWithInsecureAlgorithm`.
94+
95+
### Allow listing specific algorithms when decrypting
96+
97+
If decrypting with `disallowEncryptionWithInsecureAlgorithm: true`, you may wish to only support a subset of insecure algorithms (for example, supporting AES-256-CBC only). This can be achieved by extracting the encryption algorithm using the following code and applying validation as required.
98+
99+
~~~js
100+
const xmldom = require('@xmldom/xmldom');
101+
const xpath = require('xpath');
102+
103+
const doc = new xmldom.DOMParser().parseFromString(xmlString);
104+
const encryptionMethod = xpath.select("//*[local-name(.)='EncryptedData']/*[local-name(.)='EncryptionMethod']", doc)[0];
105+
const encryptionAlgorithm = encryptionMethod.getAttribute('Algorithm');
106+
~~~
90107

91108
## Issue Reporting
92109

lib/xmlenc.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ const insecureAlgorithms = [
77
//https://www.w3.org/TR/xmlenc-core1/#rsav15note
88
'http://www.w3.org/2001/04/xmlenc#rsa-1_5',
99
//https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA
10-
'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'];
10+
'http://www.w3.org/2001/04/xmlenc#tripledes-cbc',
11+
//https://www.w3.org/TR/xmlenc-core1/#sec-edata-attacks
12+
'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
13+
'http://www.w3.org/2001/04/xmlenc#aes128-cbc',
14+
];
1115

1216
function encryptKeyInfoWithScheme(symmetricKey, options, padding, callback) {
1317
const symmetricKeyBuffer = Buffer.isBuffer(symmetricKey) ? symmetricKey : Buffer.from(symmetricKey, 'utf-8');
@@ -44,7 +48,7 @@ function encryptKeyInfo(symmetricKey, options, callback) {
4448

4549
if (!options.keyEncryptionAlgorithm)
4650
return callback(new Error('encryption without encrypted key is not supported yet'));
47-
if (options.disallowEncryptionWithInsecureAlgorithm
51+
if (options.disallowEncryptionWithInsecureAlgorithm !== false
4852
&& insecureAlgorithms.indexOf(options.keyEncryptionAlgorithm) >= 0) {
4953
return callback(new Error('encryption algorithm ' + options.keyEncryptionAlgorithm + 'is not secure'));
5054
}
@@ -71,19 +75,24 @@ function encrypt(content, options, callback) {
7175
return callback(new Error('rsa_pub option is mandatory and you should provide a valid RSA public key'));
7276
if (!options.pem)
7377
return callback(new Error('pem option is mandatory and you should provide a valid x509 certificate encoded as PEM'));
74-
if (options.disallowEncryptionWithInsecureAlgorithm
75-
&& (insecureAlgorithms.indexOf(options.keyEncryptionAlgorithm) >= 0
76-
|| insecureAlgorithms.indexOf(options.encryptionAlgorithm) >= 0)) {
77-
return callback(new Error('encryption algorithm ' + options.keyEncryptionAlgorithm + ' is not secure'));
78+
if (options.disallowEncryptionWithInsecureAlgorithm !== false) {
79+
if (insecureAlgorithms.indexOf(options.keyEncryptionAlgorithm) >= 0) {
80+
return callback(new Error('encryption algorithm ' + options.keyEncryptionAlgorithm + ' is not secure'));
81+
}
82+
if (insecureAlgorithms.indexOf(options.encryptionAlgorithm) >= 0) {
83+
return callback(new Error('encryption algorithm ' + options.encryptionAlgorithm + ' is not secure'));
84+
}
7885
}
7986
options.input_encoding = options.input_encoding || 'utf8';
8087

8188
function generate_symmetric_key(cb) {
8289
switch (options.encryptionAlgorithm) {
8390
case 'http://www.w3.org/2001/04/xmlenc#aes128-cbc':
91+
utils.warnInsecureAlgorithm(options.encryptionAlgorithm, options.warnInsecureAlgorithm);
8492
crypto.randomBytes(16, cb); // generate a symmetric random key 16 bytes length
8593
break;
8694
case 'http://www.w3.org/2001/04/xmlenc#aes256-cbc':
95+
utils.warnInsecureAlgorithm(options.encryptionAlgorithm, options.warnInsecureAlgorithm);
8796
crypto.randomBytes(32, cb); // generate a symmetric random key 32 bytes length
8897
break;
8998
case 'http://www.w3.org/2009/xmlenc11#aes128-gcm':
@@ -104,12 +113,14 @@ function encrypt(content, options, callback) {
104113
function encrypt_content(symmetricKey, cb) {
105114
switch (options.encryptionAlgorithm) {
106115
case 'http://www.w3.org/2001/04/xmlenc#aes128-cbc':
116+
utils.warnInsecureAlgorithm(options.encryptionAlgorithm, options.warnInsecureAlgorithm);
107117
encryptWithAlgorithm('aes-128-cbc', symmetricKey, 16, content, options.input_encoding, function (err, encryptedContent) {
108118
if (err) return cb(err);
109119
cb(null, encryptedContent);
110120
});
111121
break;
112122
case 'http://www.w3.org/2001/04/xmlenc#aes256-cbc':
123+
utils.warnInsecureAlgorithm(options.encryptionAlgorithm, options.warnInsecureAlgorithm);
113124
encryptWithAlgorithm('aes-256-cbc', symmetricKey, 16, content, options.input_encoding, function (err, encryptedContent) {
114125
if (err) return cb(err);
115126
cb(null, encryptedContent);
@@ -190,7 +201,7 @@ function decrypt(xml, options, callback) {
190201
var encryptionMethod = xpath.select("//*[local-name(.)='EncryptedData']/*[local-name(.)='EncryptionMethod']", doc)[0];
191202
var encryptionAlgorithm = encryptionMethod.getAttribute('Algorithm');
192203

193-
if (options.disallowDecryptionWithInsecureAlgorithm
204+
if (options.disallowDecryptionWithInsecureAlgorithm !== false
194205
&& insecureAlgorithms.indexOf(encryptionAlgorithm) >= 0) {
195206
return callback(new Error('encryption algorithm ' + encryptionAlgorithm + ' is not secure, fail to decrypt'));
196207
}
@@ -199,8 +210,10 @@ function decrypt(xml, options, callback) {
199210
var encrypted = Buffer.from(encryptedContent.textContent, 'base64');
200211
switch (encryptionAlgorithm) {
201212
case 'http://www.w3.org/2001/04/xmlenc#aes128-cbc':
213+
utils.warnInsecureAlgorithm(encryptionAlgorithm, options.warnInsecureAlgorithm);
202214
return callback(null, decryptWithAlgorithm('aes-128-cbc', symmetricKey, 16, encrypted));
203215
case 'http://www.w3.org/2001/04/xmlenc#aes256-cbc':
216+
utils.warnInsecureAlgorithm(encryptionAlgorithm, options.warnInsecureAlgorithm);
204217
return callback(null, decryptWithAlgorithm('aes-256-cbc', symmetricKey, 16, encrypted));
205218
case 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc':
206219
utils.warnInsecureAlgorithm(encryptionAlgorithm, options.warnInsecureAlgorithm);
@@ -252,7 +265,7 @@ function decryptKeyInfo(doc, options) {
252265
}
253266

254267
var keyEncryptionAlgorithm = keyEncryptionMethod.getAttribute('Algorithm');
255-
if (options.disallowDecryptionWithInsecureAlgorithm
268+
if (options.disallowDecryptionWithInsecureAlgorithm !== false
256269
&& insecureAlgorithms.indexOf(keyEncryptionAlgorithm) >= 0) {
257270
throw new Error('encryption algorithm ' + keyEncryptionAlgorithm + ' is not secure, fail to decrypt');
258271
}

test/xmlenc.encryptedkey.js

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('encrypt', function() {
5656
name: 'des-ede3-cbc',
5757
encryptionOptions: {
5858
encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc',
59-
keyEncryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
59+
keyEncryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
6060
}
6161
}];
6262

@@ -83,8 +83,15 @@ describe('encrypt', function() {
8383
options.key = fs.readFileSync(__dirname + '/test-auth0.key'),
8484
options.warnInsecureAlgorithm = false;
8585

86+
const disallowInsecureAlgorithm = ![
87+
'http://www.w3.org/2001/04/xmlenc#tripledes-cbc',
88+
'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
89+
'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
90+
].includes(options?.encryptionAlgorithm);
91+
options.disallowEncryptionWithInsecureAlgorithm = disallowInsecureAlgorithm;
92+
8693
xmlenc.encrypt(content, options, function(err, result) {
87-
xmlenc.decrypt(result, { key: fs.readFileSync(__dirname + '/test-auth0.key'), warnInsecureAlgorithm: false}, function (err, decrypted) {
94+
xmlenc.decrypt(result, { key: fs.readFileSync(__dirname + '/test-auth0.key'), warnInsecureAlgorithm: false, disallowDecryptionWithInsecureAlgorithm: disallowInsecureAlgorithm }, function (err, decrypted) {
8895
if (err) return done(err);
8996
assert.equal(decrypted, content);
9097
done();
@@ -93,7 +100,7 @@ describe('encrypt', function() {
93100
}
94101

95102
describe('des-ede3-cbc fails', function() {
96-
it('should fail encryption when disallowEncryptionWithInsecureAlgorithm is set', function(done) {
103+
it('should fail encryption when disallowEncryptionWithInsecureAlgorithm is set to true', function(done) {
97104
const options = {
98105
rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
99106
pem: fs.readFileSync(__dirname + '/test-auth0.pem'),
@@ -133,7 +140,7 @@ describe('encrypt', function() {
133140
});
134141

135142
describe('rsa-1.5 fails', function() {
136-
it('should fail encryption when disallowEncryptionWithInsecureAlgorithm is set', function(done) {
143+
it('should fail encryption when disallowEncryptionWithInsecureAlgorithm is set to true', function(done) {
137144
const options = {
138145
rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
139146
pem: fs.readFileSync(__dirname + '/test-auth0.pem'),
@@ -195,7 +202,7 @@ describe('encrypt', function() {
195202

196203
it('should decrypt xml with odd padding (aes256-cbc)', function (done) {
197204
var encryptedContent = fs.readFileSync(__dirname + '/test-cbc256-padding.xml').toString()
198-
xmlenc.decrypt(encryptedContent, { key: fs.readFileSync(__dirname + '/test-auth0.key')}, function(err, decrypted) {
205+
xmlenc.decrypt(encryptedContent, { key: fs.readFileSync(__dirname + '/test-auth0.key'), disallowDecryptionWithInsecureAlgorithm: false }, function(err, decrypted) {
199206
assert.ifError(err);
200207
assert.equal(decrypted, 'content');
201208
done();
@@ -210,7 +217,7 @@ describe('encrypt', function() {
210217
});
211218
});
212219

213-
it('should fail encrypt when disallowEncryptionWithInsecureAlgorithm is set', function (done) {
220+
it('should fail encrypt when disallowEncryptionWithInsecureAlgorithm is set to true', function (done) {
214221
var options = {
215222
rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
216223
pem: fs.readFileSync(__dirname + '/test-auth0.pem'),
@@ -229,7 +236,8 @@ describe('encrypt', function() {
229236
var options = {
230237
rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
231238
pem: fs.readFileSync(__dirname + '/test-auth0.pem'),
232-
keyEncryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
239+
keyEncryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-1_5',
240+
disallowEncryptionWithInsecureAlgorithm: false,
233241
};
234242

235243
var plaintext = 'The quick brown fox jumps over the lazy dog';
@@ -248,4 +256,73 @@ describe('encrypt', function() {
248256
done();
249257
});
250258
});
259+
260+
it('should fail decrypt due to insecure algorithm (aes256-cbc)', function (done) {
261+
var encryptedContent = fs.readFileSync(__dirname + '/test-cbc256-padding.xml').toString()
262+
xmlenc.decrypt(encryptedContent, { key: fs.readFileSync(__dirname + '/test-auth0.key'), disallowDecryptionWithInsecureAlgorithm: true }, function(err, decrypted) {
263+
assert(err);
264+
done();
265+
});
266+
});
267+
268+
it('should decrypt with insecure algorithm (aes256-cbc) with warning when disallowDecryptionWithInsecureAlgorithm is false', function (done) {
269+
var encryptedContent = fs.readFileSync(__dirname + '/test-cbc256-padding.xml').toString()
270+
xmlenc.decrypt(encryptedContent, { key: fs.readFileSync(__dirname + '/test-auth0.key'), disallowDecryptionWithInsecureAlgorithm: false, warnInsecureAlgorithm: true }, function(err, decrypted) {
271+
assert.ifError(err);
272+
assert.equal(consoleSpy.called, true);
273+
assert.equal(decrypted, 'content');
274+
done();
275+
});
276+
});
277+
278+
it('should decrypt with insecure algorithm (aes256-cbc) without warning when disallowDecryptionWithInsecureAlgorithm is false', function (done) {
279+
var encryptedContent = fs.readFileSync(__dirname + '/test-cbc256-padding.xml').toString()
280+
xmlenc.decrypt(encryptedContent, { key: fs.readFileSync(__dirname + '/test-auth0.key'), disallowDecryptionWithInsecureAlgorithm: false, warnInsecureAlgorithm: false }, function(err, decrypted) {
281+
assert.ifError(err);
282+
assert.equal(consoleSpy.called, false);
283+
assert.equal(decrypted, 'content');
284+
done();
285+
});
286+
});
287+
288+
it('should fail decrypt with insecure algorithm (aes256-cbc)', function (done) {
289+
var encryptedContent = fs.readFileSync(__dirname + '/test-cbc256-padding.xml').toString()
290+
xmlenc.decrypt(encryptedContent, { key: fs.readFileSync(__dirname + '/test-auth0.key') }, function(err, decrypted) {
291+
assert(err);
292+
done();
293+
});
294+
});
295+
296+
it('should fail encrypt due to insecure algorithm (aes256-cbc)', function (done) {
297+
const options = {
298+
rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
299+
pem: fs.readFileSync(__dirname + '/test-auth0.pem'),
300+
key: fs.readFileSync(__dirname + '/test-auth0.key'),
301+
encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
302+
keyEncryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p',
303+
}
304+
xmlenc.encrypt('encrypt', options, function(err, result) {
305+
assert(err);
306+
assert(!result);
307+
done();
308+
});
309+
});
310+
311+
it('should encrypt with insecure algorithm (aes256-cbc) when disallowEncryptionWithInsecureAlgorithm is false', function (done) {
312+
const options = {
313+
rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
314+
pem: fs.readFileSync(__dirname + '/test-auth0.pem'),
315+
key: fs.readFileSync(__dirname + '/test-auth0.key'),
316+
encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
317+
keyEncryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p',
318+
disallowEncryptionWithInsecureAlgorithm: false,
319+
warnInsecureAlgorithm: true
320+
}
321+
xmlenc.encrypt('encrypt', options, function(err, result) {
322+
assert.ifError(err);
323+
assert.equal(consoleSpy.called, true);
324+
assert(result);
325+
done();
326+
});
327+
});
251328
});

test/xmlenc.integration.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('integration', function() {
1111
it('should decrypt assertion with aes128', function (done) {
1212
var result = fs.readFileSync(__dirname + '/assertion-sha1-128.xml').toString();
1313

14-
xmlenc.decrypt(result, { key: fs.readFileSync(__dirname + '/test-cbc128.key')}, function (err, decrypted) {
14+
xmlenc.decrypt(result, { key: fs.readFileSync(__dirname + '/test-cbc128.key'), disallowDecryptionWithInsecureAlgorithm: false }, function (err, decrypted) {
1515
// decrypted content should finish with <saml2:Assertion>
1616
assert.equal(/<\/saml2:Assertion>$/.test(decrypted), true);
1717
done();
@@ -22,7 +22,7 @@ describe('integration', function() {
2222
var encryptedContent = fs.readFileSync(__dirname + '/test-okta-enc-response.xml').toString()
2323
xmlenc.decrypt(
2424
encryptedContent,
25-
{key: fs.readFileSync(__dirname + '/test-okta.pem')},
25+
{key: fs.readFileSync(__dirname + '/test-okta.pem'), disallowDecryptionWithInsecureAlgorithm: false},
2626
(err, res) => {
2727
assert.ifError(err);
2828

0 commit comments

Comments
 (0)