aws-ses-v4 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile ADDED
@@ -0,0 +1,83 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'erb'
13
+
14
+ require 'rake/testtask'
15
+ Rake::TestTask.new(:test) do |test|
16
+ test.libs << 'lib' << 'test'
17
+ test.pattern = 'test/**/*_test.rb'
18
+ test.verbose = true
19
+ end
20
+
21
+ # require 'rcov/rcovtask'
22
+ # Rcov::RcovTask.new do |test|
23
+ # test.libs << 'test'
24
+ # test.pattern = 'test/**/*_test.rb'
25
+ # test.verbose = true
26
+ # end
27
+
28
+ task :default => :test
29
+
30
+ require 'rdoc/task'
31
+ require File.dirname(__FILE__) + '/lib/aws/ses'
32
+
33
+ namespace :doc do
34
+ Rake::RDocTask.new do |rdoc|
35
+ rdoc.rdoc_dir = 'doc'
36
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
37
+ rdoc.title = "AWS::SES -- Support for Amazon SES's REST api #{version}"
38
+ rdoc.options << '--line-numbers' << '--inline-source'
39
+ rdoc.rdoc_files.include('README.rdoc')
40
+ rdoc.rdoc_files.include('LICENSE')
41
+ rdoc.rdoc_files.include('CHANGELOG')
42
+ rdoc.rdoc_files.include('TODO')
43
+ rdoc.rdoc_files.include('VERSION')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
46
+
47
+ task :rdoc => 'doc:readme'
48
+
49
+ task :refresh => :rerdoc do
50
+ system 'open doc/index.html'
51
+ end
52
+
53
+ desc "Generate readme.rdoc from readme.erb"
54
+ task :readme do
55
+ require 'support/rdoc/code_info'
56
+ RDoc::CodeInfo.parse('lib/**/*.rb')
57
+
58
+ strip_comments = lambda {|comment| comment.gsub(/^# ?/, '')}
59
+ docs_for = lambda do |location|
60
+ info = RDoc::CodeInfo.for(location)
61
+ raise RuntimeError, "Couldn't find documentation for `#{location}'" unless info
62
+ strip_comments[info.comment]
63
+ end
64
+
65
+ open('README.rdoc', 'w') do |file|
66
+ file.write ERB.new(IO.read('README.erb')).result(binding)
67
+ end
68
+ end
69
+ end
70
+
71
+ require 'jeweler'
72
+ Jeweler::Tasks.new do |gem|
73
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
74
+ gem.name = "aws-ses"
75
+ gem.homepage = "http://github.com/drewblas/aws-ses"
76
+ gem.license = "MIT"
77
+ gem.summary = "Client library for Amazon's Simple Email Service's REST API"
78
+ gem.description = "Client library for Amazon's Simple Email Service's REST API"
79
+ gem.email = "drew.blas@gmail.com"
80
+ gem.authors = ["Drew Blas", "Marcel Molina Jr."]
81
+ # dependencies defined in Gemfile
82
+ end
83
+ Jeweler::RubygemsDotOrgTasks.new
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ * Use a better XML parser (and be consistent)
2
+ * Rename Base to something else (probably Mailer): Nothing else actually inherits from Base, so that is a very poor naming convention. I intend to change it in the future
3
+ * Integer responses should probably be cast to ints instead of left as strings
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.8.1
data/aws-ses.gemspec ADDED
@@ -0,0 +1,102 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+ # stub: aws-ses 0.7.1 ruby lib
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "aws-ses-v4".freeze
9
+ s.version = "0.8.1"
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib".freeze]
13
+ s.authors = ["Drew Blas".freeze, "Marcel Molina Jr.".freeze]
14
+ s.date = "2021-04-10"
15
+ s.description = "Client library for Amazon's Simple Email Service's REST API".freeze
16
+ s.email = "drew.blas@gmail.com".freeze
17
+ s.extra_rdoc_files = [
18
+ "CHANGELOG",
19
+ "LICENSE",
20
+ "README.erb",
21
+ "README.rdoc",
22
+ "TODO"
23
+ ]
24
+ s.files = [
25
+ ".document",
26
+ "CHANGELOG",
27
+ "Gemfile",
28
+ "Gemfile.lock",
29
+ "LICENSE",
30
+ "README.erb",
31
+ "README.rdoc",
32
+ "Rakefile",
33
+ "TODO",
34
+ "VERSION",
35
+ "aws-ses.gemspec",
36
+ "lib/aws/actionmailer/ses_extension.rb",
37
+ "lib/aws/ses.rb",
38
+ "lib/aws/ses/addresses.rb",
39
+ "lib/aws/ses/base.rb",
40
+ "lib/aws/ses/extensions.rb",
41
+ "lib/aws/ses/info.rb",
42
+ "lib/aws/ses/response.rb",
43
+ "lib/aws/ses/send_email.rb",
44
+ "lib/aws/ses/version.rb",
45
+ "test/address_test.rb",
46
+ "test/base_test.rb",
47
+ "test/extensions_test.rb",
48
+ "test/fixtures.rb",
49
+ "test/helper.rb",
50
+ "test/info_test.rb",
51
+ "test/mocks/fake_response.rb",
52
+ "test/response_test.rb",
53
+ "test/send_email_test.rb"
54
+ ]
55
+ s.homepage = "http://github.com/sertangulveren/aws-ses".freeze
56
+ s.licenses = ["MIT".freeze]
57
+ s.rubygems_version = "2.5.2.3".freeze
58
+ s.summary = "Client library for Amazon's Simple Email Service's REST API".freeze
59
+
60
+ if s.respond_to? :specification_version then
61
+ s.specification_version = 4
62
+
63
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
64
+ s.add_runtime_dependency(%q<builder>.freeze, [">= 0"])
65
+ s.add_runtime_dependency(%q<mail>.freeze, ["> 2.2.5"])
66
+ s.add_runtime_dependency(%q<mime-types>.freeze, [">= 0"])
67
+ s.add_runtime_dependency(%q<xml-simple>.freeze, [">= 0"])
68
+ s.add_development_dependency(%q<bundler>.freeze, [">= 1.17"])
69
+ s.add_development_dependency(%q<flexmock>.freeze, ["~> 0.8.11"])
70
+ s.add_development_dependency(%q<jeweler>.freeze, [">= 0"])
71
+ s.add_development_dependency(%q<rake>.freeze, [">= 0"])
72
+ s.add_development_dependency(%q<shoulda-context>.freeze, [">= 0"])
73
+ s.add_development_dependency(%q<test-unit>.freeze, [">= 0"])
74
+ s.add_development_dependency(%q<timecop>.freeze, [">= 0"])
75
+ else
76
+ s.add_dependency(%q<builder>.freeze, [">= 0"])
77
+ s.add_dependency(%q<mail>.freeze, ["> 2.2.5"])
78
+ s.add_dependency(%q<mime-types>.freeze, [">= 0"])
79
+ s.add_dependency(%q<xml-simple>.freeze, [">= 0"])
80
+ s.add_dependency(%q<bundler>.freeze, [">= 1.17"])
81
+ s.add_dependency(%q<flexmock>.freeze, ["~> 0.8.11"])
82
+ s.add_dependency(%q<jeweler>.freeze, [">= 0"])
83
+ s.add_dependency(%q<rake>.freeze, [">= 0"])
84
+ s.add_dependency(%q<shoulda-context>.freeze, [">= 0"])
85
+ s.add_dependency(%q<test-unit>.freeze, [">= 0"])
86
+ s.add_dependency(%q<timecop>.freeze, [">= 0"])
87
+ end
88
+ else
89
+ s.add_dependency(%q<builder>.freeze, [">= 0"])
90
+ s.add_dependency(%q<mail>.freeze, ["> 2.2.5"])
91
+ s.add_dependency(%q<mime-types>.freeze, [">= 0"])
92
+ s.add_dependency(%q<xml-simple>.freeze, [">= 0"])
93
+ s.add_dependency(%q<bundler>.freeze, [">= 1.17"])
94
+ s.add_dependency(%q<flexmock>.freeze, ["~> 0.8.11"])
95
+ s.add_dependency(%q<jeweler>.freeze, [">= 0"])
96
+ s.add_dependency(%q<rake>.freeze, [">= 0"])
97
+ s.add_dependency(%q<shoulda-context>.freeze, [">= 0"])
98
+ s.add_dependency(%q<test-unit>.freeze, [">= 0"])
99
+ s.add_dependency(%q<timecop>.freeze, [">= 0"])
100
+ end
101
+ end
102
+
@@ -0,0 +1,19 @@
1
+ # A quick little extension to use this lib with with rails 2.3.X
2
+ # To use it, in your environment.rb or some_environment.rb you simply set
3
+ #
4
+ # config.after_initialize do
5
+ # ActionMailer::Base.delivery_method = :amazon_ses
6
+ # ActionMailer::Base.custom_amazon_ses_mailer = AWS::SES::Base.new(:secret_access_key => S3_CONFIG[:secret_access_key], :access_key_id => S3_CONFIG[:access_key_id])
7
+ # end
8
+
9
+ module ActionMailer
10
+ class Base
11
+ cattr_accessor :custom_amazon_ses_mailer
12
+
13
+ def perform_delivery_amazon_ses(mail)
14
+ raise 'AWS::SES::Base has not been intitialized.' unless @@custom_amazon_ses_mailer
15
+ @@custom_amazon_ses_mailer.deliver!(mail)
16
+ end
17
+
18
+ end
19
+ end
data/lib/aws/ses.rb ADDED
@@ -0,0 +1,28 @@
1
+ %w[ base64 cgi openssl digest/sha1 net/https net/http rexml/document time ostruct mail].each { |f| require f }
2
+
3
+ begin
4
+ require 'URI' unless defined? URI
5
+ rescue Exception => e
6
+ # nothing
7
+ end
8
+
9
+ begin
10
+ require 'xmlsimple' unless defined? XmlSimple
11
+ rescue Exception => e
12
+ require 'xml-simple' unless defined? XmlSimple
13
+ end
14
+
15
+ $:.unshift(File.dirname(__FILE__))
16
+ require 'ses/extensions'
17
+
18
+ require 'ses/response'
19
+ require 'ses/send_email'
20
+ require 'ses/info'
21
+ require 'ses/base'
22
+ require 'ses/version'
23
+ require 'ses/addresses'
24
+
25
+ if defined?(Rails)
26
+ major, minor = Rails.version.to_s.split('.')
27
+ require 'actionmailer/ses_extension' if major == '2' && minor == '3'
28
+ end
@@ -0,0 +1,75 @@
1
+ module AWS
2
+ module SES
3
+ # AWS::SES::Addresses provides for:
4
+ # * Listing verified e-mail addresses
5
+ # * Adding new e-mail addresses to verify
6
+ # * Deleting verified e-mail addresses
7
+ #
8
+ # You can access these methods as follows:
9
+ #
10
+ # ses = AWS::SES::Base.new( ... connection info ... )
11
+ #
12
+ # # Get a list of verified addresses
13
+ # ses.addresses.list.result
14
+ #
15
+ # # Add a new e-mail address to verify
16
+ # ses.addresses.verify('jon@example.com')
17
+ #
18
+ # # Delete an e-mail address
19
+ # ses.addresses.delete('jon@example.com')
20
+ class Addresses < Base
21
+ def initialize(ses)
22
+ @ses = ses
23
+ end
24
+
25
+ # List all verified e-mail addresses
26
+ #
27
+ # Usage:
28
+ # ses.addresses.list.result
29
+ # =>
30
+ # ['email1@example.com', email2@example.com']
31
+ def list
32
+ @ses.request('ListVerifiedEmailAddresses')
33
+ end
34
+
35
+ def verify(email)
36
+ @ses.request('VerifyEmailAddress',
37
+ 'EmailAddress' => email
38
+ )
39
+ end
40
+
41
+ def delete(email)
42
+ @ses.request('DeleteVerifiedEmailAddress',
43
+ 'EmailAddress' => email
44
+ )
45
+ end
46
+ end
47
+
48
+ class ListVerifiedEmailAddressesResponse < AWS::SES::Response
49
+ def result
50
+ if members = parsed['ListVerifiedEmailAddressesResult']['VerifiedEmailAddresses']
51
+ [members['member']].flatten
52
+ else
53
+ []
54
+ end
55
+ end
56
+ memoized :result
57
+ end
58
+
59
+ class VerifyEmailAddressResponse < AWS::SES::Response
60
+ end
61
+
62
+ class DeleteVerifiedEmailAddressResponse < AWS::SES::Response
63
+ def result
64
+ success?
65
+ end
66
+ end
67
+
68
+ class Base
69
+ def addresses
70
+ @addresses ||= Addresses.new(self)
71
+ end
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,286 @@
1
+ module AWS #:nodoc:
2
+ # AWS::SES is a Ruby library for Amazon's Simple Email Service's REST API (http://aws.amazon.com/ses).
3
+ #
4
+ # == Getting started
5
+ #
6
+ # To get started you need to require 'aws/ses':
7
+ #
8
+ # % irb -rubygems
9
+ # irb(main):001:0> require 'aws/ses'
10
+ # # => true
11
+ #
12
+ # Before you can do anything, you must establish a connection using Base.new. A basic connection would look something like this:
13
+ #
14
+ # ses = AWS::SES::Base.new(
15
+ # :access_key_id => 'abc',
16
+ # :secret_access_key => '123'
17
+ # )
18
+ #
19
+ # The minimum connection options that you must specify are your access key id and your secret access key.
20
+ #
21
+ # === Connecting to a server from another region
22
+ #
23
+ # The default server API endpoint is "email.us-east-1.amazonaws.com", corresponding to the US East 1 region.
24
+ # To connect to a different one, just pass it as a parameter to the AWS::SES::Base initializer:
25
+ #
26
+ # ses = AWS::SES::Base.new(
27
+ # :access_key_id => 'abc',
28
+ # :secret_access_key => '123',
29
+ # :server => 'email.eu-west-1.amazonaws.com',
30
+ # :message_id_domain => 'eu-west-1.amazonses.com'
31
+ # )
32
+ #
33
+
34
+ module SES
35
+
36
+ API_VERSION = '2010-12-01'
37
+
38
+ DEFAULT_REGION = 'us-east-1'
39
+
40
+ SERVICE = 'ses'
41
+
42
+ DEFAULT_HOST = 'email.us-east-1.amazonaws.com'
43
+
44
+ DEFAULT_MESSAGE_ID_DOMAIN = 'email.amazonses.com'
45
+
46
+ USER_AGENT = 'github-aws-ses-ruby-gem'
47
+
48
+ DEFAULT_SIGNATURE_VERSION = 4
49
+
50
+ # Encodes the given string with the secret_access_key by taking the
51
+ # hmac-sha1 sum, and then base64 encoding it. Optionally, it will also
52
+ # url encode the result of that to protect the string if it's going to
53
+ # be used as a query string parameter.
54
+ #
55
+ # @param [String] secret_access_key the user's secret access key for signing.
56
+ # @param [String] str the string to be hashed and encoded.
57
+ # @param [Boolean] urlencode whether or not to url encode the result., true or false
58
+ # @return [String] the signed and encoded string.
59
+ def SES.encode(secret_access_key, str, urlencode=true)
60
+ digest = OpenSSL::Digest.new('sha256')
61
+ b64_hmac =
62
+ Base64.encode64(
63
+ OpenSSL::HMAC.digest(digest, secret_access_key, str)).gsub("\n","")
64
+
65
+ if urlencode
66
+ return CGI::escape(b64_hmac)
67
+ else
68
+ return b64_hmac
69
+ end
70
+ end
71
+
72
+ # Generates the HTTP Header String that Amazon looks for
73
+ #
74
+ # @param [String] key the AWS Access Key ID
75
+ # @param [String] alg the algorithm used for the signature
76
+ # @param [String] sig the signature itself
77
+ def SES.authorization_header(key, alg, sig)
78
+ "AWS3-HTTPS AWSAccessKeyId=#{key}, Algorithm=#{alg}, Signature=#{sig}"
79
+ end
80
+
81
+ def SES.authorization_header_v4(credential, signed_headers, signature)
82
+ "AWS4-HMAC-SHA256 Credential=#{credential}, SignedHeaders=#{signed_headers}, Signature=#{signature}"
83
+ end
84
+
85
+ # AWS::SES::Base is the abstract super class of all classes who make requests against SES
86
+ class Base
87
+ include SendEmail
88
+ include Info
89
+
90
+ attr_reader :use_ssl, :server, :proxy_server, :port, :message_id_domain, :signature_version, :region,
91
+ :action, :action_time, :query
92
+ attr_accessor :settings
93
+
94
+ # @option options [String] :access_key_id ("") The user's AWS Access Key ID
95
+ # @option options [String] :secret_access_key ("") The user's AWS Secret Access Key
96
+ # @option options [Boolean] :use_ssl (true) Connect using SSL?
97
+ # @option options [String] :server ("email.us-east-1.amazonaws.com") The server API endpoint host
98
+ # @option options [String] :proxy_server (nil) An HTTP proxy server FQDN
99
+ # @option options [String] :user_agent ("github-aws-ses-ruby-gem") The HTTP User-Agent header value
100
+ # @option options [String] :region ("us-east-1") The server API endpoint host
101
+ # @option options [String] :message_id_domain ("us-east-1.amazonses.com") Domain used to build message_id header
102
+ # @return [Object] the object.
103
+ def initialize( options = {} )
104
+
105
+ options = { :access_key_id => "",
106
+ :secret_access_key => "",
107
+ :use_ssl => true,
108
+ :server => DEFAULT_HOST,
109
+ :message_id_domain => DEFAULT_MESSAGE_ID_DOMAIN,
110
+ :path => "/",
111
+ :user_agent => USER_AGENT,
112
+ :proxy_server => nil,
113
+ :region => DEFAULT_REGION,
114
+ :signature_version => DEFAULT_SIGNATURE_VERSION
115
+ }.merge(options)
116
+
117
+ @signature_version = options[:signature_version]
118
+ @server = options[:server]
119
+ @message_id_domain = options[:message_id_domain]
120
+ @proxy_server = options[:proxy_server]
121
+ @use_ssl = options[:use_ssl]
122
+ @path = options[:path]
123
+ @user_agent = options[:user_agent]
124
+ @region = options[:region]
125
+ @settings = {}
126
+
127
+ raise ArgumentError, "No :access_key_id provided" if options[:access_key_id].nil? || options[:access_key_id].empty?
128
+ raise ArgumentError, "No :secret_access_key provided" if options[:secret_access_key].nil? || options[:secret_access_key].empty?
129
+ raise ArgumentError, "No :use_ssl value provided" if options[:use_ssl].nil?
130
+ raise ArgumentError, "Invalid :use_ssl value provided, only 'true' or 'false' allowed" unless options[:use_ssl] == true || options[:use_ssl] == false
131
+ raise ArgumentError, "No :server provided" if options[:server].nil? || options[:server].empty?
132
+ raise ArgumentError, ":signature_version must be 2 or 4" unless [2, 4].include?(options[:signature_version])
133
+
134
+ if options[:port]
135
+ # user-specified port
136
+ @port = options[:port]
137
+ elsif @use_ssl
138
+ # https
139
+ @port = 443
140
+ else
141
+ # http
142
+ @port = 80
143
+ end
144
+
145
+ @access_key_id = options[:access_key_id]
146
+ @secret_access_key = options[:secret_access_key]
147
+
148
+ # Use proxy server if defined
149
+ # Based on patch by Mathias Dalheimer. 20070217
150
+ proxy = @proxy_server ? URI.parse(@proxy_server) : OpenStruct.new
151
+ @http = Net::HTTP::Proxy( proxy.host,
152
+ proxy.port,
153
+ proxy.user,
154
+ proxy.password).new(options[:server], @port)
155
+
156
+ @http.use_ssl = @use_ssl
157
+ end
158
+
159
+ def connection
160
+ @http
161
+ end
162
+
163
+ # Make the connection to AWS passing in our request.
164
+ # allow us to have a one line call in each method which will do all of the work
165
+ # in making the actual request to AWS.
166
+ def request(action, params = {})
167
+ @action = action
168
+ # Use a copy so that we don't modify the caller's Hash, remove any keys that have nil or empty values
169
+ params = params.reject { |_, value| value.nil? or value.empty?}
170
+
171
+ @action_time = Time.now.getutc
172
+
173
+ params.merge!( {"Action" => action,
174
+ "SignatureVersion" => signature_version.to_s,
175
+ "SignatureMethod" => 'HmacSHA256',
176
+ "AWSAccessKeyId" => @access_key_id,
177
+ "Version" => API_VERSION,
178
+ "Timestamp" => action_time.iso8601 } )
179
+
180
+ @query = params.sort.collect do |param|
181
+ CGI::escape(param[0]) + "=" + CGI::escape(param[1])
182
+ end.join("&")
183
+ response = connection.post(@path, query, get_req_headers)
184
+
185
+ response_class = AWS::SES.const_get( "#{action}Response" )
186
+ result = response_class.new(action, response)
187
+
188
+ if result.error?
189
+ raise ResponseError.new(result)
190
+ end
191
+
192
+ result
193
+ end
194
+
195
+ def get_req_headers
196
+ headers = {}
197
+ if signature_version == 4
198
+ headers['host'] = @server
199
+ headers['authorization'] = get_aws_auth_header_v4
200
+ headers['x-amz-date'] = amzdate
201
+ headers['user-agent'] = @user_agent
202
+ else
203
+ headers['x-amzn-authorization'] = get_aws_auth_header_v2
204
+ headers['date'] = action_time.httpdate
205
+ headers['user-agent'] = @user_agent
206
+ end
207
+ headers
208
+ end
209
+
210
+ # Set the Authorization header using AWS signed header authentication
211
+ def get_aws_auth_header_v2
212
+ encoded_canonical = SES.encode(@secret_access_key, httpdate, false)
213
+ SES.authorization_header(@access_key_id, 'HmacSHA256', encoded_canonical)
214
+ end
215
+
216
+ def get_aws_auth_header_v4
217
+ SES.authorization_header_v4(sig_v4_auth_credential, sig_v4_auth_signed_headers, sig_v4_auth_signature)
218
+ end
219
+
220
+ private
221
+
222
+ def sig_v4_auth_credential
223
+ @access_key_id + '/' + credential_scope
224
+ end
225
+
226
+ def sig_v4_auth_signed_headers
227
+ 'host;x-amz-date'
228
+ end
229
+
230
+ def credential_scope
231
+ datestamp + '/' + region + '/' + SERVICE + '/' + 'aws4_request'
232
+ end
233
+
234
+ def string_to_sign
235
+ "AWS4-HMAC-SHA256\n" + amzdate + "\n" + credential_scope + "\n" + Digest::SHA256.hexdigest(canonical_request.encode('utf-8').b)
236
+ end
237
+
238
+ def amzdate
239
+ @action_time ||= Time.now.getutc
240
+ action_time.strftime('%Y%m%dT%H%M%SZ')
241
+ end
242
+
243
+ def datestamp
244
+ @action_time ||= Time.now.getutc
245
+ action_time.strftime('%Y%m%d')
246
+ end
247
+
248
+ def httpdate
249
+ @action_time ||= Time.now.getutc.httpdate
250
+ end
251
+
252
+ def canonical_request
253
+ "POST" + "\n" + "/" + "\n" + canonical_querystring + "\n" + canonical_headers + "\n" + sig_v4_auth_signed_headers + "\n" + payload_hash
254
+ end
255
+
256
+ def canonical_querystring
257
+ signature_version == 2 ? "Action=#{action}&Version=2013-10-15" : ''
258
+ end
259
+
260
+ def canonical_headers
261
+ 'host:' + server + "\n" + 'x-amz-date:' + amzdate + "\n"
262
+ end
263
+
264
+ def payload_hash
265
+ Digest::SHA256.hexdigest(query.to_s.encode('utf-8'))
266
+ end
267
+
268
+ def sig_v4_auth_signature
269
+ OpenSSL::HMAC.hexdigest("SHA256", getSignatureKey, string_to_sign.encode('utf-8'))
270
+ end
271
+
272
+ def getSignatureKey
273
+ kDate = sign(('AWS4' + @secret_access_key).encode('utf-8'), datestamp)
274
+ kRegion = sign(kDate, region)
275
+ kService = sign(kRegion, SERVICE)
276
+ kSigning = sign(kService, 'aws4_request')
277
+
278
+ kSigning
279
+ end
280
+
281
+ def sign(key, msg)
282
+ OpenSSL::HMAC.digest("SHA256", key, msg.encode('utf-8'))
283
+ end
284
+ end # class Base
285
+ end # Module SES
286
+ end # Module AWS