Files
sonr/cmd/motr/payment_security.go
T

455 lines
10 KiB
Go
Raw Normal View History

2025-10-03 14:45:52 -04:00
//go:build js && wasm
// +build js,wasm
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"regexp"
"strings"
"sync"
"time"
)
// PaymentTokenizer handles secure payment method tokenization
type PaymentTokenizer struct {
mu sync.RWMutex
tokens map[string]*TokenData
encryptKey []byte
}
// TokenData stores tokenized payment data
type TokenData struct {
Token string `json:"token"`
LastFour string `json:"last_four"`
Brand string `json:"brand"`
ExpiryMonth int `json:"expiry_month"`
ExpiryYear int `json:"expiry_year"`
CreatedAt time.Time `json:"created_at"`
UsedCount int `json:"used_count"`
}
// TransactionSigner handles transaction signing and verification
type TransactionSigner struct {
signKey []byte
}
// PCICompliance handles PCI DSS compliance requirements
type PCICompliance struct {
auditLog []AuditEntry
mu sync.RWMutex
}
// AuditEntry for PCI compliance logging
type AuditEntry struct {
Timestamp time.Time `json:"timestamp"`
Action string `json:"action"`
UserID string `json:"user_id"`
ResourceID string `json:"resource_id"`
Result string `json:"result"`
IPAddress string `json:"ip_address"`
}
var (
paymentTokenizer *PaymentTokenizer
transactionSigner *TransactionSigner
pciCompliance *PCICompliance
initOnce sync.Once
)
// InitializePaymentSecurity initializes payment security components
func InitializePaymentSecurity() {
initOnce.Do(func() {
// Generate encryption key (in production, use KMS)
encKey := make([]byte, 32)
io.ReadFull(rand.Reader, encKey)
// Generate signing key
signKey := make([]byte, 32)
io.ReadFull(rand.Reader, signKey)
paymentTokenizer = &PaymentTokenizer{
tokens: make(map[string]*TokenData),
encryptKey: encKey,
}
transactionSigner = &TransactionSigner{
signKey: signKey,
}
pciCompliance = &PCICompliance{
auditLog: make([]AuditEntry, 0),
}
})
}
// TokenizeCard tokenizes credit card data (PCI DSS compliant)
func TokenizeCard(cardNumber, cvv string, expiryMonth, expiryYear int) (string, error) {
// Validate card number using Luhn algorithm
if !validateLuhn(cardNumber) {
return "", fmt.Errorf("invalid card number")
}
// Validate CVV
if !validateCVV(cvv) {
return "", fmt.Errorf("invalid CVV")
}
// Validate expiry
if !validateExpiry(expiryMonth, expiryYear) {
return "", fmt.Errorf("card expired or invalid expiry date")
}
// Extract card info
lastFour := cardNumber[len(cardNumber)-4:]
brand := detectCardBrand(cardNumber)
// Generate secure token
token := generateSecureToken()
// Store tokenized data (never store raw card data)
tokenData := &TokenData{
Token: token,
LastFour: lastFour,
Brand: brand,
ExpiryMonth: expiryMonth,
ExpiryYear: expiryYear,
CreatedAt: time.Now(),
UsedCount: 0,
}
paymentTokenizer.mu.Lock()
paymentTokenizer.tokens[token] = tokenData
paymentTokenizer.mu.Unlock()
// Log tokenization for PCI compliance
pciCompliance.LogAction("TOKENIZE_CARD", "", token, "SUCCESS", "")
return token, nil
}
// validateLuhn validates credit card number using Luhn algorithm
func validateLuhn(cardNumber string) bool {
// Remove spaces and dashes
cardNumber = strings.ReplaceAll(cardNumber, " ", "")
cardNumber = strings.ReplaceAll(cardNumber, "-", "")
// Check if all digits
if !regexp.MustCompile(`^\d+$`).MatchString(cardNumber) {
return false
}
// Luhn algorithm
sum := 0
isEven := false
for i := len(cardNumber) - 1; i >= 0; i-- {
digit := int(cardNumber[i] - '0')
if isEven {
digit *= 2
if digit > 9 {
digit -= 9
}
}
sum += digit
isEven = !isEven
}
return sum%10 == 0
}
// validateCVV validates CVV format
func validateCVV(cvv string) bool {
// CVV should be 3 or 4 digits
return regexp.MustCompile(`^\d{3,4}$`).MatchString(cvv)
}
// validateExpiry validates card expiry date
func validateExpiry(month, year int) bool {
now := time.Now()
currentYear := now.Year()
currentMonth := int(now.Month())
// Check valid month
if month < 1 || month > 12 {
return false
}
// Check if expired
if year < currentYear || (year == currentYear && month < currentMonth) {
return false
}
// Check reasonable future date (max 20 years)
if year > currentYear+20 {
return false
}
return true
}
// detectCardBrand detects card brand from number
func detectCardBrand(cardNumber string) string {
// Remove spaces and dashes
cardNumber = strings.ReplaceAll(cardNumber, " ", "")
cardNumber = strings.ReplaceAll(cardNumber, "-", "")
// Visa
if strings.HasPrefix(cardNumber, "4") {
return "visa"
}
// Mastercard
if regexp.MustCompile(`^5[1-5]`).MatchString(cardNumber) ||
regexp.MustCompile(`^2[2-7]`).MatchString(cardNumber) {
return "mastercard"
}
// American Express
if strings.HasPrefix(cardNumber, "34") || strings.HasPrefix(cardNumber, "37") {
return "amex"
}
// Discover
if strings.HasPrefix(cardNumber, "6011") || strings.HasPrefix(cardNumber, "65") {
return "discover"
}
return "unknown"
}
// generateSecureToken generates a cryptographically secure token
func generateSecureToken() string {
b := make([]byte, 32)
rand.Read(b)
return "tok_" + base64.URLEncoding.EncodeToString(b)
}
// SignTransaction signs a transaction for integrity
func SignTransaction(transactionData map[string]interface{}) (string, error) {
// Serialize transaction data
data, err := json.Marshal(transactionData)
if err != nil {
return "", err
}
// Create HMAC signature
h := hmac.New(sha256.New, transactionSigner.signKey)
h.Write(data)
signature := hex.EncodeToString(h.Sum(nil))
// Log signing for audit
txID := ""
if id, ok := transactionData["id"].(string); ok {
txID = id
}
pciCompliance.LogAction("SIGN_TRANSACTION", "", txID, "SUCCESS", "")
return signature, nil
}
// VerifyTransactionSignature verifies a transaction signature
func VerifyTransactionSignature(transactionData map[string]interface{}, signature string) bool {
// Serialize transaction data
data, err := json.Marshal(transactionData)
if err != nil {
return false
}
// Create HMAC signature
h := hmac.New(sha256.New, transactionSigner.signKey)
h.Write(data)
expectedSignature := hex.EncodeToString(h.Sum(nil))
// Compare signatures
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
// EncryptSensitiveData encrypts sensitive payment data
func EncryptSensitiveData(plaintext string) (string, error) {
// Create cipher
block, err := aes.NewCipher(paymentTokenizer.encryptKey)
if err != nil {
return "", err
}
// Generate nonce
nonce := make([]byte, 12)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
// Encrypt
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
ciphertext := aesgcm.Seal(nil, nonce, []byte(plaintext), nil)
// Combine nonce and ciphertext
combined := append(nonce, ciphertext...)
return base64.StdEncoding.EncodeToString(combined), nil
}
// DecryptSensitiveData decrypts sensitive payment data
func DecryptSensitiveData(encrypted string) (string, error) {
// Decode from base64
combined, err := base64.StdEncoding.DecodeString(encrypted)
if err != nil {
return "", err
}
// Extract nonce and ciphertext
if len(combined) < 12 {
return "", fmt.Errorf("invalid encrypted data")
}
nonce := combined[:12]
ciphertext := combined[12:]
// Create cipher
block, err := aes.NewCipher(paymentTokenizer.encryptKey)
if err != nil {
return "", err
}
// Decrypt
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
// LogAction logs an action for PCI compliance audit
func (p *PCICompliance) LogAction(action, userID, resourceID, result, ipAddress string) {
p.mu.Lock()
defer p.mu.Unlock()
entry := AuditEntry{
Timestamp: time.Now(),
Action: action,
UserID: userID,
ResourceID: resourceID,
Result: result,
IPAddress: ipAddress,
}
p.auditLog = append(p.auditLog, entry)
// In production, persist to secure audit log storage
// For now, just keep in memory (limited to last 10000 entries)
if len(p.auditLog) > 10000 {
p.auditLog = p.auditLog[1:]
}
}
// GetAuditLog returns recent audit log entries
func (p *PCICompliance) GetAuditLog(limit int) []AuditEntry {
p.mu.RLock()
defer p.mu.RUnlock()
if limit > len(p.auditLog) {
limit = len(p.auditLog)
}
// Return most recent entries
start := len(p.auditLog) - limit
if start < 0 {
start = 0
}
return p.auditLog[start:]
}
// ValidateToken validates a payment token
func ValidateToken(token string) (*TokenData, error) {
paymentTokenizer.mu.RLock()
defer paymentTokenizer.mu.RUnlock()
tokenData, exists := paymentTokenizer.tokens[token]
if !exists {
return nil, fmt.Errorf("invalid token")
}
// Check if token is expired (tokens valid for 1 hour)
if time.Since(tokenData.CreatedAt) > time.Hour {
return nil, fmt.Errorf("token expired")
}
// Increment usage count
tokenData.UsedCount++
return tokenData, nil
}
// MaskCardNumber masks all but last 4 digits of card number
func MaskCardNumber(cardNumber string) string {
// Remove spaces and dashes
cardNumber = strings.ReplaceAll(cardNumber, " ", "")
cardNumber = strings.ReplaceAll(cardNumber, "-", "")
if len(cardNumber) < 4 {
return strings.Repeat("*", len(cardNumber))
}
lastFour := cardNumber[len(cardNumber)-4:]
masked := strings.Repeat("*", len(cardNumber)-4) + lastFour
// Format based on card type
if len(masked) == 16 {
// Format as XXXX XXXX XXXX 1234
return masked[:4] + " " + masked[4:8] + " " + masked[8:12] + " " + masked[12:]
}
return masked
}
// SanitizePaymentData removes sensitive data from payment objects
func SanitizePaymentData(data map[string]interface{}) map[string]interface{} {
sanitized := make(map[string]interface{})
// List of sensitive fields to exclude
sensitiveFields := []string{
"card_number", "cvv", "cvc", "card_code",
"account_number", "routing_number", "pin",
}
for key, value := range data {
// Check if field is sensitive
isSensitive := false
keyLower := strings.ToLower(key)
for _, sensitive := range sensitiveFields {
if strings.Contains(keyLower, sensitive) {
isSensitive = true
break
}
}
if !isSensitive {
sanitized[key] = value
}
}
return sanitized
}