|
| 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