Class: Uploadcare::WebhookSignatureVerifier

Inherits:
Object
  • Object
show all
Defined in:
lib/uploadcare/webhook_signature_verifier.rb

Overview

This object verifies a signature received along with webhook headers

Class Method Summary collapse

Class Method Details

.calculate_signature(secret, body) ⇒ String

Calculate HMAC signature for webhook body

Parameters:

  • secret (String)

    signing secret

  • body (String)

    webhook body JSON

Returns:

  • (String)

    calculated signature



37
38
39
40
# File 'lib/uploadcare/webhook_signature_verifier.rb', line 37

def self.calculate_signature(secret, body)
  digest = OpenSSL::Digest.new('sha256')
  "v1=#{OpenSSL::HMAC.hexdigest(digest, secret, body)}"
end

.secure_compare?(first, second) ⇒ Boolean

Constant-time string comparison to prevent timing attacks

Parameters:

  • first (String)

    first string

  • second (String)

    second string

Returns:

  • (Boolean)

    true if strings are equal



46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/uploadcare/webhook_signature_verifier.rb', line 46

def self.secure_compare?(first, second)
  return false if first.nil? || second.nil?
  return false unless first.bytesize == second.bytesize

  OpenSSL.fixed_length_secure_compare(first, second)
rescue NoMethodError
  result = 0
  index = 0
  while index < first.bytesize
    result |= first.getbyte(index) ^ second.getbyte(index)
    index += 1
  end
  result.zero?
end

.valid?(webhook_body: nil, signing_secret: nil, x_uc_signature_header: nil) ⇒ Boolean



8
9
10
11
12
13
14
15
16
17
18
# File 'lib/uploadcare/webhook_signature_verifier.rb', line 8

def self.valid?(webhook_body: nil, signing_secret: nil, x_uc_signature_header: nil)
  webhook_body_json = webhook_body
  signing_secret ||= ENV.fetch('UC_SIGNING_SECRET', nil)

  return false unless valid_parameters?(signing_secret, x_uc_signature_header, webhook_body_json)

  calculated_signature = calculate_signature(signing_secret, webhook_body_json)

  # Use constant-time comparison to prevent timing attacks
  secure_compare?(calculated_signature, x_uc_signature_header)
end

.valid_parameters?(signing_secret, signature_header, body) ⇒ Boolean

Check if all required parameters are present and non-empty

Parameters:

  • signing_secret (String)

    signing secret

  • signature_header (String)

    signature from header

  • body (String)

    webhook body

Returns:

  • (Boolean)

    true if all parameters are valid



25
26
27
28
29
30
31
# File 'lib/uploadcare/webhook_signature_verifier.rb', line 25

def self.valid_parameters?(signing_secret, signature_header, body)
  return false if signing_secret.nil? || signing_secret.to_s.empty?
  return false if signature_header.nil? || signature_header.to_s.empty?
  return false if body.nil? || body.to_s.empty?

  true
end