mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-23 00:56:20 +00:00
net/udprelay: use blake2s-256 MAC for handshake challenge
This commit replaces crypto/rand challenge generation with a blake2s-256 MAC. This enables the peer relay server to respond to multiple forward disco.BindUDPRelayEndpoint messages per handshake generation without sacrificing the proof of IP ownership properties of the handshake. Responding to multiple forward disco.BindUDPRelayEndpoint messages per handshake generation improves client address/path selection where lowest client->server path/addr one-way delay does not necessarily equate to lowest client<->server round trip delay. It also improves situations where outbound traffic is filtered independent of input, and the first reply disco.BindUDPRelayEndpointChallenge message is dropped on the reply path, but a later reply using a different source would make it through. Reduction in serverEndpoint state saves 112 bytes per instance, trading for slightly more expensive crypto ops: 277ns/op vs 321ns/op on an M1 Macbook Pro. Updates tailscale/corp#34414 Signed-off-by: Jordan Whited <jordan@tailscale.com>
This commit is contained in:
committed by
Jordan Whited
parent
6637003cc8
commit
755309c04e
@@ -5,6 +5,7 @@ package udprelay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"go4.org/mem"
|
||||
"golang.org/x/crypto/blake2s"
|
||||
"tailscale.com/disco"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/types/key"
|
||||
@@ -352,3 +354,117 @@ func TestServer_getNextVNILocked(t *testing.T) {
|
||||
_, err = s.getNextVNILocked()
|
||||
c.Assert(err, qt.IsNil)
|
||||
}
|
||||
|
||||
func Test_blakeMACFromBindMsg(t *testing.T) {
|
||||
var macSecret [blake2s.Size]byte
|
||||
rand.Read(macSecret[:])
|
||||
src := netip.MustParseAddrPort("[2001:db8::1]:7")
|
||||
|
||||
msgA := disco.BindUDPRelayEndpointCommon{
|
||||
VNI: 1,
|
||||
Generation: 1,
|
||||
RemoteKey: key.NewDisco().Public(),
|
||||
Challenge: [32]byte{},
|
||||
}
|
||||
macA, err := blakeMACFromBindMsg(macSecret, src, msgA)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
msgB := msgA
|
||||
msgB.VNI++
|
||||
macB, err := blakeMACFromBindMsg(macSecret, src, msgB)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if macA == macB {
|
||||
t.Fatalf("varying VNI input produced identical mac: %v", macA)
|
||||
}
|
||||
|
||||
msgC := msgA
|
||||
msgC.Generation++
|
||||
macC, err := blakeMACFromBindMsg(macSecret, src, msgC)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if macA == macC {
|
||||
t.Fatalf("varying Generation input produced identical mac: %v", macA)
|
||||
}
|
||||
|
||||
msgD := msgA
|
||||
msgD.RemoteKey = key.NewDisco().Public()
|
||||
macD, err := blakeMACFromBindMsg(macSecret, src, msgD)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if macA == macD {
|
||||
t.Fatalf("varying RemoteKey input produced identical mac: %v", macA)
|
||||
}
|
||||
|
||||
msgE := msgA
|
||||
msgE.Challenge = [32]byte{0x01} // challenge is not part of the MAC and should be ignored
|
||||
macE, err := blakeMACFromBindMsg(macSecret, src, msgE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if macA != macE {
|
||||
t.Fatalf("varying Challenge input produced varying mac: %v", macA)
|
||||
}
|
||||
|
||||
macSecretB := macSecret
|
||||
macSecretB[0] ^= 0xFF
|
||||
macF, err := blakeMACFromBindMsg(macSecretB, src, msgA)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if macA == macF {
|
||||
t.Fatalf("varying macSecret input produced identical mac: %v", macA)
|
||||
}
|
||||
|
||||
srcB := netip.AddrPortFrom(src.Addr(), src.Port()+1)
|
||||
macG, err := blakeMACFromBindMsg(macSecret, srcB, msgA)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if macA == macG {
|
||||
t.Fatalf("varying src input produced identical mac: %v", macA)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_blakeMACFromBindMsg(b *testing.B) {
|
||||
var macSecret [blake2s.Size]byte
|
||||
rand.Read(macSecret[:])
|
||||
src := netip.MustParseAddrPort("[2001:db8::1]:7")
|
||||
msg := disco.BindUDPRelayEndpointCommon{
|
||||
VNI: 1,
|
||||
Generation: 1,
|
||||
RemoteKey: key.NewDisco().Public(),
|
||||
Challenge: [32]byte{},
|
||||
}
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
_, err := blakeMACFromBindMsg(macSecret, src, msg)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_maybeRotateMACSecretLocked(t *testing.T) {
|
||||
s := &Server{}
|
||||
start := time.Now()
|
||||
s.maybeRotateMACSecretLocked(start)
|
||||
qt.Assert(t, len(s.macSecrets), qt.Equals, 1)
|
||||
macSecret := s.macSecrets[0]
|
||||
s.maybeRotateMACSecretLocked(start.Add(macSecretRotationInterval - time.Nanosecond))
|
||||
qt.Assert(t, len(s.macSecrets), qt.Equals, 1)
|
||||
qt.Assert(t, s.macSecrets[0], qt.Equals, macSecret)
|
||||
s.maybeRotateMACSecretLocked(start.Add(macSecretRotationInterval))
|
||||
qt.Assert(t, len(s.macSecrets), qt.Equals, 2)
|
||||
qt.Assert(t, s.macSecrets[1], qt.Equals, macSecret)
|
||||
qt.Assert(t, s.macSecrets[0], qt.Not(qt.Equals), s.macSecrets[1])
|
||||
s.maybeRotateMACSecretLocked(s.macSecretRotatedAt.Add(macSecretRotationInterval))
|
||||
qt.Assert(t, macSecret, qt.Not(qt.Equals), s.macSecrets[0])
|
||||
qt.Assert(t, macSecret, qt.Not(qt.Equals), s.macSecrets[1])
|
||||
qt.Assert(t, s.macSecrets[0], qt.Not(qt.Equals), s.macSecrets[1])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user