diff options
-rw-r--r-- | Gemfile | 4 | ||||
-rw-r--r-- | lib/mixlib/authentication.rb | 2 | ||||
-rw-r--r-- | lib/mixlib/authentication/digester.rb | 19 | ||||
-rw-r--r-- | lib/mixlib/authentication/http_authentication_request.rb | 6 | ||||
-rw-r--r-- | lib/mixlib/authentication/signatureverification.rb | 19 | ||||
-rw-r--r-- | lib/mixlib/authentication/signedheaderauth.rb | 140 | ||||
-rw-r--r-- | spec/mixlib/authentication/http_authentication_request_spec.rb | 4 | ||||
-rw-r--r-- | spec/mixlib/authentication/mixlib_authentication_spec.rb | 242 |
8 files changed, 396 insertions, 40 deletions
@@ -1,2 +1,6 @@ source "https://rubygems.org" gemspec + +group(:development) do + gem 'pry' +end diff --git a/lib/mixlib/authentication.rb b/lib/mixlib/authentication.rb index ab10db7..245aa24 100644 --- a/lib/mixlib/authentication.rb +++ b/lib/mixlib/authentication.rb @@ -20,6 +20,8 @@ require 'mixlib/log' module Mixlib module Authentication + DEFAULT_SERVER_API_VERSION = '0' + class AuthenticationError < StandardError end diff --git a/lib/mixlib/authentication/digester.rb b/lib/mixlib/authentication/digester.rb index 7dc6dd7..071e5a7 100644 --- a/lib/mixlib/authentication/digester.rb +++ b/lib/mixlib/authentication/digester.rb @@ -21,11 +21,10 @@ require 'mixlib/authentication' module Mixlib module Authentication class Digester - class << self - - def hash_file(f) - digester = Digest::SHA1.new + + def hash_file(digest, f) + digester = digest.new buf = "" while f.read(16384, buf) digester.update buf @@ -34,15 +33,15 @@ module Mixlib end # Digests a string, base64's and chomps the end - # + # # ====Parameters - # - def hash_string(str) - ::Base64.encode64(Digest::SHA1.digest(str)).chomp + # + def hash_string(digest, str) + ::Base64.encode64(digest.digest(str)).chomp end - + end - + end end end diff --git a/lib/mixlib/authentication/http_authentication_request.rb b/lib/mixlib/authentication/http_authentication_request.rb index cc6b566..ad8354f 100644 --- a/lib/mixlib/authentication/http_authentication_request.rb +++ b/lib/mixlib/authentication/http_authentication_request.rb @@ -64,6 +64,10 @@ module Mixlib headers[:x_ops_content_hash].chomp end + def server_api_version + (headers[:x_ops_server_api_version] || DEFAULT_SERVER_API_VERSION).chomp + end + def request_signature unless @request_signature @request_signature = headers.find_all { |h| h[0].to_s =~ /^x_ops_authorization_/ }.sort { |x,y| x.to_s <=> y.to_s}.map { |i| i[1] }.join("\n") @@ -80,8 +84,6 @@ module Mixlib raise MissingAuthenticationHeader, "missing required authentication header(s) '#{missing_headers.join("', '")}'" end end - - end end end diff --git a/lib/mixlib/authentication/signatureverification.rb b/lib/mixlib/authentication/signatureverification.rb index e91721e..3c35c28 100644 --- a/lib/mixlib/authentication/signatureverification.rb +++ b/lib/mixlib/authentication/signatureverification.rb @@ -48,6 +48,8 @@ module Mixlib def_delegator :@auth_request, :request + def_delegator :@auth_request, :server_api_version + include Mixlib::Authentication::SignedHeaderAuth def initialize(request=nil) @@ -138,8 +140,15 @@ module Mixlib def verify_signature(algorithm, version) candidate_block = canonicalize_request(algorithm, version) - request_decrypted_block = @user_secret.public_decrypt(Base64.decode64(request_signature)) - @valid_signature = (request_decrypted_block == candidate_block) + signature = Base64.decode64(request_signature) + @valid_signature = case version + when '1.3' + digest = validate_sign_version_digest!(algorithm, version) + @user_secret.verify(digest.new, signature, candidate_block) + else + request_decrypted_block = @user_secret.public_decrypt(signature) + (request_decrypted_block == candidate_block) + end # Keep the debug messages lined up so it's easy to scan them Mixlib::Authentication::Log.debug("Verifying request signature:") @@ -171,7 +180,7 @@ module Mixlib # The request signature is based on any file attached, if any. Otherwise # it's based on the body of the request. - def hashed_body + def hashed_body(digest=Digest::SHA1) unless @hashed_body # TODO: tim: 2009-112-28: It'd be nice to remove this special case, and # always hash the entire request body. In the file case it would just be @@ -205,11 +214,11 @@ module Mixlib # we hash the body. if file_param Mixlib::Authentication::Log.debug "Digesting file_param: '#{file_param.inspect}'" - @hashed_body = digester.hash_file(file_param) + @hashed_body = digester.hash_file(digest, file_param) else body = request.raw_post Mixlib::Authentication::Log.debug "Digesting body: '#{body}'" - @hashed_body = digester.hash_string(body) + @hashed_body = digester.hash_string(digest, body) end end @hashed_body diff --git a/lib/mixlib/authentication/signedheaderauth.rb b/lib/mixlib/authentication/signedheaderauth.rb index 85a31d4..8faf313 100644 --- a/lib/mixlib/authentication/signedheaderauth.rb +++ b/lib/mixlib/authentication/signedheaderauth.rb @@ -19,7 +19,7 @@ require 'time' require 'base64' -require 'digest/sha1' +require 'openssl/digest' require 'mixlib/authentication' require 'mixlib/authentication/digester' @@ -29,13 +29,21 @@ module Mixlib module SignedHeaderAuth NULL_ARG = Object.new + + ALGORITHMS_FOR_VERSION = { + '1.0' => ['sha1'], + '1.1' => ['sha1'], + '1.3' => ['sha256', 'sha1'], + }.freeze() + + # Use of SUPPORTED_ALGORITHMS and SUPPORTED_VERSIONS is deprecated. Use + # ALGORITHMS_FOR_VERSION instead SUPPORTED_ALGORITHMS = ['sha1'].freeze SUPPORTED_VERSIONS = ['1.0', '1.1'].freeze DEFAULT_SIGN_ALGORITHM = 'sha1'.freeze DEFAULT_PROTO_VERSION = '1.0'.freeze - # === signing_object # This is the intended interface for signing requests with the # Opscode/Chef signed header protocol. This wraps the constructor for a @@ -72,7 +80,10 @@ module Mixlib args[:timestamp], args[:user_id], args[:file], - args[:proto_version]) + args[:proto_version], + args[:signing_algorithm], + args[:headers] + ) end def algorithm @@ -88,28 +99,50 @@ module Mixlib # ====Parameters # private_key<OpenSSL::PKey::RSA>:: user's RSA private key. def sign(private_key, sign_algorithm=algorithm, sign_version=proto_version) + digest = validate_sign_version_digest!(sign_algorithm, sign_version) # 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" => "algorithm=#{sign_algorithm};version=#{sign_version};", "X-Ops-Userid" => user_id, "X-Ops-Timestamp" => canonical_time, - "X-Ops-Content-Hash" => hashed_body, + "X-Ops-Content-Hash" => hashed_body(digest), } - string_to_sign = canonicalize_request(sign_algorithm, sign_version) - signature = Base64.encode64(private_key.private_encrypt(string_to_sign)).chomp + signature = Base64.encode64(do_sign(private_key, digest, sign_algorithm, sign_version)).chomp signature_lines = signature.split(/\n/) signature_lines.each_index do |idx| key = "X-Ops-Authorization-#{idx + 1}" header_hash[key] = signature_lines[idx] end - Mixlib::Authentication::Log.debug "String to sign: '#{string_to_sign}'\nHeader hash: #{header_hash.inspect}" + Mixlib::Authentication::Log.debug "Header hash: #{header_hash.inspect}" header_hash end + def validate_sign_version_digest!(sign_algorithm, sign_version) + if ALGORITHMS_FOR_VERSION[sign_version].nil? + raise AuthenticationError, + "Unsupported version '#{sign_version}'" + end + + if !ALGORITHMS_FOR_VERSION[sign_version].include?(sign_algorithm) + raise AuthenticationError, + "Unsupported version '#{sign_version}'" + end + + case sign_algorithm + when 'sha1' + OpenSSL::Digest::SHA1 + when 'sha256' + OpenSSL::Digest::SHA256 + else + # This case should never happen + raise "Unknown algorithm #{sign_algorithm}" + end + end + # Build the canonicalized time based on utc & iso8601 # # ====Parameters @@ -128,13 +161,27 @@ module Mixlib p.length > 1 ? p.chomp('/') : p end - def hashed_body + def hashed_body(digest=OpenSSL::Digest::SHA1) + # This is weird. sign() is called with the digest type and signing + # version. These are also expected to be properties of the object. + # Hence, we're going to assume the one that is passed to sign is + # the correct one and needs to passed through all the functions + # that do any sort of digest. + if @hashed_body_digest != nil && @hashed_body_digest != digest + raise "hashed_body must always be called with the same digest" + else + @hashed_body_digest = digest + end # Hash the file object if it was passed in, otherwise hash based on # the body. # TODO: tim 2009-12-28: It'd be nice to just remove this special case, # always sign the entire request body, using the expanded multipart # body in the case of a file being include. - @hashed_body ||= (self.file && self.file.respond_to?(:read)) ? digester.hash_file(self.file) : digester.hash_string(self.body) + @hashed_body ||= if self.file && self.file.respond_to?(:read) + digester.hash_file(digest, self.file) + else + digester.hash_string(digest, self.body) + end end # Takes HTTP request method & headers and creates a canonical form @@ -144,20 +191,34 @@ module Mixlib # # def canonicalize_request(sign_algorithm=algorithm, sign_version=proto_version) - unless SUPPORTED_ALGORITHMS.include?(sign_algorithm) && SUPPORTED_VERSIONS.include?(sign_version) - raise AuthenticationError, "Bad algorithm '#{sign_algorithm}' (allowed: #{SUPPORTED_ALGORITHMS.inspect}) or version '#{sign_version}' (allowed: #{SUPPORTED_VERSIONS.inspect})" + digest = validate_sign_version_digest!(sign_algorithm, sign_version) + canonical_x_ops_user_id = canonicalize_user_id(user_id, sign_version, digest) + case sign_version + when "1.3" + [ + "Method:#{http_method.to_s.upcase}", + "Hashed Path:#{digester.hash_string(digest, canonical_path)}", + "X-Ops-Content-Hash:#{hashed_body(digest)}", + "X-Ops-Sign:algorithm=#{sign_algorithm};version=#{sign_version}", + "X-Ops-Timestamp:#{canonical_time}", + "X-Ops-UserId:#{canonical_x_ops_user_id}", + "X-Ops-Server-API-Version:#{server_api_version}", + ].join("\n") + else + [ + "Method:#{http_method.to_s.upcase}", + "Hashed Path:#{digester.hash_string(digest, canonical_path)}", + "X-Ops-Content-Hash:#{hashed_body(digest)}", + "X-Ops-Timestamp:#{canonical_time}", + "X-Ops-UserId:#{canonical_x_ops_user_id}" + ].join("\n") end - - canonical_x_ops_user_id = canonicalize_user_id(user_id, sign_version) - "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:#{canonical_x_ops_user_id}" end - def canonicalize_user_id(user_id, proto_version) + def canonicalize_user_id(user_id, proto_version, digest=OpenSSL::Digest::SHA1) case proto_version - when "1.1" - digester.hash_string(user_id) - when "1.0" - user_id + when "1.1", "1.3" + digester.hash_string(digest, user_id) else user_id end @@ -181,6 +242,18 @@ module Mixlib Mixlib::Authentication::Digester end + # private + def do_sign(private_key, digest, sign_algorithm, sign_version) + string_to_sign = canonicalize_request(sign_algorithm, sign_version) + Mixlib::Authentication::Log.debug "String to sign: '#{string_to_sign}'" + case sign_version + when '1.3' + private_key.sign(digest.new, string_to_sign) + else + private_key.private_encrypt(string_to_sign) + end + end + private :canonical_time, :canonical_path, :parse_signing_description, :digester, :canonicalize_user_id end @@ -189,13 +262,38 @@ module Mixlib # A Struct-based value object that contains the necessary information to # generate a request signature. `SignedHeaderAuth.signing_object()` # provides a more convenient interface to the constructor. - class SigningObject < Struct.new(:http_method, :path, :body, :host, :timestamp, :user_id, :file, :proto_version) + class SigningObject < Struct.new(:http_method, :path, :body, :host, + :timestamp, :user_id, :file, :proto_version, + :signing_algorithm, :headers) include SignedHeaderAuth def proto_version (self[:proto_version] or DEFAULT_PROTO_VERSION).to_s end - end + def algorithm + if self[:signing_algorithm] + self[:signing_algorithm] + else + case proto_version + when '1.3' + ALGORITHMS_FOR_VERSION[proto_version].first + else + DEFAULT_SIGN_ALGORITHM + end + end + end + + def server_api_version + key = (self[:headers] || {}).keys.select do |k| + k.downcase == 'x-ops-server-api-version' + end.first + if key + self[:headers][key] + else + DEFAULT_SERVER_API_VERSION + end + end + end end end diff --git a/spec/mixlib/authentication/http_authentication_request_spec.rb b/spec/mixlib/authentication/http_authentication_request_spec.rb index b0299b9..1c6c814 100644 --- a/spec/mixlib/authentication/http_authentication_request_spec.rb +++ b/spec/mixlib/authentication/http_authentication_request_spec.rb @@ -125,4 +125,8 @@ SIG expect(@http_authentication_request.request_signature).to eq(expected.chomp) end + it "defaults to server api version 0" do + expect(@http_authentication_request.server_api_version).to eq('0') + end + end diff --git a/spec/mixlib/authentication/mixlib_authentication_spec.rb b/spec/mixlib/authentication/mixlib_authentication_spec.rb index 715e56c..41cd7e9 100644 --- a/spec/mixlib/authentication/mixlib_authentication_spec.rb +++ b/spec/mixlib/authentication/mixlib_authentication_spec.rb @@ -90,6 +90,35 @@ describe "Mixlib::Authentication::SignedHeaderAuth" do expect(V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY)).to eq(EXPECTED_SIGN_RESULT_V1_1) end + it "should generate the correct string to sign and signature for version 1.3 with SHA1" do + expect(V1_3_SHA1_SIGNING_OBJECT.proto_version).to eq("1.3") + expect(V1_3_SHA1_SIGNING_OBJECT.canonicalize_request).to eq(V1_3_SHA1_CANONICAL_REQUEST) + expect(V1_3_SHA1_SIGNING_OBJECT.algorithm).to eq("sha1") + expect(V1_3_SHA1_SIGNING_OBJECT.server_api_version).to eq("1") + + # 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 + # the constants in this file. + expect(V1_3_SHA1_SIGNING_OBJECT.sign(PRIVATE_KEY)).to eq(EXPECTED_SIGN_RESULT_V1_3_SHA1) + end + + it "should default to server api version 0 for version 1.3" do + expect(V1_3_SHA1_SIGNING_OBJECT_API0.server_api_version).to eq('0') + end + + it "should generate the correct string to sign and signature for version 1.3 with SHA256" do + expect(V1_3_SHA256_SIGNING_OBJECT.proto_version).to eq("1.3") + expect(V1_3_SHA256_SIGNING_OBJECT.algorithm).to eq("sha256") + expect(V1_3_SHA256_SIGNING_OBJECT.server_api_version).to eq("1") + expect(V1_3_SHA256_SIGNING_OBJECT.canonicalize_request).to eq(V1_3_SHA256_CANONICAL_REQUEST) + + # 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 + # the constants in this file. + expect(V1_3_SHA256_SIGNING_OBJECT.sign(PRIVATE_KEY)).to eq(EXPECTED_SIGN_RESULT_V1_3_SHA256) + end + + it "should generate the correct string to sign and signature for non-default proto version when used as a mixin" do algorithm = 'sha1' version = '1.1' @@ -128,7 +157,7 @@ describe "Mixlib::Authentication::SignatureVerification" do @user_private_key = PRIVATE_KEY end - it "should authenticate a File-containing request - Merb" do + it "should authenticate a File-containing request V1.1 - Merb" do request_params = MERB_REQUEST_PARAMS.clone request_params["file"] = { "size"=>MockFile.length, "content_type"=>"application/octet-stream", "filename"=>"zsh.tar.gz", "tempfile"=>MockFile.new } @@ -141,6 +170,32 @@ describe "Mixlib::Authentication::SignatureVerification" do expect(res).not_to be_nil end + it "should authenticate a File-containing request V1.3 SHA1 - Merb" do + request_params = MERB_REQUEST_PARAMS.clone + request_params["file"] = + { "size"=>MockFile.length, "content_type"=>"application/octet-stream", "filename"=>"zsh.tar.gz", "tempfile"=>MockFile.new } + + mock_request = MockRequest.new(PATH, request_params, MERB_HEADERS_V1_3_SHA1, "") + expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ) + + service = Mixlib::Authentication::SignatureVerification.new + res = service.authenticate_user_request(mock_request, @user_private_key) + expect(res).not_to be_nil + end + + it "should authenticate a File-containing request V1.3 SHA256 - Merb" do + request_params = MERB_REQUEST_PARAMS.clone + request_params["file"] = + { "size"=>MockFile.length, "content_type"=>"application/octet-stream", "filename"=>"zsh.tar.gz", "tempfile"=>MockFile.new } + + mock_request = MockRequest.new(PATH, request_params, MERB_HEADERS_V1_3_SHA256, "") + expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ) + + service = Mixlib::Authentication::SignatureVerification.new + res = service.authenticate_user_request(mock_request, @user_private_key) + expect(res).not_to be_nil + end + it "should authenticate a File-containing request from a v1.0 client - Passenger" do request_params = PASSENGER_REQUEST_PARAMS.clone request_params["tarball"] = MockFile.new @@ -153,7 +208,25 @@ describe "Mixlib::Authentication::SignatureVerification" do expect(res).not_to be_nil end - it "should authenticate a normal (post body) request - Merb" do + it "should authenticate a normal (post body) request v1.3 SHA1 - Merb" do + mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS_V1_3_SHA1, BODY) + expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ) + + service = Mixlib::Authentication::SignatureVerification.new + res = service.authenticate_user_request(mock_request, @user_private_key) + expect(res).not_to be_nil + end + + it "should authenticate a normal (post body) request v1.3 SHA256 - Merb" do + mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS_V1_3_SHA1, BODY) + expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ) + + service = Mixlib::Authentication::SignatureVerification.new + res = service.authenticate_user_request(mock_request, @user_private_key) + expect(res).not_to be_nil + end + + it "should authenticate a normal (post body) request v1.1 - Merb" do mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS_V1_1, BODY) expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ) @@ -234,16 +307,48 @@ describe "Mixlib::Authentication::SignatureVerification" do expect(auth_req).to be_a_valid_content_hash end + it "shouldn't authenticate if the signature is wrong for v1.3 SHA1" do + headers = MERB_HEADERS_V1_3_SHA1.dup + headers["HTTP_X_OPS_AUTHORIZATION_1"] = "epicfail" + mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY) + expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ) + + auth_req = Mixlib::Authentication::SignatureVerification.new + res = auth_req.authenticate_user_request(mock_request, @user_private_key) + expect(res).to be_nil + expect(auth_req).not_to be_a_valid_request + expect(auth_req).not_to be_a_valid_signature + expect(auth_req).to be_a_valid_timestamp + expect(auth_req).to be_a_valid_content_hash + end + + it "shouldn't authenticate if the signature is wrong for v1.3 SHA256" do + headers = MERB_HEADERS_V1_3_SHA256.dup + headers["HTTP_X_OPS_AUTHORIZATION_1"] = "epicfail" + mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY) + expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ) + + auth_req = Mixlib::Authentication::SignatureVerification.new + res = auth_req.authenticate_user_request(mock_request, @user_private_key) + expect(res).to be_nil + expect(auth_req).not_to be_a_valid_request + expect(auth_req).not_to be_a_valid_signature + expect(auth_req).to be_a_valid_timestamp + expect(auth_req).to be_a_valid_content_hash + end end USER_ID = "spec-user" DIGESTED_USER_ID = Base64.encode64(Digest::SHA1.new.digest(USER_ID)).chomp +DIGESTED_USER_ID_SHA256 = Base64.encode64(Digest::SHA256.new.digest(USER_ID)).chomp BODY = "Spec Body" HASHED_BODY = "DFteJZPVv6WKdQmMqZUQUumUyRs=" # Base64.encode64(Digest::SHA1.digest("Spec Body")).chomp +HASHED_BODY_SHA256 = "hDlKNZhIhgso3Fs0S0pZwJ0xyBWtR1RBaeHs1DrzOho=" 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 +HASHED_CANONICAL_PATH_SHA256 = "Z3EsTMw/UBNY9n+q+WBWTJmeVg8hQFbdFzVWRxW4dOA=" V1_0_ARGS = { :body => BODY, @@ -264,6 +369,34 @@ V1_1_ARGS = { :proto_version => 1.1 } +V1_3_ARGS_SHA1 = { + :body => BODY, + :user_id => USER_ID, + :http_method => :post, + :timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time. + :file => MockFile.new, + :path => PATH, + :proto_version => '1.3', + :signing_algorithm => 'sha1', + :headers => { + 'X-OpS-SeRvEr-ApI-VerSiOn' => '1' + } +} + +V1_3_ARGS_SHA256 = { + :body => BODY, + :user_id => USER_ID, + :http_method => :post, + :timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time. + :file => MockFile.new, + :path => PATH, + :proto_version => '1.3', + :headers => { + 'X-OpS-SeRvEr-ApI-VerSiOn' => '1' + } + # This defaults to sha256 +} + LONG_PATH_LONG_USER_ARGS = { :body => BODY, :user_id => "A" * 200, @@ -277,6 +410,8 @@ REQUESTING_ACTOR_ID = "c0f8a68c52bffa1020222a56b23cccfa" # Content hash is ???TODO X_OPS_CONTENT_HASH = "DFteJZPVv6WKdQmMqZUQUumUyRs=" +X_OPS_CONTENT_HASH_SHA256 = "hDlKNZhIhgso3Fs0S0pZwJ0xyBWtR1RBaeHs1DrzOho=" + X_OPS_AUTHORIZATION_LINES_V1_0 = [ "jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4", "NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc", @@ -295,6 +430,23 @@ X_OPS_AUTHORIZATION_LINES = [ "FDlbAG7H8Dmvo+wBxmtNkszhzbBnEYtuwQqT8nM/8A==" ] +X_OPS_AUTHORIZATION_LINES_V1_3_SHA1 = [ + "Dh7xqnM3HabvuPVTsJCvHSWGyipvv0xkF9u7XfomC0tDHBF8wG4kEToRI7/1", + "CSa97jlHLQ+VqNq76uy2mxg0PBxPLxPcz+VREJxnxEv+gEEr6MAeMpV97ip0", + "VICuUZ3hPIVNl9hIjmaeOnQSbtJZZOIik0g0O+bpd7AQKa/Y7r2jw42D/Kgg", + "L/ts6ntD2wKb92iPZ5bEXYIJFKVKb7j10PTcHLxkMWd64Cd7GZAdHHl4z8/t", + "VZ5XCe23960z08d2P2I+iYBBCxRCOPwafBvbt0ubls2vecraHQYYXMXovjmV", + "Rxh8xRaTfEhpWwZJa1ONVvsldZlvGiHO/jhmRJ9oCA==" +] + +X_OPS_AUTHORIZATION_LINES_V1_3_SHA256 = [ + "BjR+iTK2eOgwmT2yGqLvE7Fp+VlpRGyL1dVoF2DmhUPO7EVsnxx2s32AmlOw", + "EpaACpav8SoB7K4rpOo3gfBm0XAYLnLLWzcec2OQG2O0wxxHiKVn4qWEe7Cs", + "RZ903DGM54t4uK75vx6wwoEdZqZe21npsLK+F3oAqnkgp+YXmlYv9Se5tFKB", + "0GWM1ibGJMjUIFAm7vxzjcuEvkkKN49MnXeMAAykfymcs74RU6xEKYzzSAyC", + "ygkV6xQSapDMp/aY29cVA/1FgZeVMhnFSTjtqBehchZYwXswr0A72A86gID9", + "h2QsUpmQJwbOK3bb1GptAnd5IiLzIxtu+vFeY6h4eA==" +] # We expect Mixlib::Authentication::SignedHeaderAuth#sign to return this # if passed the BODY above, based on version @@ -324,6 +476,32 @@ EXPECTED_SIGN_RESULT_V1_1 = { "X-Ops-Timestamp"=>TIMESTAMP_ISO8601 } +EXPECTED_SIGN_RESULT_V1_3_SHA1 = { + "X-Ops-Content-Hash"=>X_OPS_CONTENT_HASH, + "X-Ops-Userid"=>USER_ID, + "X-Ops-Sign"=>"algorithm=sha1;version=1.3;", + "X-Ops-Authorization-1"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[0], + "X-Ops-Authorization-2"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[1], + "X-Ops-Authorization-3"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[2], + "X-Ops-Authorization-4"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[3], + "X-Ops-Authorization-5"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[4], + "X-Ops-Authorization-6"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[5], + "X-Ops-Timestamp"=>TIMESTAMP_ISO8601 +} + +EXPECTED_SIGN_RESULT_V1_3_SHA256 = { + "X-Ops-Content-Hash"=>X_OPS_CONTENT_HASH_SHA256, + "X-Ops-Userid"=>USER_ID, + "X-Ops-Sign"=>"algorithm=sha256;version=1.3;", + "X-Ops-Authorization-1"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[0], + "X-Ops-Authorization-2"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[1], + "X-Ops-Authorization-3"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[2], + "X-Ops-Authorization-4"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[3], + "X-Ops-Authorization-5"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[4], + "X-Ops-Authorization-6"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[5], + "X-Ops-Timestamp"=>TIMESTAMP_ISO8601 +} + OTHER_HEADERS = { # An arbitrary sampling of non-HTTP_* headers are in here to # exercise that code path. @@ -340,6 +518,40 @@ MERB_REQUEST_PARAMS = { "organization_id"=>"local-test-org", "requesting_actor_id"=>REQUESTING_ACTOR_ID, } +MERB_HEADERS_V1_3_SHA1 = { + # These are used by signatureverification. + "HTTP_HOST"=>"127.0.0.1", + "HTTP_X_OPS_SIGN"=>"algorithm=sha1;version=1.3;", + "HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386", + "HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601, + "HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH, + "HTTP_X_OPS_USERID"=>USER_ID, + "HTTP_X_OPS_SERVER_API_VERSION"=>"1", + "HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[0], + "HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[1], + "HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[2], + "HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[3], + "HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[4], + "HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA1[5], +}.merge(OTHER_HEADERS) + +MERB_HEADERS_V1_3_SHA256 = { + # These are used by signatureverification. + "HTTP_HOST"=>"127.0.0.1", + "HTTP_X_OPS_SIGN"=>"algorithm=sha256;version=1.3;", + "HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386", + "HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601, + "HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH_SHA256, + "HTTP_X_OPS_USERID"=>USER_ID, + "HTTP_X_OPS_SERVER_API_VERSION"=>"1", + "HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[0], + "HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[1], + "HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[2], + "HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[3], + "HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[4], + "HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[5], +}.merge(OTHER_HEADERS) + # Tis is what will be in request.env for the Merb case. MERB_HEADERS_V1_1 = { # These are used by signatureverification. @@ -478,6 +690,32 @@ X-Ops-UserId:#{DIGESTED_USER_ID} EOS V1_1_CANONICAL_REQUEST = V1_1_CANONICAL_REQUEST_DATA.chomp +V1_3_SHA1_CANONICAL_REQUEST_DATA = <<EOS +Method:POST +Hashed Path:#{HASHED_CANONICAL_PATH} +X-Ops-Content-Hash:#{HASHED_BODY} +X-Ops-Sign:algorithm=sha1;version=1.3 +X-Ops-Timestamp:#{TIMESTAMP_ISO8601} +X-Ops-UserId:#{DIGESTED_USER_ID} +X-Ops-Server-API-Version:1 +EOS +V1_3_SHA1_CANONICAL_REQUEST = V1_3_SHA1_CANONICAL_REQUEST_DATA.chomp + +V1_3_SHA256_CANONICAL_REQUEST_DATA = <<EOS +Method:POST +Hashed Path:#{HASHED_CANONICAL_PATH_SHA256} +X-Ops-Content-Hash:#{HASHED_BODY_SHA256} +X-Ops-Sign:algorithm=sha256;version=1.3 +X-Ops-Timestamp:#{TIMESTAMP_ISO8601} +X-Ops-UserId:#{DIGESTED_USER_ID_SHA256} +X-Ops-Server-API-Version:1 +EOS +V1_3_SHA256_CANONICAL_REQUEST = V1_3_SHA256_CANONICAL_REQUEST_DATA.chomp + +V1_3_SHA256_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(V1_3_ARGS_SHA256) +V1_3_SHA1_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(V1_3_ARGS_SHA1) +V1_3_SHA1_SIGNING_OBJECT_API0 = Mixlib::Authentication::SignedHeaderAuth.signing_object( + V1_3_ARGS_SHA1.dup.tap {|x| x.delete(:headers)}) V1_1_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(V1_1_ARGS) V1_0_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(V1_0_ARGS) LONG_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(LONG_PATH_LONG_USER_ARGS) |