ey-hmac 2.3.0 → 2.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 780a6fcc90418a0389166a31e520337dced06873038bb0a9e71a6f3b70996835
4
- data.tar.gz: 073f26a6c11bd06036e23a49b50e8ffea15ca70c608d05e0e7be1a0b7547cda7
3
+ metadata.gz: 1ea57d603aa108a1bf510fcf6e61aa45b1d1d0fca0ba7aa1b97681396fae9a7c
4
+ data.tar.gz: 7ad6caf49126be2b2a4b75acbc97ecf9d1898307a896f909493850be26d8261e
5
5
  SHA512:
6
- metadata.gz: f0b5f2b1827e7a180b35ffa2d3b2e2918a470d008735dbb5b5b79430ac2201c7d58a5abcf124f5ee69119ab83b2b882f9802b70e0f00208df130f9974fc82e2f
7
- data.tar.gz: 8d5b959e371034a6a436f7bfec4602e4e477a9830b6d67a6fc2e23378d5002fb2fbbd3714a635b8efe579bcf4f07316176be42ae90985317a86f72e2d7c57a15
6
+ metadata.gz: 6c790db93637c36fe752759cdb1aac1a70d3f1378ca5c2ae6b4132b8115d97b5e75b15e75de7dc069800980846d83524dbbe58903d9fd4b2006499e0ff8f9826
7
+ data.tar.gz: 6d8745e27b9c8d01d01d9f6bc608eba2f6fd6f8476a28eba9097dda9765944b8d5ba535be0901f151e69f4b66b9e1a2d022bdd29cbc38fc68a3ec9cacfaf2500
@@ -0,0 +1,70 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL"
13
+
14
+ on:
15
+ push:
16
+ branches: [ master ]
17
+ pull_request:
18
+ # The branches below must be a subset of the branches above
19
+ branches: [ master ]
20
+ schedule:
21
+ - cron: '36 16 * * 0'
22
+
23
+ jobs:
24
+ analyze:
25
+ name: Analyze
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ actions: read
29
+ contents: read
30
+ security-events: write
31
+
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ language: [ 'ruby' ]
36
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37
+ # Learn more about CodeQL language support at https://git.io/codeql-language-support
38
+
39
+ steps:
40
+ - name: Checkout repository
41
+ uses: actions/checkout@v2
42
+
43
+ # Initializes the CodeQL tools for scanning.
44
+ - name: Initialize CodeQL
45
+ uses: github/codeql-action/init@v1
46
+ with:
47
+ languages: ${{ matrix.language }}
48
+ # If you wish to specify custom queries, you can do so here or in a config file.
49
+ # By default, queries listed here will override any specified in a config file.
50
+ # Prefix the list here with "+" to use these queries and those in the config file.
51
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
52
+
53
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54
+ # If this step fails, then you should remove it and run the build manually (see below)
55
+ - name: Autobuild
56
+ uses: github/codeql-action/autobuild@v1
57
+
58
+ # ℹ️ Command-line programs to run using the OS shell.
59
+ # 📚 https://git.io/JvXDl
60
+
61
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62
+ # and modify them (or add more) to build your code if your project
63
+ # uses a compiled language
64
+
65
+ #- run: |
66
+ # make bootstrap
67
+ # make release
68
+
69
+ - name: Perform CodeQL Analysis
70
+ uses: github/codeql-action/analyze@v1
@@ -31,5 +31,7 @@ jobs:
31
31
  with:
32
32
  ruby-version: ${{ matrix.ruby-version }}
33
33
  bundler-cache: true # runs 'bundle install' and caches installed gems automatically
34
+ - name: Lint
35
+ run: bundle exec rubocop -D
34
36
  - name: Run tests
35
37
  run: bundle exec rspec
