Skip to content

Commit

Permalink
Fixed signature
Browse files Browse the repository at this point in the history
  • Loading branch information
pboling committed Oct 14, 2010
1 parent d563c4c commit 31ca258
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 138 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,9 @@

* First public release
* Support for recurring-use tokens in the pipeline (Micah Wedemeyer)

0.0.8 - October 14, 2010

* Updated various parts for marketplace compatiblity
* Added more rspec tests
* Switched to signature parameter from awsSignature
45 changes: 6 additions & 39 deletions lib/remit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
require 'date'
require 'base64'
require 'erb'
require 'cgi'

require 'rubygems'

gem 'relax', '0.0.7'
require 'relax'

require 'remit/signature'
require 'remit/data_types'
require 'remit/common'
require 'remit/error_codes'
Expand Down Expand Up @@ -50,6 +52,8 @@

module Remit
class API < Relax::Service
include Signature

include CancelSubscriptionAndRefund
include CancelToken
include Cancel
Expand Down Expand Up @@ -101,58 +105,21 @@ def initialize(access_key, secret_key, sandbox=false)
super(@api_endpoint)
end

def new_query(query={})
SignedQuery.new(@endpoint, @secret_key, query)
end
private :new_query

def default_query
new_query({
Relax::Query.new({
:AWSAccessKeyId => @access_key,
:SignatureVersion => SIGNATURE_VERSION,
:SignatureMethod => SIGNATURE_METHOD,
:Version => API_VERSION,
:Timestamp => Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
})
end
private :default_query

def query(request)
query = super
query[:Signature] = sign_v2(query)
query[:Signature] = sign(@secret_key, @endpoint, "GET", query)
query
end
private :query

# signature version 1
def sign(values)
keys = values.keys.sort { |a, b| a.to_s.downcase <=> b.to_s.downcase }

signature = keys.inject('') do |signature, key|
signature += key.to_s + values[key].to_s
end

Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, @secret_key, signature)).strip
end
private :sign


# signature version 2
def sign_v2(values)
#keys = values.keys.sort { |a, b| a.to_s <=> b.to_s }
#puts "keys = #{pp keys}"
canonical_querystring = values.sort{ |a, b| a.to_s <=> b.to_s }.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&')
canonical_querystring = canonical_querystring.gsub('+','%20')
string_to_sign = "GET
#{@api_endpoint.match(/https:\/\/(.*)\//)[1]}
/
#{canonical_querystring}"
#puts "string_to_sign = #{string_to_sign}"

signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @secret_key, string_to_sign)).strip
#puts "signature = #{signature}"
return signature
end
private :sign_v2
end
end
84 changes: 0 additions & 84 deletions lib/remit/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,88 +69,4 @@ def node_name(name, namespace=nil)
end
end

class SignedQuery < Relax::Query
def initialize(uri, secret_key, query={})
super(query)
@uri = URI.parse(uri.to_s)
@secret_key = secret_key
end

def sign
delete(:awsSignature)
store(:awsSignature, Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, @secret_key, "#{@uri.path}?#{to_s(false)}".gsub('%20', '+'))).strip)
end

def sign_v2
delete(:signature)

sorted_keys = keys.sort { |a, b| a.to_s <=> b.to_s }
#puts "keys = #{pp keys}"

canonical_querystring = self.sort{ |a, b| a.to_s <=> b.to_s }.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&')
canonical_querystring = canonical_querystring.gsub('+','%20')
string_to_sign = "GET
#{@uri.host}
#{@uri.path}
#{canonical_querystring}"
signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @secret_key, string_to_sign)).strip

store(:signature,signature)
#puts "signature = #{signature}"
#return signature
end



def to_s(signed=true)
sign_v2 if signed
super()
end

class << self
def parse(uri, secret_key, query_string)
query = self.new(uri, secret_key)

query_string.split('&').each do |parameter|
key, value = parameter.split('=', 2)
query[key] = unescape_value(value)
end

query
end
end
end

class VerifySignature
require 'open-uri'
require 'cgi'

attr_reader :valid

def initialize( api, uri, params = nil )
begin
params = uri.split('?', 2)[1] unless params
#puts "params = #{params}"
service_url = api.endpoint.to_s + "?Action=VerifySignature&UrlEndPoint=" + CGI.escape(uri.split('?', 2)[0]) +
"&HttpParameters=" + CGI.escape(params) + "&Version=" + Remit::API::API_VERSION

msg = "Checking signature against: #{service_url}"
if defined?(Rails)
Rails.logger.info msg
else
STDOUT.puts msg
end
puts "trying to open #{service_url}"
open( service_url ) {|f| @valid = ( f.read =~ %r{<VerificationStatus>Success</VerificationStatus>})}
rescue Exception => e
if defined?(Rails)
Rails.logger.error $!.message
Rails.logger.error $!.backtrace.join("\n\t")
else
STDERR.puts( $!.message )
STDERR.puts( $!.backtrace.join("\n\t") )
end
end
end
end
end
6 changes: 5 additions & 1 deletion lib/remit/get_pipeline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
module Remit
module GetPipeline
class Pipeline
include Signature

