lib/aws/core/signature/version_4.rb
# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file 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 'time' require 'openssl' require 'digest' module AWS module Core module Signature module Version4 def self.included base base.send(:include, Signer) end def add_authorization! credentials datetime = Time.now.utc.strftime("%Y%m%dT%H%M%SZ") headers['content-type'] ||= 'application/x-www-form-urlencoded' headers['host'] = host headers['x-amz-date'] = datetime headers['x-amz-security-token'] = credentials.session_token if credentials.session_token headers['x-amz-content-sha256'] ||= hexdigest(body || '') headers['authorization'] = authorization(credentials, datetime) end protected def authorization credentials, datetime parts = [] parts << "AWS4-HMAC-SHA256 Credential=#{credentials.access_key_id}/#{credential_string(datetime)}" parts << "SignedHeaders=#{signed_headers}" parts << "Signature=#{signature(credentials, datetime)}" parts.join(', ') end def signature credentials, datetime k_secret = credentials.secret_access_key k_date = hmac("AWS4" + k_secret, datetime[0,8]) k_region = hmac(k_date, region) k_service = hmac(k_region, service) k_credentials = hmac(k_service, 'aws4_request') hexhmac(k_credentials, string_to_sign(datetime)) end def string_to_sign datetime parts = [] parts << 'AWS4-HMAC-SHA256' parts << datetime parts << credential_string(datetime) parts << hexdigest(canonical_request) parts.join("\n") end def credential_string datetime parts = [] parts << datetime[0,8] parts << region parts << service parts << 'aws4_request' parts.join("/") end def canonical_request parts = [] parts << http_method parts << path parts << querystring parts << canonical_headers + "\n" parts << signed_headers parts << headers['x-amz-content-sha256'] parts.join("\n") end def service # this method is implemented in the request class for each service raise NotImplementedError end def signed_headers to_sign = headers.keys.map{|k| k.to_s.downcase } to_sign.delete('authorization') to_sign.sort.join(";") end def canonical_headers headers = [] self.headers.each_pair do |k,v| headers << [k,v] unless k == 'authorization' end headers = headers.sort_by(&:first) headers.map{|k,v| "#{k}:#{canonical_header_values(v)}" }.join("\n") end def canonical_header_values values values = [values] unless values.is_a?(Array) values.map(&:to_s).join(',').gsub(/\s+/, ' ').strip end def hexdigest value digest = Digest::SHA256.new if value.respond_to?(:read) chunk = nil chunk_size = 1024 * 1024 # 1 megabyte digest.update(chunk) while chunk = value.read(chunk_size) value.rewind else digest.update(value) end digest.hexdigest end def hmac key, value OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), key, value) end def hexhmac key, value OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha256'), key, value) end end end end end