Skip to content

Commit cfbe212

Browse files
committed
ownership: upgrade sha1 key algorithms
1 parent 1bc5d05 commit cfbe212

File tree

4 files changed

+235
-1
lines changed

4 files changed

+235
-1
lines changed

bin/bns-prove

+18-1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,23 @@ if (!txt)
146146
if (!priv)
147147
continue;
148148

149+
let signingKey = key;
150+
151+
// Upgrade SHA1 algorithms to SHA256
152+
if (ctx.isSHA1(key.data.algorithm)) {
153+
signingKey = dnssec.upgradeDNSKEY(key);
154+
155+
console.log(`Upgrading key algorithm for key ID ${key.data.keyTag()}`);
156+
157+
// Insert upgraded key into DNSKEY RRset and re-sign with upgraded key
158+
zone.keys.length = 0;
159+
zone.keys.push(key);
160+
zone.keys.push(signingKey);
161+
zone.keys.push(
162+
dnssec.sign(signingKey, priv, [key, signingKey],lifespan)
163+
);
164+
}
165+
149166
const rr = new Record();
150167
const rd = new TXTRecord();
151168

@@ -157,7 +174,7 @@ if (!txt)
157174

158175
rd.txt.push(txt);
159176

160-
const sig = dnssec.sign(key, priv, [rr], lifespan);
177+
const sig = dnssec.sign(signingKey, priv, [rr], lifespan);
161178

162179
zone.claim.push(rr);
163180
zone.claim.push(sig);

lib/dnssec.js

+15
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,21 @@ dnssec.stripSignatures = function stripSignatures(msg) {
705705
return msg;
706706
};
707707

