Permalink
Browse files

Added canonical_string and signature helpers to the AuthHMAC class as…

… useful tools for consumers of hte library. Added new specs. Added a simple program to generate signatures on the command line.
  • Loading branch information...
1 parent 6a23d94 commit 401758285201a9f2a57384b534e2e1916cd67286 Andrew Carter committed Jan 9, 2009
Showing with 120 additions and 51 deletions.
  1. +38 −0 bin/auth-hmac
  2. +23 −2 lib/auth-hmac.rb
  3. +59 −49 spec/auth-hmac_spec.rb
View
@@ -0,0 +1,38 @@
+#!/usr/bin/env ruby
+APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+
+require 'openssl'
+require 'base64'
+
+def usage
+ puts "Usage: auth-hmac COMMAND [ARGS]"
+ puts "\nCommands:"
+ puts " signature [secret] [signature string] Creates HMAC digtest using optional secret"
+ exit
+end
+
+def signature(args)
+ secret = args[0]
+ signature = args[1]
+ digest = OpenSSL::Digest::Digest.new('sha1')
+ puts Base64.encode64(OpenSSL::HMAC.digest(digest, secret, signature)).strip
+end
+
+#
+# MAIN
+#
+
+usage if ['--help', '-h'].include?(ARGV[0])
+
+command = ARGV.shift
+args = []
+ARGV.each { |arg| args << arg }
+
+usage if command.nil?
+
+case command
+when "signature"
+ signature(args)
+end
+
+
View
@@ -78,6 +78,24 @@ def initialize(credential_store, options = nil)
parse_options(options)
end
+ # Generates canonical signing string for given request
+ #
+ # Supports same options as AuthHMAC.initialize for overriding service_id and
+ # signature method.
+ #
+ def AuthHMAC.canonical_string(request, options = nil)
+ self.new(nil, options).canonical_string(request)
+ end
+
+ # Generates signature string for a given secret
+ #
+ # Supports same options as AuthHMAC.initialize for overriding service_id and
+ # signature method.
+ #
+ def AuthHMAC.signature(request, secret, options = nil)
+ self.new(nil, options).signature(request, secret)
+ end
+
# Signs a request using a given access key id and secret.
#
# Supports same options as AuthHMAC.initialize for overriding service_id and
@@ -132,9 +150,12 @@ def authenticated?(request)
end
def signature(request, secret)
- canonical_string = @signature_method.call(request)
digest = OpenSSL::Digest::Digest.new('sha1')
- Base64.encode64(OpenSSL::HMAC.digest(digest, secret, canonical_string)).strip
+ Base64.encode64(OpenSSL::HMAC.digest(digest, secret, canonical_string(request))).strip
+ end
+
+ def canonical_string(request)
+ @signature_method.call(request)
end
private
View
@@ -17,112 +17,122 @@ def initialize(request)
end
end
+def signature(value, secret)
+ digest = OpenSSL::Digest::Digest.new('sha1')
+ Base64.encode64(OpenSSL::HMAC.digest(digest, secret, value)).strip
+end
+
describe AuthHMAC do
+ before(:each) do
+ @request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
+ 'content-type' => 'text/plain',
+ 'content-md5' => 'blahblah',
+ 'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
+ end
+
+ describe ".canonical_string" do
+ it "should generate a canonical string using default method" do
+ AuthHMAC.canonical_string(@request).should == "PUT\ntext/plain\nblahblah\nThu, 10 Jul 2008 03:29:56 GMT\n/path/to/put"
+ end
+ end
+
+ describe ".signature" do
+ it "should generate a valid signature string for a secret" do
+ AuthHMAC.signature(@request, 'secret').should == "71wAJM4IIu/3o6lcqx/tw7XnAJs="
+ end
+ end
+
describe ".sign!" do
+ before(:each) do
+ @request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
+ 'content-type' => 'text/plain',
+ 'content-md5' => 'blahblah',
+ 'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
+ end
+
it "should sign using the key passed in as a parameter" do
- request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
- 'content-type' => 'text/plain',
- 'content-md5' => 'blahblah',
- 'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
- AuthHMAC.sign!(request, "my-key-id", "secret")
- request['Authorization'].should == "AuthHMAC my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
+ AuthHMAC.sign!(@request, "my-key-id", "secret")
+ @request['Authorization'].should == "AuthHMAC my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
end
it "should sign using custom service id" do
- request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
- 'content-type' => 'text/plain',
- 'content-md5' => 'blahblah',
- 'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
- AuthHMAC.sign!(request, "my-key-id", "secret", { :service_id => 'MyService' })
- request['Authorization'].should == "MyService my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
+ AuthHMAC.sign!(@request, "my-key-id", "secret", { :service_id => 'MyService' })
+ @request['Authorization'].should == "MyService my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
end
it "should sign using custom signature method" do
- request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
- 'content-type' => 'text/plain',
- 'content-md5' => 'blahblah',
- 'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
options = {
:service_id => 'MyService',
:signature_method => lambda { |r| CustomSignature.new(r) }
}
- AuthHMAC.sign!(request, "my-key-id", "secret", options)
- request['Authorization'].should == "MyService my-key-id:/L4N1v1BZSHfAYkQjsvZn696D9c="
+ AuthHMAC.sign!(@request, "my-key-id", "secret", options)
+ @request['Authorization'].should == "MyService my-key-id:/L4N1v1BZSHfAYkQjsvZn696D9c="
end
end
describe "#sign!" do
before(:each) do
- @store = mock('store')
+ @get_request = Net::HTTP::Get.new("/")
+ @put_request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
+ 'content-type' => 'text/plain',
+ 'content-md5' => 'blahblah',
+ 'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
+ @store = mock('store')
@store.stub!(:[]).and_return("")
@authhmac = AuthHMAC.new(@store)
end
describe "default AuthHMAC with CanonicalString signature" do
it "should add an Authorization header" do
- request = Net::HTTP::Get.new("/")
- @authhmac.sign!(request, 'key-id')
- request.key?("Authorization").should be_true
+ @authhmac.sign!(@get_request, 'key-id')
+ @get_request.key?("Authorization").should be_true
end
it "should fetch the secret from the store" do
- request = Net::HTTP::Get.new("/")
@store.should_receive(:[]).with('key-id').and_return('secret')
- @authhmac.sign!(request, 'key-id')
+ @authhmac.sign!(@get_request, 'key-id')
end
it "should prefix the Authorization Header with AuthHMAC" do
- request = Net::HTTP::Get.new("/")
- @authhmac.sign!(request, 'key-id')
- request['Authorization'].should match(/^AuthHMAC /)
+ @authhmac.sign!(@get_request, 'key-id')
+ @get_request['Authorization'].should match(/^AuthHMAC /)
end
it "should include the key id as the first part of the Authorization header value" do
- request = Net::HTTP::Get.new("/")
- @authhmac.sign!(request, 'key-id')
- request['Authorization'].should match(/^AuthHMAC key-id:/)
+ @authhmac.sign!(@get_request, 'key-id')
+ @get_request['Authorization'].should match(/^AuthHMAC key-id:/)
end
it "should include the base64 encoded HMAC signature as the last part of the header value" do
- request = Net::HTTP::Get.new("/path")
- @authhmac.sign!(request, 'key-id')
- request['Authorization'].should match(/:[A-Za-z0-9+\/]{26,28}[=]{0,2}$/)
+ @authhmac.sign!(@get_request, 'key-id')
+ @get_request['Authorization'].should match(/:[A-Za-z0-9+\/]{26,28}[=]{0,2}$/)
end
it "should create a complete signature" do
@store.should_receive(:[]).with('my-key-id').and_return('secret')
- request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
- 'content-type' => 'text/plain',
- 'content-md5' => 'blahblah',
- 'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
- @authhmac.sign!(request, "my-key-id")
- request['Authorization'].should == "AuthHMAC my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
+ @authhmac.sign!(@put_request, "my-key-id")
+ @put_request['Authorization'].should == "AuthHMAC my-key-id:71wAJM4IIu/3o6lcqx/tw7XnAJs="
end
end
describe "custom signatures" do
before(:each) do
- @options = {
+ @options = {
:service_id => 'MyService',
:signature_method => lambda { |r| CustomSignature.new(r) }
}
@authhmac = AuthHMAC.new(@store, @options)
end
it "should prefix the Authorization header with custom service id" do
- request = Net::HTTP::Get.new("/")
- @authhmac.sign!(request, 'key-id')
- request['Authorization'].should match(/^MyService /)
+ @authhmac.sign!(@get_request, 'key-id')
+ @get_request['Authorization'].should match(/^MyService /)
end
it "should create a complete signature using options" do
@store.should_receive(:[]).with('my-key-id').and_return('secret')
- request = Net::HTTP::Put.new("/path/to/put?foo=bar&bar=foo",
- 'content-type' => 'text/plain',
- 'content-md5' => 'blahblah',
- 'date' => "Thu, 10 Jul 2008 03:29:56 GMT")
- @authhmac.sign!(request, "my-key-id")
- request['Authorization'].should == "MyService my-key-id:/L4N1v1BZSHfAYkQjsvZn696D9c="
+ @authhmac.sign!(@put_request, "my-key-id")
+ @put_request['Authorization'].should == "MyService my-key-id:/L4N1v1BZSHfAYkQjsvZn696D9c="
end
end
end

0 comments on commit 4017582

Please sign in to comment.