From c4721c2adec6a2f98d4fef11ab857c96943e0810 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sat, 29 Nov 2014 17:33:42 +0000 Subject: [PATCH 1/3] [AWS|Storage] allow signature version to be set to 2 (WIP) --- lib/fog/aws/storage.rb | 122 +++++++++++++++++++++++++++++++++-------- 1 file changed, 98 insertions(+), 24 deletions(-) diff --git a/lib/fog/aws/storage.rb b/lib/fog/aws/storage.rb index 83e32338b..20fed656f 100644 --- a/lib/fog/aws/storage.rb +++ b/lib/fog/aws/storage.rb @@ -421,7 +421,8 @@ module Fog @instrumentor_name = options[:instrumentor_name] || 'fog.aws.storage' @connection_options = options[:connection_options] || {} @persistent = options.fetch(:persistent, false) - + @signature_version = options.fetch(:aws_signature_version, 4) + validate_signature_version! @path_style = options[:path_style] || false if @endpoint = options[:endpoint] @@ -445,13 +446,23 @@ module Fog private + def validate_signature_version! + unless @signature_version == 2 || @signature_version == 4 + raise "Unknown signature version #{@signature_version}; valid versions are 2 or 4" + end + end + def setup_credentials(options) @aws_access_key_id = options[:aws_access_key_id] @aws_secret_access_key = options[:aws_secret_access_key] @aws_session_token = options[:aws_session_token] @aws_credentials_expire_at = options[:aws_credentials_expire_at] - @signer = Fog::AWS::SignatureV4.new( @aws_access_key_id, @aws_secret_access_key, @region, 's3') + if @signature_version == 4 + @signer = Fog::AWS::SignatureV4.new( @aws_access_key_id, @aws_secret_access_key, @region, 's3') + elsif @signature_version == 2 + @hmac = Fog::HMAC.new('sha1', @aws_secret_access_key) + end end def connection(scheme, host, port) @@ -477,37 +488,43 @@ module Fog params[:headers] = (params[:headers] || {}).dup params[:headers]['x-amz-security-token'] = @aws_session_token if @aws_session_token - - if params[:body].respond_to?(:read) - # See http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html - params[:headers]['x-amz-content-sha256'] = 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD' - params[:headers]['x-amz-decoded-content-length'] = params[:headers].delete 'Content-Length' - - encoding = "aws-chunked" - - encoding += ", #{params[:headers]['Content-Encoding']}" if params[:headers]['Content-Encoding'] - params[:headers]['Content-Encoding'] = encoding - else - params[:headers]['x-amz-content-sha256'] ||= Digest::SHA256.hexdigest(params[:body] || '') - end params[:headers]['x-amz-date'] = date.to_iso8601_basic + if @signature_version == 2 + params[:headers]['Authorization'] = "AWS #{@aws_access_key_id}:#{signature_v2(params)}" + end + params = request_params(params) scheme = params.delete(:scheme) host = params.delete(:host) port = params.delete(:port) || DEFAULT_SCHEME_PORT[scheme] params[:headers]['Host'] = host - signature_components = @signer.signature_components(params, date, params[:headers]['x-amz-content-sha256']) - params[:headers]['Authorization'] = @signer.components_to_header(signature_components) + + if @signature_version == 4 + if params[:body].respond_to?(:read) + # See http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html + params[:headers]['x-amz-content-sha256'] = 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD' + params[:headers]['x-amz-decoded-content-length'] = params[:headers].delete 'Content-Length' + + encoding = "aws-chunked" + + encoding += ", #{params[:headers]['Content-Encoding']}" if params[:headers]['Content-Encoding'] + params[:headers]['Content-Encoding'] = encoding + else + params[:headers]['x-amz-content-sha256'] ||= Digest::SHA256.hexdigest(params[:body] || '') + end + signature_components = @signer.signature_components(params, date, params[:headers]['x-amz-content-sha256']) + params[:headers]['Authorization'] = @signer.components_to_header(signature_components) + + if params[:body].respond_to?(:read) + body = params.delete :body + params[:request_block] = S3Streamer.new(body, signature_components['X-Amz-Signature'], @signer, date) + end + end # FIXME: ToHashParser should make this not needed original_params = params.dup - if params[:body].respond_to?(:read) - body = params.delete :body - params[:request_block] = S3Streamer.new(body, signature_components['X-Amz-Signature'], @signer, date) - end - if @instrumentor @instrumentor.instrument("#{@instrumentor_name}.request", params) do _request(scheme, host, port, params, original_params, &block) @@ -537,8 +554,10 @@ module Fog else %r{s3-([^\.]*).amazonaws.com}.match(new_params[:host]).captures.first end - @signer = Fog::AWS::SignatureV4.new(@aws_access_key_id, @aws_secret_access_key, @region, 's3') - original_params[:headers].delete('Authorization') + if @signature_version == 4 + @signer = Fog::AWS::SignatureV4.new(@aws_access_key_id, @aws_secret_access_key, @region, 's3') + original_params[:headers].delete('Authorization') + end response = request(original_params.merge(new_params), &block) @region, @signer = original_region, original_signer response @@ -603,6 +622,61 @@ DATA hmac.sign(string_to_sign.strip).unpack('H*').first end end + + def signature_v2(params) + headers = params[:headers] || {} + + string_to_sign = +<<-DATA +#{params[:method].to_s.upcase} +#{headers['Content-MD5']} +#{headers['Content-Type']} + +DATA + + amz_headers, canonical_amz_headers = {}, '' + for key, value in headers + if key[0..5] == 'x-amz-' + amz_headers[key] = value + end + end + amz_headers = amz_headers.sort {|x, y| x[0] <=> y[0]} + for key, value in amz_headers + canonical_amz_headers << "#{key}:#{value}\n" + end + string_to_sign << canonical_amz_headers + + query_string = '' + if params[:query] + query_args = [] + for key in params[:query].keys.sort + if VALID_QUERY_KEYS.include?(key) + value = params[:query][key] + if value + query_args << "#{key}=#{value}" + else + query_args << key + end + end + end + if query_args.any? + query_string = '?' + query_args.join('&') + end + end + + canonical_path = (params[:path] || object_to_path(params[:object_name])).to_s + canonical_path = '/' + canonical_path if canonical_path[0..0] != '/' + + if params[:bucket_name] + canonical_resource = "/#{params[:bucket_name]}#{canonical_path}" + else + canonical_resource = canonical_path + end + canonical_resource << query_string + string_to_sign << canonical_resource + signed_string = @hmac.sign(string_to_sign) + Base64.encode64(signed_string).chomp! + end end end end From dd00c3bc81ceb604a5607b930a837959983c55fb Mon Sep 17 00:00:00 2001 From: Jon K Hellan Date: Mon, 1 Dec 2014 09:14:38 +0100 Subject: [PATCH 2/3] recognize aws_signature_version option --- lib/fog/aws/storage.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fog/aws/storage.rb b/lib/fog/aws/storage.rb index 20fed656f..a18f38aa0 100644 --- a/lib/fog/aws/storage.rb +++ b/lib/fog/aws/storage.rb @@ -44,7 +44,7 @@ module Fog ] requires :aws_access_key_id, :aws_secret_access_key - recognizes :endpoint, :region, :host, :port, :scheme, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at, :path_style, :instrumentor, :instrumentor_name + recognizes :endpoint, :region, :host, :port, :scheme, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at, :path_style, :instrumentor, :instrumentor_name, :aws_signature_version secrets :aws_secret_access_key, :hmac From fc29620996a0c0791b362f3113066ac9e6f38d42 Mon Sep 17 00:00:00 2001 From: Jon K Hellan Date: Mon, 1 Dec 2014 09:39:54 +0100 Subject: [PATCH 3/3] Use old style date header for signature v2 --- lib/fog/aws/storage.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/fog/aws/storage.rb b/lib/fog/aws/storage.rb index a18f38aa0..23f19fb02 100644 --- a/lib/fog/aws/storage.rb +++ b/lib/fog/aws/storage.rb @@ -488,10 +488,11 @@ module Fog params[:headers] = (params[:headers] || {}).dup params[:headers]['x-amz-security-token'] = @aws_session_token if @aws_session_token - params[:headers]['x-amz-date'] = date.to_iso8601_basic if @signature_version == 2 - params[:headers]['Authorization'] = "AWS #{@aws_access_key_id}:#{signature_v2(params)}" + expires = date.to_date_header + params[:headers]['Date'] = expires + params[:headers]['Authorization'] = "AWS #{@aws_access_key_id}:#{signature_v2(params, expires)}" end params = request_params(params) @@ -502,6 +503,7 @@ module Fog if @signature_version == 4 + params[:headers]['x-amz-date'] = date.to_iso8601_basic if params[:body].respond_to?(:read) # See http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html params[:headers]['x-amz-content-sha256'] = 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD' @@ -623,7 +625,7 @@ DATA end end - def signature_v2(params) + def signature_v2(params, expires) headers = params[:headers] || {} string_to_sign = @@ -631,7 +633,7 @@ DATA #{params[:method].to_s.upcase} #{headers['Content-MD5']} #{headers['Content-Type']} - +#{expires} DATA amz_headers, canonical_amz_headers = {}, ''