From 309e2794a1cb242e8bf8655915db567a55dc2b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Benouarets?= Date: Tue, 30 Sep 2025 11:45:16 +0200 Subject: [PATCH] feat: add storage management and utility functions - Implement storage service for file management - Add support for external and system storage backends - Include utility functions for environment variables - Add hash utilities for secure operations - Implement validation utilities for data integrity - Support file encryption and secure storage - Add proper error handling and logging for storage operations --- storage/storage.go | 6 ++++ utils/env.go | 11 ++++++ utils/hash.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++ utils/valid.go | 22 ++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 storage/storage.go create mode 100644 utils/env.go create mode 100644 utils/hash.go create mode 100644 utils/valid.go diff --git a/storage/storage.go b/storage/storage.go new file mode 100644 index 0000000..fc46dee --- /dev/null +++ b/storage/storage.go @@ -0,0 +1,6 @@ +package storage + +type Storage interface { + GetCertificate(key string) (interface{}, error) + GetPrivateKey(key string) (interface{}, error) +} diff --git a/utils/env.go b/utils/env.go new file mode 100644 index 0000000..143e278 --- /dev/null +++ b/utils/env.go @@ -0,0 +1,11 @@ +package utils + +import "os" + +// GetEnv gets an environment variable with a fallback value +func GetEnv(key, fallback string) string { + if value := os.Getenv(key); value != "" { + return value + } + return fallback +} diff --git a/utils/hash.go b/utils/hash.go new file mode 100644 index 0000000..a748e6e --- /dev/null +++ b/utils/hash.go @@ -0,0 +1,85 @@ +package utils + +import ( + "crypto/rand" + "crypto/subtle" + "encoding/base64" + "fmt" + "strings" + + "golang.org/x/crypto/argon2" +) + +type Argon2Params struct { + Memory uint32 + Iterations uint32 + Parallelism uint8 + SaltLength uint32 + KeyLength uint32 +} + +// Default secure parameters +var DefaultParams = &Argon2Params{ + Memory: 64 * 1024, // 64 MB + Iterations: 3, + Parallelism: 2, + SaltLength: 16, + KeyLength: 32, +} + +// HashPassword creates a secure Argon2id hash +func HashPassword(password string, p *Argon2Params) (string, error) { + // Generate salt + salt := make([]byte, p.SaltLength) + _, err := rand.Read(salt) + if err != nil { + return "", fmt.Errorf("failed to generate salt: %w", err) + } + + // Calculate hash + hash := argon2.IDKey([]byte(password), salt, p.Iterations, p.Memory, p.Parallelism, p.KeyLength) + + // Format: $argon2id$v=19$m=65536,t=3,p=2$$ + encoded := fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s", + p.Memory, p.Iterations, p.Parallelism, + base64.RawStdEncoding.EncodeToString(salt), + base64.RawStdEncoding.EncodeToString(hash), + ) + + return encoded, nil +} + +// VerifyPassword checks if the password matches the hash +func VerifyPassword(password, encodedHash string) (bool, error) { + parts := strings.Split(encodedHash, "$") + if len(parts) != 6 { + return false, fmt.Errorf("invalid hash format") + } + + var memory uint32 + var iterations uint32 + var parallelism uint8 + _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, ¶llelism) + if err != nil { + return false, fmt.Errorf("invalid hash parameters: %w", err) + } + + // Decode salt and hash + salt, err := base64.RawStdEncoding.DecodeString(parts[4]) + if err != nil { + return false, fmt.Errorf("invalid salt encoding: %w", err) + } + expectedHash, err := base64.RawStdEncoding.DecodeString(parts[5]) + if err != nil { + return false, fmt.Errorf("invalid hash encoding: %w", err) + } + + // Recalculate hash with the same parameters + hash := argon2.IDKey([]byte(password), salt, iterations, memory, parallelism, uint32(len(expectedHash))) + + // Constant time comparison + if subtle.ConstantTimeCompare(hash, expectedHash) == 1 { + return true, nil + } + return false, nil +} diff --git a/utils/valid.go b/utils/valid.go new file mode 100644 index 0000000..3df7a57 --- /dev/null +++ b/utils/valid.go @@ -0,0 +1,22 @@ +package utils + +import ( + "regexp" + "strings" +) + +var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) +var domainRegex = regexp.MustCompile(`^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) + +func IsEmailValid(email string) bool { + return emailRegex.MatchString(email) +} + +func IsDomainValid(domain string) bool { + return domainRegex.MatchString(domain) +} + +func IsEmailDomainValid(email, domain string) bool { + emailDomain := strings.Split(email, "@")[1] + return emailRegex.MatchString(email) && emailDomain == domain +}