summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/mixlib/authentication/digester.rb44
-rw-r--r--lib/mixlib/authentication/signatureverification.rb4
-rw-r--r--lib/mixlib/authentication/signedheaderauth.rb31
-rw-r--r--spec/mixlib/authentication/mixlib_authentication_spec.rb34
4 files changed, 59 insertions, 54 deletions
diff --git a/lib/mixlib/authentication/digester.rb b/lib/mixlib/authentication/digester.rb
index f3a3597..7dc6dd7 100644
--- a/lib/mixlib/authentication/digester.rb
+++ b/lib/mixlib/authentication/digester.rb
@@ -21,36 +21,28 @@ require 'mixlib/authentication'
module Mixlib
module Authentication
class Digester
- attr_reader :hashed_body
- def initialize()
- @hashed_body = nil
- end
+ class << self
+
+ def hash_file(f)
+ digester = Digest::SHA1.new
+ buf = ""
+ while f.read(16384, buf)
+ digester.update buf
+ end
+ ::Base64.encode64(digester.digest).chomp
+ end
- # Compare the request timestamp with boundary time
- #
- #
- # ====Parameters
- # time1<Time>:: minuend
- # time2<Time>:: subtrahend
- #
- def hash_file(f)
- digester = Digest::SHA1.new
- buf = ""
- while f.read(16384, buf)
- digester.update buf
- end
- @hashed_body ||= ::Base64.encode64(digester.digest).chomp
+ # Digests a string, base64's and chomps the end
+ #
+ # ====Parameters
+ #
+ def hash_string(str)
+ ::Base64.encode64(Digest::SHA1.digest(str)).chomp
+ end
+
end
- # Digests the body, base64's and chomps the end
- #
- #
- # ====Parameters
- #
- def hash_body(body)
- @hashed_body ||= ::Base64.encode64(Digest::SHA1.digest(body)).chomp
- end
end
end
end
diff --git a/lib/mixlib/authentication/signatureverification.rb b/lib/mixlib/authentication/signatureverification.rb
index ee837ee..d067a2e 100644
--- a/lib/mixlib/authentication/signatureverification.rb
+++ b/lib/mixlib/authentication/signatureverification.rb
@@ -44,7 +44,7 @@ module Mixlib
Mixlib::Authentication::Log.debug "Initializing header auth : #{request.inspect}"
headers ||= request.env.inject({ }) { |memo, kv| memo[$2.gsub(/\-/,"_").downcase.to_sym] = kv[1] if kv[0] =~ /^(HTTP_)(.*)/; memo }
- digester = Mixlib::Authentication::Digester.new
+ digester = Mixlib::Authentication::Digester
begin
@allowed_time_skew = time_skew # in seconds
@@ -95,7 +95,7 @@ module Mixlib
else
body = request.raw_post
Mixlib::Authentication::Log.debug "Digesting body: '#{body}'"
- @hashed_body = digester.hash_body(body)
+ @hashed_body = digester.hash_string(body)
end
Mixlib::Authentication::Log.debug "Authenticating user : #{user_id}, User secret is : #{@user_secret}, Request signature is :\n#{@request_signature}, Hashed Body is : #{@hashed_body}"
diff --git a/lib/mixlib/authentication/signedheaderauth.rb b/lib/mixlib/authentication/signedheaderauth.rb
index 993d927..7e9aaa4 100644
--- a/lib/mixlib/authentication/signedheaderauth.rb
+++ b/lib/mixlib/authentication/signedheaderauth.rb
@@ -1,7 +1,7 @@
#
# Author:: Christopher Brown (<cb@opscode.com>)
# Author:: Christopher Walters (<cw@opscode.com>)
-# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# Copyright:: Copyright (c) 2009, 2010 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -37,28 +37,21 @@ module Mixlib
OpenStruct.new(args).extend SignedHeaderAuth
end
end
-
+
# Build the canonicalized request based on the method, other headers, etc.
# compute the signature from the request, using the looked-up user secret
# ====Parameters
# private_key<OpenSSL::PKey::RSA>:: user's RSA private key.
def sign(private_key)
- digester = Mixlib::Authentication::Digester.new
- @hashed_body = if self.file
- digester.hash_file(self.file)
- else
- digester.hash_body(self.body)
- end
-
+ # Our multiline hash for authorization will be encoded in multiple header
+ # lines - X-Ops-Authorization-1, ... (starts at 1, not 0!)
header_hash = {
"X-Ops-Sign" => SIGNING_DESCRIPTION,
"X-Ops-Userid" => user_id,
"X-Ops-Timestamp" => canonical_time,
- "X-Ops-Content-Hash" =>@hashed_body,
+ "X-Ops-Content-Hash" => hashed_body,
}
- # Our multiline hash for authorization will be encoded in multiple header
- # lines - X-Ops-Authorization-1, ... (starts at 1, not 0!)
string_to_sign = canonicalize_request
signature = Base64.encode64(private_key.private_encrypt(string_to_sign)).chomp
signature_lines = signature.split(/\n/)
@@ -77,7 +70,7 @@ module Mixlib
# ====Parameters
#
def canonical_time
- Time.parse(timestamp).utc.iso8601
+ Time.parse(timestamp).utc.iso8601
end
# Build the canonicalized path, which collapses multiple slashes (/) and
@@ -90,6 +83,10 @@ module Mixlib
p.length > 1 ? p.chomp('/') : p
end
+ def hashed_body
+ @hashed_body ||= self.file ? digester.hash_file(self.file) : digester.hash_string(self.body)
+ end
+
# Takes HTTP request method & headers and creates a canonical form
# to create the signature
#
@@ -97,7 +94,7 @@ module Mixlib
#
#
def canonicalize_request
- "Method:#{http_method.to_s.upcase}\nPath:#{canonical_path}\nX-Ops-Content-Hash:#{@hashed_body}\nX-Ops-Timestamp:#{canonical_time}\nX-Ops-UserId:#{user_id}"
+ "Method:#{http_method.to_s.upcase}\nHashed Path:#{digester.hash_string(canonical_path)}\nX-Ops-Content-Hash:#{hashed_body}\nX-Ops-Timestamp:#{canonical_time}\nX-Ops-UserId:#{user_id}"
end
# Parses signature version information, algorithm used, etc.
@@ -113,7 +110,11 @@ module Mixlib
Mixlib::Authentication::Log.debug "Parsed signing description: #{parts.inspect}"
end
- private :canonical_time, :canonical_path, :canonicalize_request, :parse_signing_description
+ def digester
+ Mixlib::Authentication::Digester
+ end
+
+ private :canonical_time, :canonical_path, :parse_signing_description, :digester
end
end
diff --git a/spec/mixlib/authentication/mixlib_authentication_spec.rb b/spec/mixlib/authentication/mixlib_authentication_spec.rb
index 1b94d9b..f371e54 100644
--- a/spec/mixlib/authentication/mixlib_authentication_spec.rb
+++ b/spec/mixlib/authentication/mixlib_authentication_spec.rb
@@ -1,6 +1,7 @@
#
# Author:: Tim Hinderliter (<tim@opscode.com>)
-# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2009, 2010 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,8 +17,8 @@
# limitations under the License.
#
-$:.push File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib")) # lib in mixlib-authentication
-$:.push File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "mixlib-log", "lib")) # mixlib-log/log
+$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib")) # lib in mixlib-authentication
+$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "mixlib-log", "lib")) # mixlib-log/log
require 'rubygems'
@@ -66,7 +67,7 @@ end
#Mixlib::Authentication::Log.level :debug
describe "Mixlib::Authentication::SignedHeaderAuth" do
- it "should sign headers" do
+ it "should generate the correct string to sign and signature" do
# fix the timestamp, private key and body so we get the same answer back
# every time.
args = {
@@ -81,6 +82,15 @@ describe "Mixlib::Authentication::SignedHeaderAuth" do
private_key = OpenSSL::PKey::RSA.new(PRIVATE_KEY)
signing_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(args)
+
+ expected_string_to_sign = <<EOS
+Method:POST
+Hashed Path:#{HASHED_CANONICAL_PATH}
+X-Ops-Content-Hash:#{HASHED_BODY}
+X-Ops-Timestamp:#{TIMESTAMP_ISO8601}
+X-Ops-UserId:#{USER_ID}
+EOS
+ signing_obj.canonicalize_request.should == expected_string_to_sign.chomp
# If you need to regenerate the constants in this test spec, print out
# the results of res.inspect and copy them as appropriate into the
@@ -102,7 +112,7 @@ describe "Mixlib::Authentication::SignedHeaderAuth" do
private_key = OpenSSL::PKey::RSA.new(PRIVATE_KEY)
signing_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(args)
-
+
lambda { signing_obj.sign(private_key) }.should_not raise_error
end
end
@@ -163,21 +173,23 @@ end
USER_ID = "spec-user"
BODY = "Spec Body"
+HASHED_BODY = "DFteJZPVv6WKdQmMqZUQUumUyRs=" # Base64.encode64(Digest::SHA1.digest("Spec Body")).chomp
TIMESTAMP_ISO8601 = "2009-01-01T12:00:00Z"
TIMESTAMP_OBJ = Time.parse("Thu Jan 01 12:00:00 -0000 2009")
PATH = "/organizations/clownco"
+HASHED_CANONICAL_PATH = "YtBWDn1blGGuFIuKksdwXzHU9oE=" # Base64.encode64(Digest::SHA1.digest("/organizations/clownco")).chomp
REQUESTING_ACTOR_ID = "c0f8a68c52bffa1020222a56b23cccfa"
# Content hash is ???TODO
X_OPS_CONTENT_HASH = "DFteJZPVv6WKdQmMqZUQUumUyRs="
X_OPS_AUTHORIZATION_LINES = [
- "fXOBxpvfVpDFexP+Dl2k3RO/m4RLws8ii6+PsAY4pgwk6IxIFew7lL4ErWKA",
- "80HVVx3lKALfGqx+XLZiX1ZwbHhMhfS2oUAbxssx+g3nlxSmN/c+cg2/hprj",
- "0m+xwkpONuwVj06IKGe9WtMyDE0GNu7VFx/HWf4BCXMU3DZTsxD40Rm5Aqml",
- "r8paPDo5ozoJRing2NOfj9uPpRCnjmUamwSVo/tgTOmWQxnp7YeI82YLfl6Z",
- "i5qXzLMPzq/rA5Bt0dAZQxkCCvyVVXyn0pX0xHPgoeFcAJ/atc7hnIVxMzzM",
- "xZn9HIwo9wRYbWdwvRLVdbz/jmlGbBcDbInFtVOjpA=="
+ "jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4",
+ "NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
+ "3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O",
+ "IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
+ "9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0",
+ "utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w=="
]
# We expect Mixlib::Authentication::SignedHeaderAuth#sign to return this