Module: Certynix::Webhooks
- Defined in:
- lib/certynix/webhooks.rb
Overview
Utilitários para validação de assinatura de webhooks Certynix.
Constant Summary collapse
- TOLERANCE_SECONDS =
5 minutos
300
Class Method Summary collapse
-
.validate_signature(raw_body:, signature:, secret:) ⇒ Hash
Valida a assinatura HMAC-SHA256 de um delivery de webhook.
Class Method Details
.validate_signature(raw_body:, signature:, secret:) ⇒ Hash
Valida a assinatura HMAC-SHA256 de um delivery de webhook.
CRÍTICO: raw_body deve ser o body bruto ANTES de qualquer JSON.parse. Usa OpenSSL.secure_compare (Ruby 2.7+) para constant-time comparison.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/certynix/webhooks.rb', line 22 def self.validate_signature(raw_body:, signature:, secret:) # 1. Parse: "t=timestamp,v1=hash" = nil hash = nil signature.split(',').each do |part| = part[2..] if part.start_with?('t=') hash = part[3..] if part.start_with?('v1=') end if .nil? || .empty? || hash.nil? || hash.empty? raise WebhookSignatureError, 'Invalid signature format: expected t=timestamp,v1=hash' end # 2. Anti-replay ts = .to_i diff = (Time.now.to_i - ts).abs if diff > TOLERANCE_SECONDS raise WebhookReplayError, "Webhook timestamp is #{diff}s old — exceeds #{TOLERANCE_SECONDS}s tolerance" end # 3. Calcular HMAC-SHA256("{timestamp}.{raw_body}") expected = OpenSSL::HMAC.hexdigest('SHA256', secret, "#{}.#{raw_body}") # 4. Constant-time comparison unless OpenSSL.secure_compare(expected, hash) raise WebhookSignatureError, 'Webhook signature mismatch' end # 5. Parse payload begin payload = JSON.parse(raw_body, symbolize_names: true) rescue JSON::ParserError raise WebhookSignatureError, 'Failed to parse webhook payload as JSON' end { type: payload[:type].to_s, payload: payload, timestamp: ts, } end |