mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
@@ -0,0 +1,454 @@
|
||||
//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
|
||||
}
|
||||
Reference in New Issue
Block a user