Skip to content

Commit e226c22

Browse files
adamjmcgrathelbuo8
andauthored
BREAKING CHANGE: Fix digest method algorithm id (#135)
* fix!: digest support * chore: add tests * chore: comment and test updates --------- Co-authored-by: Yamil Asusta <yamil.asusta@okta.com>
1 parent 099bfa3 commit e226c22

3 files changed

Lines changed: 171 additions & 5 deletions

File tree

lib/templates/keyinfo.tpl.xml.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
var escapehtml = require('escape-html');
22

3-
module.exports = ({ encryptionPublicCert, encryptedKey, keyEncryptionMethod, keyEncryptionDigest }) => `
3+
const DIGEST_ALGORITHMS = {
4+
// SHA-2 was published after 2000/09/xmldsig was locked, so sha256/sha512 live under 2001/04/xmlenc.
5+
'sha1': 'http://www.w3.org/2000/09/xmldsig#sha1',
6+
'sha256': 'http://www.w3.org/2001/04/xmlenc#sha256',
7+
'sha512': 'http://www.w3.org/2001/04/xmlenc#sha512'
8+
};
9+
10+
module.exports = ({ encryptionPublicCert, encryptedKey, keyEncryptionMethod, keyEncryptionDigest }) => {
11+
const digestUri = DIGEST_ALGORITHMS[keyEncryptionDigest] || keyEncryptionDigest;
12+
13+
// RSA-1.5 doesn't hash the key, so it has no digest or DigestMethod. RSA-OAEP does.
14+
const isOAEP = keyEncryptionMethod && keyEncryptionMethod.includes('rsa-oaep');
15+
return `
416
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
517
<e:EncryptedKey xmlns:e="http://www.w3.org/2001/04/xmlenc#">
618
<e:EncryptionMethod Algorithm="${escapehtml(keyEncryptionMethod)}">
7-
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#${escapehtml(keyEncryptionDigest)}" />
19+
${isOAEP ? `<DigestMethod Algorithm="${escapehtml(digestUri)}" />` : ''}
820
</e:EncryptionMethod>
921
<KeyInfo>
1022
${encryptionPublicCert}
@@ -15,4 +27,4 @@ module.exports = ({ encryptionPublicCert, encryptedKey, keyEncryptionMethod, key
1527
</e:EncryptedKey>
1628
</KeyInfo>
1729
`;
18-
30+
}

lib/xmlenc.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,12 @@ function decryptKeyInfo(doc, options) {
255255
if (keyDigestMethod) {
256256
const keyDigestMethodAlgorithm = keyDigestMethod.getAttribute('Algorithm');
257257
switch (keyDigestMethodAlgorithm) {
258-
case 'http://www.w3.org/2000/09/xmldsig#sha256':
258+
case 'http://www.w3.org/2001/04/xmlenc#sha256':
259+
case 'http://www.w3.org/2000/09/xmldsig#sha256': // backwards compatibility for previous wrong usage
259260
oaepHash = 'sha256';
260261
break;
261-
case 'http://www.w3.org/2000/09/xmldsig#sha512':
262+
case 'http://www.w3.org/2001/04/xmlenc#sha512':
263+
case 'http://www.w3.org/2000/09/xmldsig#sha512': // backwards compatibility for previous wrong usage
262264
oaepHash = 'sha512';
263265
break;
264266
}

test/xmlenc.digest.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
var assert = require('assert');
2+
var fs = require('fs');
3+
var xmlenc = require('../lib');
4+
var keyinfoTemplate = require('../lib/templates/keyinfo.tpl.xml');
5+
6+
var RSA_OAEP = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
7+
var RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
8+
9+
describe('keyEncryptionDigest', function () {
10+
11+
describe('keyinfo template DigestMethod', function () {
12+
function render(overrides) {
13+
return keyinfoTemplate(Object.assign({
14+
encryptionPublicCert: '<X509Data></X509Data>',
15+
encryptedKey: 'ZW5jcnlwdGVkS2V5',
16+
keyEncryptionMethod: RSA_OAEP,
17+
keyEncryptionDigest: 'sha1'
18+
}, overrides));
19+
}
20+
21+
it('maps sha1 to the xmldsig sha1 URI', function () {
22+
var xml = render({ keyEncryptionDigest: 'sha1' });
23+
assert(xml.includes('<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />'));
24+
});
25+
26+
it('maps sha256 to the xmlenc sha256 URI', function () {
27+
var xml = render({ keyEncryptionDigest: 'sha256' });
28+
assert(xml.includes('<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />'));
29+
});
30+
31+
it('maps sha512 to the xmlenc sha512 URI', function () {
32+
var xml = render({ keyEncryptionDigest: 'sha512' });
33+
assert(xml.includes('<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512" />'));
34+
});
35+
36+
it('passes through an unknown digest value unchanged', function () {
37+
var custom = 'http://example.org/custom#sha3-256';
38+
var xml = render({ keyEncryptionDigest: custom });
39+
assert(xml.includes('<DigestMethod Algorithm="' + custom + '" />'));
40+
});
41+
42+
it('does NOT include a DigestMethod for RSA-1.5', function () {
43+
var xml = render({ keyEncryptionMethod: RSA_1_5, keyEncryptionDigest: 'sha256' });
44+
assert(!xml.includes('<DigestMethod'));
45+
});
46+
});
47+
48+
describe('encrypt/decrypt round trip with digest', function () {
49+
function baseOptions() {
50+
return {
51+
rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
52+
pem: fs.readFileSync(__dirname + '/test-auth0.pem'),
53+
key: fs.readFileSync(__dirname + '/test-auth0.key'),
54+
encryptionAlgorithm: 'http://www.w3.org/2009/xmlenc11#aes256-gcm',
55+
keyEncryptionAlgorithm: RSA_OAEP
56+
};
57+
}
58+
59+
['sha1', 'sha256', 'sha512'].forEach(function (digest) {
60+
it('round trips with ' + digest, function (done) {
61+
var options = baseOptions();
62+
options.keyEncryptionDigest = digest;
63+
var content = 'content encrypted with ' + digest;
64+
65+
xmlenc.encrypt(content, options, function (err, result) {
66+
if (err) return done(err);
67+
xmlenc.decrypt(result, { key: fs.readFileSync(__dirname + '/test-auth0.key') }, function (err, decrypted) {
68+
if (err) return done(err);
69+
assert.equal(decrypted, content);
70+
done();
71+
});
72+
});
73+
});
74+
});
75+
76+
it('emits the correct xmldsig URI in the produced XML for sha1', function (done) {
77+
var options = baseOptions();
78+
options.keyEncryptionDigest = 'sha1';
79+
xmlenc.encrypt('content', options, function (err, result) {
80+
if (err) return done(err);
81+
assert(result.includes('http://www.w3.org/2000/09/xmldsig#sha1'));
82+
done();
83+
});
84+
});
85+
86+
it('emits the correct xmlenc URI in the produced XML for sha256', function (done) {
87+
var options = baseOptions();
88+
options.keyEncryptionDigest = 'sha256';
89+
xmlenc.encrypt('content', options, function (err, result) {
90+
if (err) return done(err);
91+
assert(result.includes('http://www.w3.org/2001/04/xmlenc#sha256'));
92+
assert(!result.includes('http://www.w3.org/2000/09/xmldsig#sha256'));
93+
done();
94+
});
95+
});
96+
97+
it('emits the correct xmlenc URI in the produced XML for sha512', function (done) {
98+
var options = baseOptions();
99+
options.keyEncryptionDigest = 'sha512';
100+
xmlenc.encrypt('content', options, function (err, result) {
101+
if (err) return done(err);
102+
assert(result.includes('http://www.w3.org/2001/04/xmlenc#sha512'));
103+
assert(!result.includes('http://www.w3.org/2000/09/xmldsig#sha512'));
104+
done();
105+
});
106+
});
107+
});
108+
109+
describe('decrypt backwards compatibility with old (wrong) xmldsig digest URIs', function () {
110+
function encryptWith(digest, cb) {
111+
var options = {
112+
rsa_pub: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
113+
pem: fs.readFileSync(__dirname + '/test-auth0.pem'),
114+
key: fs.readFileSync(__dirname + '/test-auth0.key'),
115+
encryptionAlgorithm: 'http://www.w3.org/2009/xmlenc11#aes256-gcm',
116+
keyEncryptionAlgorithm: RSA_OAEP,
117+
keyEncryptionDigest: digest
118+
};
119+
xmlenc.encrypt('legacy digest content', options, cb);
120+
}
121+
122+
it('decrypts a document using the legacy xmldsig#sha256 URI', function (done) {
123+
encryptWith('sha256', function (err, result) {
124+
if (err) return done(err);
125+
var legacy = result.replace(
126+
'http://www.w3.org/2001/04/xmlenc#sha256',
127+
'http://www.w3.org/2000/09/xmldsig#sha256'
128+
);
129+
xmlenc.decrypt(legacy, { key: fs.readFileSync(__dirname + '/test-auth0.key') }, function (err, decrypted) {
130+
if (err) return done(err);
131+
assert.equal(decrypted, 'legacy digest content');
132+
done();
133+
});
134+
});
135+
});
136+
137+
it('decrypts a document using the legacy xmldsig#sha512 URI', function (done) {
138+
encryptWith('sha512', function (err, result) {
139+
if (err) return done(err);
140+
var legacy = result.replace(
141+
'http://www.w3.org/2001/04/xmlenc#sha512',
142+
'http://www.w3.org/2000/09/xmldsig#sha512'
143+
);
144+
xmlenc.decrypt(legacy, { key: fs.readFileSync(__dirname + '/test-auth0.key') }, function (err, decrypted) {
145+
if (err) return done(err);
146+
assert.equal(decrypted, 'legacy digest content');
147+
done();
148+
});
149+
});
150+
});
151+
});
152+
});

0 commit comments

Comments
 (0)