mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-03 20:12:58 +00:00
226 lines
5.4 KiB
Go
226 lines
5.4 KiB
Go
package utils
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
// GenerateSecureSecret generates a cryptographically secure random secret
|
|
func GenerateSecureSecret(byteLength int) (string, error) {
|
|
bytes := make([]byte, byteLength)
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
return "", fmt.Errorf("failed to generate random bytes: %w", err)
|
|
}
|
|
return base64.URLEncoding.EncodeToString(bytes), nil
|
|
}
|
|
|
|
// GenerateSecureKey generates a hex-encoded encryption key
|
|
func GenerateSecureKey(bitLength int) (string, error) {
|
|
byteLength := bitLength / 8
|
|
bytes := make([]byte, byteLength)
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
return "", fmt.Errorf("failed to generate random bytes: %w", err)
|
|
}
|
|
return hex.EncodeToString(bytes), nil
|
|
}
|
|
|
|
// GetOrCreateJWTSecret retrieves JWT secret from file or generates a new one
|
|
func GetOrCreateJWTSecret() (string, error) {
|
|
secretFile := "jwt_secret.key"
|
|
|
|
// Try to read existing secret
|
|
if secret, err := readSecretFromFile(secretFile); err == nil {
|
|
return secret, nil
|
|
}
|
|
|
|
// Generate new secret
|
|
secret, err := GenerateSecureSecret(32) // 32 bytes = 256 bits
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to generate JWT secret: %w", err)
|
|
}
|
|
|
|
// Save to file
|
|
if err := saveSecretToFile(secretFile, secret); err != nil {
|
|
return "", fmt.Errorf("failed to save JWT secret: %w", err)
|
|
}
|
|
|
|
return secret, nil
|
|
}
|
|
|
|
// GetOrCreateEncryptionKey retrieves encryption key from file or generates a new one
|
|
func GetOrCreateEncryptionKey() (string, error) {
|
|
keyFile := "encryption.key"
|
|
|
|
// Try to read existing key
|
|
if key, err := readSecretFromFile(keyFile); err == nil {
|
|
return key, nil
|
|
}
|
|
|
|
// Generate new key
|
|
key, err := GenerateSecureKey(256) // 256 bits
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to generate encryption key: %w", err)
|
|
}
|
|
|
|
// Save to file
|
|
if err := saveSecretToFile(keyFile, key); err != nil {
|
|
return "", fmt.Errorf("failed to save encryption key: %w", err)
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// readSecretFromFile reads a secret from a file
|
|
func readSecretFromFile(filename string) (string, error) {
|
|
data, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
secret := string(data)
|
|
if secret == "" {
|
|
return "", fmt.Errorf("empty secret file")
|
|
}
|
|
|
|
return secret, nil
|
|
}
|
|
|
|
// saveSecretToFile saves a secret to a file with secure permissions
|
|
func saveSecretToFile(filename, secret string) error {
|
|
// Create the file with restricted permissions (only readable by owner)
|
|
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
_, err = file.WriteString(secret)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateSecretStrength checks if a secret meets minimum security requirements
|
|
func ValidateSecretStrength(secret string, minLength int) error {
|
|
if len(secret) < minLength {
|
|
return fmt.Errorf("secret too short: minimum %d characters required", minLength)
|
|
}
|
|
|
|
// Check entropy (basic check)
|
|
entropy := calculateEntropy(secret)
|
|
if entropy < 3.0 { // Minimum entropy threshold
|
|
return fmt.Errorf("secret has low entropy: %.2f (minimum 3.0)", entropy)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// calculateEntropy calculates the Shannon entropy of a string
|
|
func calculateEntropy(s string) float64 {
|
|
if len(s) == 0 {
|
|
return 0
|
|
}
|
|
|
|
// Count character frequencies
|
|
freq := make(map[rune]int)
|
|
for _, char := range s {
|
|
freq[char]++
|
|
}
|
|
|
|
// Calculate entropy
|
|
entropy := 0.0
|
|
length := float64(len(s))
|
|
|
|
for _, count := range freq {
|
|
if count > 0 {
|
|
p := float64(count) / length
|
|
entropy -= p * log2(p)
|
|
}
|
|
}
|
|
|
|
return entropy
|
|
}
|
|
|
|
// log2 calculates base-2 logarithm
|
|
func log2(x float64) float64 {
|
|
const ln2 = 0.6931471805599453 // ln(2)
|
|
return 1.0 / ln2 * logNatural(x)
|
|
}
|
|
|
|
// logNatural calculates natural logarithm using approximation
|
|
func logNatural(x float64) float64 {
|
|
if x <= 0 {
|
|
return 0
|
|
}
|
|
if x == 1 {
|
|
return 0
|
|
}
|
|
|
|
// Simple approximation for ln(x)
|
|
// For production, use math.Log from the standard library
|
|
n := 0.0
|
|
for x > 1.0 {
|
|
x /= 2.718281828459045 // e
|
|
n++
|
|
}
|
|
return n
|
|
}
|
|
|
|
// RotateSecret generates a new secret and updates the file
|
|
func RotateSecret(filename string) (string, error) {
|
|
// Generate new secret
|
|
var newSecret string
|
|
var err error
|
|
|
|
if filename == "jwt_secret.key" {
|
|
newSecret, err = GenerateSecureSecret(32)
|
|
} else if filename == "encryption.key" {
|
|
newSecret, err = GenerateSecureKey(256)
|
|
} else {
|
|
return "", fmt.Errorf("unknown secret file type: %s", filename)
|
|
}
|
|
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to generate new secret: %w", err)
|
|
}
|
|
|
|
// Backup old secret if it exists
|
|
if _, err := os.Stat(filename); err == nil {
|
|
backupFile := fmt.Sprintf("%s.backup.%d", filename, time.Now().Unix())
|
|
if err := os.Rename(filename, backupFile); err != nil {
|
|
return "", fmt.Errorf("failed to backup old secret: %w", err)
|
|
}
|
|
}
|
|
|
|
// Save new secret
|
|
if err := saveSecretToFile(filename, newSecret); err != nil {
|
|
return "", fmt.Errorf("failed to save new secret: %w", err)
|
|
}
|
|
|
|
return newSecret, nil
|
|
}
|
|
|
|
// GetSecretFilePath returns the full path to a secret file
|
|
func GetSecretFilePath(filename string) string {
|
|
// Store secrets in a secure directory
|
|
secretDir := os.Getenv("SECRET_DIR")
|
|
if secretDir == "" {
|
|
secretDir = "./secrets"
|
|
}
|
|
|
|
// Create directory if it doesn't exist
|
|
if err := os.MkdirAll(secretDir, 0700); err != nil {
|
|
// Fallback to current directory
|
|
return filename
|
|
}
|
|
|
|
return filepath.Join(secretDir, filename)
|
|
}
|