package crypto import ( "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "strings" "time" "golang.org/x/crypto/bcrypt" ) const ( // TokenLength defines the length of generated tokens in bytes TokenLength = 32 // TokenPrefix is prepended to all tokens for identification TokenPrefix = "kms_" ) // TokenGenerator provides secure token generation and validation type TokenGenerator struct { hmacKey []byte } // NewTokenGenerator creates a new token generator with the provided HMAC key func NewTokenGenerator(hmacKey string) *TokenGenerator { return &TokenGenerator{ hmacKey: []byte(hmacKey), } } // GenerateSecureToken generates a cryptographically secure random token func (tg *TokenGenerator) GenerateSecureToken() (string, error) { // Generate random bytes tokenBytes := make([]byte, TokenLength) if _, err := rand.Read(tokenBytes); err != nil { return "", fmt.Errorf("failed to generate random token: %w", err) } // Encode to base64 for safe transmission tokenData := base64.URLEncoding.EncodeToString(tokenBytes) // Add prefix for identification token := TokenPrefix + tokenData return token, nil } // HashToken creates a secure hash of the token for storage func (tg *TokenGenerator) HashToken(token string) (string, error) { // Use bcrypt for secure password-like hashing hash, err := bcrypt.GenerateFromPassword([]byte(token), bcrypt.DefaultCost) if err != nil { return "", fmt.Errorf("failed to hash token: %w", err) } return string(hash), nil } // VerifyToken verifies a token against its stored hash func (tg *TokenGenerator) VerifyToken(token, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(token)) return err == nil } // GenerateHMACKey generates a new HMAC key for token signing func GenerateHMACKey() (string, error) { key := make([]byte, 32) // 256-bit key if _, err := rand.Read(key); err != nil { return "", fmt.Errorf("failed to generate HMAC key: %w", err) } return hex.EncodeToString(key), nil } // SignToken creates an HMAC signature for a token func (tg *TokenGenerator) SignToken(token string, timestamp time.Time) string { h := hmac.New(sha256.New, tg.hmacKey) h.Write([]byte(token)) h.Write([]byte(timestamp.Format(time.RFC3339))) signature := h.Sum(nil) return hex.EncodeToString(signature) } // VerifyTokenSignature verifies an HMAC signature for a token func (tg *TokenGenerator) VerifyTokenSignature(token, signature string, timestamp time.Time) bool { expectedSignature := tg.SignToken(token, timestamp) return hmac.Equal([]byte(signature), []byte(expectedSignature)) } // ExtractTokenFromHeader extracts a token from an Authorization header func ExtractTokenFromHeader(authHeader string) string { // Support both "Bearer token" and "token" formats if strings.HasPrefix(authHeader, "Bearer ") { return strings.TrimPrefix(authHeader, "Bearer ") } return authHeader } // IsValidTokenFormat checks if a token has the expected format func IsValidTokenFormat(token string) bool { if !strings.HasPrefix(token, TokenPrefix) { return false } // Remove prefix and check if remaining part is valid base64 tokenData := strings.TrimPrefix(token, TokenPrefix) if len(tokenData) == 0 { return false } // Try to decode base64 _, err := base64.URLEncoding.DecodeString(tokenData) return err == nil } // TokenInfo holds information about a token type TokenInfo struct { Token string Hash string Signature string CreatedAt time.Time } // GenerateTokenWithInfo generates a complete token with hash and signature func (tg *TokenGenerator) GenerateTokenWithInfo() (*TokenInfo, error) { // Generate the token token, err := tg.GenerateSecureToken() if err != nil { return nil, fmt.Errorf("failed to generate token: %w", err) } // Hash the token for storage hash, err := tg.HashToken(token) if err != nil { return nil, fmt.Errorf("failed to hash token: %w", err) } // Create timestamp and signature now := time.Now() signature := tg.SignToken(token, now) return &TokenInfo{ Token: token, Hash: hash, Signature: signature, CreatedAt: now, }, nil } // ValidateTokenInfo validates a complete token with all its components func (tg *TokenGenerator) ValidateTokenInfo(token, hash, signature string, createdAt time.Time) error { // Check token format if !IsValidTokenFormat(token) { return fmt.Errorf("invalid token format") } // Verify token against hash if !tg.VerifyToken(token, hash) { return fmt.Errorf("token verification failed") } // Verify signature if !tg.VerifyTokenSignature(token, signature, createdAt) { return fmt.Errorf("token signature verification failed") } return nil }