htauth 2.2.0 → 3.0.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/HISTORY.md +21 -1
  3. data/Manifest.txt +5 -27
  4. data/README.md +51 -31
  5. data/exe/htdigest-ruby +14 -0
  6. data/exe/htpasswd-ruby +14 -0
  7. data/htauth.gemspec +33 -0
  8. data/lib/htauth/algorithm.rb +42 -29
  9. data/lib/htauth/argon2.rb +86 -0
  10. data/lib/htauth/bcrypt.rb +17 -11
  11. data/lib/htauth/cli/digest.rb +42 -49
  12. data/lib/htauth/cli/passwd.rb +127 -114
  13. data/lib/htauth/cli.rb +5 -4
  14. data/lib/htauth/console.rb +9 -6
  15. data/lib/htauth/crypt.rb +11 -9
  16. data/lib/htauth/descendant_tracker.rb +11 -9
  17. data/lib/htauth/digest_entry.rb +22 -19
  18. data/lib/htauth/digest_file.rb +25 -18
  19. data/lib/htauth/entry.rb +3 -1
  20. data/lib/htauth/error.rb +6 -5
  21. data/lib/htauth/file.rb +35 -39
  22. data/lib/htauth/md5.rb +35 -34
  23. data/lib/htauth/passwd_entry.rb +29 -38
  24. data/lib/htauth/passwd_file.rb +32 -27
  25. data/lib/htauth/plaintext.rb +7 -5
  26. data/lib/htauth/sha1.rb +9 -7
  27. data/lib/htauth/version.rb +3 -1
  28. data/lib/htauth.rb +29 -28
  29. metadata +24 -113
  30. data/Rakefile +0 -27
  31. data/bin/htdigest-ruby +0 -12
  32. data/bin/htpasswd-ruby +0 -12
  33. data/spec/algorithm_spec.rb +0 -8
  34. data/spec/bcrypt_spec.rb +0 -33
  35. data/spec/cli/digest_spec.rb +0 -149
  36. data/spec/cli/passwd_spec.rb +0 -330
  37. data/spec/crypt_spec.rb +0 -12
  38. data/spec/digest_entry_spec.rb +0 -60
  39. data/spec/digest_file_spec.rb +0 -65
  40. data/spec/md5_spec.rb +0 -13
  41. data/spec/passwd_entry_spec.rb +0 -159
  42. data/spec/passwd_file_spec.rb +0 -84
  43. data/spec/plaintext_spec.rb +0 -11
  44. data/spec/sha1_spec.rb +0 -11
  45. data/spec/spec_helper.rb +0 -28
  46. data/spec/test.add.digest +0 -3
  47. data/spec/test.add.passwd +0 -3
  48. data/spec/test.delete.digest +0 -1
  49. data/spec/test.delete.passwd +0 -1
  50. data/spec/test.original.digest +0 -2
  51. data/spec/test.original.passwd +0 -2
  52. data/spec/test.update.digest +0 -2
  53. data/spec/test.update.passwd +0 -2
  54. data/tasks/default.rake +0 -242
  55. data/tasks/this.rb +0 -208
  56. /data/{LICENSE → LICENSE.txt} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a392e544654ec732a878871166ba48129a72dc8e7d2b3b9e1ed37d99117546b9
4
- data.tar.gz: fb244f0b94603e5829d36d10e1309d98608a584c1bc8ea3a3d95f60f9950b7fb
3
+ metadata.gz: bf3dca478538f6a659cfe1cbe39b0688782e34f606cba0b116a63a87c42f022e
4
+ data.tar.gz: 83508be980a643ec2a481582ecc8ad02f3f8271b2d34359ab26bad2369d4eddd
5
5
  SHA512:
