Skip to content

Commit

Permalink
Merge pull request #363 from HubSpot/feature/enhance-signature
Browse files Browse the repository at this point in the history
Enhance Signature
  • Loading branch information
alzheltkovskiy-hubspot authored Feb 3, 2025
2 parents 7c0701a + 39214e7 commit 52e31dd
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 30 deletions.
70 changes: 45 additions & 25 deletions lib/hubspot/helpers/signature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,33 @@
module Hubspot
module Helpers
class Signature
MAX_ALLOWED_TIMESTAMP = 3000
MAX_ALLOWED_TIMESTAMP = 300_000

def timestamp_valid?(timestamp)
return false if timestamp.nil?

begin
get_current_timestamp_microseconds - timestamp.to_i < MAX_ALLOWED_TIMESTAMP
rescue StandardError
false
end
end

def is_valid(
signature: String,
client_secret: String,
request_body: String,
signature:,
client_secret:,
request_body:,
http_uri: nil,
http_method: 'POST',
signature_version: 'v2',
timestamp: nil
)
if signature_version == "v3"
current_time = DateTime.now.strftime("%s").to_i
if current_time - timestamp.to_i > MAX_ALLOWED_TIMESTAMP
raise InvalidSignatureTimestampError.new(timestamp)
unless timestamp_valid?(timestamp)
raise InvalidSignatureTimestampError, "Invalid timestamp: #{timestamp}"
end
end

hashed_signature = get_signature(
client_secret: client_secret,
request_body: request_body,
Expand All @@ -29,34 +40,43 @@ def is_valid(
timestamp: timestamp
)

signature == hashed_signature
secure_compare(signature, hashed_signature)
end

def get_current_timestamp_microseconds
(Time.now.to_f * 1000).to_i
end

def get_signature(
client_secret: String,
request_body: String,
signature_version: String,
client_secret:,
request_body:,
signature_version:,
http_uri: nil,
http_method: "POST",
timestamp: nil
)
case signature_version
when "v1"
source_string = "#{client_secret}#{request_body}"
hash_result = Digest::SHA2.hexdigest(source_string.encode('utf-8'))
return hash_result
when "v2"
source_string = "#{client_secret}#{http_method}#{http_uri}#{request_body}"
hash_result = Digest::SHA2.hexdigest(source_string.encode('utf-8'))
return hash_result
when "v3"
source_string = "#{http_method}#{http_uri}#{request_body}#{timestamp}"
hash_result = OpenSSL::HMAC.base64digest('SHA256', client_secret, source_string.encode('utf-8'))
return hash_result
else
raise InvalidSignatureVersionError.new(signature_version)
when "v1"
source_string = "#{client_secret}#{request_body}"
Digest::SHA2.hexdigest(source_string.encode('utf-8'))
when "v2"
source_string = "#{client_secret}#{http_method}#{http_uri}#{request_body}"
Digest::SHA2.hexdigest(source_string.encode('utf-8'))
when "v3"
source_string = "#{http_method}#{http_uri}#{request_body}#{timestamp}"
OpenSSL::HMAC.base64digest('SHA256', client_secret, source_string.encode('utf-8'))
else
raise InvalidSignatureVersionError, "Invalid signature version: #{signature_version}"
end
end

def secure_compare(a, b)
return false unless a.is_a?(String) && b.is_a?(String) && a.bytesize == b.bytesize

result = 0
a.bytes.zip(b.bytes) { |x, y| result |= x ^ y }
result.zero?
end
end
end
end
20 changes: 15 additions & 5 deletions spec/helpers/signature_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
:request_body=> "{'example_field':'example_value'}",
:url=> "https://www.example.com/webhook_uri",
:http_method=> "POST",
:timestamp=> 15000000,
:timestamp=> 1_700_000_300_000,
:v1_hash=> "69fc6631a867edd4f9e9e627fc5c1148e3fbdd8b21837b6d2b8901c1fa57f750",
:v2_hash=> "4fe4e3a7d3cf09db53be39d0a58130e2aaba074ec123a9e355b876a689a1c383",
:v3_hash=> "HPW73RUtKmcYoEDADG0s6MmGFWUzWJKAW07r8RDgcQw=",
:v3_hash=> "RnbPH7+UMKVkbV32P8bz450N4M56aPmcru1+D3kSDtw=",
}


Expand Down Expand Up @@ -77,12 +77,12 @@
expect(result).to be true
end
it "should return true for v3 signature version" do
test_timestamp = DateTime.now.strftime("%s")
test_timestamp_microseconds = (Time.now.to_f * 1000).to_i
test_signature = signature.get_signature(
client_secret: TEST_DATA[:client_secret],
request_body: TEST_DATA[:request_body],
http_uri: TEST_DATA[:http_uri],
timestamp: test_timestamp,
timestamp: test_timestamp_microseconds,
signature_version: "v3"
)

Expand All @@ -91,7 +91,7 @@
client_secret: TEST_DATA[:client_secret],
request_body: TEST_DATA[:request_body],
http_uri: TEST_DATA[:http_uri],
timestamp: test_timestamp,
timestamp: test_timestamp_microseconds,
signature_version: "v3"
)
expect(result).to be true
Expand All @@ -105,6 +105,16 @@
signature_version: "v3"
) }.to raise_error(Hubspot::InvalidSignatureTimestampError)
end
it "should raise exception if :signature_version=>v3 and :timestamp=>expired" do
expect { signature.is_valid(
signature: TEST_DATA[:v3_hash],
client_secret: TEST_DATA[:client_secret],
request_body: TEST_DATA[:request_body],
http_uri: TEST_DATA[:http_uri],
timestamp: 1_700_000_000_000,
signature_version: "v3"
) }.to raise_error(Hubspot::InvalidSignatureTimestampError)
end
it "should raise exception if :signature_version=>v3 and :timestamp=>wrong_timestamp" do
expect { signature.is_valid(
signature: TEST_DATA[:v3_hash],
Expand Down

0 comments on commit 52e31dd

Please sign in to comment.