keep track of knock sequences which are already used

This commit is contained in:
Steven Polley 2024-04-07 07:59:23 -06:00
parent 4d948fca6b
commit 27c2f28429

View File

@ -11,21 +11,30 @@ import (
"github.com/google/gopacket/pcap" "github.com/google/gopacket/pcap"
) )
// type client is used to keept track of a client attempting to perform an authentic knock sequence // Client is used to keep track of a client attempting to perform an authentic knock sequence
type Client struct { type Client struct {
Progress int // index of current progress in sequence. Value of 1 means first port has been matched Progress int // index of current progress in sequence. Value of 1 means first port has been matched
Sequence [4]uint16 // stores the knock sequence the current client is attempting. It's set and tracked here to prevent race conditions during a knock sequence being received and key rotations Sequence [4]uint16 // stores the knock sequence the current client is attempting. It's set and tracked here to prevent race conditions during a knock sequence being received and key rotations
} }
// KnockSequence is used keep track of an ordered knock sequence and whether it's been marked for use (to prevent replay attacks)
type KnockSequence struct {
Used bool
PortSequence [4]uint16
}
var ( var (
clients map[string]*Client // Contains a map of clients clients map[string]*Client // Contains a map of clients
portSequences [][4]uint16 knockSequences []KnockSequence
sharedSecret string // base32 encoded shared secret used for totp sharedSecret string // base32 encoded shared secret used for totp
) )
// packetServer is the main function when operating in server mode
// it sets up the pcap on the capture deivce and starts a goroutine
// to rotate the knock sequence
func packetServer(captureDevice string) { func packetServer(captureDevice string) {
clients = make(map[string]*Client, 0) // key is flow, value is the current progress through the sequence. i.e. value of 1 means that the first port in the sequence was successful clients = make(map[string]*Client, 0) // key is flow, value is the current progress through the sequence. i.e. value of 1 means that the first port in the sequence was successful
portSequences = [][4]uint16{} // Slice of accepted port sequences, there have to be several to account for clock skew between client and server knockSequences = []KnockSequence{} // Slice of accepted port sequences, there have to be several to account for clock skew between client and server
handle, err := pcap.OpenLive(captureDevice, 1600, true, pcap.BlockForever) handle, err := pcap.OpenLive(captureDevice, 1600, true, pcap.BlockForever)
if err != nil { if err != nil {
@ -45,9 +54,14 @@ func handlePacket(packet gopacket.Packet) {
srcip := packet.NetworkLayer().NetworkFlow().Src().String() srcip := packet.NetworkLayer().NetworkFlow().Src().String()
client, ok := clients[srcip] client, ok := clients[srcip]
if !ok { // create the client, identify which authentic knock sequence is matched if !ok { // create the client, identify which authentic knock sequence is matched
for _, sequence := range portSequences { for i, knockSequence := range knockSequences {
if port == sequence[0] { if knockSequence.Used { // skip over sequences that are already used to prevent replay attack
clients[srcip] = &Client{Progress: 1, Sequence: sequence} continue
}
if port == knockSequence.PortSequence[0] {
clients[srcip] = &Client{Progress: 1, Sequence: knockSequence.PortSequence}
knockSequences[i].Used = true
} }
} }
return return
@ -74,18 +88,19 @@ func handlePacket(packet gopacket.Packet) {
func rotateSequence(handle *pcap.Handle) { func rotateSequence(handle *pcap.Handle) {
for { for {
if len(portSequences) < 3 { // Generate new knock sequences with time skew support
t := time.Now().Add(time.Second * -30) t := time.Now().Add(time.Second * -30)
for i := 0; i < 3; i++ { for i := len(knockSequences); i < 3; i++ {
portSequence, err := otphyp.GeneratePorts(sharedSecret, t.Add((time.Second * 30 * time.Duration(i)))) portSequence, err := otphyp.GeneratePorts(sharedSecret, t.Add((time.Second * 30 * time.Duration(i))))
if err != nil { if err != nil {
log.Fatalf("failed to generate port knock sequence: %v", err) log.Fatalf("failed to generate port knock sequence: %v", err)
} }
portSequences = append(portSequences, portSequence) knockSequence := KnockSequence{PortSequence: portSequence}
} knockSequences = append(knockSequences, knockSequence)
} }
fmt.Println("New sequences:", knockSequences)
fmt.Println("New sequences:", portSequences) // Set BPF filter
err := setPacketFilter(handle) err := setPacketFilter(handle)
if err != nil { if err != nil {
log.Printf("failed to change packet filter: %v", err) log.Printf("failed to change packet filter: %v", err)
@ -94,16 +109,16 @@ func rotateSequence(handle *pcap.Handle) {
// Sleep until next 30 second offset // Sleep until next 30 second offset
time.Sleep(time.Until(time.Now().Truncate(time.Second * 30).Add(time.Second * 30))) time.Sleep(time.Until(time.Now().Truncate(time.Second * 30).Add(time.Second * 30)))
// TBD: pop first value and only generate latest (time.Now().Add(time.Second*30)) value instead of re-initializing completely // pop first value, next iteration pushes new value
portSequences = [][4]uint16{} knockSequences = knockSequences[1:]
} }
} }
// Given a pcap handle and list of authentic port knock sequences, configures a BPF filter // Given a pcap handle and list of authentic port knock sequences, configures a BPF filter
func setPacketFilter(handle *pcap.Handle) error { func setPacketFilter(handle *pcap.Handle) error {
filter := "udp && (" filter := "udp && ("
for i, portSequence := range portSequences { for i, knockSequence := range knockSequences {
for j, port := range portSequence { for j, port := range knockSequence.PortSequence {
if i == 0 && j == 0 { if i == 0 && j == 0 {
filter += fmt.Sprint("port ", port) filter += fmt.Sprint("port ", port)
} else { } else {