package signing import ( "context" "crypto/hmac" "crypto/sha256" "encoding/hex" "strconv" "strings" "time" "github.com/tech/sendico/edge/callbacks/internal/secrets" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" "go.uber.org/zap" ) type service struct { logger mlogger.Logger provider secrets.Provider } func newService(deps Dependencies) (Signer, error) { if deps.Provider == nil { return nil, merrors.InvalidArgument("signing: secrets provider is required", "provider") } logger := deps.Logger if logger == nil { logger = zap.NewNop() } return &service{ logger: logger.Named("signing"), provider: deps.Provider, }, nil } func (s *service) Sign(ctx context.Context, mode, secretRef string, payload []byte, now time.Time) (*SignedPayload, error) { normalizedMode := strings.ToLower(strings.TrimSpace(mode)) if normalizedMode == "" { normalizedMode = ModeNone } switch normalizedMode { case ModeNone: return &SignedPayload{ Body: append([]byte(nil), payload...), Headers: map[string]string{}, }, nil case ModeHMACSHA256: if strings.TrimSpace(secretRef) == "" { return nil, merrors.InvalidArgument("signing: secret reference is required for hmac", "secret_ref") } secret, err := s.provider.GetSecret(ctx, secretRef) if err != nil { s.logger.Warn("Failed to load signing secret", zap.String("secret_ref", secretRef), zap.Error(err)) return nil, err } ts := now.UTC().Format(time.RFC3339Nano) mac := hmac.New(sha256.New, []byte(secret)) message := append([]byte(ts+"."), payload...) if _, err := mac.Write(message); err != nil { return nil, merrors.InternalWrap(err, "signing: failed to compute hmac") } signature := hex.EncodeToString(mac.Sum(nil)) return &SignedPayload{ Body: append([]byte(nil), payload...), Headers: map[string]string{ "X-Callback-Timestamp": ts, "X-Callback-Signature": signature, "X-Callback-Algorithm": "hmac-sha256", "Content-Length": strconv.Itoa(len(payload)), }, }, nil default: return nil, merrors.InvalidArgument("signing: unsupported mode", "mode") } }