708+
dnssec.upgradeDNSKEY = function upgradeDNSKEY(ksk) {
709+
assert(ksk instanceof Record);
710+
assert(ksk.type === types.DNSKEY);
711+
const key = ksk.deepClone();
712+
switch(key.data.algorithm) {
713+
case algs.RSASHA1:
714+
case algs.RSASHA1NSEC3SHA1:
715+
key.data.algorithm = algs.RSASHA256;
716+
break;
717+
default:
718+
throw new Error('Algorithm is not SHA1.');
719+
}
720+
return key;
721+
};
722+
708723
/*
709724
* Helpers
710725
*/
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Private-key-format: v1.3
2+
Algorithm: 5 (RSASHA1)
3+
Modulus: vExWE8PxRiW0zoaec/vit/SiAq2qr0lI9Se4ZbyVbBGVRckLGqeXx1t6z1yAvfUUGc/UXiz6EtZ8kPrVIx4RfRpur+V0pLjHtw9VEA6ocjm9tGirsYcR+HDPiT+ebJn4YxcLjqQ3rMNGpIOon9oNErpdtLJT88Piif0+Ix5Zgt8x7OCCr4dB9vfDIJU+JwkdaXaIJmxNqrXL23q3uZrdL8n8GurzYqb+OGj+kpVa9htxrJ9IPPhV1+IzXLGtMsm91uZgV29BnhWKS2I7IRnvP48izPPUaYmZNKNaXEl7PLfukJB/3jeBaHb3UR36p1y9tlj82FuiCsNZ82OgLMBMeQ==
4+
PublicExponent: AQAB
5+
PrivateExponent: gVUVUmIlWH8Rn0ELmLKL2LoohRpvwBHgELMFjqtnHmE9XD9oZxhxwbZttfvdWZv6AHilb0IJSeMUkCgZOROwA3OiTyKVaYaZdn0Legn3XO2YBVBXjYoup6wkgCq4T/O5jaYIhCL7Mqi13s2nhpQoopJrRm+uBiKneQv+H/T6mk4wwJH9mX9OrDgtHWXlSm1Pn+axmdo4QOmitLoSHYGb0bHC8QuDp3jQqrjdyKHaZjQalhxjI3J/YU9Gcqg/7zV3G+/z9/57SIXiT3pYhrKyHtXVPAnEkY7Mlv4mChBB2kXVz+sjQBzRWxLwEupzOt/MTciy2zU3BmTaCqmzRx9g4Q==
6+
Prime1: 7pM3ICtPxu2TyUo0BBFP/MC1x/4dtthGNb9u/NvtaDz7hebbVpivma6sUYy2Cr/qU6LKtla8S4n/2ZFDDBZ/m7dOYH7S4pTamR1sg31utYaaXXz/NaOqvflLhL9zHIG0cVFXBJbBk1TxQCPnPMBxxIAO71YqlxpkJDpmc7dSaQ0=
7+
Prime2: yg0QNP0BiBjU+K3+2OD3TILotg3UKuh9vPNVP4yAThBb8Tj42T+v5Lp0ngAbjorNTcwwFHcWaNkW+xIERt7Qszpc168aidTkBtzKHq8auPUXjYLTqmPOMTjkuKQpJA8mkSFrK0EXOD+PzEbTXT9hf2tJmwQYqpnzfksDTgIOfh0=
8+
Exponent1: FZRrYBWK2cuTmpmDqvqF6a7kwnpR5cAaWbI/L9AU4WDiv3HqVSLqf1Q1cgrwTHifkYEgJO4jihGpetyQ2/8M++DQzhmqqaQdQcxPjHVEFldejyZHT2rGfviVxCQtHV7+G2HoJRod2F3OlKaqu+wMXmHW9/8rSw+wDzbZHY5vfeE=
9+
Exponent2: sw4MlMi5+L/4zEB1ngEPZvCEBgIPSc8qzq3dmCmM8qEwuX7BmNUFrW9HNVXnFdUPx6fx29EFPPlVkj4PKDcBQDOyXzlADXFqemJjg/mtdsdMjg2oQlGcQqammrJv+xKqHOc+r+BJEYHSjzcczIAEiQStAAELUvSHgh472X+dFEk=
10+
Coefficient: s0ZyxTFjS5t/G95NNUd4eUpX6rfw4NIl93O6ZfEB0VLoqw3B0XOShE4jR5bDx5kQv0y023jVct69rrrw9qsJP7dYor5JnjJq7Wx6RGYEYXs0crD9JLI8Jk+teVGkxA0iFbhEp/cScS48D+cWHW4wC7aTIOyaciYVZTnaarZuLhA=
11+
Created: 20220429130726
12+
Publish: 20220429130726
13+
Activate: 20220429130726