data/.rubocop.yml ADDED
@@ -0,0 +1,31 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ # The behavior of RuboCop can be controlled via the .rubocop.yml
4
+ # configuration file. It makes it possible to enable/disable
5
+ # certain cops (checks) and to alter their behavior if they accept
6
+ # any parameters. The file can be placed either in your home
7
+ # directory or in some project directory.
8
+ #
9
+ # RuboCop will start looking for the configuration file in the directory
10
+ # where the inspected file is and continue its way up to the root directory.
11
+ #
12
+ # See https://docs.rubocop.org/rubocop/configuration
13
+ AllCops:
14
+ SuggestExtensions: false
15
+ NewCops: 'disable'
16
+ require:
17
+ - rubocop-rspec
18
+ Metrics/BlockLength:
19
+ Enabled: false
20
+ Metrics/AbcSize:
21
+ Enabled: false
22
+ Style/ClassAndModuleChildren:
23
+ EnforcedStyle: compact
24
+ Metrics/MethodLength:
25
+ Enabled: false
26
+ Metrics/ClassLength:
27
+ Enabled: false
28
+ RSpec/ExampleLength:
29
+ Enabled: false
30
+ RSpec/MultipleExpectations:
31
+ Enabled: false
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,82 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config --auto-gen-only-exclude`
3
+ # on 2022-02-08 03:20:19 UTC using RuboCop version 1.25.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Configuration parameters: Include.
11
+ # Include: **/*.gemspec
12
+ Gemspec/RequiredRubyVersion:
13
+ Exclude:
14
+ - 'ey-hmac.gemspec'
15
+
16
+ # Offense count: 1
17
+ # Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms.
18
+ # CheckDefinitionPathHierarchyRoots: lib, spec, test, src
19
+ # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
20
+ Naming/FileName:
21
+ Exclude:
22
+ - 'lib/ey-hmac.rb'
23
+
24
+ # Offense count: 2
25
+ # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
26
+ # AllowedNames: at, by, db, id, in, io, ip, of, on, os, pp, to
27
+ Naming/MethodParameterName:
28
+ Exclude:
29
+ - 'lib/ey-hmac/adapter.rb'
30
+
31
+ # Offense count: 2
32
+ RSpec/BeforeAfterAll:
33
+ Exclude:
34
+ - 'spec/spec_helper.rb'
35
+ - 'spec/rails_helper.rb'
36
+ - 'spec/support/**/*.rb'
37
+ - 'spec/faraday_spec.rb'
38
+ - 'spec/rack_spec.rb'
39
+
40
+ # Offense count: 2
41
+ # Configuration parameters: IgnoredMetadata.
42
+ RSpec/DescribeClass:
43
+ Exclude:
44
+ - '**/spec/features/**/*'
45
+ - '**/spec/requests/**/*'
46
+ - '**/spec/routing/**/*'
47
+ - '**/spec/system/**/*'
48
+ - '**/spec/views/**/*'
49
+ - 'spec/faraday_spec.rb'
50
+ - 'spec/rack_spec.rb'
51
+
52
+ # Offense count: 1
53
+ RSpec/LetSetup:
54
+ Exclude:
55
+ - 'spec/faraday_spec.rb'
56
+
57
+ # Offense count: 4
58
+ # Configuration parameters: AllowedConstants.
59
+ Style/Documentation:
60
+ Exclude:
61
+ - 'spec/**/*'
62
+ - 'test/**/*'
63
+ - 'lib/ey-hmac.rb'
64
+ - 'lib/ey-hmac/adapter/faraday.rb'
65
+ - 'lib/ey-hmac/adapter/rack.rb'
66
+ - 'lib/ey-hmac/faraday.rb'
67
+
68
+ # Offense count: 3
69
+ # Configuration parameters: MinBodyLength.
70
+ Style/GuardClause:
71
+ Exclude:
72
+ - 'lib/ey-hmac/adapter.rb'
73
+ - 'lib/ey-hmac/adapter/faraday.rb'
74
+ - 'lib/ey-hmac/adapter/rack.rb'
75
+
76
+ # Offense count: 2
77
+ # Cop supports --auto-correct.
78
+ # Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
79
+ # URISchemes: http, https
80
+ Layout/LineLength:
81
+ Exclude:
82
+ - 'lib/ey-hmac/adapter.rb'
data/Gemfile CHANGED
@@ -5,6 +5,9 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in ey-hmac.gemspec
6
6
  gemspec
7
7
 
8
+ gem 'rubocop', require: false
9
+ gem 'rubocop-rspec', require: false
10
+
8
11
  group(:test) do
9
12
  gem 'pry-nav'
10
13
  gem 'rspec', '~> 3.3'
@@ -17,6 +20,6 @@ group(:rack) do
17
20
  end
18
21
 
19
22
  group(:faraday) do
20
- gem 'faraday', '~> 1.3'
23
+ gem 'faraday', '>= 1.3'
21
24
  gem 'faraday_middleware'
22
25
  end
data/Rakefile CHANGED
@@ -1 +1,3 @@
1
- require "bundler/gem_tasks"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Ey::Hmac::Adapter::Faraday < Ey::Hmac::Adapter
2
4
  def method
3
5
  request[:method].to_s.upcase
@@ -5,13 +7,13 @@ class Ey::Hmac::Adapter::Faraday < Ey::Hmac::Adapter
5
7
 
6
8
  def content_type
7
9
  @content_type ||= find_header(
8
- *%w[CONTENT-TYPE CONTENT_TYPE Content-Type Content_Type]
10
+ 'CONTENT-TYPE', 'CONTENT_TYPE', 'Content-Type', 'Content_Type'
9
11
  )
10
12
  end
11
13
 
12
14
  def content_digest
13
15
  @content_digest ||= find_header(
14
- *%w[CONTENT-DIGEST CONTENT_DIGEST Content-Digest Content_Digest]
16
+ 'CONTENT-DIGEST', 'CONTENT_DIGEST', 'Content-Digest', 'Content_Digest'
15
17
  )
16
18
  end
17
19
 
@@ -25,25 +27,21 @@ class Ey::Hmac::Adapter::Faraday < Ey::Hmac::Adapter
25
27
  body.to_s
26
28
  end
27
29
 
28
- if digestable && digestable != ""
30
+ if digestable && digestable != ''
29
31
  @content_digest = request[:request_headers]['Content-Digest'] = Digest::MD5.hexdigest(digestable)
30
32
  end
31
33
  end
32
34
 
33
35
  def body
34
- if request[:body] && request[:body].to_s != ""
35
- request[:body]
36
- end
36
+ request[:body] if request[:body] && request[:body].to_s != ''
37
37
  end
38
38
 
39
39
  def date
40
- find_header(*%w[DATE Date])
40
+ find_header('DATE', 'Date')
41
41
  end
42
42
 
43
43
  def set_date
44
- unless date
45
- request[:request_headers]['Date'] = Time.now.httpdate
46
- end
44
+ request[:request_headers]['Date'] = Time.now.httpdate unless date
47
45
  end
48
46
 
49
47
  def path
@@ -54,15 +52,13 @@ class Ey::Hmac::Adapter::Faraday < Ey::Hmac::Adapter
54
52
  set_content_digest
55
53
  set_date
56
54
 
57
- if options[:version]
58
- request[:request_headers]['X-Signature-Version'] = options[:version]
59
- end
55
+ request[:request_headers]['X-Signature-Version'] = options[:version] if options[:version]
60
56
 
61
57
  request[:request_headers][authorization_header] = authorization(key_id, key_secret)
62
58
  end
63
59
 
64
60
  def authorization_signature
65
- find_header(*%w[Authorization AUTHORIZATION])
61
+ find_header('Authorization', 'AUTHORIZATION')
66
62
  end
67
63
 
68
64
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack'
2
4
 
3
5
  class Ey::Hmac::Adapter::Rack < Ey::Hmac::Adapter
@@ -19,18 +21,15 @@ class Ey::Hmac::Adapter::Rack < Ey::Hmac::Adapter
19
21
  end
20
22
 
21
23
  def set_content_digest
22
- if body
23
- request.env['HTTP_CONTENT_DIGEST'] = Digest::MD5.hexdigest(body)
24
- end
24
+ request.env['HTTP_CONTENT_DIGEST'] = Digest::MD5.hexdigest(body) if body
25
25
  end
26
26
 
27
27
  def body
28
- if request.env["rack.input"]
29
- request.env["rack.input"].rewind
30
- body = request.env["rack.input"].read
31
- request.env["rack.input"].rewind
32
- body == "" ? nil : body
33
- else nil
28
+ if request.env['rack.input']
29
+ request.env['rack.input'].rewind
30
+ body = request.env['rack.input'].read
31
+ request.env['rack.input'].rewind
32
+ body == '' ? nil : body
34
33
  end
35
34
  end
36
35
 
@@ -50,9 +49,7 @@ class Ey::Hmac::Adapter::Rack < Ey::Hmac::Adapter
50
49
  set_date
51
50
  set_content_digest
52
51
 
53
- if options[:version]
54
- request.env['HTTP_X_SIGNATURE_VERSION'] = options[:version]
55
- end
52
+ request.env['HTTP_X_SIGNATURE_VERSION'] = options[:version] if options[:version]
56
53
 
57
54
  request.env["HTTP_#{authorization_header.to_s.upcase}"] = authorization(key_id, key_secret)
58
55
  end
@@ -1,12 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This class is responsible for forming the canonical string to used to sign requests
2
4
  # @abstract override methods {#method}, {#path}, {#body}, {#content_type} and {#content_digest}
3
5
  class Ey::Hmac::Adapter
4
- AUTHORIZATION_REGEXP = /\w+ ([^:]+):(.+)$/
6
+ AUTHORIZATION_REGEXP = /\w+ ([^:]+):(.+)$/.freeze
7
+ DEFAULT_CANONICALIZE_WITH = %i[method content_type content_digest date path].freeze
5
8
 
6
- autoload :Rack, "ey-hmac/adapter/rack"
7
- autoload :Faraday, "ey-hmac/adapter/faraday"
9
+ autoload :Rack, 'ey-hmac/adapter/rack'
10
+ autoload :Faraday, 'ey-hmac/adapter/faraday'
8
11
 
9
- attr_reader :request, :options, :authorization_header, :service, :sign_with, :accept_digests
12
+ attr_reader :request,
13
+ :options,
14
+ :authorization_header,
15
+ :service,
16
+ :sign_with,
17
+ :accept_digests,
18
+ :include_query_string,
19
+ :canonicalize_with
10
20
 
11
21
  # @param [Object] request signer-specific request implementation
12
22
  # @option options [Integer] :version signature version
@@ -14,15 +24,21 @@ class Ey::Hmac::Adapter
14
24
  # @option options [String] :authorization_header ('Authorization') Authorization header key.
15
25
  # @option options [String] :server ('EyHmac') service name prefixed to {#authorization}. set to {#service}
16
26
  # @option options [Symbol] :sign_with (:sha_256) outgoing signature digest algorithm. See {OpenSSL::Digest#new}
27
+ # @option options [Symbol] :include_query_string (false) canonicalize with the request query string.
17
28
  # @option options [Array] :accepted_digests ([:sha_256]) accepted incoming signature digest algorithm. See {OpenSSL::Digest#new}
18
- def initialize(request, options={})
19
- @request, @options = request, options
29
+ def initialize(request, options = {})
30
+ @request = request
31
+ @options = options
20
32
 
21
33
  @ttl = options[:ttl]
22
34
  @authorization_header = options[:authorization_header] || 'Authorization'
23
35
  @service = options[:service] || 'EyHmac'
24
36
  @sign_with = options[:sign_with] || :sha256
25
- @accept_digests = Array(options[:accept_digests] || :sha256)
37
+ @include_query_string = options.fetch(:include_query_string, false)
38
+ @accept_digests = Array(options[:accept_digests] || :sha256)
39
+
40
+ @canonicalize_with = DEFAULT_CANONICALIZE_WITH
41
+ @canonicalize_with += :query_string if include_query_string
26
42
  end
27
43
 
28
44
  # In order for the server to correctly authorize the request, the client and server MUST AGREE on this format
@@ -30,16 +46,18 @@ class Ey::Hmac::Adapter
30
46
  # default canonical string formation is '{#method}\\n{#content_type}\\n{#content_digest}\\n{#date}\\n{#path}'
31
47
  # @return [String] canonical string used to form the {#signature}
32
48
  def canonicalize
33
- [method, content_type, content_digest, date, path].join("\n")
49
+ canonicalize_with.map { |message| public_send(message) }.join("\n")
34
50
  end
35
51
 
36
52
  # @param [String] key_secret private HMAC key
37
53
  # @param [String] signature digest hash function. Defaults to #sign_with
38
54
  # @return [String] HMAC signature of {#request}
39
- def signature(key_secret, digest = self.sign_with)
55
+ def signature(key_secret, digest = sign_with)
40
56
  Base64.strict_encode64(
41
57
  OpenSSL::HMAC.digest(
42
- OpenSSL::Digest.new(digest.to_s), key_secret, canonicalize)).strip
58
+ OpenSSL::Digest.new(digest.to_s), key_secret, canonicalize
59
+ )
60
+ ).strip
43
61
  end
44
62
 
45
63
  # @param [String] key_id public HMAC key
@@ -106,7 +124,7 @@ class Ey::Hmac::Adapter
106
124
  # @yieldparam key_id [String] public HMAC key
107
125
  # @return [Boolean] true if block yields matching private key and signature matches, else false
108
126
  # @see #authenticated!
109
- def authenticated?(options={}, &block)
127
+ def authenticated?(_options = {}, &block)
110
128
  authenticated!(&block)
111
129
  rescue Ey::Hmac::Error
112
130
  false
@@ -119,18 +137,18 @@ class Ey::Hmac::Adapter
119
137
 
120
138
  unless key_secret
121
139
  raise Ey::Hmac::MissingSecret,
122
- "Failed to find secret matching #{key_id.inspect}"
140
+ "Failed to find secret matching #{key_id.inspect}"
123
141
  end
124
142
 
125
143
  check_ttl!
126
144
 
127
- calculated_signatures = accept_digests.map { |ad| signature(key_secret, ad) }
128
- matching_signature = calculated_signatures.any? { |cs| secure_compare(signature_value, cs) }
145
+ matching_signature =
146
+ accept_digests
147
+ .lazy
148
+ .map { |ad| signature(key_secret, ad) }
149
+ .any? { |cs| secure_compare(signature_value, cs) }
129
150
 
130
- unless matching_signature
131
- raise Ey::Hmac::SignatureMismatch,
132
- "Calculated signature #{signature_value} does not match #{calculated_signatures.inspect} using #{canonicalize.inspect}"
133
- end
151
+ raise Ey::Hmac::SignatureMismatch unless matching_signature
134
152
 
135
153
  true
136
154
  end
@@ -143,11 +161,12 @@ class Ey::Hmac::Adapter
143
161
  def secure_compare(a, b)
144
162
  return false unless a.bytesize == b.bytesize
145
163
 
146
- l = a.unpack("C*")
164
+ l = a.unpack('C*')
147
165
 
148
- r, i = 0, -1
149
- b.each_byte { |v| r |= v ^ l[i+=1] }
150
- r == 0
166
+ r = 0
167
+ i = -1
168
+ b.each_byte { |v| r |= v ^ l[i += 1] }
169
+ r.zero?
151
170
  end
152
171
 
153
172
  def check_ttl!
@@ -157,7 +176,7 @@ class Ey::Hmac::Adapter
157
176
 
158
177
  unless expiry > current_time
159
178
  raise Ey::Hmac::ExpiredHmac,
160
- "Signature has expired passed #{expiry}. Current time is #{current_time}"
179
+ "Signature has expired passed #{expiry}. Current time is #{current_time}"
161
180
  end
162
181
  end
163
182
  end
@@ -167,7 +186,7 @@ class Ey::Hmac::Adapter
167
186
 
168
187
  unless authorization_match
169
188
  raise Ey::Hmac::MissingAuthorization,
170
- "Failed to parse authorization_signature #{authorization_signature}"
189
+ "Failed to parse authorization_signature #{authorization_signature}"
171
190
  end
172
191
 
173
192
  [authorization_match[1], authorization_match[2]]
@@ -1,21 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ey-hmac'
2
4
  require 'faraday'
3
5
 
4
- class Ey::Hmac::Faraday < Faraday::Response::Middleware
5
- dependency("ey-hmac")
6
+ class Ey::Hmac::Faraday < Faraday::Middleware
7
+ dependency('ey-hmac') if respond_to?(:dependency)
6
8
 
7
9
  attr_reader :key_id, :key_secret, :options
8
10
 
9
11
  def initialize(app, key_id, key_secret, options = {})
10
12
  super(app)
11
- @key_id, @key_secret = key_id, key_secret
13
+ @key_id = key_id
14
+ @key_secret = key_secret
12
15
  @options = options
13
16
  end
14
17
 
15
18
  def call(env)
16
- Ey::Hmac.sign!(env, key_id, key_secret, {adapter: Ey::Hmac::Adapter::Faraday}.merge(options))
19
+ Ey::Hmac.sign!(env, key_id, key_secret, { adapter: Ey::Hmac::Adapter::Faraday }.merge(options))
17
20
  @app.call(env)
18
21
  end
19
22
  end
20
23
 
21
- Faraday::Middleware.register_middleware :hmac => Ey::Hmac::Faraday
24
+ Faraday::Middleware.register_middleware hmac: Ey::Hmac::Faraday
data/lib/ey-hmac/rack.rb CHANGED
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Request middleware that performs HMAC request signing
2
4
  class Ey::Hmac::Rack
3
5
  attr_reader :key_id, :key_secret, :options
4
6
 
5
7
  def initialize(app, key_id, key_secret, options = {})
6
8
  @app = app
7
- @key_id, @key_secret = key_id, key_secret
9
+ @key_id = key_id
10
+ @key_secret = key_secret
8
11
  @options = options
9
12
  end
10
13
 
@@ -1,5 +1,7 @@
1
- module Ey
1
+ # frozen_string_literal: true
2
+
3
+ module Ey # rubocop:disable Style/ClassAndModuleChildren
2
4
  module Hmac
3
- VERSION = "2.3.0"
5
+ VERSION = '2.4.0'
4
6
  end
5
7
  end