diff options
Diffstat (limited to 'spec/mixlib/authentication/mixlib_authentication_spec.rb')
-rw-r--r-- | spec/mixlib/authentication/mixlib_authentication_spec.rb | 242 |
1 files changed, 240 insertions, 2 deletions
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) |