1package dns 2 3import ( 4 "encoding/binary" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "strings" 9 "testing" 10 "time" 11) 12 13func newTsig(algo string) *Msg { 14 m := new(Msg) 15 m.SetQuestion("example.org.", TypeA) 16 m.SetTsig("example.", algo, 300, time.Now().Unix()) 17 return m 18} 19 20func TestTsig(t *testing.T) { 21 m := newTsig(HmacSHA256) 22 buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 23 if err != nil { 24 t.Fatal(err) 25 } 26 err = TsigVerify(buf, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 27 if err != nil { 28 t.Fatal(err) 29 } 30 31 // TSIG accounts for ID substitution. This means if the message ID is 32 // changed by a forwarder, we should still be able to verify the TSIG. 33 m = newTsig(HmacSHA256) 34 buf, _, err = TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 35 if err != nil { 36 t.Fatal(err) 37 } 38 39 binary.BigEndian.PutUint16(buf[0:2], 42) 40 err = TsigVerify(buf, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 41 if err != nil { 42 t.Fatal(err) 43 } 44} 45 46func TestTsigCase(t *testing.T) { 47 m := newTsig(strings.ToUpper(HmacSHA256)) 48 buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 49 if err != nil { 50 t.Fatal(err) 51 } 52 err = TsigVerify(buf, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 53 if err != nil { 54 t.Fatal(err) 55 } 56} 57 58const ( 59 // A template wire-format DNS message (in hex form) containing a TSIG RR. 60 // Its time signed field will be filled by tests. 61 wireMsg = "c60028000001000000010001076578616d706c6503636f6d00000600010161c00c0001000100000e100004c0000201077465" + 62 "73746b65790000fa00ff00000000003d0b686d61632d73686132353600" + 63 "%012x" + // placeholder for the "time signed" field 64 "012c00208cf23e0081d915478a182edcea7ff48ad102948e6c7ef8e887536957d1fa5616c60000000000" 65 // A secret (in base64 format) with which the TSIG in wireMsg will be validated 66 testSecret = "NoTCJU+DMqFWywaPyxSijrDEA/eC3nK0xi3AMEZuPVk=" 67 // the 'time signed' field value that would make the TSIG RR valid with testSecret 68 timeSigned uint64 = 1594855491 69) 70 71func TestTsigErrors(t *testing.T) { 72 // Helper shortcut to build wire-format test message. 73 // TsigVerify can modify the slice, so we need to create a new one for each test case below. 74 buildMsgData := func(tm uint64) []byte { 75 msgData, err := hex.DecodeString(fmt.Sprintf(wireMsg, tm)) 76 if err != nil { 77 t.Fatal(err) 78 } 79 return msgData 80 } 81 82 // the signature is valid but 'time signed' is too far from the "current time". 83 if err := tsigVerify(buildMsgData(timeSigned), tsigHMACProvider(testSecret), "", false, timeSigned+301); err != ErrTime { 84 t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err) 85 } 86 if err := tsigVerify(buildMsgData(timeSigned), tsigHMACProvider(testSecret), "", false, timeSigned-301); err != ErrTime { 87 t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err) 88 } 89 90 // the signature is invalid and 'time signed' is too far. 91 // the signature should be checked first, so we should see ErrSig. 92 if err := tsigVerify(buildMsgData(timeSigned+301), tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrSig { 93 t.Fatalf("expected an error '%v' but got '%v'", ErrSig, err) 94 } 95 96 // tweak the algorithm name in the wire data, resulting in the "unknown algorithm" error. 97 msgData := buildMsgData(timeSigned) 98 copy(msgData[67:], "bogus") 99 if err := tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrKeyAlg { 100 t.Fatalf("expected an error '%v' but got '%v'", ErrKeyAlg, err) 101 } 102 103 // call TsigVerify with a message that doesn't contain a TSIG 104 msgData, tsig, err := stripTsig(buildMsgData(timeSigned)) 105 if err != nil { 106 t.Fatal(err) 107 } 108 if err := tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned); err != ErrNoSig { 109 t.Fatalf("expected an error '%v' but got '%v'", ErrNoSig, err) 110 } 111 112 // replace the test TSIG with a bogus one with large "other data", which would cause overflow in TsigVerify. 113 // The overflow should be caught without disruption. 114 tsig.OtherData = strings.Repeat("00", 4096) 115 tsig.OtherLen = uint16(len(tsig.OtherData) / 2) 116 msg := new(Msg) 117 if err = msg.Unpack(msgData); err != nil { 118 t.Fatal(err) 119 } 120 msg.Extra = append(msg.Extra, tsig) 121 if msgData, err = msg.Pack(); err != nil { 122 t.Fatal(err) 123 } 124 err = tsigVerify(msgData, tsigHMACProvider(testSecret), "", false, timeSigned) 125 if err == nil || !strings.Contains(err.Error(), "overflow") { 126 t.Errorf("expected error to contain %q, but got %v", "overflow", err) 127 } 128} 129 130// This test exercises some more corner cases for TsigGenerate. 131func TestTsigGenerate(t *testing.T) { 132 // This is a template TSIG to be used for signing. 133 tsig := TSIG{ 134 Hdr: RR_Header{Name: "testkey.", Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}, 135 Algorithm: HmacSHA256, 136 TimeSigned: timeSigned, 137 Fudge: 300, 138 OrigId: 42, 139 Error: RcodeBadTime, // use a non-0 value to make sure it's indeed used 140 } 141 142 tests := []struct { 143 desc string // test description 144 requestMAC string // request MAC to be passed to TsigGenerate (arbitrary choice) 145 otherData string // other data specified in the TSIG (arbitrary choice) 146 expectedMAC string // pre-computed expected (correct) MAC in hex form 147 }{ 148 {"with request MAC", "3684c225", "", 149 "c110e3f62694755c10761dc8717462431ee34340b7c9d1eee09449150757c5b1"}, 150 {"no request MAC", "", "", 151 "385449a425c6d52b9bf2c65c0726eefa0ad8084cdaf488f24547e686605b9610"}, 152 {"with other data", "3684c225", "666f6f", 153 "15b91571ca80b3b410a77e2b44f8cc4f35ace22b26020138439dd94803e23b5d"}, 154 } 155 for _, tc := range tests { 156 tc := tc 157 t.Run(tc.desc, func(t *testing.T) { 158 // Build TSIG for signing from the template 159 testTSIG := tsig 160 testTSIG.OtherLen = uint16(len(tc.otherData) / 2) 161 testTSIG.OtherData = tc.otherData 162 req := &Msg{ 163 MsgHdr: MsgHdr{Opcode: OpcodeUpdate}, 164 Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}}, 165 Extra: []RR{&testTSIG}, 166 } 167 168 // Call generate, and check the returned MAC against the expected value 169 msgData, mac, err := TsigGenerate(req, testSecret, tc.requestMAC, false) 170 if err != nil { 171 t.Error(err) 172 } 173 if mac != tc.expectedMAC { 174 t.Fatalf("MAC doesn't match: expected '%s', but got '%s'", tc.expectedMAC, mac) 175 } 176 177 // Retrieve the TSIG to be sent out, confirm the MAC in it 178 _, outTSIG, err := stripTsig(msgData) 179 if err != nil { 180 t.Error(err) 181 } 182 if outTSIG.MAC != tc.expectedMAC { 183 t.Fatalf("MAC doesn't match: expected '%s', but got '%s'", tc.expectedMAC, outTSIG.MAC) 184 } 185 // Confirm other fields of MAC. 186 // RDLENGTH should be valid as stripTsig succeeded, so we exclude it from comparison 187 outTSIG.MACSize = 0 188 outTSIG.MAC = "" 189 testTSIG.Hdr.Rdlength = outTSIG.Hdr.Rdlength 190 if *outTSIG != testTSIG { 191 t.Fatalf("TSIG RR doesn't match: expected '%v', but got '%v'", *outTSIG, testTSIG) 192 } 193 }) 194 } 195} 196 197func TestTSIGHMAC224And384(t *testing.T) { 198 tests := []struct { 199 algorithm string // TSIG algorithm, also used as test description 200 secret string // (arbitrarily chosen) secret suitable for the algorithm in base64 format 201 expectedMAC string // pre-computed expected (correct) MAC in hex form 202 }{ 203 {HmacSHA224, "hVEkQuAqnTmBuRrT9KF1Udr91gOMGWPw9LaTtw==", 204 "d6daf9ea189e48bc38f9aed63d6cc4140cdfa38a7a333ee2eefdbd31", 205 }, 206 {HmacSHA384, "Qjer2TL2lAdpq9w6Gjs98/ClCQx/L3vtgVHCmrZ8l/oKEPjqUUMFO18gMCRwd5H4", 207 "89a48936d29187870c325cbdba5ad71609bd038d0459d6010c844d659c570e881d3650e4fe7310be53ebe5178d0d1001", 208 }, 209 } 210 for _, tc := range tests { 211 tc := tc 212 t.Run(tc.algorithm, func(t *testing.T) { 213 // Build a DNS message with TSIG for the test scenario 214 tsig := TSIG{ 215 Hdr: RR_Header{Name: "testkey.", Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}, 216 Algorithm: tc.algorithm, 217 TimeSigned: timeSigned, 218 Fudge: 300, 219 OrigId: 42, 220 } 221 req := &Msg{ 222 MsgHdr: MsgHdr{Opcode: OpcodeUpdate}, 223 Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}}, 224 Extra: []RR{&tsig}, 225 } 226 227 // Confirm both Generate and Verify recognize the algorithm and handle it correctly 228 msgData, mac, err := TsigGenerate(req, tc.secret, "", false) 229 if err != nil { 230 t.Error(err) 231 } 232 if mac != tc.expectedMAC { 233 t.Fatalf("MAC doesn't match: expected '%s' but got '%s'", tc.expectedMAC, mac) 234 } 235 if err = tsigVerify(msgData, tsigHMACProvider(tc.secret), "", false, timeSigned); err != nil { 236 t.Error(err) 237 } 238 }) 239 } 240} 241 242const testGoodKeyName = "goodkey." 243 244var ( 245 errBadKey = errors.New("this is an intentional error") 246 testGoodMAC = []byte{0, 1, 2, 3} 247) 248 249// testProvider always generates the same MAC and only accepts the one signature 250type testProvider struct { 251 GenerateAllKeys bool 252} 253 254func (provider *testProvider) Generate(_ []byte, t *TSIG) ([]byte, error) { 255 if t.Hdr.Name == testGoodKeyName || provider.GenerateAllKeys { 256 return testGoodMAC, nil 257 } 258 return nil, errBadKey 259} 260 261func (*testProvider) Verify(_ []byte, t *TSIG) error { 262 if t.Hdr.Name == testGoodKeyName { 263 return nil 264 } 265 return errBadKey 266} 267 268func TestTsigGenerateProvider(t *testing.T) { 269 tables := []struct { 270 keyname string 271 mac []byte 272 err error 273 }{ 274 { 275 testGoodKeyName, 276 testGoodMAC, 277 nil, 278 }, 279 { 280 "badkey.", 281 nil, 282 errBadKey, 283 }, 284 } 285 286 for _, table := range tables { 287 t.Run(table.keyname, func(t *testing.T) { 288 tsig := TSIG{ 289 Hdr: RR_Header{Name: table.keyname, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}, 290 Algorithm: HmacSHA1, 291 TimeSigned: timeSigned, 292 Fudge: 300, 293 OrigId: 42, 294 } 295 req := &Msg{ 296 MsgHdr: MsgHdr{Opcode: OpcodeUpdate}, 297 Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}}, 298 Extra: []RR{&tsig}, 299 } 300 301 _, mac, err := tsigGenerateProvider(req, new(testProvider), "", false) 302 if err != table.err { 303 t.Fatalf("error doesn't match: expected '%s' but got '%s'", table.err, err) 304 } 305 expectedMAC := hex.EncodeToString(table.mac) 306 if mac != expectedMAC { 307 t.Fatalf("MAC doesn't match: expected '%s' but got '%s'", table.mac, expectedMAC) 308 } 309 }) 310 } 311} 312 313func TestTsigVerifyProvider(t *testing.T) { 314 tables := []struct { 315 keyname string 316 err error 317 }{ 318 { 319 testGoodKeyName, 320 nil, 321 }, 322 { 323 "badkey.", 324 errBadKey, 325 }, 326 } 327 328 for _, table := range tables { 329 t.Run(table.keyname, func(t *testing.T) { 330 tsig := TSIG{ 331 Hdr: RR_Header{Name: table.keyname, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}, 332 Algorithm: HmacSHA1, 333 TimeSigned: timeSigned, 334 Fudge: 300, 335 OrigId: 42, 336 } 337 req := &Msg{ 338 MsgHdr: MsgHdr{Opcode: OpcodeUpdate}, 339 Question: []Question{{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}}, 340 Extra: []RR{&tsig}, 341 } 342 343 provider := &testProvider{true} 344 msgData, _, err := tsigGenerateProvider(req, provider, "", false) 345 if err != nil { 346 t.Error(err) 347 } 348 if err = tsigVerify(msgData, provider, "", false, timeSigned); err != table.err { 349 t.Fatalf("error doesn't match: expected '%s' but got '%s'", table.err, err) 350 } 351 }) 352 } 353} 354