# Security Package This package provides secure password hashing and memory-safe storage for sensitive data. ## Packages ### Password (`pkg/security/password`) Provides Argon2id-based password hashing and verification with secure default parameters following OWASP recommendations. ### SecureString (`pkg/security/securestring`) Memory-safe string storage using `memguard` to protect sensitive data like passwords and API keys from memory dumps and swap files. ### SecureBytes (`pkg/security/securebytes`) Memory-safe byte slice storage using `memguard` to protect sensitive binary data. ### IPCountryBlocker (`pkg/security/ipcountryblocker`) GeoIP-based country blocking using MaxMind's GeoLite2 database to block requests from specific countries. ## Installation The packages are included in the project. Required dependencies: - `github.com/awnumar/memguard` - For secure memory management - `golang.org/x/crypto/argon2` - For password hashing - `github.com/oschwald/geoip2-golang` - For GeoIP lookups ## Usage ### Password Hashing ```go import ( "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/password" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/securestring" ) // Create password provider passwordProvider := password.NewPasswordProvider() // Hash a password plainPassword := "mySecurePassword123!" securePass, err := securestring.NewSecureString(plainPassword) if err != nil { // Handle error } defer securePass.Wipe() // Always wipe after use hashedPassword, err := passwordProvider.GenerateHashFromPassword(securePass) if err != nil { // Handle error } // Verify a password match, err := passwordProvider.ComparePasswordAndHash(securePass, hashedPassword) if err != nil { // Handle error } if match { // Password is correct } ``` ### Secure String Storage ```go import "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/securestring" // Store sensitive data securely apiKey := "secret-api-key-12345" secureKey, err := securestring.NewSecureString(apiKey) if err != nil { // Handle error } defer secureKey.Wipe() // Always wipe when done // Use the secure string keyValue := secureKey.String() // Get the value when needed // ... use keyValue ... // The original string should be cleared apiKey = "" ``` ### Secure Bytes Storage ```go import "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/securebytes" // Store sensitive binary data sensitiveData := []byte{0x01, 0x02, 0x03, 0x04} secureData, err := securebytes.NewSecureBytes(sensitiveData) if err != nil { // Handle error } defer secureData.Wipe() // Use the secure bytes data := secureData.Bytes() // ... use data ... // Clear the original slice for i := range sensitiveData { sensitiveData[i] = 0 } ``` ### Generate Random Values ```go passwordProvider := password.NewPasswordProvider() // Generate random bytes randomBytes, err := passwordProvider.GenerateSecureRandomBytes(32) // Generate random hex string (length * 2 characters) randomString, err := passwordProvider.GenerateSecureRandomString(16) // Returns a 32-character hex string ``` ### IP Country Blocking ```go import ( "context" "net" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/config" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/ipcountryblocker" ) // Create the blocker (typically done via dependency injection) cfg, _ := config.Load() blocker := ipcountryblocker.NewProvider(cfg, logger) defer blocker.Close() // Check if an IP is blocked ip := net.ParseIP("192.0.2.1") if blocker.IsBlockedIP(context.Background(), ip) { // Handle blocked IP return errors.New("access denied: your country is blocked") } // Check if a country code is blocked if blocker.IsBlockedCountry("CN") { // Country is in the blocked list } // Get country code for an IP countryCode, err := blocker.GetCountryCode(context.Background(), ip) if err != nil { // Handle error } // countryCode will be ISO 3166-1 alpha-2 code like "US", "CA", "GB" ``` **Configuration**: ```bash # Environment variables APP_GEOLITE_DB_PATH=/path/to/GeoLite2-Country.mmdb APP_BANNED_COUNTRIES=CN,RU,KP # Comma-separated ISO 3166-1 alpha-2 codes ``` ## Password Hashing Details ### Algorithm: Argon2id Argon2id is the recommended password hashing algorithm by OWASP. It combines: - Argon2i: Resistant to side-channel attacks - Argon2d: Resistant to GPU cracking attacks ### Default Parameters ``` Memory: 64 MB (65536 KB) Iterations: 3 Parallelism: 2 threads Salt Length: 16 bytes Key Length: 32 bytes ``` These parameters provide strong security while maintaining reasonable performance for authentication systems. ### Hash Format ``` $argon2id$v=19$m=65536,t=3,p=2$$ ``` Example: ``` $argon2id$v=19$m=65536,t=3,p=2$YWJjZGVmZ2hpamtsbW5vcA$9XJqrJ8fQvVrMz0FqJ7gBGqKvYLvLxC8HzPqKvYLvLxC ``` The hash includes all parameters, so it can be verified even if you change the default parameters later. ## Security Best Practices ### 1. Always Wipe Sensitive Data ```go securePass, _ := securestring.NewSecureString(password) defer securePass.Wipe() // Ensures cleanup even on panic // ... use securePass ... ``` ### 2. Clear Original Data After creating a secure string/bytes, clear the original data: ```go password := "secret" securePass, _ := securestring.NewSecureString(password) password = "" // Clear the original string // Even better for byte slices: data := []byte("secret") secureData, _ := securebytes.NewSecureBytes(data) for i := range data { data[i] = 0 } ``` ### 3. Minimize Exposure Time Get values from secure storage only when needed: ```go // Bad - exposes value for too long value := secureString.String() // ... lots of code ... useValue(value) // Good - get value right before use // ... lots of code ... useValue(secureString.String()) ``` ### 4. Use Dependency Injection ```go import "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/password" // In your Wire provider set wire.NewSet( password.ProvidePasswordProvider, // ... other providers ) // Use in your service type AuthService struct { passwordProvider password.PasswordProvider } func NewAuthService(pp password.PasswordProvider) *AuthService { return &AuthService{passwordProvider: pp} } ``` ### 5. Handle Errors Properly ```go securePass, err := securestring.NewSecureString(password) if err != nil { return fmt.Errorf("failed to create secure string: %w", err) } defer securePass.Wipe() ``` ### 6. Clean Up GeoIP Resources ```go import "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/ipcountryblocker" // Always close the provider when done to release database resources blocker := ipcountryblocker.NewProvider(cfg, logger) defer blocker.Close() ``` ## Memory Safety ### How It Works The `memguard` library provides: 1. **Locked Memory**: Prevents sensitive data from being swapped to disk 2. **Guarded Heap**: Detects buffer overflows and underflows 3. **Secure Wiping**: Overwrites memory with random data before freeing 4. **Read Protection**: Makes memory pages read-only when not in use ### When to Use Use secure storage for: - Passwords and password hashes (during verification) - API keys and tokens - Encryption keys - Private keys - Database credentials - OAuth secrets - JWT signing keys - Session tokens - Any sensitive user data ### When NOT to Use Don't use for: - Public data - Non-sensitive configuration - Data that needs to be logged - Data that will be stored long-term in memory ## Performance Considerations ### Password Hashing Argon2id is intentionally slow to prevent brute-force attacks: - Expected time: ~50-100ms per hash on modern hardware - This is acceptable for authentication (login) operations - DO NOT use for high-throughput operations ### Memory Usage SecureString/SecureBytes use locked memory: - Each instance locks a page in RAM (typically 4KB minimum) - Don't create thousands of instances - Reuse instances when possible - Always wipe when done ## Examples ### Complete Login Example ```go func (s *AuthService) Login(ctx context.Context, email, password string) (*User, error) { // Create secure string from password securePass, err := securestring.NewSecureString(password) if err != nil { return nil, err } defer securePass.Wipe() // Clear the original password password = "" // Get user from database user, err := s.userRepo.GetByEmail(ctx, email) if err != nil { return nil, err } // Verify password match, err := s.passwordProvider.ComparePasswordAndHash(securePass, user.PasswordHash) if err != nil { return nil, err } if !match { return nil, ErrInvalidCredentials } return user, nil } ``` ### Complete Registration Example ```go func (s *AuthService) Register(ctx context.Context, email, password string) (*User, error) { // Validate password strength first if len(password) < 8 { return nil, ErrWeakPassword } // Create secure string from password securePass, err := securestring.NewSecureString(password) if err != nil { return nil, err } defer securePass.Wipe() // Clear the original password password = "" // Hash the password hashedPassword, err := s.passwordProvider.GenerateHashFromPassword(securePass) if err != nil { return nil, err } // Create user with hashed password user := &User{ Email: email, PasswordHash: hashedPassword, } if err := s.userRepo.Create(ctx, user); err != nil { return nil, err } return user, nil } ``` ## Troubleshooting ### "failed to create buffer" **Problem**: memguard couldn't allocate locked memory **Solutions**: - Check system limits for locked memory (`ulimit -l`) - Reduce number of concurrent SecureString/SecureBytes instances - Ensure proper cleanup with `Wipe()` ### "buffer is not alive" **Problem**: Trying to use a SecureString/SecureBytes after it was wiped **Solutions**: - Don't use secure data after calling `Wipe()` - Check your defer ordering - Create new instances if you need the data again ### Slow Performance **Problem**: Password hashing is too slow **Solutions**: - This is by design for security - Don't hash passwords in high-throughput operations - Consider caching authentication results (with care) - Use async operations for registration/password changes ### "failed to open GeoLite2 DB" **Problem**: Cannot open the GeoIP2 database **Solutions**: - Verify APP_GEOLITE_DB_PATH points to a valid .mmdb file - Download the GeoLite2-Country database from MaxMind - Check file permissions - Ensure the database file is not corrupted ### "no country found for IP" **Problem**: IP address lookup returns no country **Solutions**: - This is normal for private IP ranges (10.x.x.x, 192.168.x.x, etc.) - The IP might not be in the GeoIP2 database - Update to a newer GeoLite2 database - By default, unknown IPs are allowed (returns false from IsBlockedIP) ## IP Country Blocking Details ### GeoLite2 Database The IP country blocker uses MaxMind's GeoLite2-Country database for IP geolocation. **How to Get the Database**: 1. Create a free account at https://www.maxmind.com/en/geolite2/signup 2. Generate a license key 3. Download GeoLite2-Country database (.mmdb format) 4. Set APP_GEOLITE_DB_PATH to the file location **Database Updates**: - MaxMind updates GeoLite2 databases weekly - Set up automated updates for production systems - Database file is typically 5-10 MB ### Country Codes Uses ISO 3166-1 alpha-2 country codes: - US - United States - CA - Canada - GB - United Kingdom - CN - China - RU - Russia - KP - North Korea - etc. Full list: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 ### Blocking Behavior **Default Behavior**: - If IP lookup fails → Allow (returns false) - If country not found → Allow (returns false) - If country is blocked → Block (returns true) **To block unknown IPs**, modify IsBlockedIP to return true on error (line 101 in ipcountryblocker.go). ### Thread Safety The provider is thread-safe: - Uses sync.RWMutex for concurrent access to blocked countries map - GeoIP2 Reader is thread-safe by design - Safe to use in HTTP middleware and concurrent handlers ### Performance **Lookup Speed**: - In-memory database lookups are very fast (~microseconds) - Database is memory-mapped for efficiency - Suitable for high-traffic applications **Memory Usage**: - GeoLite2-Country database: ~5-10 MB in memory - Blocked countries map: negligible (few KB) ## References - [Argon2 RFC](https://tools.ietf.org/html/rfc9106) - [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) - [memguard Documentation](https://github.com/awnumar/memguard) - [Alex Edwards: How to Hash and Verify Passwords With Argon2 in Go](https://www.alexedwards.net/blog/how-to-hash-and-verify-passwords-with-argon2-in-go) - [MaxMind GeoLite2 Free Geolocation Data](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data) - [ISO 3166-1 Country Codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)