argon2id 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d82db47aaab5dae5c5123f1a7e333256d736be27a93ba3817b9b569501d5ef1
4
- data.tar.gz: 0dad93f3f34a8c3bc592b5b839aeecdf4975cf42c93f6690cfdb5bbb033be2b4
3
+ metadata.gz: 76d76a4abc46d90ed4599f134b0bdf29c308d3d580181ba7dfab8f3ba12d7689
4
+ data.tar.gz: 346056ab708ca4bc0d371875a32dbae0cf2d21c28e3d5049538f02024344e08e
5
5
  SHA512:
6
- metadata.gz: 3fb64986c811e4fa6f510d73475184305f23a233e6f9486cbd10e1bdd8d8f24e59b755fb71cd67eb4edadb22122d0313c815f789e6d86cd14b5e99ab57e0ef6d
7
- data.tar.gz: b0b406c29495e2c8f5b79bc6ffe30cfbae4fc4bb889005159b07f7e148d09f20d0ad88044e699f0b0553b40849d5abf702f576d2e496e856126ad461f306e0f9
6
+ metadata.gz: 9a2c086660509a67f697fe74262bcbb0c4e2910d731269994556eed179b712b4d23de7506deb42c336376ee9c0a943701cfded584e51f0f7199486f67f4aa132
7
+ data.tar.gz: d108365e5d029efe1fb02e3d3588961bcbb8c263ea027176f4963c9bfe7d4ecb1176948135603d72407982242ae24d90fdf41710e9780765f9ff6d822d93296c
data/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.1] - 2024-11-01
9
+
10
+ ### Added
11
+
12
+ - RDoc documentation for the API
13
+
14
+ ### Fixed
15
+
16
+ - Saved a superfluous extra byte when allocating the buffer for the encoded
17
+ hash
18
+
8
19
  ## [0.1.0] - 2024-10-31
9
20
 
10
21
  ### Added
@@ -13,4 +24,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
13
24
  reference C implementation of Argon2, the password-hashing function that won
14
25
  the Password Hashing Competition.
15
26
 
27
+ [0.1.1]: https://github.com/mudge/argon2id/releases/tag/v0.1.1
16
28
  [0.1.0]: https://github.com/mudge/argon2id/releases/tag/v0.1.0
data/README.md CHANGED
@@ -1,18 +1,22 @@
1
- # Argon2id - Ruby bindings to the award-winning password-hashing function
1
+ # Argon2id - Ruby bindings to the OWASP recommended password-hashing function
2
2
 
3
3
  Ruby bindings to the reference C implementation of [Argon2][], the password-hashing
4
4
  function that won the 2015 [Password Hashing Competition][].
5
5
 