test/proof-key-upgrade-test.js

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/* eslint-env mocha */
2+
/* eslint prefer-arrow-callback: "off" */
3+
4+
'use strict';
5+
6+
const assert = require('bsert');
7+
const path = require('path');
8+
const Ownership = require('../lib/ownership');
9+
const dnssec = require('../lib/dnssec');
10+
const {
11+
types,
12+
Message,
13+
Record
14+
} = require('../lib/wire');
15+
16+
describe('Ownership Proof Key Upgrade', function () {
17+
// Create a fake stub resolver with our own root zone and one TLD.
18+
// Root zone is signed with ED25519 but weakkeytld is signed with RSASHA1.
19+
// The RRSIGs in these zones expire on Dec. 31 2080. So, you know. Heads up.
20+
class Stub {
21+
lookup (name, type) {
22+
const msg = new Message();
23+
switch (name) {
24+
case 'weakkeytld.':
25+
switch (type) {
26+
case types.DS:
27+
msg.answer.push(Record.fromString(
28+
'weakkeytld. DS 3810 5 2 ' +
29+
'BE06B4A3C4E50914DDE8670D68EFE6E04' +
30+
'24941B3F9BD364210BDFAFD6AF23526'
31+
));
32+
msg.answer.push(Record.fromString(
33+
'weakkeytld. 300 RRSIG DS 15 1 300 ' +
34+
'20801231000000 20220429134556 28750 . ' +
35+
'3LfCEc+Yyx9OjWONSx41iphYjqJSKN2mUXM2' +
36+
'A1MCLBqx93b+T7FGPJUxAmumxR6T5v/VZ37+' +
37+
'9d6LwTfOxgVfBg=='
38+
39+
));
40+
break;
41+
case types.TXT:
42+
msg.answer.push(Record.fromString(
43+
'weakkeytld. 300 TXT "hns-claim"'
44+
));
45+
msg.answer.push(Record.fromString(
46+
'weakkeytld. 300 RRSIG TXT 5 1 300 ' +
47+
'20801231000000 20220429134807 3810 weakkeytld. ' +
48+
'K9VKxanJnr8jI9zGtStG37TIIBmks9w2LH2Y' +
49+
'jD5SKkKiBPB4Orc9sLLlEm1FL5eOl8er4xYK' +
50+
'wue68e5xJn3njkuwmEx2SpalDKCubQrxuIQJ' +
51+
'dEHiAr2+bl++vvVn4V30s6rcU5rK4XmnjzUt' +
52+
'z12dB+++EMW3K17tbbhzVgFB5T/iBeCgrsjw' +
53+
'M+iKc0DYH8xo/yE4y/y64SjzfNy9Q2zxA1Zm' +
54+
'wkgqxVTKxw1W1GwuQiUkmy8xTTpSwFBpadOJ' +
55+
'GHmjUTiopRMr46aqO1p9VB/vO5V1hNzVa9Zp' +
56+
'G7aoAOANhqYSQ7um2//oCPr4xRQHgaCf0wdz' +
57+
'yczVBX4oLOLk4uvUeg=='
58+
));
59+
break;
60+
case types.DNSKEY:
61+
msg.answer.push(Record.fromString(
62+
'weakkeytld. 300 DNSKEY 256 3 5 ' +
63+
'AwEAAbxMVhPD8UYltM6GnnP74rf0ogKtqq9J' +
64+
'SPUnuGW8lWwRlUXJCxqnl8dbes9cgL31FBnP' +
65+
'1F4s+hLWfJD61SMeEX0abq/ldKS4x7cPVRAO' +
66+
'qHI5vbRoq7GHEfhwz4k/nmyZ+GMXC46kN6zD' +
67+
'RqSDqJ/aDRK6XbSyU/PD4on9PiMeWYLfMezg' +
68+
'gq+HQfb3wyCVPicJHWl2iCZsTaq1y9t6t7ma' +
69+
'3S/J/Brq82Km/jho/pKVWvYbcayfSDz4Vdfi' +
70+
'M1yxrTLJvdbmYFdvQZ4ViktiOyEZ7z+PIszz' +
71+
'1GmJmTSjWlxJezy37pCQf943gWh291Ed+qdc' +
72+
'vbZY/NhbogrDWfNjoCzATHk='
73+
));
74+
msg.answer.push(Record.fromString(
75+
'weakkeytld. 300 RRSIG DNSKEY 5 1 300 ' +
76+
'20801231000000 20220429134807 3810 weakkeytld. ' +
77+
'kiTnUHoXHB8MC42iJImgIi2U4+xrUILV7sKw' +
78+
'f3KA6NK4AaMgReRqMCo67IOt/vwu6g47qeaq' +
79+
'/47AYXGA4vXYXdv49EOeUQvwB7AD/tAMHSy0' +
80+
'+TwyJOHgja0Fl0kgkGoIhkWB8MSWHT86E0qN' +
81+
'OnoO9rFFgHx91hgxZM5Nll/pEr/kQuDTJK/o' +
82+
'ixjluxGkkN+AgIN3spZTWx4fN2hOvWriLv8+' +
83+
'CwFGdqZuOa+lSpXh+EcnVBOJhagCrrxpsyN8' +
84+
'8NI+E5nsxyNS8rWRWnE7gX3GQi+xBFbe2RzJ' +
85+
'pQW+D+MDS8ofo9QWTIV7ho6xSjzfq8uKi/nt' +
86+
'2PBkdBNo35e1c30YLw=='
87+
));
88+
break;
89+
}
90+
break;
91+
92+
case '.':
93+
switch (type) {
94+
case types.DNSKEY:
95+
msg.answer.push(Record.fromString(
96+
'. 300 DNSKEY 257 3 15 '+
97+
'2cUGwCbFjpVkvUS1ZLH0sA+K4K4nExUtMjr7iCvoTWQ='
98+
));
99+
msg.answer.push(Record.fromString(
100+
'. 300 RRSIG DNSKEY 15 0 300 ' +
101+
'20801231000000 20220429134556 28750 . ' +
102+
'KeXA54XobZC8MV3OTgtUdTNvd5nt40lFDech' +
103+
'LUt+ngF5cvZV5CnSoFWgMc4/LEKRVVIhAKzc' +
104+
'v6PqrzgJCuTTDA=='
105+
));
106+
break;
107+
}
108+
}
109+
return msg;
110+
}
111+
};
112+
113+
const ownership = new Ownership();
114+
115+
// Trust anchor for our fake local root zone
116+
ownership.anchors = [Record.fromString(
117+
'. DS 28750 15 2 '+
118+
'3B194170CF9EDF967B50CA146386F08E573E52919983BAF03B8E66E2A43D7900'
119+
)];
120+
121+
// Hack instanceof assertion in ownership._prove()
122+
ownership.Resolver = Stub;
123+
124+
it('should fail by default to generate RSASHA1 proof', async () => {
125+
// They don't even count as actual RRSIGs
126+
await assert.rejects(
127+
ownership._prove(new Stub(), 'weakkeytld.', false),
128+
{message: 'No RRSIG(TXT) records for weakkeytld.'}
129+
);
130+
131+
// Sanity check
132+
const res = new Stub().lookup('weakkeytld.', types.TXT);
133+
assert(res.answer[1].type === types.RRSIG);
134+
assert(ownership.isSHA1(res.answer[1].data.algorithm));
135+
});
136+
137+
it('should generate RSASHA1 proof with secure=false', async () => {
138+
try {
139+
ownership.secure = false;
140+
const proof = await ownership._prove(new Stub(), 'weakkeytld.', false);
141+
assert(proof.zones[1].claim[1].type === types.RRSIG);
142+
assert(ownership.isSHA1(proof.zones[1].claim[1].data.algorithm));
143+
} finally {
144+
ownership.secure = true; // default
145+
}
146+
});
147+
148+
it('should upgrade weak key algorithm', async () => {
149+
let proof, target;
150+
try {
151+
ownership.secure = false; // need secure=false just to get proof template
152+
proof = await ownership._prove(new Stub(), 'weakkeytld.', true);
153+
target = proof.zones[1];
154+
const key = target.keys[0];
155+
const txtRR = proof.zones[1].claim[0];
156+
157+
assert(key.type === types.DNSKEY);
158+
assert(txtRR.type === types.TXT);
159+
160+
// Kweakkeytld.+005+03810.private
161+
const priv = await dnssec.readPrivateAsync(
162+
path.join(__dirname, 'data'),
163+
key
164+
);
165+
166+
// Here's the sneaky magic: create a duplicate key with better algorithm.
167+
const key256 = dnssec.upgradeDNSKEY(key);
168+
169+
// Sign DNSKEY RRset now including both old and new keys.
170+
const keySig = dnssec.sign(key256, priv, [key256, key], 24 * 60 * 60);
171+
target.keys[0] = key;
172+
target.keys[1] = key256;
173+
target.keys[2] = keySig;
174+
175+
// Now sign the claim TXT with the new key
176+
const txtSig = dnssec.sign(key256, priv, [txtRR], 24 * 60 * 60);
177+
target.claim[1] = txtSig;
178+
} finally {
179+
ownership.secure = true; // default, and required by HNS consenus rules
180+
}
181+
182+
assert(ownership.isSane(proof));
183+
assert(ownership.verifySignatures(proof));
184+
185+
// Sanity check
186+
assert(target.claim[1].type === types.RRSIG);
187+
assert(!ownership.isSHA1(target.claim[1].data.algorithm));
188+
});
189+
});

0 commit comments

Comments
 (0)