@parameters = []
attr_reader :parameters

Expand Down Expand Up @@ -72,7 +74,9 @@ def url
# Remove any unused optional parameters
query.reject! { |key, value| value.nil? || (value.is_a?(String) && value.empty?) }

uri.query = SignedQuery.new(@api.pipeline_url, @api.secret_key, query).to_s
signature = sign(@api.secret_key, @api.pipeline_url, "GET", query)
uri.query = query.merge('signature' => signature).collect {|k,v| "#{CGI::escape(k.to_s)}=#{CGI::escape(v.to_s)}"}.join("&")

uri.to_s
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/remit/ipn_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def initialize(params, secret_key, api, uri, raw_post = nil)
@params = strip_keys_from(params, 'action', 'controller')
@supplied_signature = @params.delete(SIGNATURE_KEY)
@secret_key = secret_key
#TODO: Next three vars don't seem to be used. Remove? --pboling
@raw_post = raw_post
@api = api
@uri = uri
Expand Down
20 changes: 8 additions & 12 deletions lib/remit/pipeline_response.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
module Remit
class PipelineResponse
include Signature

def initialize(uri, secret_key)
@uri = URI.parse(uri)
@secret_key = secret_key
end

# Returns +true+ if the response is correctly signed (awsSignature).
# Returns +true+ if the response is correctly signed (signature).
#
#--
# The unescape_value method is used here because the awsSignature value
# The unescape_value method is used here because the signature value
# pulled from the request is filtered through the same method.
#++
def valid?( api = nil)
return false unless given_signature
# Relax::Query.unescape_value(correct_signature(api)) == given_signature
correct_signature(api)
Relax::Query.unescape_value(correct_signature) == given_signature
end

# Returns +true+ if the response returns a successful state.
Expand All @@ -36,7 +37,7 @@ def method_missing(method, *args) #:nodoc:
end

def request_query(reload = false)
@query ||= Remit::SignedQuery.parse(@uri, @secret_key, @uri.query || '')
@query ||= sign(@secret_key, @uri.path, "GET", request_query)
end
private :request_query

Expand All @@ -45,13 +46,8 @@ def given_signature
end
private :given_signature

def correct_signature( api = nil)
return nil unless api

Rails.logger.debug "FPS: Computed signature: " + Remit::SignedQuery.new(@uri.path, @secret_key, request_query).sign
Rails.logger.debug "FPS: Real signature: " + request_query[:signature]
# Verifign a responses signature against a webservice seems....silly?
Remit::VerifySignature.new(api, @uri.to_s).valid
def correct_signature
sign(@secret_key, @uri.path, "GET", request_query)
end
private :correct_signature
end
Expand Down
38 changes: 38 additions & 0 deletions lib/remit/signature.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module Remit
module Signature
def sign(secret_key, endpoint, http_verb, params)
s = string_to_sign(endpoint, http_verb, params)
Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret_key, s)).strip
end

def string_to_sign(endpoint, http_verb, values)
if values.is_a?(String)
values = CGI::parse(values)
end
uri = URI::parse(endpoint.to_s)

path = uri.path
path = "/" if (path.nil? || path.empty?)
path = urlencode(path).gsub("%2F", "/")

host = uri.host.downcase

# Explicitly remove the Signature param if found
sorted_keys = values.keys.reject{ |k| k.to_s == "Signature" }.sort { |x,y| x.to_s <=> y.to_s }
converted = sorted_keys.collect do |k|
v = values[k]
s = urlencode(k)
s << "=#{urlencode(v)}" unless v.nil?
s
end
params = converted.join("&")

"#{http_verb}\n#{host}\n#{path}\n#{params}"
end

# Convert a string into URL encoded form that Amazon accepts
def urlencode(plaintext)
CGI.escape(plaintext.to_s).gsub("+", "%20").gsub("%7E", "~")
end
end
end
2 changes: 2 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

require File.expand_path(File.dirname(__FILE__) + '/../lib/remit')

include Remit::Signature

def remit
@remit ||= Remit::API.new(ACCESS_KEY, SECRET_KEY, true)
end
Expand Down
19 changes: 17 additions & 2 deletions spec/units/get_pipeline_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,27 @@
}
end

describe 'with signed url' do
before(:each) do
@uri = URI.parse(@pipeline.url)
end
it "should have a signature" do
@uri.query.should =~ /signature=/
end
it "should specify a signature version" do
@uri.query.should =~ /signatureVersion=2/
end
it "should specify a signature method" do
@uri.query.should =~ /signatureMethod=HmacSHA256/
end
end

it 'should sign its URL' do
uri = URI.parse(@pipeline.url)
pipeline = Remit::SignedQuery.parse(uri, remit.secret_key, uri.query)
signature = sign(remit.secret_key, uri, "GET", uri.query)
query = Relax::Query.parse(uri)

pipeline[:awsSignature].should == query[:awsSignature]
signature == query[:signature]
end
end

Expand Down

0 comments on commit 31ca258

Please sign in to comment.