6
6
  [![Build Status](https://github.com/mudge/argon2id/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/mudge/argon2id/actions)
7
7
 
8
- **Current version:** 0.1.0
8
+ **Current version:** 0.1.1
9
9
  **Bundled Argon2 version:** libargon2.1 (20190702)
10
10
 
11
11
  ```ruby
12
12
  Argon2::Password.create("opensesame").to_s
13
13
  #=> "$argon2id$v=19$m=19456,t=2,p=1$ZS2nBFWBpnt28HjtzNOW4w$SQ+p+dIcWbpzWpZQ/ZZFj8IQkyhYZf127U4QdkRmKFU"
14
- Argon2::Password.create("opensesame") == "opensesame" #=> true
15
- Argon2::Password.create("opensesame") == "notopensesame" #=> false
14
+
15
+ Argon2::Password.create("opensesame") == "opensesame"
16
+ #=> true
17
+
18
+ Argon2::Password.new("$argon2id$v=19$m=19456,t=2,p=1$ZS2nBFWBpnt28HjtzNOW4w$SQ+p+dIcWbpzWpZQ/ZZFj8IQkyhYZf127U4QdkRmKFU") == "opensesame"
19
+ #=> true
16
20
  ```
17
21
 
18
22
  ## Table of contents
@@ -156,6 +160,17 @@ This gem requires the following to run:
156
160
 
157
161
  ### Native gems
158
162
 
163
+ Where possible, a pre-compiled native gem will be provided for the following platforms:
164
+
165
+ * Linux
166
+ * `aarch64-linux` and `arm-linux` (requires [glibc](https://www.gnu.org/software/libc/) 2.29+)
167
+ * `x86-linux` and `x86_64-linux` (requires [glibc](https://www.gnu.org/software/libc/) 2.17+)
168
+ * [musl](https://musl.libc.org/)-based systems such as [Alpine](https://alpinelinux.org) are supported as long as a [glibc-compatible library is installed](https://wiki.alpinelinux.org/wiki/Running_glibc_programs)
169
+ * macOS `x86_64-darwin` and `arm64-darwin`
170
+ * Windows `x64-mingw32` and `x64-mingw-ucrt`
171
+
172
+ ### Verifying the gems
173
+
159
174
  SHA256 checksums are included in the [release
160
175
  notes](https://github.com/mudge/argon2id/releases) for each version and can be
161
176
  checked with `sha256sum`, e.g.
@@ -165,7 +180,7 @@ $ gem fetch argon2id -v 0.1.0
165
180
  Fetching argon2id-0.1.0-arm64-darwin.gem
166
181
  Downloaded argon2id-0.1.0-arm64-darwin
167
182
  $ sha256sum argon2id-0.1.0-arm64-darwin.gem
168
- e71e4acaa5cae6ca763bc078bde121fb76ea07bad72bc471c9efd2ba444be604 argon2id-0.1.0-arm64-darwin.gem
183
+ 652ba4ebe4176c3fa944652b5db3bee52670c1e6b76632f921dd1455ec0810aa argon2id-0.1.0-arm64-darwin.gem
169
184
  ```
170
185
 
171
186
  [GPG](https://www.gnupg.org/) signatures are attached to each release (the
@@ -176,7 +191,7 @@ from a public keyserver, e.g. `gpg --keyserver keyserver.ubuntu.com --recv-key
176
191
 
177
192
  ```console
178
193
  $ gpg --verify argon2id-0.1.0-arm64-darwin.gem.sig argon2id-0.1.0-arm64-darwin.gem
179
- gpg: Signature made Thu 31 Oct 11:16:18 2024 BST
194
+ gpg: Signature made Thu 31 Oct 16:09:45 2024 GMT
180
195
  gpg: using RSA key 702609D9C790F45B577D7BEC39AC3530070E0F75
181
196
  gpg: Good signature from "Paul Mucur <mudge@mudge.name>" [unknown]
182
197
  gpg: aka "Paul Mucur <paul@ghostcassette.com>" [unknown]
@@ -229,7 +244,7 @@ Issues](https://github.com/mudge/argon2id/issues).
229
244
 
230
245
  ## License
231
246
 
232
- This library is licensed under the BSD 3-Clause License, see `LICENSE.txt`.
247
+ This library is licensed under the BSD 3-Clause License, see `LICENSE`.
233
248
 
234
249
  Copyright © 2024, Paul Mucur.
235
250
 
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  require "rake/extensiontask"
2
2
  require "rake_compiler_dock"
3
- require "rake/testtask"
3
+ require "minitest/test_task"
4
4
 
5
5
  CLEAN.add("lib/**/*.{o,so,bundle}", "pkg")
6
6
 
@@ -27,9 +27,7 @@ Rake::ExtensionTask.new("argon2id", gemspec) do |e|
27
27
  e.cross_platform = cross_platforms
28
28
  end
29
29
 
30
- Rake::TestTask.new do |t|
31
- t.warning = true
32
- end
30
+ Minitest::TestTask.create
33
31
 
34
32
  begin
35
33
  require "ruby_memcheck"
data/argon2id.gemspec CHANGED
@@ -51,6 +51,7 @@ Gem::Specification.new do |s|
51
51
  "test/test_password.rb",
52
52
  "test/test_verify.rb"
53
53
  ]
54
+ s.rdoc_options = ["--main", "README.md"]
54
55
 
55
56
  s.add_development_dependency("rake-compiler", "~> 1.2")
56
57
  s.add_development_dependency("rake-compiler-dock", "~> 1.5")
@@ -7,6 +7,17 @@
7
7
 
8
8
  VALUE mArgon2id, cArgon2idError;
9
9
 
10
+ /* call-seq: Argon2id.hash_encode(t_cost, m_cost, parallelism, pwd, salt, output_len)
11
+ *
12
+ * Hashes a password with Argon2id, producing an encoded hash.
13
+ *
14
+ * - +t_cost+: number of iterations
15
+ * - +m_cost+: sets memory usage to +m_cost+ kibibytes
16
+ * - +parallelism+: number of threads and compute lanes
17
+ * - +pwd+: the password
18
+ * - +salt+: the salt
19
+ * - +output_len+: desired length of the hash in bytes
20
+ */
10
21
  static VALUE
11
22
  rb_argon2id_hash_encoded(VALUE module, VALUE iterations, VALUE memory, VALUE threads, VALUE pwd, VALUE salt, VALUE hashlen)
12
23
  {
@@ -24,7 +35,7 @@ rb_argon2id_hash_encoded(VALUE module, VALUE iterations, VALUE memory, VALUE thr
24
35
  outlen = FIX2INT(hashlen);
25
36
 
26
37
  encodedlen = argon2_encodedlen(t_cost, m_cost, parallelism, (uint32_t)RSTRING_LEN(salt), (uint32_t)outlen, Argon2_id);
27
- encoded = malloc(encodedlen + 1);
38
+ encoded = malloc(encodedlen);
28
39
  if (!encoded) {
29
40
  rb_raise(rb_eNoMemError, "not enough memory to allocate for encoded password");
30
41
  }
@@ -36,12 +47,16 @@ rb_argon2id_hash_encoded(VALUE module, VALUE iterations, VALUE memory, VALUE thr
36
47
  rb_raise(cArgon2idError, "%s", argon2_error_message(result));
37
48
  }
38
49
 
39
- hash = rb_str_new(encoded, strlen(encoded));
50
+ hash = rb_str_new_cstr(encoded);
40
51
  free(encoded);
41
52
 
42
53
  return hash;
43
54
  }
44
55
 
56
+ /* call-seq: Argon2id.verify(encoded, pwd)
57
+ *
58
+ * Verifies a password against an encoded string.
59
+ */
45
60
  static VALUE
46
61
  rb_argon2id_verify(VALUE module, VALUE encoded, VALUE pwd) {
47
62
  int result;
@@ -3,9 +3,29 @@
3
3
  require "openssl"
4
4
 
5
5
  module Argon2id
6
+ # The Password class encapsulates an encoded Argon2id password hash.
7
+ #
8
+ # To hash a plain text password, use Argon2id::Password.create:
9
+ #
10
+ # password = Argon2id::Password.create("password")
11
+ # password.to_s
12
+ # #=> "$argon2id$v=19$m=19456,t=2,p=1$+Lrjry9Ifq0poLr15OGU1Q$utkDvejJB0ugwm4s9+a+vF6+1a/W+Y3CYa5Wte/85ig"
13
+ #
14
+ # To verify an encoded Argon2id password hash, use Argon2id::Password.new:
15
+ #
16
+ # password = Argon2id::Password.new("$argon2id$v=19$m=19456,t=2,p=1$+Lrjry9Ifq0poLr15OGU1Q$utkDvejJB0ugwm4s9+a+vF6+1a/W+Y3CYa5Wte/85ig")
17
+ # password == "password"
18
+ # #=> true
6
19
  class Password
7
20
  attr_reader :encoded
8
21
 
22
+ # Create a new Password object that hashes a given plain text password.
23
+ #
24
+ # - +:t_cost+: integer (default 2) the "time cost" given as a number of iterations
25
+ # - +:m_cost+: integer (default 19456) the "memory cost" given in kibibytes
26
+ # - +:parallelism+: integer (default 1) the number of threads and compute lanes to use
27
+ # - +:salt_len+: integer (default 16) the salt size in bytes
28
+ # - +:output_len+: integer (default 32) the desired length of the hash in bytes
9
29
  def self.create(pwd, t_cost: Argon2id.t_cost, m_cost: Argon2id.m_cost, parallelism: Argon2id.parallelism, salt_len: Argon2id.salt_len, output_len: Argon2id.output_len)
10
30
  new(
11
31
  Argon2id.hash_encoded(
@@ -19,14 +39,20 @@ module Argon2id
19
39
  )
20
40
  end
21
41
 
42
+ # call-seq: Argon2id::Password.new(encoded)
43
+ #
44
+ # Create a new Password with the given encoded password hash.
22
45
  def initialize(encoded)
23
46
  @encoded = encoded
24
47
  end
25
48
 
49
+ # Return the encoded password hash.
26
50
  def to_s
27
51
  encoded
28
52
  end
29
53
 
54
+ # Compare the password with given plain text, returning true if it verifies
55
+ # successfully.
30
56
  def ==(other)
31
57
  Argon2id.verify(encoded, String(other))
32
58
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Argon2id
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/argon2id.rb CHANGED
@@ -24,6 +24,19 @@ module Argon2id
24
24
  @output_len = DEFAULT_OUTPUT_LEN
25
25
 
26
26
  class << self
27
- attr_accessor :t_cost, :m_cost, :parallelism, :salt_len, :output_len
27
+ # The default number of iterations used by Argon2id::Password.create
28
+ attr_accessor :t_cost
29
+
30
+ # The default memory cost in kibibytes used by Argon2id::Password.create
31
+ attr_accessor :m_cost
32
+
33
+ # The default number of threads and compute lanes used by Argon2id::Password.create
34
+ attr_accessor :parallelism
35
+
36
+ # The default salt size in bytes used by Argon2id::Password.create
37
+ attr_accessor :salt_len
38
+
39
+ # The default desired length of the hash in bytes used by Argon2id::Password.create
40
+ attr_accessor :output_len
28
41
  end
29
42
  end
@@ -5,34 +5,28 @@ require "argon2id"
5
5
 
6
6
  class TestHashEncoded < Minitest::Test
7
7
  def test_valid_password_and_salt_encodes_successfully
8
- encoded = Argon2id.hash_encoded(2, 19456, 1, "opensesame", OpenSSL::Random.random_bytes(16), 32)
8
+ encoded = Argon2id.hash_encoded(2, 256, 1, "password", "somesalt", 32)
9
9
 
10
- assert encoded.start_with?("$argon2id$")
10
+ assert_equal "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4", encoded
11
11
  end
12
12
 
13
13
  def test_valid_password_does_not_include_trailing_null_byte
14
- encoded = Argon2id.hash_encoded(2, 19456, 1, "opensesame", OpenSSL::Random.random_bytes(16), 32)
14
+ encoded = Argon2id.hash_encoded(2, 256, 1, "password", "somesalt", 32)
15
15
 
16
16
  refute encoded.end_with?("\x00")
17
17
  end
18
18
 
19
19
  def test_raises_with_too_short_output
20
20
  error = assert_raises(Argon2id::Error) do
21
- Argon2id.hash_encoded(2, 19456, 1, "opensesame", OpenSSL::Random.random_bytes(16), 1)
21
+ Argon2id.hash_encoded(2, 256, 1, "password", "somesalt", 1)
22
22
  end
23
23
 
24
24
  assert_equal "Output is too short", error.message
25
25
  end
26
26
 
27
- def test_raises_with_too_large_output
28
- assert_raises(RangeError) do
29
- Argon2id.hash_encoded(2, 19456, 1, "opensesame", OpenSSL::Random.random_bytes(16), 4294967296)
30
- end
31
- end
32
-
33
27
  def test_raises_with_too_few_lanes
34
28
  error = assert_raises(Argon2id::Error) do
35
- Argon2id.hash_encoded(2, 19456, 0, "opensesame", OpenSSL::Random.random_bytes(16), 32)
29
+ Argon2id.hash_encoded(2, 256, 0, "password", "somesalt", 32)
36
30
  end
37
31
 
38
32
  assert_equal "Too few lanes", error.message
@@ -40,51 +34,25 @@ class TestHashEncoded < Minitest::Test
40
34
 
41
35
  def test_raises_with_too_small_memory_cost
42
36
  error = assert_raises(Argon2id::Error) do
43
- Argon2id.hash_encoded(2, 0, 1, "opensesame", OpenSSL::Random.random_bytes(16), 32)
37
+ Argon2id.hash_encoded(2, 0, 1, "password", "somesalt", 32)
44
38
  end
45
39
 
46
40
  assert_equal "Memory cost is too small", error.message
47
41
  end
48
42
 
49
- def test_raises_with_too_large_memory_cost
50
- assert_raises(RangeError) do
51
- Argon2id.hash_encoded(2, 4294967296, 1, "opensesame", OpenSSL::Random.random_bytes(16), 32)
52
- end
53
- end
54
-
55
43
  def test_raises_with_too_small_time_cost
56
44
  error = assert_raises(Argon2id::Error) do
57
- Argon2id.hash_encoded(0, 19456, 1, "opensesame", OpenSSL::Random.random_bytes(16), 32)
45
+ Argon2id.hash_encoded(0, 256, 1, "password", "somesalt", 32)
58
46
  end
59
47
 
60
48
  assert_equal "Time cost is too small", error.message
61
49
  end
62
50
 
63
- def test_raises_with_too_large_time_cost
64
- assert_raises(RangeError) do
65
- Argon2id.hash_encoded(4294967296, 19456, 1, "opensesame", OpenSSL::Random.random_bytes(16), 32)
66
- end
67
- end
68
-
69
- def test_raises_with_too_long_password
70
- error = assert_raises(Argon2id::Error) do
71
- Argon2id.hash_encoded(2, 19456, 1, "a" * 4294967296, OpenSSL::Random.random_bytes(16), 32)
72
- end
73
-
74
- assert_equal "Password is too long", error.message
75
- end
76
-
77
51
  def test_raises_with_too_short_salt
78
52
  error = assert_raises(Argon2id::Error) do
79
- Argon2id.hash_encoded(2, 19456, 1, "opensesame", OpenSSL::Random.random_bytes(1), 32)
53
+ Argon2id.hash_encoded(0, 256, 1, "password", "", 32)
80
54
  end
81
55
 
82
56
  assert_equal "Salt is too short", error.message
83
57
  end
84
-
85
- def test_raises_with_too_long_salt
86
- assert_raises(RangeError) do
87
- Argon2id.hash_encoded(2, 19456, 1, "opensesame", OpenSSL::Random.random_bytes(4294967296), 32)
88
- end
89
- end
90
58
  end
@@ -13,33 +13,33 @@ class TestPassword < Minitest::Test
13
13
  end
14
14
 
15
15
  def test_create_options_can_override_parameters
16
- password = Argon2id::Password.create("opensesame", t_cost: 3, m_cost: 12288)
16
+ password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256)
17
17
 
18
- assert password.to_s.include?("t=3")
19
- assert password.to_s.include?("m=12288")
18
+ assert password.to_s.include?("t=2")
19
+ assert password.to_s.include?("m=256")
20
20
  end
21
21
 
22
22
  def test_create_uses_argon2id_configuration
23
- Argon2id.parallelism = 4
24
- Argon2id.m_cost = 9216
23
+ Argon2id.t_cost = 2
24
+ Argon2id.m_cost = 256
25
25
 
26
26
  password = Argon2id::Password.create("opensesame")
27
27
 
28
- assert password.to_s.include?("p=4")
29
- assert password.to_s.include?("m=9216")
28
+ assert password.to_s.include?("t=2")
29
+ assert password.to_s.include?("m=256")
30
30
  ensure
31
- Argon2id.parallelism = Argon2id::DEFAULT_PARALLELISM
31
+ Argon2id.t_cost = Argon2id::DEFAULT_T_COST
32
32
  Argon2id.m_cost = Argon2id::DEFAULT_M_COST
33
33
  end
34
34
 
35
35
  def test_create_coerces_pwd_to_string
36
- password = Argon2id::Password.create(123)
36
+ password = Argon2id::Password.create(123, t_cost: 2, m_cost: 256)
37
37
 
38
38
  assert password.to_s.start_with?("$argon2id$")
39
39
  end
40
40
 
41
41
  def test_create_coerces_costs_to_integer
42
- password = Argon2id::Password.create("opensesame", t_cost: "5", m_cost: "7168", parallelism: "1", salt_len: "16", output_len: "32")
42
+ password = Argon2id::Password.create("opensesame", t_cost: "2", m_cost: "256", parallelism: "1", salt_len: "8", output_len: "32")
43
43
 
44
44
  assert password.to_s.start_with?("$argon2id$")
45
45
  end
@@ -51,25 +51,25 @@ class TestPassword < Minitest::Test
51
51
  end
52
52
 
53
53
  def test_equals_correct_password
54
- password = Argon2id::Password.create("opensesame")
54
+ password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256)
55
55
 
56
56
  assert password == "opensesame"
57
57
  end
58
58
 
59
59
  def test_does_not_equal_invalid_password
60
- password = Argon2id::Password.create("opensesame")
60
+ password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256)
61
61
 
62
62
  refute password == "notopensesame"
63
63
  end
64
64
 
65
65
  def test_is_password_returns_true_with_correct_password
66
- password = Argon2id::Password.create("opensesame")
66
+ password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256)
67
67
 
68
68
  assert password.is_password?("opensesame")
69
69
  end
70
70
 
71
71
  def test_is_password_returns_false_with_incorrect_password
72
- password = Argon2id::Password.create("opensesame")
72
+ password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256)
73
73
 
74
74
  refute password.is_password?("notopensesame")
75
75
  end
data/test/test_verify.rb CHANGED
@@ -5,13 +5,13 @@ require "argon2id"
5
5
 
6
6
  class TestVerify < Minitest::Test
7
7
  def test_returns_true_with_correct_password
8
- encoded = Argon2id.hash_encoded(2, 19456, 1, "opensesame", OpenSSL::Random.random_bytes(16), 32)
8
+ encoded = Argon2id.hash_encoded(2, 256, 1, "password", "somesalt", 32)
9
9
 
10
- assert Argon2id.verify(encoded, "opensesame")
10
+ assert Argon2id.verify(encoded, "password")
11
11
  end
12
12
 
13
13
  def test_returns_false_with_incorrect_password
14
- encoded = Argon2id.hash_encoded(2, 19456, 1, "opensesame", OpenSSL::Random.random_bytes(16), 32)
14
+ encoded = Argon2id.hash_encoded(2, 256, 1, "password", "somesalt", 32)
15
15
 
16
16
  refute Argon2id.verify(encoded, "notopensesame")
17
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: argon2id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Mucur
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-31 00:00:00.000000000 Z
11
+ date: 2024-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -100,7 +100,9 @@ metadata:
100
100
  source_code_uri: https://github.com/mudge/argon2id
101
101
  rubygems_mfa_required: 'true'
102
102
  post_install_message:
103
- rdoc_options: []
103
+ rdoc_options:
104
+ - "--main"
105
+ - README.md
104
106
  require_paths:
105
107
  - lib
106
108
  required_ruby_version: !ruby/object:Gem::Requirement