Skip to content

Commit

Permalink
Improve the cookie mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
weiiwang01 committed Sep 29, 2023
1 parent 7342f3c commit 9df2c13
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 254 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
wpex
74 changes: 40 additions & 34 deletions internal/analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package analyzer

import (
"context"
"crypto/rand"
"encoding/binary"
"fmt"
"github.com/weiiwang01/wpex/internal/exchange"
"log/slog"
"net"
"time"
)

const (
Expand All @@ -20,7 +20,7 @@ const (

type WireguardAnalyzer struct {
table exchange.ExchangeTable
checker EndpointChecker
checker macChecker
}

func (t *WireguardAnalyzer) decodeIndex(index []byte) uint32 {
Expand All @@ -33,32 +33,32 @@ func (t *WireguardAnalyzer) analyseHandshakeInitiation(packet []byte, peer net.U
logger.Warn(fmt.Sprintf("invalid handshake initiation: expected length %d, got %d", handshakeInitiationSize, len(packet)))
return nil, nil
}
sender := t.decodeIndex(packet[4:8])
if t.table.Contains(peer) {
goto send
}
switch t.checker.VerifyHandshakeInitiation(peer, packet) {
case illegalEndpoint:
return nil, nil
case endpointNotVerified:
logger.Debug("send cookie reply to unknown endpoint")
reply, err := t.checker.CreateReply(peer, packet)
if err != nil {
logger.Error(fmt.Sprintf("failed to create cookie reply: %s", err))

if t.checker.RequireCheck() {
pubkey := t.checker.MatchPubkey(packet)
if pubkey == nil {
logger.Warn("invalid mac1 in handshake initiation")
return nil, nil
}
return []net.UDPAddr{peer}, reply
case endpointPendingVerification:
logger.Debug("ignore handshake initiation from endpoint pending verification")
return nil, nil
case endpointVerified:
newPacket := make([]byte, handshakeInitiationSize)
copy(newPacket, packet[:handshakeInitiationSize-macSize])
packet = newPacket
default:
panic("unknown endpoint verification status")
mac2Ok := t.checker.VerifyMac2(peer, packet)
known := t.table.Contains(peer)
if !mac2Ok && !known {
logger.Debug("send cookie reply to handshake initiation from unknown endpoint")
reply, err := t.checker.CreateReply(pubkey, peer, packet)
if err != nil {
logger.Error(fmt.Sprintf("failed to create cookie reply: %s", err))
return nil, nil
}
return []net.UDPAddr{peer}, reply
}
if mac2Ok {
logger.Debug("strip mac2 from handshake initiation")
newPacket := make([]byte, handshakeInitiationSize)
copy(newPacket, packet[:handshakeInitiationSize-macSize])
packet = newPacket
}
}
send:
sender := t.decodeIndex(packet[4:8])
if err := t.table.AddPeerAddr(sender, peer); err != nil {
logger.Error(fmt.Sprintf("failed to add address: %s", err))
return nil, nil
Expand All @@ -74,8 +74,14 @@ func (t *WireguardAnalyzer) analyseHandshakeResponse(packet []byte, peer net.UDP
logger.Warn(fmt.Sprintf("invalid handshake response: expected length %d, got %d", handshakeResponseSize, len(packet)))
return nil, nil
}
if !t.checker.VerifyHandshakeResponse(peer, packet) {
return nil, nil
if t.checker.RequireCheck() {
if t.checker.MatchPubkey(packet) == nil {
logger.Warn("invalid mac1 in handshake response")
return nil, nil
}
if t.checker.VerifyMac2(peer, packet) {
logger.Debug("strip mac2 from handshake response")
}
}
sender := t.decodeIndex(packet[4:8])
if err := t.table.AddPeerAddr(sender, peer); err != nil {
Expand All @@ -89,7 +95,7 @@ func (t *WireguardAnalyzer) analyseHandshakeResponse(packet []byte, peer net.UDP
logger.Warn(fmt.Sprintf("unknown receiver in handshake response: %s", err))
return nil, nil
}
err = t.table.LinkPeers(sender, receiverIdx)
err = t.table.AssociatePeers(sender, receiverIdx)
if err != nil {
logger.Error(fmt.Sprintf("failed to link peers: %s", err))
return nil, nil
Expand Down Expand Up @@ -173,16 +179,16 @@ func (t *WireguardAnalyzer) Analyse(packet []byte, peer net.UDPAddr) ([]net.UDPA
}

func MakeWireguardAnalyzer(pubkeys [][]byte) WireguardAnalyzer {
salt := make([]byte, 32)
_, err := rand.Read(salt)
secret, err := token(32)
if err != nil {
panic(fmt.Errorf("failed to generate salt: %w", err))
panic(fmt.Errorf("failed to generate cookie secret: %w", err))
}
return WireguardAnalyzer{
table: exchange.MakeExchangeTable(),
checker: EndpointChecker{
endpoints: make(map[string]unverifiedEndpoint),
pubkeys: pubkeys,
checker: macChecker{
pubkeys: pubkeys,
secret: [32]byte(secret),
start: time.Now(),
},
}
}
66 changes: 66 additions & 0 deletions internal/analyzer/checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package analyzer

import (
"crypto/hmac"
"encoding/binary"
"net"
"time"
)

type macChecker struct {
secret [32]byte
start time.Time
pubkeys [][]byte
}

func (c *macChecker) cookie(addr net.UDPAddr) [16]byte {
ticks := uint64(time.Now().Sub(c.start) / (time.Duration(120) * time.Minute))
addrBytes, _ := addr.AddrPort().MarshalBinary()
return mac32(nil, c.secret, binary.BigEndian.AppendUint64(nil, ticks), addrBytes)
}

func (c *macChecker) VerifyMac2(addr net.UDPAddr, message []byte) bool {
l := len(message)
mac2 := mac16(nil, c.cookie(addr), message[:l-macSize])
return hmac.Equal(message[l-macSize:], mac2[:])
}

func (c *macChecker) MatchPubkey(message []byte) []byte {
if len(message) < 2*macSize {
return nil
}
if len(c.pubkeys) == 0 {
return nil
}
l := len(message)
mac1 := message[l-2*macSize : l-macSize]
d := message[:l-2*macSize]
for _, pubkey := range c.pubkeys {
key := hash(nil, []byte("mac1----"), pubkey)
m := mac32(nil, key, d)
if hmac.Equal(mac1, m[:]) {
return pubkey
}
}
return nil
}

func (c *macChecker) CreateReply(pubkey []byte, addr net.UDPAddr, msg []byte) ([]byte, error) {
cookie := c.cookie(addr)
nonce, err := token(24)
if err != nil {
return nil, err
}
key := hash(nil, []byte("cookie--"), pubkey)
reply := make([]byte, 64)
reply[0] = 3
copy(reply[4:8], msg[4:8])
copy(reply[8:32], nonce)
mac1 := msg[handshakeInitiationSize-2*macSize : handshakeInitiationSize-macSize]
xaead(reply[:32], key, [24]byte(nonce), cookie[:], mac1)
return reply, nil
}

func (c *macChecker) RequireCheck() bool {
return len(c.pubkeys) > 0
}
136 changes: 0 additions & 136 deletions internal/analyzer/endpoints.go

This file was deleted.

Loading

0 comments on commit 9df2c13

Please sign in to comment.