diff --git a/hypd/cmd/server.go b/hypd/cmd/server.go index b539b91..d1d8df5 100644 --- a/hypd/cmd/server.go +++ b/hypd/cmd/server.go @@ -47,9 +47,15 @@ Example Usage: hypdConfiguration, err := configuration.LoadConfiguration(args[0]) if err != nil { - panic(fmt.Errorf("failed to start packet server: %w", err)) + panic(fmt.Errorf("failed to load configuration file '%s': %w", args[0], err)) } - err = server.PacketServer(hypdConfiguration) + + secrets, err := configuration.LoadSecrets(hypdConfiguration.PreSharedKeyDirectory) + if err != nil { + panic(fmt.Errorf("failed to load secrets from directory '%s': %w", hypdConfiguration.PreSharedKeyDirectory, err)) + } + + err = server.PacketServer(hypdConfiguration, secrets) if err != nil { panic(fmt.Errorf("failed to start packet server: %w", err)) } diff --git a/hypd/configuration/configuration.go b/hypd/configuration/configuration.go index 3c50d36..ee9a81a 100644 --- a/hypd/configuration/configuration.go +++ b/hypd/configuration/configuration.go @@ -12,7 +12,6 @@ type HypdConfiguration struct { SuccessAction string `json:"successAction"` // The action to take for a successful knock, each argument is a separate string TimeoutSeconds int `json:"timeoutSeconds"` // If > 0, once a knock sequence has been successful this value will count down and when it reaches 0, it will perform the TimeoutAction on the client TimeoutAction string `json:"timeoutAction"` // The action to take after TimeoutSeconds has elapsed. only applicable if TimeoutSeconds is > 0, each argument is a separate string - } // LoadConfiguration opens and parses the configuration file into a HypdConfiguration struct diff --git a/hypd/configuration/secrets.go b/hypd/configuration/secrets.go new file mode 100644 index 0000000..80cfacb --- /dev/null +++ b/hypd/configuration/secrets.go @@ -0,0 +1,49 @@ +package configuration + +import ( + "encoding/base32" + "fmt" + "io/fs" + "os" + "path/filepath" +) + +var secrets [][]byte + +// LoadSecrets processes all files within the specified directory and attempts to +// convert the file contents to secrets to by used by hypd +func LoadSecrets(preSharedKeyDirectory string) ([][]byte, error) { + secrets = make([][]byte, 0) + err := filepath.Walk(preSharedKeyDirectory, processSecretFile) + if err != nil { + return nil, fmt.Errorf("failed to walk directory '%s': %w", preSharedKeyDirectory, err) + } + + return secrets, nil +} + +// processSecretFile is called against each file in the preSharedKeyDirectory +// It reads each file and attemts to base32 decode their contents +func processSecretFile(path string, info fs.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("failed to process file '%s': %w", path, err) + } + + if info.IsDir() { + return nil + } + + secretBytes, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read file '%s': %w", path, err) + } + + decodedSecret, err := base32.StdEncoding.DecodeString(string(secretBytes)) + if err != nil { + return fmt.Errorf("failed to base32 decode secret '%s': %w", path, err) + } + + secrets = append(secrets, decodedSecret) + + return nil +} diff --git a/hypd/server/packet.go b/hypd/server/packet.go index e85ed91..7918a54 100644 --- a/hypd/server/packet.go +++ b/hypd/server/packet.go @@ -11,7 +11,6 @@ import ( "fmt" "log" "net" - "os" "os/exec" "time" @@ -43,26 +42,21 @@ const ( var ( clients map[uint32]*Client // Contains a map of clients, key is IPv4 address knockSequences []KnockSequence // We have 3 valid knock sequences at any time to account for clock skew - sharedSecret string // base32 encoded shared secret used for totp serverConfig *configuration.HypdConfiguration + sharedSecrets [][]byte // A slice of byte slices, each being a secret key ) // PacketServer is the main function when operating in server mode // it sets up the pcap on the capture device and starts a goroutine // to rotate the knock sequence -func PacketServer(config *configuration.HypdConfiguration) error { +func PacketServer(config *configuration.HypdConfiguration, secrets [][]byte) error { serverConfig = config + sharedSecrets = secrets iface, err := net.InterfaceByName(serverConfig.NetworkInterface) if err != nil { log.Fatalf("lookup network iface %q: %v", serverConfig.NetworkInterface, err) } - secretBytes, err := os.ReadFile("hyp.secret") - if err != nil { - log.Fatalf("failed to read file 'hyp.secret': %v", err) - } - sharedSecret = string(secretBytes) - clients = make(map[uint32]*Client, 0) knockSequences = []KnockSequence{} @@ -182,14 +176,15 @@ func rotateSequence() { // 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) + for _, secret := range sharedSecrets { + portSequence, err := otphyp.GeneratePorts(secret, 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) } - knockSequence := KnockSequence{PortSequence: portSequence} - knockSequences = append(knockSequences, knockSequence) } - fmt.Println("New sequences:", knockSequences) // Sleep until next 30 second offset time.Sleep(time.Until(time.Now().Truncate(time.Second * 30).Add(time.Second * 30))) diff --git a/otphyp/otphyp.go b/otphyp/otphyp.go index cf19edc..dc58dbc 100644 --- a/otphyp/otphyp.go +++ b/otphyp/otphyp.go @@ -15,12 +15,7 @@ import ( // A loose implementation of hotp meant for our specific purposes of generating four random port numbers // Accepts a base32 encoded shared secret and a time -func GeneratePorts(sharedSecret string, t time.Time) (ports [4]uint16, err error) { - - sharedSecretBytes, err := base32.StdEncoding.DecodeString(sharedSecret) - if err != nil { - return [4]uint16{0, 0, 0, 0}, fmt.Errorf("failed to base32 decode shared secret string to bytes: %v", err) - } +func GeneratePorts(sharedSecret []byte, t time.Time) (ports [4]uint16, err error) { // 30 second key rotation movingFactor := uint64(math.Floor(float64(t.Unix()) / float64(30))) @@ -28,7 +23,7 @@ func GeneratePorts(sharedSecret string, t time.Time) (ports [4]uint16, err error binary.BigEndian.PutUint64(buf, movingFactor) // calculate hmac and offset - mac := hmac.New(sha1.New, sharedSecretBytes) + mac := hmac.New(sha1.New, sharedSecret) mac.Write(buf) sum := mac.Sum(nil) offset := sum[len(sum)-1] & 0xf