summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJay Mundrawala <jdmundrawala@gmail.com>2015-11-30 11:15:45 -0800
committerJay Mundrawala <jdmundrawala@gmail.com>2015-11-30 11:15:45 -0800
commit36a11952dc027797c72bcd912efc05a8c659b791 (patch)
treefb58c97b59a65b0b834dc06f5256ab34ed9193cb
parentda05c14ab9456aa0af803aecb2b606060fbf38e3 (diff)
parent53fa0aa303713a99261d5182ccfea0c3bb06deb4 (diff)
downloadmixlib-authentication-36a11952dc027797c72bcd912efc05a8c659b791.tar.gz
Merge pull request #8 from chef/jdm/sign-v1.3
Add signing version 1.3
-rw-r--r--Gemfile4
-rw-r--r--lib/mixlib/authentication.rb2
-rw-r--r--lib/mixlib/authentication/digester.rb19
-rw-r--r--lib/mixlib/authentication/http_authentication_request.rb6
-rw-r--r--lib/mixlib/authentication/signatureverification.rb19
-rw-r--r--lib/mixlib/authentication/signedheaderauth.rb140
-rw-r--r--spec/mixlib/authentication/http_authentication_request_spec.rb4
-rw-r--r--spec/mixlib/authentication/mixlib_authentication_spec.rb242
8 files changed, 396 insertions, 40 deletions
diff --git a/Gemfile b/Gemfile
index 3be9c3c..eee77c5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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)