From ac2ca8d566305aa3e7ad53fff84893040b1fa897 Mon Sep 17 00:00:00 2001 From: gkwang Date: Thu, 2 Jan 2020 19:31:44 -0800 Subject: [PATCH] Add support to disallow insecure Algorithms This addresses issue #45 --- README.md | 10 +++-- lib/xmlenc.js | 40 +++++++++++++------ test/xmlenc.encryptedkey.js | 78 +++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 5f6a304..c7b33c1 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ var options = { rsa_pub: fs.readFileSync(__dirname + '/your_rsa.pub'), pem: fs.readFileSync(__dirname + '/your_public_cert.pem'), encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc', - keyEncryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' + keyEncryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p', + disallowInsecureEncryptionAlgorithm: true }; xmlenc.encrypt('content to encrypt', options, function(err, result) { @@ -51,6 +52,7 @@ Result: ~~~js var options = { key: fs.readFileSync(__dirname + '/your_private_key.key'), + disallowInsecureDecryptionAlgorithm: true; }; xmlenc.decrypt('', options, function(err, result) { @@ -68,12 +70,14 @@ Currently the library supports: * EncryptedKey to transport symmetric key using: * http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p - * http://www.w3.org/2001/04/xmlenc#rsa-1_5 + * http://www.w3.org/2001/04/xmlenc#rsa-1_5 (Insecure Algorithm) * EncryptedData using: * http://www.w3.org/2001/04/xmlenc#aes128-cbc * http://www.w3.org/2001/04/xmlenc#aes256-cbc - * http://www.w3.org/2001/04/xmlenc#tripledes-cbc + * http://www.w3.org/2001/04/xmlenc#tripledes-cbc (Insecure Algorithm) + +Insecure Algorithms can be disabled via disallowInsecureEncryptionAlgorithm/disallowInsecureDecryptionAlgorithm flags when encrypting/decrypting. This flag is off by default in 0.x versions. However, you can fork and implement your own algorithm. The code supports adding more algorithms easily diff --git a/lib/xmlenc.js b/lib/xmlenc.js index fa39ad7..35010a1 100644 --- a/lib/xmlenc.js +++ b/lib/xmlenc.js @@ -4,11 +4,16 @@ var xpath = require('xpath'); var utils = require('./utils'); var pki = require('node-forge').pki; +const insecureAlgorithms = [ + //https://www.w3.org/TR/xmlenc-core1/#rsav15note + 'http://www.w3.org/2001/04/xmlenc#rsa-1_5', + //https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA + 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc']; function encryptKeyInfoWithScheme(symmetricKey, options, scheme, callback) { try { var rsa_pub = pki.publicKeyFromPem(options.rsa_pub); var encrypted = rsa_pub.encrypt(symmetricKey.toString('binary'), scheme); - var base64EncodedEncryptedKey = new Buffer(encrypted, 'binary').toString('base64'); + var base64EncodedEncryptedKey = Buffer.from(encrypted, 'binary').toString('base64'); var params = { encryptedKey: base64EncodedEncryptedKey, @@ -33,7 +38,10 @@ function encryptKeyInfo(symmetricKey, options, callback) { if (!options.keyEncryptionAlgorighm) return callback(new Error('encryption without encrypted key is not supported yet')); - + if (options.disallowEncryptionWithInsecureAlgorithm + && insecureAlgorithms.indexOf(options.keyEncryptionAlgorighm) >= 0) { + return callback(new Error('encryption algorithm ' + options.keyEncryptionAlgorighm + 'is not secure')); + } switch (options.keyEncryptionAlgorighm) { case 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p': return encryptKeyInfoWithScheme(symmetricKey, options, 'RSA-OAEP', callback); @@ -55,7 +63,10 @@ function encrypt(content, options, callback) { return callback(new Error('rsa_pub option is mandatory and you should provide a valid RSA public key')); if (!options.pem) return callback(new Error('pem option is mandatory and you should provide a valid x509 certificate encoded as PEM')); - + if (options.disallowEncryptionWithInsecureAlgorithm + && insecureAlgorithms.indexOf(options.keyEncryptionAlgorighm) >= 0) { + return callback(new Error('encryption algorithm ' + options.keyEncryptionAlgorighm + 'is not secure')); + } options.input_encoding = options.input_encoding || 'utf8'; function generate_symmetric_key(cb) { @@ -144,7 +155,6 @@ function decrypt(xml, options, callback) { return callback(new Error('must provide XML to encrypt')); if (!options.key) return callback(new Error('key option is mandatory and you should provide a valid RSA private key')); - try { var doc = typeof xml === 'string' ? new xmldom.DOMParser().parseFromString(xml) : xml; @@ -152,9 +162,13 @@ function decrypt(xml, options, callback) { var encryptionMethod = xpath.select("//*[local-name(.)='EncryptedData']/*[local-name(.)='EncryptionMethod']", doc)[0]; var encryptionAlgorithm = encryptionMethod.getAttribute('Algorithm'); + if (options.disallowDecryptionWithInsecureAlgorithm + && insecureAlgorithms.indexOf(encryptionAlgorithm) >= 0) { + throw new Error('encryption algorithm ' + encryptionAlgorithm + ' is not secure, fail to decrypt'); + } var encryptedContent = xpath.select("//*[local-name(.)='EncryptedData']/*[local-name(.)='CipherData']/*[local-name(.)='CipherValue']", doc)[0]; - var encrypted = new Buffer(encryptedContent.textContent, 'base64'); + var encrypted = Buffer.from(encryptedContent.textContent, 'base64'); switch (encryptionAlgorithm) { case 'http://www.w3.org/2001/04/xmlenc#aes128-cbc': @@ -188,12 +202,16 @@ function decryptKeyInfo(doc, options) { throw new Error('cant find encryption algorithm'); } - var keyEncryptionAlgorighm = keyEncryptionMethod.getAttribute('Algorithm'); + var keyEncryptionAlgorithm = keyEncryptionMethod.getAttribute('Algorithm'); + if (options.disallowDecryptionWithInsecureAlgorithm + && insecureAlgorithms.indexOf(keyEncryptionAlgorithm) >= 0) { + throw new Error('encryption algorithm ' + keyEncryptionAlgorithm + ' is not secure, fail to decrypt'); + } var encryptedKey = keyRetrievalMethodUri ? xpath.select("//*[local-name(.)='EncryptedKey' and @Id='" + keyRetrievalMethodUri.substring(1) + "']/*[local-name(.)='CipherData']/*[local-name(.)='CipherValue']", keyInfo)[0] : xpath.select("//*[local-name(.)='CipherValue']", keyInfo)[0]; - switch (keyEncryptionAlgorighm) { + switch (keyEncryptionAlgorithm) { case 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p': return decryptKeyInfoWithScheme(encryptedKey, options, 'RSA-OAEP'); case 'http://www.w3.org/2001/04/xmlenc#rsa-1_5': @@ -204,10 +222,10 @@ function decryptKeyInfo(doc, options) { } function decryptKeyInfoWithScheme(encryptedKey, options, scheme) { - var key = new Buffer(encryptedKey.textContent, 'base64').toString('binary'); + var key = Buffer.from(encryptedKey.textContent, 'base64').toString('binary'); var private_key = pki.privateKeyFromPem(options.key); var decrypted = private_key.decrypt(key, scheme); - return new Buffer(decrypted, 'binary'); + return Buffer.from(decrypted, 'binary'); } function encryptWithAlgorithm(algorithm, symmetricKey, ivLength, content, encoding, callback) { @@ -218,7 +236,7 @@ function encryptWithAlgorithm(algorithm, symmetricKey, ivLength, content, encodi var cipher = crypto.createCipheriv(algorithm, symmetricKey, iv); // encrypted content var encrypted = cipher.update(content, encoding, 'binary') + cipher.final('binary'); - return callback(null, Buffer.concat([iv, new Buffer(encrypted, 'binary')])); + return callback(null, Buffer.concat([iv, Buffer.from(encrypted, 'binary')])); }); } @@ -237,7 +255,7 @@ function decryptWithAlgorithm(algorithm, symmetricKey, ivLength, content) { return; } - return new Buffer(decrypted, 'binary').toString('utf8'); + return Buffer.from(decrypted, 'binary').toString('utf8'); } exports = module.exports = { diff --git a/test/xmlenc.encryptedkey.js b/test/xmlenc.encryptedkey.js index e9bab01..72d73a8 100644 --- a/test/xmlenc.encryptedkey.js +++ b/test/xmlenc.encryptedkey.js @@ -55,6 +55,45 @@ describe('encrypt', function() { }); } + describe('des-ede3-cbc fails', function() { + it('should fail encryption when disallowInsecureEncryptionAlgorithm is set', function(done) { + const options = { + rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'), + pem: fs.readFileSync(__dirname + '/test-auth0.pem'), + key: fs.readFileSync(__dirname + '/test-auth0.key'), + disallowInsecureEncryptionAlgorithm: true, + encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes128-cbc', + keyEncryptionAlgorighm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' + } + //options.encryptionAlgorithm = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc', + //options.keyEncryptionAlgorighm = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'; + xmlenc.encrypt('encrypt me', options, function(err, result) { + assert(err); + done(); + }); + }); + + it('should fail decryption when disallowInsecureDecryptionAlgorithm is set', function(done) { + const options = { + rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'), + pem: fs.readFileSync(__dirname + '/test-auth0.pem'), + key: fs.readFileSync(__dirname + '/test-auth0.key'), + encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes128-cbc', + keyEncryptionAlgorighm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' + } + xmlenc.encrypt('encrypt me', options, function(err, result) { + xmlenc.decrypt(result, + { key: fs.readFileSync(__dirname + '/test-auth0.key'), + disallowInsecureDecryptionAlgorithm: true}, + function (err, decrypted) { + assert(err); + done(); + }); + }); + }); + }); + + it('should encrypt and decrypt keyinfo', function (done) { var options = { rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'), @@ -94,4 +133,43 @@ describe('encrypt', function() { }); }); + it('should fail encrypt when disallowInsecureDecryptionAlgorithm is set', function (done) { + var options = { + rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'), + pem: fs.readFileSync(__dirname + '/test-auth0.pem'), + keyEncryptionAlgorighm: 'http://www.w3.org/2001/04/xmlenc#rsa-1_5', + disallowInsecureEncryptionAlgorithm: true + }; + + var plaintext = 'The quick brown fox jumps over the lazy dog'; + + xmlenc.encryptKeyInfo(plaintext, options, function(err, encryptedKeyInfo) { + assert(err); + done(); + }); + }); + + it('should encrypt and fail decrypt due to insecure algorithms', function (done) { + var options = { + rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'), + pem: fs.readFileSync(__dirname + '/test-auth0.pem'), + keyEncryptionAlgorighm: 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' + }; + + var plaintext = 'The quick brown fox jumps over the lazy dog'; + + xmlenc.encryptKeyInfo(plaintext, options, function(err, encryptedKeyInfo) { + if (err) return done(err); + + assert.throws( + function(){xmlenc.decryptKeyInfo( + encryptedKeyInfo, + {key: fs.readFileSync(__dirname + '/test-auth0.key'), + disallowDecryptionWithInsecureAlgorithm: true})}, + Error, + "Error thrown due to disallowing insecure algorithms."); + + done(); + }); + }); });