Ctrl K
ComunidadGuías…

internal/webhooks/webhook_service_crypto.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package webhooks

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io"

	svcerrors "github.com/sarathsp06/sparrow/internal/errors"
)

// EncryptSecretHeaders encrypts a plaintext secret headers map to bytes for storage.
// Returns nil if the map is empty or nil, or if encryption is not configured.
func (s *WebhookService) EncryptSecretHeaders(headers map[string]string) ([]byte, error) {
	if len(headers) == 0 {
		return nil, nil
	}
	if s.crypto == nil || !s.crypto.Enabled() {
		return nil, svcerrors.Error(svcerrors.FailedPrecondition, "encryption is required for secret headers but SPARROW_ENCRYPTION_KEYS is not configured")
	}
	return s.crypto.EncryptJSON(headers)
}

// DecryptSecretHeaders decrypts encrypted secret headers bytes back to a plaintext map.
// Returns nil map if the encrypted data is nil/empty or if encryption is not configured.
func (s *WebhookService) DecryptSecretHeaders(encrypted []byte) (map[string]string, error) {
	if len(encrypted) == 0 {
		return nil, nil
	}
	if s.crypto == nil || !s.crypto.Enabled() {
		return nil, svcerrors.Error(svcerrors.FailedPrecondition, "encryption key not configured; cannot decrypt secret headers")
	}
	var headers map[string]string
	if err := s.crypto.DecryptJSON(encrypted, &headers); err != nil {
		return nil, fmt.Errorf("failed to decrypt secret headers: %w", err)
	}
	return headers, nil
}

// EncryptWebhookSecret encrypts a plaintext webhook secret string to bytes for storage.
// Returns nil if the secret is empty, or if encryption is not configured.
func (s *WebhookService) EncryptWebhookSecret(secret string) ([]byte, error) {
	if secret == "" {
		return nil, nil
	}
	if s.crypto == nil || !s.crypto.Enabled() {
		return nil, svcerrors.Error(svcerrors.FailedPrecondition, "encryption is required for webhook secrets but SPARROW_ENCRYPTION_KEYS is not configured")
	}
	return s.crypto.EncryptString(secret)
}

// DecryptWebhookSecret decrypts encrypted webhook secret bytes back to a plaintext string.
// Returns "" if the encrypted data is nil/empty or if encryption is not configured.
func (s *WebhookService) DecryptWebhookSecret(encrypted []byte) (string, error) {
	if len(encrypted) == 0 {
		return "", nil
	}
	if s.crypto == nil || !s.crypto.Enabled() {
		return "", svcerrors.Error(svcerrors.FailedPrecondition, "encryption key not configured; cannot decrypt webhook secret")
	}
	return s.crypto.DecryptString(encrypted)
}

// WebhookSigningPublicKeyHex decrypts the envelope-encrypted Ed25519 private key
// and returns the hex-encoded public key. It returns an empty string on any
// error or if the key is absent, so transport modules never handle private-key
// material directly.
func (s *WebhookService) WebhookSigningPublicKeyHex(encryptedPrivKey []byte) string {
	if len(encryptedPrivKey) == 0 || s.crypto == nil {
		return ""
	}
	decrypted, err := s.crypto.DecryptString(encryptedPrivKey)
	if err != nil {
		return ""
	}
	privKey := ed25519.PrivateKey([]byte(decrypted))
	if len(privKey) != ed25519.PrivateKeySize {
		return ""
	}
	pubKey := privKey.Public().(ed25519.PublicKey)
	return hex.EncodeToString(pubKey)
}

// generateEncryptedEd25519Key generates a new Ed25519 keypair and returns the
// envelope-encrypted private key for storage. The public key is derived from
// the private key at runtime (see WebhookSigningPublicKeyHex).
// Returns nil if encryption is not configured.
func (s *WebhookService) generateEncryptedEd25519Key() ([]byte, error) {
	if s.crypto == nil || !s.crypto.Enabled() {
		return nil, nil
	}
	_, privKey, err := ed25519.GenerateKey(nil) // crypto/rand by default
	if err != nil {
		return nil, fmt.Errorf("failed to generate Ed25519 keypair: %w", err)
	}
	encPrivKey, err := s.crypto.EncryptString(string(privKey))
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt Ed25519 private key: %w", err)
	}
	return encPrivKey, nil
}

// generateWebhookSecret generates a new cryptographically random secret
// in Standard Webhooks format: "whsec_" prefix + base64 encoded entropy.
func generateWebhookSecret() (string, error) {
	// 24 bytes of entropy = 32 chars in base64
	key := make([]byte, 24)
	if _, err := io.ReadFull(rand.Reader, key); err != nil {
		return "", fmt.Errorf("failed to generate random bytes: %w", err)
	}
	return "whsec_" + base64.StdEncoding.EncodeToString(key), nil
}