# # Author:: Tim Hinderliter () # Author:: Christopher Walters () # Author:: Christopher Brown () # Copyright:: Copyright (c) 2009, 2010 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) require "rubygems" require "ostruct" require "openssl" require "mixlib/authentication/signatureverification" require "time" # TODO: should make these regular spec-based mock objects. class MockRequest attr_accessor :env, :params, :path, :raw_post def initialize(path, params, headers, raw_post) @path = path @params = params @env = headers @raw_post = raw_post end def method "POST" end end class MockFile def initialize @have_read = nil end def self.length BODY.length end def read(len, out_str) if @have_read.nil? @have_read = 1 out_str[0..-1] = BODY BODY else nil end end end # Uncomment this to get some more info from the methods we're testing. #Mixlib::Authentication.logger.level = :debug describe "Mixlib::Authentication::SignedHeaderAuth" do # NOTE: Version 1.0 will be the default until Chef 11 is released. it "should generate the correct string to sign and signature, version 1.0 (default)" do expect(V1_0_SIGNING_OBJECT.canonicalize_request).to eq(V1_0_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_0_SIGNING_OBJECT.sign(PRIVATE_KEY)).to eq(EXPECTED_SIGN_RESULT_V1_0) end it "should generate the correct string to sign and signature, version 1.1" do expect(V1_1_SIGNING_OBJECT.proto_version).to eq("1.1") expect(V1_1_SIGNING_OBJECT.canonicalize_request).to eq(V1_1_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_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 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" V1_1_SIGNING_OBJECT.proto_version = "1.0" expect(V1_1_SIGNING_OBJECT.proto_version).to eq("1.0") expect(V1_1_SIGNING_OBJECT.canonicalize_request(algorithm, version)).to eq(V1_1_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_1_SIGNING_OBJECT.sign(PRIVATE_KEY, algorithm, version)).to eq(EXPECTED_SIGN_RESULT_V1_1) end it "should not choke when signing a request for a long user id with version 1.1" do expect { LONG_SIGNING_OBJECT.sign(PRIVATE_KEY, "sha1", "1.1") }.not_to raise_error end it "should choke when signing a request for a long user id with version 1.0" do expect { LONG_SIGNING_OBJECT.sign(PRIVATE_KEY, "sha1", "1.0") }.to raise_error(OpenSSL::PKey::RSAError) end it "should choke when signing a request with a bad version" do expect { V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, "sha1", "poo") }.to raise_error(Mixlib::Authentication::AuthenticationError) end it "should choke when signing a request with a bad algorithm" do expect { V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, "sha_poo", "1.1") }.to raise_error(Mixlib::Authentication::AuthenticationError) end end describe "Mixlib::Authentication::SignatureVerification" do before(:each) do @user_private_key = PRIVATE_KEY end 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 } mock_request = MockRequest.new(PATH, request_params, MERB_HEADERS_V1_1, "") 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 mock_request = MockRequest.new(PATH, request_params, PASSENGER_HEADERS_V1_0, "") 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).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_SHA256, 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) 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 from a v1.0 client - Merb" do mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS_V1_0, 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 "shouldn't authenticate if an Authorization header is missing" do headers = MERB_HEADERS_V1_1.clone headers.delete("HTTP_X_OPS_SIGN") mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY) allow(Time).to receive(:now).and_return(TIMESTAMP_OBJ) #Time.stub!(:now).and_return(TIMESTAMP_OBJ) auth_req = Mixlib::Authentication::SignatureVerification.new expect { auth_req.authenticate_user_request(mock_request, @user_private_key) }.to raise_error(Mixlib::Authentication::AuthenticationError) expect(auth_req).not_to be_a_valid_request expect(auth_req).not_to be_a_valid_timestamp expect(auth_req).not_to be_a_valid_signature expect(auth_req).not_to be_a_valid_content_hash end it "shouldn't authenticate if Authorization header is wrong" do headers = MERB_HEADERS_V1_1.clone headers["HTTP_X_OPS_CONTENT_HASH"] += "_" 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).to be_a_valid_timestamp expect(auth_req).to be_a_valid_signature expect(auth_req).not_to be_a_valid_content_hash end it "shouldn't authenticate if the timestamp is not within bounds" 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 - 1000) 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_timestamp expect(auth_req).to be_a_valid_signature expect(auth_req).to be_a_valid_content_hash end it "shouldn't authenticate if the signature is wrong" do headers = MERB_HEADERS_V1_1.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 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 V1_0_ARGS = { :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, } V1_1_ARGS = { :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.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, :http_method => :put, :timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time. :file => MockFile.new, :path => PATH + "/nodes/#{"A" * 250}", } 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", "3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O", "IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy", "9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0", "utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w==", ] X_OPS_AUTHORIZATION_LINES = [ "UfZD9dRz6rFu6LbP5Mo1oNHcWYxpNIcUfFCffJS1FQa0GtfU/vkt3/O5HuCM", "1wIFl/U0f5faH9EWpXWY5NwKR031Myxcabw4t4ZLO69CIh/3qx1XnjcZvt2w", "c2R9bx/43IWA/r8w8Q6decuu0f6ZlNheJeJhaYPI8piX/aH+uHBH8zTACZu8", "vMnl5MF3/OIlsZc8cemq6eKYstp8a8KYq9OmkB5IXIX6qVMJHA6fRvQEB/7j", "281Q7oI/O+lE8AmVyBbwruPb7Mp6s4839eYiOdjbDwFjYtbS3XgAjrHlaD7W", "FDlbAG7H8Dmvo+wBxmtNkszhzbBnEYtuwQqT8nM/8A==", ] X_OPS_AUTHORIZATION_LINES_V1_3_SHA256 = [ "FZOmXAyOBAZQV/uw188iBljBJXOm+m8xQ/8KTGLkgGwZNcRFxk1m953XjE3W", "VGy1dFT76KeaNWmPCNtDmprfH2na5UZFtfLIKrPv7xm80V+lzEzTd9WBwsfP", "42dZ9N+V9I5SVfcL/lWrrlpdybfceJC5jOcP5tzfJXWUITwb6Z3Erg3DU3Uh", "H9h9E0qWlYGqmiNCVrBnpe6Si1gU/Jl+rXlRSNbLJ4GlArAPuL976iTYJTzE", "MmbLUIm3JRYi00Yb01IUCCKdI90vUq1HHNtlTEu93YZfQaJwRxXlGkCNwIJe", "fy49QzaCIEu1XiOx5Jn+4GmkrZch/RrK9VzQWXgs+w==", ] # We expect Mixlib::Authentication::SignedHeaderAuth#sign to return this # if passed the BODY above, based on version EXPECTED_SIGN_RESULT_V1_0 = { "X-Ops-Content-Hash" => X_OPS_CONTENT_HASH, "X-Ops-Userid" => USER_ID, "X-Ops-Sign" => "algorithm=sha1;version=1.0;", "X-Ops-Authorization-1" => X_OPS_AUTHORIZATION_LINES_V1_0[0], "X-Ops-Authorization-2" => X_OPS_AUTHORIZATION_LINES_V1_0[1], "X-Ops-Authorization-3" => X_OPS_AUTHORIZATION_LINES_V1_0[2], "X-Ops-Authorization-4" => X_OPS_AUTHORIZATION_LINES_V1_0[3], "X-Ops-Authorization-5" => X_OPS_AUTHORIZATION_LINES_V1_0[4], "X-Ops-Authorization-6" => X_OPS_AUTHORIZATION_LINES_V1_0[5], "X-Ops-Timestamp" => TIMESTAMP_ISO8601, } EXPECTED_SIGN_RESULT_V1_1 = { "X-Ops-Content-Hash" => X_OPS_CONTENT_HASH, "X-Ops-Userid" => USER_ID, "X-Ops-Sign" => "algorithm=sha1;version=1.1;", "X-Ops-Authorization-1" => X_OPS_AUTHORIZATION_LINES[0], "X-Ops-Authorization-2" => X_OPS_AUTHORIZATION_LINES[1], "X-Ops-Authorization-3" => X_OPS_AUTHORIZATION_LINES[2], "X-Ops-Authorization-4" => X_OPS_AUTHORIZATION_LINES[3], "X-Ops-Authorization-5" => X_OPS_AUTHORIZATION_LINES[4], "X-Ops-Authorization-6" => X_OPS_AUTHORIZATION_LINES[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. "REMOTE_ADDR" => "127.0.0.1", "PATH_INFO" => "/organizations/local-test-org/cookbooks", "REQUEST_PATH" => "/organizations/local-test-org/cookbooks", "CONTENT_TYPE" => "multipart/form-data; boundary=----RubyMultipartClient6792ZZZZZ", "CONTENT_LENGTH" => "394", } # This is what will be in request.params for the Merb case. MERB_REQUEST_PARAMS = { "name" => "zsh", "action" => "create", "controller" => "chef_server_api/cookbooks", "organization_id" => "local-test-org", "requesting_actor_id" => REQUESTING_ACTOR_ID } 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. "HTTP_HOST" => "127.0.0.1", "HTTP_X_OPS_SIGN" => "algorithm=sha1;version=1.1;", "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_AUTHORIZATION_1" => X_OPS_AUTHORIZATION_LINES[0], "HTTP_X_OPS_AUTHORIZATION_2" => X_OPS_AUTHORIZATION_LINES[1], "HTTP_X_OPS_AUTHORIZATION_3" => X_OPS_AUTHORIZATION_LINES[2], "HTTP_X_OPS_AUTHORIZATION_4" => X_OPS_AUTHORIZATION_LINES[3], "HTTP_X_OPS_AUTHORIZATION_5" => X_OPS_AUTHORIZATION_LINES[4], "HTTP_X_OPS_AUTHORIZATION_6" => X_OPS_AUTHORIZATION_LINES[5], }.merge(OTHER_HEADERS) # Tis is what will be in request.env for the Merb case. MERB_HEADERS_V1_0 = { # These are used by signatureverification. "HTTP_HOST" => "127.0.0.1", "HTTP_X_OPS_SIGN" => "version=1.0", "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_AUTHORIZATION_1" => X_OPS_AUTHORIZATION_LINES_V1_0[0], "HTTP_X_OPS_AUTHORIZATION_2" => X_OPS_AUTHORIZATION_LINES_V1_0[1], "HTTP_X_OPS_AUTHORIZATION_3" => X_OPS_AUTHORIZATION_LINES_V1_0[2], "HTTP_X_OPS_AUTHORIZATION_4" => X_OPS_AUTHORIZATION_LINES_V1_0[3], "HTTP_X_OPS_AUTHORIZATION_5" => X_OPS_AUTHORIZATION_LINES_V1_0[4], "HTTP_X_OPS_AUTHORIZATION_6" => X_OPS_AUTHORIZATION_LINES_V1_0[5], }.merge(OTHER_HEADERS) PASSENGER_REQUEST_PARAMS = { "action" => "create", #"tarball"=>#, "controller" => "api/v1/cookbooks", "cookbook" => "{\"category\":\"databases\"}", } PASSENGER_HEADERS_V1_1 = { # These are used by signatureverification. "HTTP_HOST" => "127.0.0.1", "HTTP_X_OPS_SIGN" => "algorithm=sha1;version=1.1;", "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_AUTHORIZATION_1" => X_OPS_AUTHORIZATION_LINES[0], "HTTP_X_OPS_AUTHORIZATION_2" => X_OPS_AUTHORIZATION_LINES[1], "HTTP_X_OPS_AUTHORIZATION_3" => X_OPS_AUTHORIZATION_LINES[2], "HTTP_X_OPS_AUTHORIZATION_4" => X_OPS_AUTHORIZATION_LINES[3], "HTTP_X_OPS_AUTHORIZATION_5" => X_OPS_AUTHORIZATION_LINES[4], "HTTP_X_OPS_AUTHORIZATION_6" => X_OPS_AUTHORIZATION_LINES[5], }.merge(OTHER_HEADERS) PASSENGER_HEADERS_V1_0 = { # These are used by signatureverification. "HTTP_HOST" => "127.0.0.1", "HTTP_X_OPS_SIGN" => "version=1.0", "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_AUTHORIZATION_1" => X_OPS_AUTHORIZATION_LINES_V1_0[0], "HTTP_X_OPS_AUTHORIZATION_2" => X_OPS_AUTHORIZATION_LINES_V1_0[1], "HTTP_X_OPS_AUTHORIZATION_3" => X_OPS_AUTHORIZATION_LINES_V1_0[2], "HTTP_X_OPS_AUTHORIZATION_4" => X_OPS_AUTHORIZATION_LINES_V1_0[3], "HTTP_X_OPS_AUTHORIZATION_5" => X_OPS_AUTHORIZATION_LINES_V1_0[4], "HTTP_X_OPS_AUTHORIZATION_6" => X_OPS_AUTHORIZATION_LINES_V1_0[5], }.merge(OTHER_HEADERS) # generated with # openssl genrsa -out private.pem 2048 # openssl rsa -in private.pem -out public.pem -pubout PUBLIC_KEY_DATA = <