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.
- checksums.yaml +4 -4
- data/HISTORY.md +21 -1
- data/Manifest.txt +5 -27
- data/README.md +51 -31
- data/exe/htdigest-ruby +14 -0
- data/exe/htpasswd-ruby +14 -0
- data/htauth.gemspec +33 -0
- data/lib/htauth/algorithm.rb +42 -29
- data/lib/htauth/argon2.rb +86 -0
- data/lib/htauth/bcrypt.rb +17 -11
- data/lib/htauth/cli/digest.rb +42 -49
- data/lib/htauth/cli/passwd.rb +127 -114
- data/lib/htauth/cli.rb +5 -4
- data/lib/htauth/console.rb +9 -6
- data/lib/htauth/crypt.rb +11 -9
- data/lib/htauth/descendant_tracker.rb +11 -9
- data/lib/htauth/digest_entry.rb +22 -19
- data/lib/htauth/digest_file.rb +25 -18
- data/lib/htauth/entry.rb +3 -1
- data/lib/htauth/error.rb +6 -5
- data/lib/htauth/file.rb +35 -39
- data/lib/htauth/md5.rb +35 -34
- data/lib/htauth/passwd_entry.rb +29 -38
- data/lib/htauth/passwd_file.rb +32 -27
- data/lib/htauth/plaintext.rb +7 -5
- data/lib/htauth/sha1.rb +9 -7
- data/lib/htauth/version.rb +3 -1
- data/lib/htauth.rb +29 -28
- metadata +24 -113
- data/Rakefile +0 -27
- data/bin/htdigest-ruby +0 -12
- data/bin/htpasswd-ruby +0 -12
- data/spec/algorithm_spec.rb +0 -8
- data/spec/bcrypt_spec.rb +0 -33
- data/spec/cli/digest_spec.rb +0 -149
- data/spec/cli/passwd_spec.rb +0 -330
- data/spec/crypt_spec.rb +0 -12
- data/spec/digest_entry_spec.rb +0 -60
- data/spec/digest_file_spec.rb +0 -65
- data/spec/md5_spec.rb +0 -13
- data/spec/passwd_entry_spec.rb +0 -159
- data/spec/passwd_file_spec.rb +0 -84
- data/spec/plaintext_spec.rb +0 -11
- data/spec/sha1_spec.rb +0 -11
- data/spec/spec_helper.rb +0 -28
- data/spec/test.add.digest +0 -3
- data/spec/test.add.passwd +0 -3
- data/spec/test.delete.digest +0 -1
- data/spec/test.delete.passwd +0 -1
- data/spec/test.original.digest +0 -2
- data/spec/test.original.passwd +0 -2
- data/spec/test.update.digest +0 -2
- data/spec/test.update.passwd +0 -2
- data/tasks/default.rake +0 -242
- data/tasks/this.rb +0 -208
- /data/{LICENSE → LICENSE.txt} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf3dca478538f6a659cfe1cbe39b0688782e34f606cba0b116a63a87c42f022e
|
|
4
|
+
data.tar.gz: 83508be980a643ec2a481582ecc8ad02f3f8271b2d34359ab26bad2369d4eddd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 48c8f83ca6e2c893731098c076680c37eeb6ff8fe1bde0589957c56239f8239cdb2e8fad01ea2278c6c10f25879e9adc4ba43385f6250435c08207d7b9381a87
|
|
7
|
+
data.tar.gz: a059b27747b6ca12201171500c8f08672b2b378d4e80c83ed5caa9725e009f0366470607e756c02905c253f9c2c4dd173aa58760c7978238a3862c85b038b431
|
data/HISTORY.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
-
## Version
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
11
|
-
htpasswd
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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,
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
+
## Supported Hash Algorithms
|
|
65
84
|
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
88
|
+
- Built in
|
|
89
|
+
- Generally accepted
|
|
90
|
+
- MD5 (default for compatibility reasons)
|
|
91
|
+
- bcrypt (probably the better choice)
|
|
75
92
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
- **Not Recommended** - available only for backwards compatibility with `htpasswd`
|
|
94
|
+
- SHA1
|
|
95
|
+
- crypt
|
|
96
|
+
- plaintext
|
|
79
97
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
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
|
data/lib/htauth/algorithm.rb
CHANGED
|
@@ -1,48 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
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[
|
|
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"
|
|
20
|
+
BCRYPT = "bcrypt"
|
|
18
21
|
# Public: flag for the md5 algorithm
|
|
19
|
-
MD5 = "md5"
|
|
22
|
+
MD5 = "md5"
|
|
20
23
|
# Public: flag for the sha1 algorithm
|
|
21
|
-
SHA1 = "sha1"
|
|
24
|
+
SHA1 = "sha1"
|
|
22
25
|
# Public: flag for the plaintext algorithm
|
|
23
|
-
PLAINTEXT = "plaintext"
|
|
26
|
+
PLAINTEXT = "plaintext"
|
|
24
27
|
# Public: flag for the crypt algorithm
|
|
25
|
-
CRYPT = "crypt"
|
|
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"
|
|
32
|
-
|
|
34
|
+
EXISTING = "existing"
|
|
33
35
|
|
|
34
36
|
class << self
|
|
35
37
|
def algorithm_name
|
|
36
|
-
|
|
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
|
-
|
|
42
|
-
names = children.map
|
|
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
|
-
|
|
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
|
-
|
|
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, "#{
|
|
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(
|
|
76
|
-
return false unless
|
|
77
|
+
def secure_compare(lhs, rhs)
|
|
78
|
+
return false unless lhs.bytesize == rhs.bytesize
|
|
77
79
|
|
|
78
|
-
l =
|
|
80
|
+
l = lhs.unpack("C*")
|
|
79
81
|
|
|
80
|
-
r
|
|
81
|
-
|
|
82
|
-
r
|
|
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)
|
|
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
|
|
110
|
+
def to64(number, rounds)
|
|
98
111
|
r = StringIO.new
|
|
99
|
-
rounds.times do |
|
|
112
|
+
rounds.times do |_x|
|
|
100
113
|
r.print(SALT_CHARS[number % 64])
|
|
101
114
|
number >>= 6
|
|
102
115
|
end
|
|
103
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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, :
|
|
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
|