6
- metadata.gz: 3350108830c0ce7bb7e406ec2d449c89eaf3ce1047f095cab78cffe8c197fb03eb9a5ffe7ad1b782e6d15c1ed88017ef96cb48e87cd8e91993454e00bc1fffda
7
- data.tar.gz: 3b4183cd67ab796c5de2c2fcee847a94ab7d83e925b5283640bd7718274667e141d1618a10f927cf5c6e70cc5a667c284577d83d449a7566770ea60e5a2bc20e
6
+ metadata.gz: 48c8f83ca6e2c893731098c076680c37eeb6ff8fe1bde0589957c56239f8239cdb2e8fad01ea2278c6c10f25879e9adc4ba43385f6250435c08207d7b9381a87
7
+ data.tar.gz: a059b27747b6ca12201171500c8f08672b2b378d4e80c83ed5caa9725e009f0366470607e756c02905c253f9c2c4dd173aa58760c7978238a3862c85b038b431
data/HISTORY.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
- ## Version 2.2.0 - 2023-02-XX
2
+ ## Version 3.0.0 - 2026-05-23
3
+
4
+ * Modernize project structure
5
+ * Bump minimum Ruby version from 2.3.0 to 3.0.0
6
+ * Add rubocop for code linting with multiple plugins
7
+ * Rename `is_entry?`/`is_entry_for?` methods to `entry?`/`entry_for?` (Ruby naming conventions)
8
+ * Update CI build matrix: Ruby 3.3, 3.4, 4.0, JRuby 10.1, TruffleRuby 34.0
9
+ * Remove Ruby 3.2 from build matrix
10
+ * Move development dependencies from gemspec to Gemfile
11
+ * Add `ostruct` as a runtime dependency
12
+ * Remove test files from gem package
13
+ * Rename `LICENSE` to `LICENSE.txt`
14
+ * Apply rubocop autocorrections across all source and spec files
15
+
16
+ ## Version 2.3.0 - 2024-02-03
17
+
18
+ * Add support for argon2 encryption [#18](https://github.com/copiousfreetime/htauth/pull/18)
19
+ * Update supported ruby version, now supporting ruby 3.x only
20
+ * Semaphore updates
21
+
22
+ ## Version 2.2.0 - 2023-02-06
3
23
 
4
24
  * Update ruby versions
5
25
  * Add to sempahore for CI
data/Manifest.txt CHANGED
@@ -1,13 +1,14 @@
1
1
  CONTRIBUTING.md
2
2
  HISTORY.md
3
- LICENSE
3
+ LICENSE.txt
4
4
  Manifest.txt
5
5
  README.md
6
- Rakefile
7
- bin/htdigest-ruby
8
- bin/htpasswd-ruby
6
+ exe/htdigest-ruby
7
+ exe/htpasswd-ruby
8
+ htauth.gemspec
9
9
  lib/htauth.rb
10
10
  lib/htauth/algorithm.rb
11
+ lib/htauth/argon2.rb
11
12
  lib/htauth/bcrypt.rb
12
13
  lib/htauth/cli.rb
13
14
  lib/htauth/cli/digest.rb
@@ -26,26 +27,3 @@ lib/htauth/passwd_file.rb
26
27
  lib/htauth/plaintext.rb
27
28
  lib/htauth/sha1.rb
28
29
  lib/htauth/version.rb
29
- spec/algorithm_spec.rb
30
- spec/bcrypt_spec.rb
31
- spec/cli/digest_spec.rb
32
- spec/cli/passwd_spec.rb
33
- spec/crypt_spec.rb
34
- spec/digest_entry_spec.rb
35
- spec/digest_file_spec.rb
36
- spec/md5_spec.rb
37
- spec/passwd_entry_spec.rb
38
- spec/passwd_file_spec.rb
39
- spec/plaintext_spec.rb
40
- spec/sha1_spec.rb
41
- spec/spec_helper.rb
42
- spec/test.add.digest
43
- spec/test.add.passwd
44
- spec/test.delete.digest
45
- spec/test.delete.passwd
46
- spec/test.original.digest
47
- spec/test.original.passwd
48
- spec/test.update.digest
49
- spec/test.update.passwd
50
- tasks/default.rake
51
- tasks/this.rb
data/README.md CHANGED
@@ -7,25 +7,45 @@
7
7
 
8
8
  ## DESCRIPTION
9
9
 
10
- HTAuth is a pure ruby replacement for the Apache support programs htdigest and
11
- htpasswd. Command line and API access are provided for access to htdigest and
12
- htpasswd files.
10
+ HTAuth provides an API and commandline tools for managing Apache/httpd style
11
+ htpasswd and htdigest files.
13
12
 
14
13
  ## FEATURES
15
14
 
16
- HTAuth provides to drop in commands *htdigest-ruby* and *htpasswd-ruby* that
17
- can manipulate the digest and passwd files in the same manner as Apache's
18
- original commands.
15
+ HTAuth provides an API allowing direct manipulation of Apache/httpd style
16
+ `htdigest` and `htpasswd` files. Supporting full programmatic manipulation and
17
+ authentication of user credentials.
19
18
 
20
- *htdigest-ruby* and *htpasswd-ruby* are command line compatible with *htdigest*
21
- and *htpasswd*. They support the same exact same command line options as the
22
- originals, and have some extras.
19
+ HTAuth also includes drop-in, commandline compatible replacements for the Apache
20
+ utilities `htpasswd` and `htdigest` with the respective `htpasswd-ruby` and
21
+ `htdigest-ruby` commands.
23
22
 
24
- Additionally, you can access all the functionality of *htdigest-ruby* and
25
- *htpasswd-ruby* through an API.
23
+ Additionally, support for the [argon2](https://github.com/technion/ruby-argon2)
24
+ password hashing algorithm is provided for most platforms.
26
25
 
27
26
  ## SYNOPSIS
28
27
 
28
+ ### API Usage
29
+
30
+ HTAuth::DigestFile.open("some.htdigest") do |df|
31
+ df.add_or_update('someuser', 'myrealm', 'a password')
32
+ df.delete('someolduser', 'myotherrealm')
33
+ end
34
+
35
+ HTAuth::PasswdFile.open("some.htpasswd", HTAuth::File::CREATE) do |pf|
36
+ pf.add('someuser', 'a password', 'md5')
37
+ pf.add('someotheruser', 'a different password', 'sha1')
38
+ end
39
+
40
+ HTAuth::PasswdFile.open("some.htpasswd", HTAuth::File::ALTER) do |pf|
41
+ pf.update('someuser', 'a password', 'bcrypt')
42
+ end
43
+
44
+ HTAuth::PasswdFile.open("some.htpasswd") do |pf|
45
+ pf.authenticated?('someuser', 'a password')
46
+ end
47
+
48
+
29
49
  ### htpasswd-ruby command line application
30
50
 
31
51
  Usage:
@@ -35,7 +55,8 @@ Additionally, you can access all the functionality of *htdigest-ruby* and
35
55
  htpasswd-ruby -n[imBdps] [-C cost] username
36
56
  htpasswd-ruby -nb[mBdps] [-C cost] username password
37
57
 
38
- -b, --batch Batch mode, get the password from the command line, rather than prompt
58
+ --argon2 Force argon2 encryption of the password.
59
+ -b, --batch Batch mode, get the password from the command line, rather than prompt.
39
60
  -B, --bcrypt Force bcrypt encryption of the password.
40
61
  -C, --cost COST Set the computing time used for the bcrypt algorithm
41
62
  (higher is more secure but slower, default: 5, valid: 4 to 31).
@@ -49,9 +70,7 @@ Additionally, you can access all the functionality of *htdigest-ruby* and
49
70
  -p, --plaintext Do not encrypt the password (plaintext).
50
71
  -s, --sha1 Force SHA encryption of the password.
51
72
  -v, --version Show version info.
52
- --verify Verify password for the specified user
53
-
54
- The SHA algorithm does not use a salt and is less secure than the MD5 algorithm.
73
+ --verify Verify password for the specified user.
55
74
 
56
75
  ### htdigest-ruby command line application
57
76
 
@@ -61,30 +80,31 @@ Additionally, you can access all the functionality of *htdigest-ruby* and
61
80
  -h, --help Display this help.
62
81
  -v, --version Show version info.
63
82
 
64
- ### API Usage
83
+ ## Supported Hash Algorithms
65
84
 
66
- HTAuth::DigestFile.open("some.htdigest") do |df|
67
- df.add_or_update('someuser', 'myrealm', 'a password')
68
- df.delete('someolduser', 'myotherrealm')
69
- end
85
+ Out of the box, `htauth` supports the classic algorithms that ship with Apache
86
+ `htpasswd`.
70
87
 
71
- HTAuth::PasswdFile.open("some.htpasswd", HTAuth::File::CREATE) do |pf|
72
- pf.add('someuser', 'a password', 'md5')
73
- pf.add('someotheruser', 'a different password', 'sha1')
74
- end
88
+ - Built in
89
+ - Generally accepted
90
+ - MD5 (default for compatibility reasons)
91
+ - bcrypt (probably the better choice)
75
92
 
76
- HTAuth::PasswdFile.open("some.htpasswd", HTAuth::File::ALTER) do |pf|
77
- pf.update('someuser', 'a password', 'bcrypt')
78
- end
93
+ - **Not Recommended** - available only for backwards compatibility with `htpasswd`
94
+ - SHA1
95
+ - crypt
96
+ - plaintext
79
97
 
80
- HTAuth::PasswdFile.open("some.htpasswd") do |pf|
81
- pf.authenticated?('someuser', 'a password')
82
- end
98
+ - Available with the installation of additional libraries:
99
+ - argon2 - to use, add `gem 'argon2'` to your `Gemfile`. `argon2` will
100
+ now be a valid algorithm to use in `HTAuth::PasswdFile` API. Currently
101
+ argon2 is not supported on windows as the upstream `argon2` gem does not
102
+ support windows.
83
103
 
84
104
  ## CREDITS
85
105
 
86
106
  * [The Apache Software Foundation](http://www.apache.org/)
87
- * all the folks who contributed to htdigest and htpassword
107
+ * all the folks who contributed to htdigest and htpasswd
88
108
 
89
109
  ## MIT LICENSE
90
110
 
data/exe/htdigest-ruby ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "htauth/cli/digest"
6
+ rescue LoadError
7
+ path = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
8
+ raise if $LOAD_PATH.include?(path)
9
+
10
+ $LOAD_PATH << path
11
+ retry
12
+ end
13
+
14
+ HTAuth::CLI::Digest.new.run(ARGV, ENV)
data/exe/htpasswd-ruby ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "htauth/cli/passwd"
6
+ rescue LoadError
7
+ path = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
8
+ raise if $LOAD_PATH.include?(path)
9
+
10
+ $LOAD_PATH << path
11
+ retry
12
+ end
13
+
14
+ HTAuth::CLI::Passwd.new.run(ARGV, ENV)
data/htauth.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # DO NOT EDIT - This file is automatically generated
2
+ # Make changes to Manifest.txt and/or Rakefile and regenerate
3
+ # -*- encoding: utf-8 -*-
4
+ # stub: htauth 3.0.0 ruby lib
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "htauth".freeze
8
+ s.version = "3.0.0".freeze
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
11
+ s.metadata = { "bug_tracker_uri" => "https://github.com/copiousfreetime/htauth/issues", "changelog_uri" => "https://github.com/copiousfreetime/htauth/blob/master/HISTORY.md", "homepage_uri" => "https://github.com/copiousfreetime/htauth", "source_code_uri" => "https://github.com/copiousfreetime/htauth" } if s.respond_to? :metadata=
12
+ s.require_paths = ["lib".freeze]
13
+ s.authors = ["Jeremy Hinegardner".freeze]
14
+ s.bindir = "exe".freeze
15
+ s.date = "2026-05-23"
16
+ s.description = "HTAuth provides an API and commandline tools for managing Apache/httpd style htpasswd and htdigest files.".freeze
17
+ s.email = "jeremy@copiousfreetime.org".freeze
18
+ s.executables = ["htdigest-ruby".freeze, "htpasswd-ruby".freeze]
19
+ s.extra_rdoc_files = ["CONTRIBUTING.md".freeze, "HISTORY.md".freeze, "LICENSE.txt".freeze, "Manifest.txt".freeze, "README.md".freeze]
20
+ s.files = ["CONTRIBUTING.md".freeze, "HISTORY.md".freeze, "LICENSE.txt".freeze, "Manifest.txt".freeze, "README.md".freeze, "exe/htdigest-ruby".freeze, "exe/htpasswd-ruby".freeze, "htauth.gemspec".freeze, "lib/htauth.rb".freeze, "lib/htauth/algorithm.rb".freeze, "lib/htauth/argon2.rb".freeze, "lib/htauth/bcrypt.rb".freeze, "lib/htauth/cli.rb".freeze, "lib/htauth/cli/digest.rb".freeze, "lib/htauth/cli/passwd.rb".freeze, "lib/htauth/console.rb".freeze, "lib/htauth/crypt.rb".freeze, "lib/htauth/descendant_tracker.rb".freeze, "lib/htauth/digest_entry.rb".freeze, "lib/htauth/digest_file.rb".freeze, "lib/htauth/entry.rb".freeze, "lib/htauth/error.rb".freeze, "lib/htauth/file.rb".freeze, "lib/htauth/md5.rb".freeze, "lib/htauth/passwd_entry.rb".freeze, "lib/htauth/passwd_file.rb".freeze, "lib/htauth/plaintext.rb".freeze, "lib/htauth/sha1.rb".freeze, "lib/htauth/version.rb".freeze]
21
+ s.homepage = "http://github.com/copiousfreetime/htauth".freeze
22
+ s.licenses = ["MIT".freeze]
23
+ s.rdoc_options = ["--main".freeze, "README.md".freeze, "--markup".freeze, "tomdoc".freeze]
24
+ s.required_ruby_version = Gem::Requirement.new(">= 3.0.0".freeze)
25
+ s.rubygems_version = "4.0.10".freeze
26
+ s.summary = "HTAuth provides an API and commandline tools for managing Apache/httpd style htpasswd and htdigest files.".freeze
27
+
28
+ s.specification_version = 4
29
+
30
+ s.add_runtime_dependency(%q<bcrypt>.freeze, ["~> 3.1".freeze])
31
+ s.add_runtime_dependency(%q<base64>.freeze, ["~> 0.2".freeze])
32
+ s.add_runtime_dependency(%q<ostruct>.freeze, ["~> 0.6".freeze])
33
+ end
@@ -1,48 +1,50 @@
1
- require 'htauth/error'
2
- require 'htauth/descendant_tracker'
3
- require 'securerandom'
1
+ # frozen_string_literal: true
2
+
3
+ require "htauth/error"
4
+ require "htauth/descendant_tracker"
5
+ require "securerandom"
4
6
  module HTAuth
5
7
  class InvalidAlgorithmError < Error; end
6
8
 
7
9
  # Internal: Base class all the password algorithms derive from
8
10
  #
9
11
  class Algorithm
10
-
11
12
  extend DescendantTracker
12
13
 
13
- SALT_CHARS = (%w[ . / ] + ("0".."9").to_a + ('A'..'Z').to_a + ('a'..'z').to_a).freeze
14
+ SALT_CHARS = (%w[. /] + ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a).freeze
14
15
  SALT_LENGTH = 8
15
16
 
17
+ # Public: flag for the argon2 algorithm
18
+ ARGON2 = "argon2"
16
19
  # Public: flag for the bcrypt algorithm
17
- BCRYPT = "bcrypt".freeze
20
+ BCRYPT = "bcrypt"
18
21
  # Public: flag for the md5 algorithm
19
- MD5 = "md5".freeze
22
+ MD5 = "md5"
20
23
  # Public: flag for the sha1 algorithm
21
- SHA1 = "sha1".freeze
24
+ SHA1 = "sha1"
22
25
  # Public: flag for the plaintext algorithm
23
- PLAINTEXT = "plaintext".freeze
26
+ PLAINTEXT = "plaintext"
24
27
  # Public: flag for the crypt algorithm
25
- CRYPT = "crypt".freeze
28
+ CRYPT = "crypt"
26
29
 
27
30
  # Public: flag for the default algorithm
28
31
  DEFAULT = MD5
29
32
 
30
33
  # Public: flag to indicate using the existing algorithm of the entry
31
- EXISTING = "existing".freeze
32
-
34
+ EXISTING = "existing"
33
35
 
34
36
  class << self
35
37
  def algorithm_name
36
- self.name.split("::").last.downcase
38
+ name.split("::").last.downcase
37
39
  end
38
40
 
39
41
  def algorithm_from_name(a_name, params = {})
40
42
  found = children.find { |c| c.algorithm_name == a_name }
41
- if !found then
42
- names = children.map { |c| c.algorithm_name }
43
+ unless found
44
+ names = children.map(&:algorithm_name)
43
45
  raise InvalidAlgorithmError, "`#{a_name}' is an unknown encryption algorithm, use one of #{names.join(', ')}"
44
46
  end
45
- return found.new(params)
47
+ found.new(params)
46
48
  end
47
49
 
48
50
  # NOTE: if it is plaintext, and the length is 13 - it may matched crypt
@@ -55,13 +57,13 @@ module HTAuth
55
57
 
56
58
  raise InvalidAlgorithmError, "unknown encryption algorithm used for `#{password_field}`" if match.nil?
57
59
 
58
- return match.new(:existing => password_field)
60
+ match.new(existing: password_field)
59
61
  end
60
62
 
61
63
  # Internal: Does this class handle this type of password entry
62
64
  #
63
65
  def handles?(password_entry)
64
- raise NotImplementedError, "#{self.name} must implement #{self.name}.handles?(password_entry)"
66
+ raise NotImplementedError, "#{name} must implement #{name}.handles?(password_entry)"
65
67
  end
66
68
 
67
69
  # Internal: Constant time string comparison.
@@ -72,35 +74,46 @@ module HTAuth
72
74
  # that have already been processed by HMAC. This should not be used
73
75
  # on variable length plaintext strings because it could leak length info
74
76
  # via timing attacks.
75
- def secure_compare(a, b)
76
- return false unless a.bytesize == b.bytesize
77
+ def secure_compare(lhs, rhs)
78
+ return false unless lhs.bytesize == rhs.bytesize
77
79
 
78
- l = a.unpack("C*")
80
+ l = lhs.unpack("C*")
79
81
 
80
- r, i = 0, -1
81
- b.each_byte { |v| r |= v ^ l[i+=1] }
82
- r == 0
82
+ r = 0
83
+ i = -1
84
+ rhs.each_byte { |v| r |= v ^ l[i += 1] }
85
+ r.zero?
83
86
  end
84
87
  end
85
88
 
86
89
  # Internal
87
- def encode(password) ; end
90
+ def encode(password)
91
+ raise NotImplementedError, "#{self.class.name} must implement #{self.class.name}.encode(password)"
92
+ end
93
+
94
+ # Internal: Does the given password match the digest, the default just
95
+ # encodes and secure compares the result, different algorithms may overide
96
+ # this method
97
+ def verify_password?(password, digest)
98
+ encoded = encode(password)
99
+ self.class.secure_compare(encoded, digest)
100
+ end
88
101
 
89
102
  # Internal: 8 bytes of random items from SALT_CHARS
90
103
  def gen_salt(length = SALT_LENGTH)
91
- Array.new(length) { SALT_CHARS.sample }.join('')
104
+ Array.new(length) { SALT_CHARS.sample }.join
92
105
  end
93
106
 
94
107
  # Internal: this is not the Base64 encoding, this is the to64()
95
108
  # method from the Apache Portable Runtime (APR) library
96
109
  # https://github.com/apache/apr/blob/trunk/crypto/apr_md5.c#L493-L502
97
- def to_64(number, rounds)
110
+ def to64(number, rounds)
98
111
  r = StringIO.new
99
- rounds.times do |x|
112
+ rounds.times do |_x|
100
113
  r.print(SALT_CHARS[number % 64])
101
114
  number >>= 6
102
115
  end
103
- return r.string
116
+ r.string
104
117
  end
105
118
  end
106
119
  end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "htauth/algorithm"
4
+ begin
5
+ require "argon2"
6
+ rescue LoadError
7
+ end
8
+
9
+ module HTAuth
10
+ # Internal: Support of the argon2id algorithm and password format.
11
+ class Argon2 < Algorithm
12
+ # Internal: Error class when argon2 is specified on windows
13
+ class NotSupportedError < ::HTAuth::InvalidAlgorithmError
14
+ def message
15
+ [
16
+ "Unfortunately Argon2 passwords are not supported on `#{RUBY_PLATFORM} at this time.",
17
+ "This because the upstream argon2 gem does not support windows.",
18
+ ].join(" ")
19
+ end
20
+ end
21
+
22
+ # Internal: Error class when argon2 is not installed
23
+ class NotInstalledError < ::HTAuth::InvalidAlgorithmError
24
+ def message
25
+ "Argon2 passwords are supported if the `argon2' gem is installed. Add `gem 'argon2', '~> 2.3'` to your Gemfile"
26
+ end
27
+ end
28
+
29
+ # from upstream, used to help make a nice error message if its not installed
30
+ # https://github.com/technion/ruby-argon2/blob/3388d7e05e8b486ea4ba8bd2aeb1e9988f025f13/lib/argon2/hash_format.rb#L45
31
+ PREFIX = /^\$argon2(id?|d).{,113}/
32
+ ARGON2_GEM_INSTALLED = defined?(::Argon2)
33
+
34
+ def self.supported?
35
+ !::Gem.win_platform?
36
+ end
37
+
38
+ def self.ensure_available!
39
+ raise NotSupportedError unless supported?
40
+ raise NotInstalledError unless ARGON2_GEM_INSTALLED
41
+ end
42
+
43
+ attr_accessor :options
44
+
45
+ def self.handles?(password_entry)
46
+ return false unless PREFIX.match?(password_entry)
47
+
48
+ ensure_available!
49
+
50
+ ::Argon2::Password.valid_hash?(password_entry)
51
+ end
52
+
53
+ def self.extract_options_from_existing_password_field(existing)
54
+ hash_format = ::Argon2::HashFormat.new(existing)
55
+
56
+ # m_cost on the input is the 2**m_cost, but in the hash its the number of
57
+ # bytes, so need to convert it back to a power of 2, which is the
58
+ # log2(m_cost)
59
+
60
+ {
61
+ t_cost: hash_format.t_cost,
62
+ m_cost: ::Math.log2(hash_format.m_cost).floor,
63
+ p_cost: hash_format.p_cost,
64
+ }
65
+ end
66
+
67
+ def initialize(params = { profile: :rfc_9106_low_memory })
68
+ super()
69
+ self.class.ensure_available!
70
+ @options = if (existing = params["existing"] || params[:existing])
71
+ self.class.extract_options_from_existing_password_field(existing)
72
+ else
73
+ params
74
+ end
75
+ end
76
+
77
+ def encode(password)
78
+ argon2 = ::Argon2::Password.new(options)
79
+ argon2.create(password)
80
+ end
81
+
82
+ def verify_password?(password, digest)
83
+ ::Argon2::Password.verify_password(password, digest)
84
+ end
85
+ end
86
+ end
data/lib/htauth/bcrypt.rb CHANGED
@@ -1,18 +1,18 @@
1
- require 'htauth/algorithm'
2
- require 'bcrypt'
1
+ # frozen_string_literal: true
2
+
3
+ require "htauth/algorithm"
4
+ require "bcrypt"
3
5
 
4
6
  module HTAuth
5
7
  # Internal: an implementation of the Bcrypt based encoding algorithm
6
8
  # as used in the apache htpasswd -B option
7
-
8
9
  class Bcrypt < Algorithm
9
-
10
10
  attr_accessor :cost
11
11
 
12
12
  DEFAULT_APACHE_COST = 5 # this is the default cost from htpasswd
13
13
 
14
14
  def self.handles?(password_entry)
15
- return ::BCrypt::Password.valid_hash?(password_entry)
15
+ ::BCrypt::Password.valid_hash?(password_entry)
16
16
  end
17
17
 
18
18
  def self.extract_cost_from_existing_password_field(existing)
@@ -21,15 +21,21 @@ module HTAuth
21
21
  end
22
22
 
23
23
  def initialize(params = {})
24
- if existing = (params['existing'] || params[:existing]) then
25
- @cost = self.class.extract_cost_from_existing_password_field(existing)
26
- else
27
- @cost = params['cost'] || params[:cost] || DEFAULT_APACHE_COST
28
- end
24
+ super()
25
+ @cost = if (existing = params["existing"] || params[:existing])
26
+ self.class.extract_cost_from_existing_password_field(existing)
27
+ else
28
+ params["cost"] || params[:cost] || DEFAULT_APACHE_COST
29
+ end
29
30
  end
30
31
 
31
32
  def encode(password)
32
- ::BCrypt::Password.create(password, :cost => cost)
33
+ ::BCrypt::Password.create(password, cost: cost)
34
+ end
35
+
36
+ def verify_password?(password, digest)
37
+ bc = ::BCrypt::Password.new(digest)
38
+ bc.is_password?(password)
33
39
  end
34
40
  end
35
41
  end