diff --git a/gumble/gumble/udp15_test.go b/gumble/gumble/udp15_test.go index e51adde..e38599f 100644 --- a/gumble/gumble/udp15_test.go +++ b/gumble/gumble/udp15_test.go @@ -128,6 +128,31 @@ func TestCryptState15RoundTrip(t *testing.T) { t.Logf("Server IV after 2 decrypts: %s", hex.EncodeToString(csServer.decryptIV[:])) } +// Regression coverage for the native UDP replay window: a captured packet +// must not be accepted twice after its IV byte has entered history. +func TestCryptState15RejectsReplay(t *testing.T) { + key := mustDecodeHex("93360b0f86a926c4561563469026eb94") + clientNonce := mustDecodeHex("d4e53c00a2f6512a61cbe8540eba6314") + serverNonce := mustDecodeHex("1463352a4d2375a2695ae0800b22d71d") + out, in := &cryptState15{}, &cryptState15{} + if err := out.setup15(key, clientNonce, serverNonce); err != nil { + t.Fatal(err) + } + if err := in.setup15(key, serverNonce, clientNonce); err != nil { + t.Fatal(err) + } + packet, err := out.encrypt15([]byte("captured frame")) + if err != nil { + t.Fatal(err) + } + if _, err := in.decrypt15(packet); err != nil { + t.Fatal(err) + } + if _, err := in.decrypt15(packet); err == nil { + t.Fatal("replayed UDP packet was accepted") + } +} + // Test protobuf encode/decode round-trip. func TestUDPAudioProtobuf(t *testing.T) { tests := []struct {