class AWS::Core::Signers::Version4

@api private

Constants

EMPTY_DIGEST

@api private SHA256 hex digest of the empty string

STREAMING_CHECKSUM

@api private

Attributes

credentials[R]

@return [CredentialProviders::Provider]

region[R]

@return [String]

service_name[R]

@return [String]

Public Class Methods

new(credentials, service_name, region) click to toggle source

@param [CredentialProviders::Provider] credentials @param [String] #service_name @param [String] region

# File lib/aws/core/signers/version_4.rb, line 36
def initialize credentials, service_name, region
  @credentials = credentials
  @service_name = service_name
  @region = region
end

Public Instance Methods

credential(datetime) click to toggle source
# File lib/aws/core/signers/version_4.rb, line 87
def credential(datetime)
  "#{credentials.access_key_id}/#{key_path(datetime)}"
end
derive_key(datetime) click to toggle source
# File lib/aws/core/signers/version_4.rb, line 91
def derive_key(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_name)
  k_credentials = hmac(k_service, 'aws4_request')
end
sign_request(req, options = {}) click to toggle source

@param [Http::Request] req @option options [Boolean] :chunk_signing (false) When true, the

request body will be signed in chunk.

@option options [DateTime String<YYYYMMDDTHHMMSSZ>] :datetime @return [Http::Request]

# File lib/aws/core/signers/version_4.rb, line 56
def sign_request req, options = {}
  datetime = options[:datetime] || Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
  key = derive_key(datetime)
  token = credentials.session_token
  chunk_signing = !!options[:chunk_signing]
  content_sha256 = req.headers['x-amz-content-sha256'] || body_digest(req, chunk_signing)

  req.headers['host'] = req.host
  req.headers['x-amz-date'] = datetime
  req.headers['x-amz-security-token'] = token if token
  req.headers['x-amz-content-sha256'] = content_sha256

  if chunk_signing
    orig_size = req.headers['content-length'].to_i
    signed_size = ChunkSignedStream.signed_size(orig_size.to_i)
    req.headers['content-length'] = signed_size.to_s
    req.headers['x-amz-decoded-content-length'] = orig_size.to_s
  end

  req.headers['authorization'] = authorization(req, key, datetime, content_sha256)

  req.body_stream = chunk_signed_stream(req, key) if chunk_signing

  req
end
signature(request, key, datetime, content_sha256) click to toggle source
# File lib/aws/core/signers/version_4.rb, line 82
def signature(request, key, datetime, content_sha256)
  string = string_to_sign(request, datetime, content_sha256)
  hexhmac(key, string)
end

Private Instance Methods

authorization(req, key, datetime, content_sha256) click to toggle source
# File lib/aws/core/signers/version_4.rb, line 118
def authorization req, key, datetime, content_sha256
  parts = []
  parts << "AWS4-HMAC-SHA256 Credential=#{credential(datetime)}"
  parts << "SignedHeaders=#{signed_headers(req)}"
  parts << "Signature=#{signature(req, key, datetime, content_sha256)}"
  parts.join(', ')
end
body_digest(req, chunk_signing) click to toggle source

@param [Http::Request] req @param [Boolean] chunk_signing @return [String]

# File lib/aws/core/signers/version_4.rb, line 184
def body_digest req, chunk_signing
  case
  when chunk_signing then STREAMING_CHECKSUM
  when ['', nil].include?(req.body) then EMPTY_DIGEST
  else hexdigest(req.body)
  end
end
canonical_header_values(values) click to toggle source

@param [String,Array<String>] values

# File lib/aws/core/signers/version_4.rb, line 176
def canonical_header_values values
  values = [values] unless values.is_a?(Array)
  values.map(&:to_s).join(',').gsub(/\s+/, ' ').strip
end
canonical_headers(req) click to toggle source

@param [Http::Request] req

# File lib/aws/core/signers/version_4.rb, line 166
def canonical_headers req
  headers = []
  req.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
canonical_request(req, content_sha256) click to toggle source

@param [Http::Request] req

# File lib/aws/core/signers/version_4.rb, line 147
def canonical_request req, content_sha256
  parts = []
  parts << req.http_method
  parts << req.path
  parts << req.querystring
  parts << canonical_headers(req) + "\n"
  parts << signed_headers(req)
  parts << content_sha256
  parts.join("\n")
end
chunk_signed_stream(req, key) click to toggle source

Wraps the req body stream with another stream. The wrapper signs the original body as it is read, injecting signatures of indiviaul chunks into the resultant stream. @param [Http::Request] req @param [String] key @param [String] datetime

# File lib/aws/core/signers/version_4.rb, line 107
def chunk_signed_stream req, key
  args = []
  args << req.body_stream
  args << req.headers['x-amz-decoded-content-length'].to_i
  args << key
  args << key_path(req.headers['x-amz-date'])
  args << req.headers['x-amz-date']
  args << req.headers['authorization'].split('Signature=')[1]
  ChunkSignedStream.new(*args)
end
hexdigest(value) click to toggle source

@param [String] value @return [String]

# File lib/aws/core/signers/version_4.rb, line 194
def hexdigest value
  digest = OpenSSL::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
hexhmac(key, value) click to toggle source

@param [String] key @param [String] value @return [String]

# File lib/aws/core/signers/version_4.rb, line 217
def hexhmac key, value
  OpenSSL::HMAC.hexdigest(sha256_digest, key, value)
end
hmac(key, value) click to toggle source

@param [String] key @param [String] value @return [String]

# File lib/aws/core/signers/version_4.rb, line 210
def hmac key, value
  OpenSSL::HMAC.digest(sha256_digest, key, value)
end
key_path(datetime) click to toggle source

@param [String] datetime @return [String] the signature scope.

# File lib/aws/core/signers/version_4.rb, line 137
def key_path datetime
  parts = []
  parts << datetime[0,8]
  parts << region
  parts << service_name
  parts << 'aws4_request'
  parts.join("/")
end
sha256_digest() click to toggle source
# File lib/aws/core/signers/version_4.rb, line 221
def sha256_digest
  OpenSSL::Digest.new('sha256')
end
signed_headers(req) click to toggle source

@param [Http::Request] req

# File lib/aws/core/signers/version_4.rb, line 159
def signed_headers req
  to_sign = req.headers.keys.map{|k| k.to_s.downcase }
  to_sign.delete('authorization')
  to_sign.sort.join(";")
end
string_to_sign(req, datetime, content_sha256) click to toggle source
# File lib/aws/core/signers/version_4.rb, line 126
def string_to_sign req, datetime, content_sha256
  parts = []
  parts << 'AWS4-HMAC-SHA256'
  parts << datetime
  parts << key_path(datetime)
  parts << hexdigest(canonical_request(req, content_sha256))
  parts.join("\n")
end