initital commit

This commit is contained in:
2026-05-05 08:44:55 +00:00
commit a1922f5be1
22 changed files with 2922 additions and 0 deletions
+195
View File
@@ -0,0 +1,195 @@
// Package hwid collects stable hardware identifiers to bind licenses
// to a specific machine. Works on Linux, Windows, and macOS.
package hwid
import (
"crypto/sha256"
"fmt"
"net"
"os"
"os/exec"
"runtime"
"strings"
)
// Fingerprint holds raw hardware identifiers collected from the system.
type Fingerprint struct {
MACAddresses []string // Physical network interface MACs
Hostname string
CPUInfo string // CPU model / identifier
MachineID string // OS-level machine UUID
OS string // runtime.GOOS
}
// Collect gathers hardware identifiers from the current machine.
func Collect() (*Fingerprint, error) {
fp := &Fingerprint{OS: runtime.GOOS}
fp.Hostname, _ = os.Hostname()
fp.MACAddresses = collectMACs()
fp.CPUInfo = collectCPU()
fp.MachineID = collectMachineID()
return fp, nil
}
// Hash returns a stable SHA-256 fingerprint string derived from the
// collected identifiers. Use this as the machine ID in your license claims.
// The hash is hex-encoded and always 64 characters long.
func (f *Fingerprint) Hash() string {
// Sort MACs for stability (interface order can vary)
macs := stableJoin(f.MACAddresses)
parts := []string{
"mac=" + macs,
"host=" + f.Hostname,
"cpu=" + f.CPUInfo,
"mid=" + f.MachineID,
"os=" + f.OS,
}
raw := strings.Join(parts, "|")
sum := sha256.Sum256([]byte(raw))
return fmt.Sprintf("%x", sum)
}
// --- MAC addresses -------------------------------------------------------
// collectMACs returns the hardware MAC addresses of all physical interfaces.
// Loopback and virtual interfaces are excluded.
func collectMACs() []string {
ifaces, err := net.Interfaces()
if err != nil {
return nil
}
var macs []string
for _, iface := range ifaces {
mac := iface.HardwareAddr.String()
if mac == "" {
continue
}
// Skip loopback, virtual, docker, VPN interfaces
name := strings.ToLower(iface.Name)
if isVirtual(name) {
continue
}
macs = append(macs, strings.ToUpper(mac))
}
return macs
}
func isVirtual(name string) bool {
prefixes := []string{"lo", "docker", "veth", "br-", "virbr", "vmnet", "vbox", "utun", "tun", "tap"}
for _, p := range prefixes {
if strings.HasPrefix(name, p) {
return true
}
}
return false
}
// --- CPU info ------------------------------------------------------------
func collectCPU() string {
switch runtime.GOOS {
case "linux":
return readLinuxCPU()
case "darwin":
return runCmd("sysctl", "-n", "machdep.cpu.brand_string")
case "windows":
return runCmd("wmic", "cpu", "get", "ProcessorId", "/value")
}
return ""
}
func readLinuxCPU() string {
data, err := os.ReadFile("/proc/cpuinfo")
if err != nil {
return ""
}
for _, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, "model name") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
return strings.TrimSpace(parts[1])
}
}
}
return ""
}
// --- Machine ID ----------------------------------------------------------
// collectMachineID reads the OS-level machine UUID:
// - Linux: /etc/machine-id (systemd) or /var/lib/dbus/machine-id
// - macOS: ioreg hardware UUID
// - Windows: registry MachineGuid
func collectMachineID() string {
switch runtime.GOOS {
case "linux":
return readLinuxMachineID()
case "darwin":
out := runCmd("ioreg", "-rd1", "-c", "IOPlatformExpertDevice")
return extractIORegValue(out, "IOPlatformUUID")
case "windows":
out := runCmd("reg", "query",
`HKLM\SOFTWARE\Microsoft\Cryptography`, "/v", "MachineGuid")
return parseRegOutput(out)
}
return ""
}
func readLinuxMachineID() string {
for _, path := range []string{"/etc/machine-id", "/var/lib/dbus/machine-id"} {
data, err := os.ReadFile(path)
if err == nil {
return strings.TrimSpace(string(data))
}
}
return ""
}
func extractIORegValue(output, key string) string {
for _, line := range strings.Split(output, "\n") {
if strings.Contains(line, key) {
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
return strings.Trim(strings.TrimSpace(parts[1]), `"`)
}
}
}
return ""
}
func parseRegOutput(output string) string {
for _, line := range strings.Split(output, "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "MachineGuid") {
parts := strings.Fields(line)
if len(parts) >= 3 {
return parts[len(parts)-1]
}
}
}
return ""
}
// --- Helpers -------------------------------------------------------------
func runCmd(name string, args ...string) string {
out, err := exec.Command(name, args...).Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
func stableJoin(ss []string) string {
// Simple insertion sort for stability without importing sort
a := append([]string(nil), ss...)
for i := 1; i < len(a); i++ {
for j := i; j > 0 && a[j] < a[j-1]; j-- {
a[j], a[j-1] = a[j-1], a[j]
}
}
return strings.Join(a, ",")
}