Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add optional server-side HMAC TTL to prevent replay attacks #3

Merged
merged 1 commit into from
Dec 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/ey-hmac.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module Hmac
MissingSecret = Class.new(Error)
MissingAuthorization = Class.new(Error)
SignatureMismatch = Class.new(Error)
ExpiredHmac = Class.new(Error)

autoload :Adapter, "ey-hmac/adapter"
autoload :Faraday, "ey-hmac/faraday"
Expand Down
10 changes: 10 additions & 0 deletions lib/ey-hmac/adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ class Ey::Hmac::Adapter

# @param [Object] request signer-specific request implementation
# @option options [Integer] :version signature version
# @option options [Integer] :ttl (nil) duration during which HMAC is valid after signed date
# @option options [String] :authorization_header ('Authorization') Authorization header key.
# @option options [String] :server ('EyHmac') service name prefixed to {#authorization}. set to {#service}
# @option options [Symbol] :sign_with (:sha_256) outgoing signature digest algorithm. See {OpenSSL::Digest#new}
# @option options [Array] :accepted_digests ([:sha_256]) accepted incoming signature digest algorithm. See {OpenSSL::Digest#new}
def initialize(request, options={})
@request, @options = request, options

@ttl = options[:ttl]
@authorization_header = options[:authorization_header] || 'Authorization'
@service = options[:service] || 'EyHmac'
@sign_with = options[:sign_with] || :sha256
Expand Down Expand Up @@ -121,6 +123,14 @@ def authenticated!(&block)
raise(Ey::Hmac::MissingSecret, "Failed to find secret matching #{key_id.inspect}")
end

unless @ttl.nil?
expiry = Time.parse(date).to_i + @ttl
current_time = Time.now.to_i
unless expiry > current_time
raise(Ey::Hmac::ExpiredHmac, "Signature has expired passed #{expiry}. Current time is #{current_time}")
end
end

calculated_signatures = self.accept_digests.map { |ad| signature(key_secret, ad) }

unless calculated_signatures.any? { |cs| secure_compare(signature_value, cs) }
Expand Down
20 changes: 20 additions & 0 deletions spec/shared/authenticated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,25 @@
end).to be_falsey
}.to raise_exception(Ey::Hmac::MissingAuthorization)
end

context "when the server specifies an HMAC TTL" do
it "should not authenticate expired hmac" do
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
expect {
Ey::Hmac.authenticate!(request, adapter: adapter, ttl: 0) do |auth_id|
(auth_id == key_id) && key_secret
end
}.to raise_exception(Ey::Hmac::ExpiredHmac)
end

it "should authenticate non-expired hmac" do
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
expect {
Ey::Hmac.authenticate!(request, adapter: adapter, ttl: 100) do |auth_id|
(auth_id == key_id) && key_secret
end
}.to_not raise_exception
end
end
end
end