Skip to content

Fix autopadding issue with for 3DES algorithm #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 28 additions & 48 deletions lib/xmlenc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ function encryptKeyInfoWithScheme(symmetricKey, options, scheme, callback) {
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 params = {
encryptedKey: base64EncodedEncryptedKey,
encryptionPublicCert: '<X509Data><X509Certificate>' + utils.pemToCert(options.pem.toString()) + '</X509Certificate></X509Data>',
keyEncryptionMethod: options.keyEncryptionAlgorighm
};

var result = utils.renderTemplate('keyinfo', params);
callback(null, result);
} catch (e) {
Expand All @@ -31,7 +31,7 @@ function encryptKeyInfo(symmetricKey, options, callback) {
return callback(new Error('must provide options.rsa_pub with public key RSA'));
if (!options.pem)
return callback(new Error('must provide options.pem with certificate'));

if (!options.keyEncryptionAlgorighm)
return callback(new Error('encryption without encrypted key is not supported yet'));

Expand Down Expand Up @@ -122,8 +122,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'));

var decrypted;

try {
var doc = typeof xml === 'string' ? new xmldom.DOMParser().parseFromString(xml) : xml;
Expand All @@ -132,59 +130,23 @@ function decrypt(xml, options, callback) {
var encryptionMethod = xpath.select("//*[local-name(.)='EncryptedData']/*[local-name(.)='EncryptionMethod']", doc)[0];
var encryptionAlgorithm = encryptionMethod.getAttribute('Algorithm');

var decipher;
var padding;
var encryptedContent = xpath.select("//*[local-name(.)='EncryptedData']/*[local-name(.)='CipherData']/*[local-name(.)='CipherValue']", doc)[0];

var encrypted = new Buffer(encryptedContent.textContent, 'base64');

switch (encryptionAlgorithm) {
case 'http://www.w3.org/2001/04/xmlenc#aes128-cbc':
decipher = crypto.createDecipheriv('aes-128-cbc', symmetricKey, encrypted.slice(0, 16));

decipher.setAutoPadding(false);
decrypted = decipher.update(encrypted.slice(16), null, 'binary') + decipher.final('binary');

// Remove padding bytes equal to the value of the last byte of the returned data.
padding = decrypted.charCodeAt(decrypted.length - 1);
if (1 <= padding && padding <= 16) {
decrypted = decrypted.substr(0, decrypted.length - padding);
} else {
callback(new Error('padding length invalid'));
return;
}

decrypted = new Buffer(decrypted, 'binary').toString('utf8');
break;
return callback(null, decryptWithAlgorithm('aes-128-cbc', symmetricKey, 16, encrypted));
case 'http://www.w3.org/2001/04/xmlenc#aes256-cbc':
decipher = crypto.createDecipheriv('aes-256-cbc', symmetricKey, encrypted.slice(0, 16));

decipher.setAutoPadding(false);
decrypted = decipher.update(encrypted.slice(16), null, 'binary') + decipher.final('binary');

// Remove padding bytes equal to the value of the last byte of the returned data.
padding = decrypted.charCodeAt(decrypted.length - 1);
if (1 <= padding && padding <= 16) {
decrypted = decrypted.substr(0, decrypted.length - padding);
} else {
callback(new Error('padding length invalid'));
return;
}
decrypted = new Buffer(decrypted, 'binary').toString('utf8');
break;
return callback(null, decryptWithAlgorithm('aes-256-cbc', symmetricKey, 16, encrypted));
case 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc':
decipher = crypto.createDecipheriv('des-ede3-cbc', symmetricKey, encrypted.slice(0,8));
decrypted = decipher.update(encrypted.slice(8), null, 'binary') + decipher.final('binary');
decrypted = new Buffer(decrypted, 'binary').toString('utf8');
break;
return callback(null, decryptWithAlgorithm('des-ede3-cbc', symmetricKey, 8, encrypted));
default:
return callback(new Error('encryption algorithm ' + encryptionAlgorithm + ' not supported'));
}
} catch (e) {
return callback(e);
}

callback(null, decrypted);
}

function decryptKeyInfo(doc, options) {
Expand All @@ -193,7 +155,7 @@ function decryptKeyInfo(doc, options) {
var keyRetrievalMethodUri;
var keyInfo = xpath.select("//*[local-name(.)='KeyInfo' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", doc)[0];
var keyEncryptionMethod = xpath.select("//*[local-name(.)='KeyInfo']/*[local-name(.)='EncryptedKey']/*[local-name(.)='EncryptionMethod']", doc)[0];

if (!keyEncryptionMethod) { // try with EncryptedData->KeyInfo->RetrievalMethod
var keyRetrievalMethod = xpath.select("//*[local-name(.)='EncryptedData']/*[local-name(.)='KeyInfo']/*[local-name(.)='RetrievalMethod']", doc)[0];
keyRetrievalMethodUri = keyRetrievalMethod ? keyRetrievalMethod.getAttribute('URI') : null;
Expand Down Expand Up @@ -235,14 +197,32 @@ function encryptWithAlgorithm(algorithm, symmetricKey, ivLength, content, encodi
// create a random iv for algorithm
crypto.randomBytes(ivLength, function(err, iv) {
if (err) return callback(err);

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')]));
});
}

function decryptWithAlgorithm(algorithm, symmetricKey, ivLength, content) {
var decipher = crypto.createDecipheriv(algorithm, symmetricKey, content.slice(0,ivLength));
decipher.setAutoPadding(false);

var decrypted = decipher.update(content.slice(ivLength), null, 'binary') + decipher.final('binary');

// Remove padding bytes equal to the value of the last byte of the returned data.
var padding = decrypted.charCodeAt(decrypted.length - 1);
if (1 <= padding && padding <= ivLength) {
decrypted = decrypted.substr(0, decrypted.length - padding);
} else {
callback(new Error('padding length invalid'));
return;
}

return new Buffer(decrypted, 'binary').toString('utf8');
}

exports = module.exports = {
decrypt: decrypt,
encrypt: encrypt,
Expand Down
117 changes: 31 additions & 86 deletions test/xmlenc.encryptedkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,110 +8,55 @@ var xpath = require('xpath');

describe('encrypt', function() {

it('should encrypt and decrypt xml (aes256-cbc)', function (done) {
// cert created with:
// openssl req -x509 -new -newkey rsa:2048 -nodes -subj '/CN=auth0.auth0.com/O=Auth0 LLC/C=US/ST=Washington/L=Redmond' -keyout auth0.key -out auth0.pem
// pub key extracted from (only the RSA public key between BEGIN PUBLIC KEY and END PUBLIC KEY)
// openssl x509 -in "test-auth0.pem" -pubkey

var options = {
rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
pem: fs.readFileSync(__dirname + '/test-auth0.pem'),
key: fs.readFileSync(__dirname + '/test-auth0.key'),
var algorithms = [{
name: 'aes-256-cbc',
encryptionOptions: {
encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
keyEncryptionAlgorighm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
};

xmlenc.encrypt('content to encrypt', options, function(err, result) {
xmlenc.decrypt(result, { key: fs.readFileSync(__dirname + '/test-auth0.key')}, function(err, decrypted) {
assert.equal(decrypted, 'content to encrypt');
done();
});
});
});

it('should encrypt and decrypt xml (aes256-cbc with utf8 chars)', function (done) {
// cert created with:
// openssl req -x509 -new -newkey rsa:2048 -nodes -subj '/CN=auth0.auth0.com/O=Auth0 LLC/C=US/ST=Washington/L=Redmond' -keyout auth0.key -out auth0.pem
// pub key extracted from (only the RSA public key between BEGIN PUBLIC KEY and END PUBLIC KEY)
// openssl x509 -in "test-auth0.pem" -pubkey

var 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#aes256-cbc',
keyEncryptionAlgorighm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
};

xmlenc.encrypt('Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge', options, function(err, result) {
xmlenc.decrypt(result, { key: fs.readFileSync(__dirname + '/test-auth0.key')}, function(err, decrypted) {
assert.equal(decrypted, 'Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge');
done();
});
});
});

it('should encrypt and decrypt xml (aes128-cbc) with utf8 chars', function (done) {
// cert created with:
// openssl req -x509 -new -newkey rsa:2048 -nodes -subj '/CN=auth0.auth0.com/O=Auth0 LLC/C=US/ST=Washington/L=Redmond' -keyout auth0.key -out auth0.pem
// pub key extracted from (only the RSA public key between BEGIN PUBLIC KEY and END PUBLIC KEY)
// openssl x509 -in "test-auth0.pem" -pubkey

var options = {
rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
pem: fs.readFileSync(__dirname + '/test-auth0.pem'),
key: fs.readFileSync(__dirname + '/test-auth0.key'),
}
}, {
name: 'aes-128-cbc',
encryptionOptions: {
encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes128-cbc',
keyEncryptionAlgorighm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
};
}
}, {
name: 'des-ede3-cbc',
encryptionOptions: {
encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc',
keyEncryptionAlgorighm: 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
}
}];

xmlenc.encrypt('Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge', options, function (err, result) {
xmlenc.decrypt(result, { key: fs.readFileSync(__dirname + '/test-auth0.key')}, function (err, decrypted) {
assert.equal(decrypted, 'Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge');
done();
algorithms.forEach(function (algorithm) {
describe(algorithm.name, function () {
it('should encrypt and decrypt xml', function (done) {
_shouldEncryptAndDecrypt('content to encrypt', algorithm.encryptionOptions, done);
});
});
});

it('should encrypt and decrypt xml (aes128-cbc)', function (done) {
var 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('content to encrypt', options, function (err, result) {
xmlenc.decrypt(result, { key: fs.readFileSync(__dirname + '/test-auth0.key')}, function (err, decrypted) {
assert.equal(decrypted, 'content to encrypt');
done();
it('should encrypt and decrypt xml with utf8 chars', function (done) {
_shouldEncryptAndDecrypt('Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge Gnügge', algorithm.encryptionOptions, done);
});
});
});

it('should encrypt and decrypt xml (encryption: http://www.w3.org/2001/04/xmlenc#tripledes-cbc, keyEncryption: http://www.w3.org/2001/04/xmlenc#rsa-1_5)', function (done) {
function _shouldEncryptAndDecrypt(content, options, done) {
// cert created with:
// openssl req -x509 -new -newkey rsa:2048 -nodes -subj '/CN=auth0.auth0.com/O=Auth0 LLC/C=US/ST=Washington/L=Redmond' -keyout auth0.key -out auth0.pem
// pub key extracted from (only the RSA public key between BEGIN PUBLIC KEY and END PUBLIC KEY)
// openssl x509 -in "test-auth0.pem" -pubkey
// openssl x509 -in "test-auth0.pem" -pubkey

var 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#tripledes-cbc',
keyEncryptionAlgorighm: 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
};
options.rsa_pub = fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
options.pem = fs.readFileSync(__dirname + '/test-auth0.pem'),
options.key = fs.readFileSync(__dirname + '/test-auth0.key'),

xmlenc.encrypt('content to encrypt', options, function(err, result) {
xmlenc.decrypt(result, { key: fs.readFileSync(__dirname + '/test-auth0.key')}, function(err, decrypted) {
assert.equal(decrypted, 'content to encrypt');
xmlenc.encrypt(content, options, function(err, result) {
xmlenc.decrypt(result, { key: fs.readFileSync(__dirname + '/test-auth0.key')}, function (err, decrypted) {
assert.equal(decrypted, content);
done();
});
});
});
}

it('should encrypt and decrypt keyinfo', function (done) {
var options = {
Expand All @@ -122,7 +67,7 @@ describe('encrypt', function() {

crypto.randomBytes(32, function(err, randomBytes) {
if (err) return done(err);
xmlenc.encryptKeyInfo(randomBytes, options, function(err, result) {
xmlenc.encryptKeyInfo(randomBytes, options, function(err, result) {
if (err) return done(err);
var decryptedRandomBytes = xmlenc.decryptKeyInfo(result, { key: fs.readFileSync(__dirname + '/test-auth0.key')});

Expand Down