package infra import ( "encoding/base64" "fmt" "net" "regexp" "strings" qrcode "github.com/skip2/go-qrcode" ) // wgEndpointRe matches "host:port" where host is a hostname or IP and port is 1–65535. var wgEndpointRe = regexp.MustCompile(`^[a-zA-Z0-9._\-]+:\d{1,5}$`) // WGClientConfigGen generates the wg0.conf content for a WireGuard peer (client) // and a unicode-block QR string suitable for mobile enrollment via Element or a terminal. // // Pure: no I/O, fully deterministic given the inputs. Returns error on invalid inputs. func WGClientConfigGen(in WGClientConfigInput) (WGClientConfig, error) { if err := validateWGClientInput(in); err != nil { return WGClientConfig{}, err } ka := in.PersistentKA if ka == 0 { ka = 25 } var b strings.Builder // [Interface] section b.WriteString("[Interface]\n") fmt.Fprintf(&b, "PrivateKey = %s\n", in.DevicePrivateKey) fmt.Fprintf(&b, "Address = %s\n", in.DeviceAddress) if in.DNS != "" { fmt.Fprintf(&b, "DNS = %s\n", in.DNS) } b.WriteString("\n") // [Peer] section (hub) b.WriteString("[Peer]\n") fmt.Fprintf(&b, "PublicKey = %s\n", in.HubPublicKey) if in.PresharedKey != "" { fmt.Fprintf(&b, "PresharedKey = %s\n", in.PresharedKey) } fmt.Fprintf(&b, "Endpoint = %s\n", in.HubEndpoint) fmt.Fprintf(&b, "AllowedIPs = %s\n", in.HubAllowedIPs) fmt.Fprintf(&b, "PersistentKeepalive = %d\n", ka) ini := b.String() qr, err := qrcode.New(ini, qrcode.Medium) if err != nil { return WGClientConfig{}, fmt.Errorf("wg_client_config: qr encode: %w", err) } return WGClientConfig{ INI: ini, QR: qr.ToString(false), Filename: "wg0.conf", }, nil } // validateWGClientInput checks all required fields for correctness. func validateWGClientInput(in WGClientConfigInput) error { if err := validateWGBase64Key("DevicePrivateKey", in.DevicePrivateKey); err != nil { return err } if err := validateWGBase64Key("HubPublicKey", in.HubPublicKey); err != nil { return err } if in.PresharedKey != "" { if err := validateWGBase64Key("PresharedKey", in.PresharedKey); err != nil { return err } } // Validate DeviceAddress (CIDR) if _, _, err := net.ParseCIDR(in.DeviceAddress); err != nil { return fmt.Errorf("wg_client_config: DeviceAddress %q is not a valid CIDR: %w", in.DeviceAddress, err) } // Validate HubAllowedIPs (comma-separated CIDRs) for _, cidr := range strings.Split(in.HubAllowedIPs, ",") { cidr = strings.TrimSpace(cidr) if cidr == "" { continue } if _, _, err := net.ParseCIDR(cidr); err != nil { return fmt.Errorf("wg_client_config: HubAllowedIPs entry %q is not a valid CIDR: %w", cidr, err) } } // Validate HubEndpoint if !wgEndpointRe.MatchString(in.HubEndpoint) { return fmt.Errorf("wg_client_config: HubEndpoint %q must be host:port", in.HubEndpoint) } parts := strings.SplitN(in.HubEndpoint, ":", 2) port := 0 if _, err := fmt.Sscanf(parts[1], "%d", &port); err != nil || port < 1 || port > 65535 { return fmt.Errorf("wg_client_config: HubEndpoint port %q out of range 1-65535", parts[1]) } if in.DevicePrivateKey == "" { return fmt.Errorf("wg_client_config: DevicePrivateKey is required") } if in.HubPublicKey == "" { return fmt.Errorf("wg_client_config: HubPublicKey is required") } if in.HubEndpoint == "" { return fmt.Errorf("wg_client_config: HubEndpoint is required") } if in.HubAllowedIPs == "" { return fmt.Errorf("wg_client_config: HubAllowedIPs is required") } return nil } // validateWGBase64Key checks that a WireGuard key is a valid 32-byte base64-encoded string (44 chars). func validateWGBase64Key(field, key string) error { if len(key) != 44 { return fmt.Errorf("wg_client_config: %s must be 44 base64 chars, got %d", field, len(key)) } decoded, err := base64.StdEncoding.DecodeString(key) if err != nil { return fmt.Errorf("wg_client_config: %s is not valid base64: %w", field, err) } if len(decoded) != 32 { return fmt.Errorf("wg_client_config: %s must decode to 32 bytes, got %d", field, len(decoded)) } return nil }