diff --git a/server/packet.go b/server/packet.go index 81a5f02..74d67bb 100644 --- a/server/packet.go +++ b/server/packet.go @@ -11,21 +11,30 @@ import ( "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 { 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 } +// 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 ( - clients map[string]*Client // Contains a map of clients - portSequences [][4]uint16 - sharedSecret string // base32 encoded shared secret used for totp + clients map[string]*Client // Contains a map of clients + knockSequences []KnockSequence + 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) { 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) if err != nil { @@ -45,9 +54,14 @@ func handlePacket(packet gopacket.Packet) { srcip := packet.NetworkLayer().NetworkFlow().Src().String() client, ok := clients[srcip] if !ok { // create the client, identify which authentic knock sequence is matched - for _, sequence := range portSequences { - if port == sequence[0] { - clients[srcip] = &Client{Progress: 1, Sequence: sequence} + for i, knockSequence := range knockSequences { + if knockSequence.Used { // skip over sequences that are already used to prevent replay attack + continue + } + + if port == knockSequence.PortSequence[0] { + clients[srcip] = &Client{Progress: 1, Sequence: knockSequence.PortSequence} + knockSequences[i].Used = true } } return @@ -74,18 +88,19 @@ func handlePacket(packet gopacket.Packet) { func rotateSequence(handle *pcap.Handle) { for { - if len(portSequences) < 3 { - t := time.Now().Add(time.Second * -30) - for i := 0; i < 3; i++ { - portSequence, err := otphyp.GeneratePorts(sharedSecret, t.Add((time.Second * 30 * time.Duration(i)))) - if err != nil { - log.Fatalf("failed to generate port knock sequence: %v", err) - } - portSequences = append(portSequences, portSequence) + // Generate new knock sequences with time skew support + t := time.Now().Add(time.Second * -30) + for i := len(knockSequences); i < 3; i++ { + portSequence, err := otphyp.GeneratePorts(sharedSecret, t.Add((time.Second * 30 * time.Duration(i)))) + if err != nil { + log.Fatalf("failed to generate port knock sequence: %v", err) } + knockSequence := KnockSequence{PortSequence: portSequence} + knockSequences = append(knockSequences, knockSequence) } + fmt.Println("New sequences:", knockSequences) - fmt.Println("New sequences:", portSequences) + // Set BPF filter err := setPacketFilter(handle) if err != nil { log.Printf("failed to change packet filter: %v", err) @@ -94,16 +109,16 @@ func rotateSequence(handle *pcap.Handle) { // Sleep until next 30 second offset 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 - portSequences = [][4]uint16{} + // pop first value, next iteration pushes new value + knockSequences = knockSequences[1:] } } // Given a pcap handle and list of authentic port knock sequences, configures a BPF filter func setPacketFilter(handle *pcap.Handle) error { filter := "udp && (" - for i, portSequence := range portSequences { - for j, port := range portSequence { + for i, knockSequence := range knockSequences { + for j, port := range knockSequence.PortSequence { if i == 0 && j == 0 { filter += fmt.Sprint("port ", port) } else {