File: signaturev4.rb

package info (click to toggle)
ruby-fog-aws 3.18.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 8,140 kB
  • sloc: ruby: 73,328; javascript: 14; makefile: 9; sh: 4
file content (122 lines) | stat: -rw-r--r-- 4,096 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# See http://docs.amazonwebservices.com/general/latest/gr/signature-version-4.html

module Fog
  module AWS
    class SignatureV4
      ALGORITHM = 'AWS4-HMAC-SHA256'

      def initialize(aws_access_key_id, secret_key, region, service)
        @region = region
        @service = service
        @aws_access_key_id  = aws_access_key_id
        @hmac = Fog::HMAC.new('sha256', 'AWS4' + secret_key)
      end

      def signature_parameters(params, date, body_sha = nil)
        params = params.dup.merge(:query => params[:query].merge(
          'X-Amz-Algorithm' => ALGORITHM,
          'X-Amz-Credential' => "#{@aws_access_key_id}/#{credential_scope(date)}",
          'X-Amz-SignedHeaders' => signed_headers(params[:headers])
        ))
        signature_components(params, date, body_sha)
      end

      def signature_header(params, date, body_sha = nil)
        components_to_header(signature_components(params, date, body_sha))
      end

      def sign(params, date) #legacy method name
        signature_header(params, date)
      end

      def components_to_header components
        "#{components['X-Amz-Algorithm']} Credential=#{components['X-Amz-Credential']}, SignedHeaders=#{components['X-Amz-SignedHeaders']}, Signature=#{components['X-Amz-Signature']}" 
      end

      def signature_components(params, date, body_sha)
        canonical_request = <<-DATA
#{params[:method].to_s.upcase}
#{canonical_path(params[:path])}
#{canonical_query_string(params[:query])}
#{canonical_headers(params[:headers])}
#{signed_headers(params[:headers])}
#{body_sha || OpenSSL::Digest::SHA256.hexdigest(params[:body] || '')}
DATA
        canonical_request.chop!

        string_to_sign = <<-DATA
#{ALGORITHM}
#{date.to_iso8601_basic}
#{credential_scope(date)}
#{OpenSSL::Digest::SHA256.hexdigest(canonical_request)}
DATA

        string_to_sign.chop!

        signature = derived_hmac(date).sign(string_to_sign)

        {
          'X-Amz-Algorithm' => ALGORITHM,
          'X-Amz-Credential' => "#{@aws_access_key_id}/#{credential_scope(date)}",
          'X-Amz-SignedHeaders' => signed_headers(params[:headers]),
          'X-Amz-Signature' => signature.unpack('H*').first
        }
      end

      def derived_hmac(date)
        kDate = @hmac.sign(date.utc.strftime('%Y%m%d'))
        kRegion = Fog::HMAC.new('sha256', kDate).sign(@region)
        kService = Fog::HMAC.new('sha256', kRegion).sign(@service)
        kSigning = Fog::HMAC.new('sha256', kService).sign('aws4_request')
        Fog::HMAC.new('sha256', kSigning)
      end


      def credential_scope(date)
        "#{date.utc.strftime('%Y%m%d')}/#{@region}/#{@service}/aws4_request"
      end

      protected

      def canonical_path(path)
        unless @service == 's3' #S3 implements signature v4 different - paths are not canonialized
          #leading and trailing repeated slashes are collapsed, but not ones that appear elsewhere
          path = path.gsub(%r{\A/+},'/').gsub(%r{/+\z},'/')
          components = path.split('/',-1)
          path = components.inject([]) do |acc, component|
            case component
            when '.'   #canonicalize by removing .
            when '..' then acc.pop#canonicalize by reducing ..
            else
              acc << component
            end
            acc
          end.join('/')
        end
        path.empty? ? '/' : path
      end

      def canonical_query_string(query)
        canonical_query_string = []
        for key in (query || {}).keys.sort_by {|k| k.to_s}
          component = "#{Fog::AWS.escape(key.to_s)}=#{Fog::AWS.escape(query[key].to_s)}"
          canonical_query_string << component
        end
        canonical_query_string.join("&")
      end

      def canonical_headers(headers)
        canonical_headers = ''

        for key in headers.keys.sort_by {|k| k.to_s.downcase}
          canonical_headers << "#{key.to_s.downcase}:#{headers[key].to_s.strip}\n"
        end
        canonical_headers
      end

      def signed_headers(headers)
        headers.keys.map {|key| key.to_s.downcase}.sort.join(';')
      end
    end
  end
end