mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
455 lines
10 KiB
Go
455 lines
10 KiB
Go
//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
|
||
|
|
}
|