wikk_password 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/History.txt +36 -0
- data/Manifest.txt +5 -0
- data/README.md +4 -4
- data/Rakefile +16 -17
- data/lib/unix-crypt/lib/base.rb +63 -0
- data/lib/unix-crypt/lib/des.rb +19 -0
- data/lib/unix-crypt/lib/md5.rb +52 -0
- data/lib/unix-crypt/lib/sha.rb +98 -0
- data/lib/unix-crypt/unix_crypt.rb +45 -0
- data/lib/wikk_password.rb +128 -119
- metadata +22 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: da2a0031e8433f4f21bcd5e95d2cba7af04cfc877f19f0b22c7ab3db5b92d170
|
4
|
+
data.tar.gz: cf9519efb1c8dd06c978a934cc020238df5a0e51ba7286646abe24e3728ffbf7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b00e403e9a2a84904cecee98832134767d05c5f713e64c861a5bde300955e92d437acd279e7318c8edac4afe4a5748f0fda9d8272371a8bdb93dd1d3024306b
|
7
|
+
data.tar.gz: dad6e0d5c5dc5c9caa3d12aa2b27f69d1eaa8b2c43fd831c5b8971b5fbed2f9632294c95870c62ce6cc4f2b2e2c500f2a12205abad3a26b68735645c651d6515
|
data/History.txt
CHANGED
@@ -1,3 +1,39 @@
|
|
1
|
+
robertburrowes Sun Jun 5 18:39:33 2022 +1200
|
2
|
+
#{PROJECT} release 0.1.3
|
3
|
+
robertburrowes Sun Jun 5 18:35:04 2022 +1200
|
4
|
+
typo
|
5
|
+
robertburrowes Sun Jun 5 18:33:40 2022 +1200
|
6
|
+
unix-crypt gem temp fix
|
7
|
+
robertburrowes Sun Jun 5 18:29:56 2022 +1200
|
8
|
+
Test against the library, not the installed gem.
|
9
|
+
robertburrowes Sun Jun 5 18:29:33 2022 +1200
|
10
|
+
typo
|
11
|
+
robertburrowes Sun Jun 5 18:09:38 2022 +1200
|
12
|
+
Included Roger Nesbitt's unix-crypt directly, as his gem has gone. Will fix later
|
13
|
+
robertburrowes Sun Oct 25 21:47:30 2020 +1300
|
14
|
+
Tidy up yard comments
|
15
|
+
robertburrowes Sun Oct 25 21:07:20 2020 +1300
|
16
|
+
reformated for Hoe
|
17
|
+
robertburrowes Sun Oct 25 21:07:01 2020 +1300
|
18
|
+
Update dependencies to stop warnings
|
19
|
+
robertburrowes Sun Oct 25 21:06:37 2020 +1300
|
20
|
+
Included .gitignore in repo
|
21
|
+
robertburrowes Sun Oct 25 21:06:17 2020 +1300
|
22
|
+
Moved dev scripts to sbin
|
23
|
+
robertburrowes Mon Apr 13 23:09:22 2020 +1200
|
24
|
+
Qualify directory for tests
|
25
|
+
robertburrowes Mon Apr 13 23:08:11 2020 +1200
|
26
|
+
bump version for test change
|
27
|
+
robertburrowes Mon Apr 13 23:07:55 2020 +1200
|
28
|
+
remove unneeded require.
|
29
|
+
robertburrowes Sat Jun 25 10:11:46 2016 +1200
|
30
|
+
Upped version and autogen git tag for gem release.
|
31
|
+
robertburrowes Sat Jun 25 10:11:19 2016 +1200
|
32
|
+
added explicit DES,MD5,SHA256 and SHA512 tests
|
33
|
+
robertburrowes Sat Jun 25 10:10:35 2016 +1200
|
34
|
+
Dropped from archive as they are generated by testing
|
35
|
+
robertburrowes Sat Jun 25 10:07:22 2016 +1200
|
36
|
+
DES constant to String, added self.valid_sha256_response?
|
1
37
|
robertburrowes Wed Jun 22 22:53:58 2016 +1200
|
2
38
|
Wrong spelling of unix-crypt
|
3
39
|
robertburrowes Wed Jun 22 22:50:55 2016 +1200
|
data/Manifest.txt
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# wikk_password
|
2
2
|
|
3
|
-
*
|
4
|
-
* Source https://github.com/wikarekare/wikk_password
|
5
|
-
* Gem https://rubygems.org/gems/wikk_password
|
3
|
+
* Docs :: https://wikarekare.github.io/wikk_password/
|
4
|
+
* Source :: https://github.com/wikarekare/wikk_password
|
5
|
+
* Gem :: https://rubygems.org/gems/wikk_password
|
6
6
|
|
7
7
|
## DESCRIPTION:
|
8
8
|
|
@@ -38,7 +38,7 @@ rob:$aes256$cxpzz9BMCOvyqfyngashHA==$Z9qOyqgMa4V7ffnI0NOjIhPv+ObAfhC0vyNPXoR5bbw
|
|
38
38
|
## REQUIREMENTS:
|
39
39
|
|
40
40
|
###Gem requires
|
41
|
-
* require 'unix_crypt'
|
41
|
+
* require 'unix_crypt' (No longer available. Temp fix has been to include this gem's code)
|
42
42
|
* require 'wikk_aes_256'
|
43
43
|
|
44
44
|
## INSTALL:
|
data/Rakefile
CHANGED
@@ -4,27 +4,26 @@ require 'rubygems'
|
|
4
4
|
require 'hoe'
|
5
5
|
Hoe.plugin :yard
|
6
6
|
|
7
|
-
Hoe.spec 'wikk_password' do
|
8
|
-
self.readme_file =
|
9
|
-
self.developer(
|
7
|
+
Hoe.spec 'wikk_password' do
|
8
|
+
self.readme_file = 'README.md'
|
9
|
+
self.developer( 'Rob Burrowes', 'r.burrowes@auckland.ac.nz')
|
10
10
|
remote_rdoc_dir = '' # Release to root
|
11
|
-
|
11
|
+
|
12
12
|
self.yard_title = 'wikk_password'
|
13
|
-
self.yard_options = ['--markup', 'markdown', '--protected']
|
14
|
-
|
15
|
-
self.dependency "unix-crypt", [">= 1.3.0"]
|
16
|
-
self.dependency "wikk_aes_256", [">= 0.1.4"]
|
17
|
-
end
|
13
|
+
self.yard_options = [ '--markup', 'markdown', '--protected' ]
|
18
14
|
|
15
|
+
# self.dependency "unix-crypt", ['~> 1.3', '>= 1.3.0']
|
16
|
+
self.dependency 'wikk_aes_256', [ '~> 0.1', '>= 0.1.4' ]
|
17
|
+
end
|
19
18
|
|
20
|
-
#Validate manfest.txt
|
21
|
-
#rake check_manifest
|
19
|
+
# Validate manfest.txt
|
20
|
+
# rake check_manifest
|
22
21
|
|
23
|
-
#Local checking. Creates pkg/
|
24
|
-
#rake gem
|
22
|
+
# Local checking. Creates pkg/
|
23
|
+
# rake gem
|
25
24
|
|
26
|
-
#create doc/
|
27
|
-
#rake docs
|
25
|
+
# create doc/
|
26
|
+
# rake docs
|
28
27
|
|
29
|
-
#Copy up to rubygem.org
|
30
|
-
#rake release VERSION=1.0.1
|
28
|
+
# Copy up to rubygem.org
|
29
|
+
# rake release VERSION=1.0.1
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module UnixCrypt
|
2
|
+
class Base
|
3
|
+
def self.build(password, salt = nil, rounds = nil)
|
4
|
+
salt ||= generate_salt
|
5
|
+
if salt.length > max_salt_length
|
6
|
+
raise UnixCrypt::SaltTooLongError, "Salts longer than #{max_salt_length} characters are not permitted"
|
7
|
+
end
|
8
|
+
|
9
|
+
construct_password(password, salt, rounds)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.hash(password, salt, rounds = nil)
|
13
|
+
bit_specified_base64encode internal_hash(prepare_password(password), salt, rounds)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.generate_salt
|
17
|
+
# Generates a random salt using the same character set as the base64 encoding
|
18
|
+
# used by the hash encoder.
|
19
|
+
SecureRandom.base64((default_salt_length * 6 / 8.0).ceil).tr('+', '.')[0...default_salt_length]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.construct_password(password, salt, rounds)
|
23
|
+
"$#{identifier}$#{rounds_marker rounds}#{salt}$#{hash(password, salt, rounds)}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.bit_specified_base64encode(input)
|
27
|
+
b64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
28
|
+
input = input.bytes.to_a
|
29
|
+
output = ''
|
30
|
+
byte_indexes.each do |i3, i2, i1|
|
31
|
+
b1 = i1 && input[i1] || 0
|
32
|
+
b2 = i2 && input[i2] || 0
|
33
|
+
b3 = i3 && input[i3] || 0
|
34
|
+
output <<
|
35
|
+
b64[b1 & 0b00111111] <<
|
36
|
+
b64[((b1 & 0b11000000) >> 6) |
|
37
|
+
((b2 & 0b00001111) << 2)] <<
|
38
|
+
b64[((b2 & 0b11110000) >> 4) |
|
39
|
+
((b3 & 0b00000011) << 4)] <<
|
40
|
+
b64[(b3 & 0b11111100) >> 2]
|
41
|
+
end
|
42
|
+
|
43
|
+
remainder = 3 - (length % 3)
|
44
|
+
remainder = 0 if remainder == 3
|
45
|
+
output[0..(-1 - remainder)]
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.prepare_password(password)
|
49
|
+
# For Ruby 1.9+, convert the password to UTF-8, then treat that new string
|
50
|
+
# as binary for the digest methods.
|
51
|
+
if password.respond_to?(:encode)
|
52
|
+
password = password.encode('UTF-8')
|
53
|
+
password.force_encoding('ASCII-8BIT')
|
54
|
+
end
|
55
|
+
|
56
|
+
password
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.rounds_marker(_rounds)
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module UnixCrypt
|
2
|
+
class DES < UnixCrypt::Base
|
3
|
+
def self.hash(*_args)
|
4
|
+
raise 'Unimplemented for DES'
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.construct_password(password, salt, _rounds)
|
8
|
+
password.crypt(salt)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.default_salt_length
|
12
|
+
2
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.max_salt_length
|
16
|
+
2
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module UnixCrypt
|
2
|
+
class MD5 < UnixCrypt::Base
|
3
|
+
def self.digest
|
4
|
+
Digest::MD5
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.length
|
8
|
+
16
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.default_salt_length
|
12
|
+
8
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.max_salt_length
|
16
|
+
8
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.identifier
|
20
|
+
1
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.byte_indexes
|
24
|
+
[[ 0, 6, 12 ], [ 1, 7, 13 ], [ 2, 8, 14 ], [ 3, 9, 15 ], [ 4, 10, 5 ], [ nil, nil, 11 ]]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.internal_hash(password, salt, _ignored = nil)
|
28
|
+
salt = salt[0..7]
|
29
|
+
|
30
|
+
b = digest.digest("#{password}#{salt}#{password}")
|
31
|
+
a_string = "#{password}$1$#{salt}#{b * (password.length / length)}#{b[0...password.length % length]}"
|
32
|
+
|
33
|
+
password_length = password.length
|
34
|
+
while password_length > 0
|
35
|
+
a_string += password_length & 1 == 0 ? password[0].chr : "\x0"
|
36
|
+
password_length >>= 1
|
37
|
+
end
|
38
|
+
|
39
|
+
input = digest.digest(a_string)
|
40
|
+
|
41
|
+
1000.times do |index|
|
42
|
+
c_string = (index & 1 == 0 ? input : password)
|
43
|
+
c_string += salt unless index % 3 == 0
|
44
|
+
c_string += password unless index % 7 == 0
|
45
|
+
c_string += (index & 1 == 0 ? password : input)
|
46
|
+
input = digest.digest(c_string)
|
47
|
+
end
|
48
|
+
|
49
|
+
input
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module UnixCrypt
|
2
|
+
class SHABase < Base
|
3
|
+
def self.default_salt_length
|
4
|
+
16
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.max_salt_length
|
8
|
+
16
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.default_rounds
|
12
|
+
5000
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.internal_hash(password, salt, rounds = nil)
|
16
|
+
rounds = apply_rounds_bounds(rounds || default_rounds)
|
17
|
+
salt = salt[0..15]
|
18
|
+
|
19
|
+
b = digest.digest("#{password}#{salt}#{password}")
|
20
|
+
|
21
|
+
a_string = password + salt + b * (password.length / length) + b[0...password.length % length]
|
22
|
+
|
23
|
+
password_length = password.length
|
24
|
+
while password_length > 0
|
25
|
+
a_string += password_length & 1 == 0 ? password : b
|
26
|
+
password_length >>= 1
|
27
|
+
end
|
28
|
+
|
29
|
+
input = digest.digest(a_string)
|
30
|
+
|
31
|
+
dp = digest.digest(password * password.length)
|
32
|
+
p = dp * (password.length / length) + dp[0...password.length % length]
|
33
|
+
|
34
|
+
ds = digest.digest(salt * (16 + input.bytes.first))
|
35
|
+
s = ds * (salt.length / length) + ds[0...salt.length % length]
|
36
|
+
|
37
|
+
rounds.times do |index|
|
38
|
+
c_string = (index & 1 == 0 ? input : p)
|
39
|
+
c_string += s unless index % 3 == 0
|
40
|
+
c_string += p unless index % 7 == 0
|
41
|
+
c_string += (index & 1 == 0 ? p : input)
|
42
|
+
input = digest.digest(c_string)
|
43
|
+
end
|
44
|
+
|
45
|
+
input
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.apply_rounds_bounds(rounds)
|
49
|
+
rounds = 1000 if rounds < 1000
|
50
|
+
rounds = 999_999_999 if rounds > 999_999_999
|
51
|
+
rounds
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.rounds_marker(rounds)
|
55
|
+
if rounds && rounds != default_rounds
|
56
|
+
"rounds=#{apply_rounds_bounds(rounds)}$"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class SHA256 < SHABase
|
62
|
+
def self.digest
|
63
|
+
Digest::SHA256
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.length
|
67
|
+
32
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.identifier
|
71
|
+
5
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.byte_indexes
|
75
|
+
[[ 0, 10, 20 ], [ 21, 1, 11 ], [ 12, 22, 2 ], [ 3, 13, 23 ], [ 24, 4, 14 ], [ 15, 25, 5 ], [ 6, 16, 26 ], [ 27, 7, 17 ], [ 18, 28, 8 ], [ 9, 19, 29 ], [ nil, 31, 30 ]]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class SHA512 < SHABase
|
80
|
+
def self.digest
|
81
|
+
Digest::SHA512
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.length
|
85
|
+
64
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.identifier
|
89
|
+
6
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.byte_indexes
|
93
|
+
[[ 0, 21, 42 ], [ 22, 43, 1 ], [ 44, 2, 23 ], [ 3, 24, 45 ], [ 25, 46, 4 ], [ 47, 5, 26 ], [ 6, 27, 48 ], [ 28, 49, 7 ], [ 50, 8, 29 ], [ 9, 30, 51 ], [ 31, 52, 10 ],
|
94
|
+
[ 53, 11, 32 ], [ 12, 33, 54 ], [ 34, 55, 13 ], [ 56, 14, 35 ], [ 15, 36, 57 ], [ 37, 58, 16 ], [ 59, 17, 38 ], [ 18, 39, 60 ], [ 40, 61, 19 ], [ 62, 20, 41 ], [ nil, nil, 63 ]
|
95
|
+
]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Copyright (c) 2013, Roger Nesbitt
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
5
|
+
#
|
6
|
+
# Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
7
|
+
# Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
8
|
+
# documentation and/or other materials provided with the distribution.
|
9
|
+
# Neither the name of the unix-crypt nor the names of its contributors may be used to endorse or promote products derived from this software
|
10
|
+
# without specific prior written permission.
|
11
|
+
#
|
12
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
13
|
+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
14
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
15
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
16
|
+
|
17
|
+
require 'digest'
|
18
|
+
require 'securerandom'
|
19
|
+
|
20
|
+
module UnixCrypt
|
21
|
+
Error = Class.new(StandardError)
|
22
|
+
SaltTooLongError = Class.new(Error)
|
23
|
+
|
24
|
+
def self.valid?(password, string)
|
25
|
+
# Handle the original DES-based crypt(3)
|
26
|
+
return password.crypt(string) == string if string.length == 13
|
27
|
+
|
28
|
+
# All other types of password follow a standard format
|
29
|
+
return false unless (m = string.match(/\A\$([156])\$(?:rounds=(\d+)\$)?(.+)\$(.+)/))
|
30
|
+
|
31
|
+
hash = IDENTIFIER_MAPPINGS[m[1]].hash(password, m[3], m[2] && m[2].to_i)
|
32
|
+
hash == m[4]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
require_relative 'lib/base'
|
37
|
+
require_relative 'lib/des'
|
38
|
+
require_relative 'lib/md5'
|
39
|
+
require_relative 'lib/sha'
|
40
|
+
|
41
|
+
UnixCrypt::IDENTIFIER_MAPPINGS = {
|
42
|
+
'1' => UnixCrypt::MD5,
|
43
|
+
'5' => UnixCrypt::SHA256,
|
44
|
+
'6' => UnixCrypt::SHA512
|
45
|
+
}
|
data/lib/wikk_password.rb
CHANGED
@@ -1,170 +1,184 @@
|
|
1
|
-
|
1
|
+
# Keep in our own namespace
|
2
|
+
module WIKK
|
2
3
|
require 'wikk_aes_256'
|
3
4
|
require 'digest/sha2'
|
4
|
-
require 'unix_crypt'
|
5
5
|
require 'base64'
|
6
|
-
|
7
|
-
|
6
|
+
require_relative 'unix-crypt/unix_crypt.rb' # Temp fix, as the original gem has gone away
|
7
|
+
|
8
|
+
# READS/WRITES our private password file entries.
|
9
|
+
#
|
8
10
|
# @attr_reader [String] user the decrypted text
|
9
11
|
# @attr_reader [String] password the encrypted password, in form $type$initial_vector$encrypted_text
|
10
12
|
class Password
|
11
|
-
VERSION = '0.1.
|
13
|
+
VERSION = '0.1.3'
|
12
14
|
|
13
15
|
attr_reader :user, :password
|
14
|
-
|
15
|
-
#New. Fetches a user entry from the password file, or creates a new user (call via Passwd::add_user)
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
|
17
|
+
# New. Fetches a user entry from the password file, or creates a new user (call via Passwd::add_user)
|
18
|
+
#
|
19
|
+
# @param user [String] User name to fetch from password file, or to create, if new_user == true
|
20
|
+
# @param config [WIKK:Configuration] or hash or class with attr_readers :passwordFile, :encryption, :key
|
21
|
+
# @param new_user [Boolean] If true, then the user shouldn't be in password file.
|
22
|
+
# @return [WIKK::Password]
|
23
|
+
# @raise [IndexError] if the user entry exists.
|
24
|
+
def initialize(user, config, new_user = false)
|
25
|
+
if config.instance_of?(Hash)
|
26
|
+
sym = config.transform_keys(&:to_sym)
|
24
27
|
@config = Struct.new(*(k = sym.keys)).new(*sym.values_at(*k))
|
25
28
|
else
|
26
|
-
|
29
|
+
@config = config
|
27
30
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
raise IndexError, "User \"#{user}\" not found" if getpwnam(user) == false && !new_user
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets the user password, but does not save this. You must call save().
|
35
|
+
#
|
36
|
+
# @param password [String] the clear text password to encypt
|
37
|
+
# @return [String] the password file password entry.
|
38
|
+
def set_password(password) # rubocop: disable Naming/AccessorMethodName
|
35
39
|
@password = encrypt(password, @config.encryption)
|
36
40
|
end
|
37
|
-
|
38
|
-
#Compare an SHA256 hashed password + challenge with this users password
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
41
|
+
|
42
|
+
# Compare an SHA256 hashed password + challenge with this users password
|
43
|
+
#
|
44
|
+
# @param challenge [String] a random string, sent to the remote client, added to the password, and SHA256 hashed
|
45
|
+
# @param response [String] the remote clients hex_SHA256(password + challenge)
|
46
|
+
# @return [Boolean] True if the users password matches the one that created the response.
|
47
|
+
# @note The password entry must be decryptable, not a UNIX style hash.
|
48
|
+
# @raise [ArgumentError] if the encryption method is unknown.
|
44
49
|
def valid_sha256_response?(challenge, response)
|
45
|
-
return response == Digest::SHA256.digest(decrypt + challenge).
|
50
|
+
return response == Digest::SHA256.digest(decrypt + challenge).unpack1('H*')
|
46
51
|
end
|
47
|
-
|
48
|
-
#Compare an SHA256 hashed password + challenge with this users password
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
52
|
+
|
53
|
+
# Compare an SHA256 hashed password + challenge with this users password
|
54
|
+
#
|
55
|
+
# @param user [String] User name to fetch from password file, or to create, if new_user == true
|
56
|
+
# @param config [WIKK:Configuration] or hash or class with attr_readers :passwordFile, :encryption, :key
|
57
|
+
# @param challenge [String] a random string, sent to the remote client, added to the password, and SHA256 hashed
|
58
|
+
# @param response [String] the remote clients hex_SHA256(password + challenge)
|
59
|
+
# @return [Boolean] True if the users password matches the one that created the response.
|
60
|
+
# @note The password entry must be decryptable, not a UNIX style hash.
|
61
|
+
# @raise [ArgumentError] if the encryption method is unknown.
|
56
62
|
def self.valid_sha256_response?(user, config, challenge, response)
|
57
63
|
self.new(user, config).valid_sha256_response?(challenge, response)
|
58
64
|
end
|
59
|
-
|
60
|
-
#Compares the password with the user's password by encrypting the password passed in
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
65
|
+
|
66
|
+
# Compares the password with the user's password by encrypting the password passed in
|
67
|
+
#
|
68
|
+
# @param password [String] The clear text password
|
69
|
+
# @return [Boolean] True if the passwords match
|
70
|
+
# @raise [ArgumentError] if the encryption method is unknown.
|
64
71
|
def valid?(ct_password)
|
65
|
-
ignore,encryption,iv,
|
66
|
-
encryption = 'DES' if ignore != '' #No $'s in DES password, so ignore has text.
|
72
|
+
ignore, encryption, iv, _password = @password.split('$')
|
73
|
+
encryption = 'DES' if ignore != '' # No $'s in DES password, so ignore has text.
|
67
74
|
case encryption
|
68
|
-
when 'ct'
|
69
|
-
when 'aes256'
|
70
|
-
when 'DES'
|
71
|
-
when 'MD5','1','SHA256','5','SHA512','6'
|
75
|
+
when 'ct' then return ct_password == @password
|
76
|
+
when 'aes256' then return encrypt(ct_password, encryption, iv) == @password
|
77
|
+
when 'DES' then return UnixCrypt.valid?(ct_password, @password)
|
78
|
+
when 'MD5', '1', 'SHA256', '5', 'SHA512', '6' then return UnixCrypt.valid?(ct_password, @password) # rubocop: disable Lint/DuplicateBranch
|
72
79
|
else raise ArgumentError, "Unsupported encryption algorithm $#{encryption}"
|
73
80
|
end
|
74
81
|
end
|
75
|
-
|
76
|
-
#Adds a user to the password file
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
|
82
|
+
|
83
|
+
# Adds a user to the password file
|
84
|
+
#
|
85
|
+
# @param user [String] New user name. Raises an error if the user exists
|
86
|
+
# @param password [String] Clear text password. Raises an error if this is nil or ''
|
87
|
+
# @note Modifies the password file.
|
88
|
+
# @raise [IndexError] if the user entry exists.
|
89
|
+
# @raise [ArgumentError] if the password is nil or empty.
|
90
|
+
def self.add_user(user, password, config)
|
83
91
|
user_record = self.new(user, config, true)
|
84
|
-
raise IndexError, "User \"#{user}\" is already present"
|
85
|
-
raise ArgumentError, "Password can't be empty" if password
|
92
|
+
raise IndexError, "User \"#{user}\" is already present" if user_record.password != nil
|
93
|
+
raise ArgumentError, "Password can't be empty" if password.nil? || password == ''
|
94
|
+
|
86
95
|
user_record.set_password(password)
|
87
96
|
user_record.save
|
88
97
|
end
|
89
|
-
|
90
|
-
#Saves changes or a new user entry into the password file
|
98
|
+
|
99
|
+
# Saves changes or a new user entry into the password file
|
100
|
+
#
|
91
101
|
def save
|
92
102
|
loadfile
|
93
103
|
@pwent[@user] = @password
|
94
104
|
writefile
|
95
105
|
end
|
96
|
-
|
97
|
-
#Outputs password file entry as a string
|
98
|
-
#
|
106
|
+
|
107
|
+
# Outputs password file entry as a string
|
108
|
+
#
|
109
|
+
# @return [String] password file entry.
|
99
110
|
def to_s
|
100
111
|
"#{@user}:#{@password}"
|
101
112
|
end
|
102
113
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
114
|
+
# Fetch a password file entry by user's name
|
115
|
+
#
|
116
|
+
# @param user [String] user name
|
117
|
+
# @return [Boolean] True if user entry exists
|
118
|
+
private def getpwnam(user)
|
119
|
+
loadfile
|
120
|
+
@user = user
|
121
|
+
@password = @pwent[@user]
|
122
|
+
return @password != nil # i.e. Found a user entry
|
123
|
+
end
|
124
|
+
|
125
|
+
# Read the password file into the @pwent hash
|
126
|
+
#
|
127
|
+
private def loadfile
|
128
|
+
@pwent = {}
|
129
|
+
File.open(@config.passwordFile, 'r') do |fd|
|
130
|
+
fd.each do |line|
|
131
|
+
tokens = line.chomp.split(/:/)
|
132
|
+
@pwent[tokens[0]] = tokens[1] if tokens[0] != ''
|
133
|
+
end
|
123
134
|
end
|
124
135
|
end
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
136
|
+
|
137
|
+
# Overwrite the password file from the @pwent hash
|
138
|
+
#
|
139
|
+
private def writefile
|
140
|
+
File.open(@config.passwordFile, 'w+') do |fd|
|
141
|
+
@pwent.each do |k, v|
|
142
|
+
fd.puts "#{k}:#{v}"
|
143
|
+
end
|
132
144
|
end
|
133
145
|
end
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
#
|
139
|
-
|
146
|
+
|
147
|
+
# Encrypts a clear text password
|
148
|
+
#
|
149
|
+
# @param password [String] The clear text password
|
150
|
+
# @param challenge [String] Norm
|
151
|
+
# @raise [ArgumentError] if the encryption algorithm isn't known.
|
152
|
+
private def encrypt(password, algorithm = 'aes256', pwd_iv = nil)
|
140
153
|
case algorithm
|
141
|
-
when
|
142
|
-
password,
|
154
|
+
when 'aes256'
|
155
|
+
password, _key, iv = WIKK::AES_256.cipher_to_s(password, @config.key, pwd_iv)
|
143
156
|
return "$aes256$#{iv}$#{password}"
|
144
|
-
when
|
157
|
+
when 'DES'
|
145
158
|
return UnixCrypt::DES.build(password)
|
146
|
-
when
|
159
|
+
when 'MD5', '1' # Unix password digest, which is multiple hashes
|
147
160
|
return UnixCrypt::MD5.build(password)
|
148
|
-
when
|
161
|
+
when 'SHA256', '5' # Unix password digest, which is multiple hashes
|
149
162
|
return UnixCrypt::SHA256.build(password)
|
150
|
-
when
|
163
|
+
when 'SHA512', '6' # Unix password digest, which is multiple hashes
|
151
164
|
return UnixCrypt::SHA512.build(password)
|
152
|
-
when 'ct' #ct == clear text
|
165
|
+
when 'ct' # ct == clear text
|
153
166
|
return "$ct$$#{password}"
|
154
167
|
else
|
155
168
|
raise ArgumentError, "Unsupported Encryption algorithm #{@config.encryption}"
|
156
169
|
end
|
157
170
|
end
|
158
|
-
|
159
|
-
#Decrypts @password, if this is possible
|
160
|
-
#
|
161
|
-
#
|
162
|
-
|
163
|
-
|
171
|
+
|
172
|
+
# Decrypts @password, if this is possible
|
173
|
+
#
|
174
|
+
# @return [String] the clear text password
|
175
|
+
# @raise [ArgumentError] if the encryption type can't be decrypted.
|
176
|
+
private def decrypt
|
177
|
+
_ignore, encryption, iv, password = @password.split('$')
|
164
178
|
case encryption
|
165
|
-
when 'ct'
|
179
|
+
when 'ct' then password
|
166
180
|
when 'aes256'
|
167
|
-
ct_password,
|
181
|
+
ct_password, _ct_key, _ct_iv = WIKK::AES_256.decrypt(password, true, @config.key, iv)
|
168
182
|
return ct_password
|
169
183
|
else
|
170
184
|
raise ArgumentError, "Unsupported decryption algorithm #{@config.encryption}"
|
@@ -172,8 +186,3 @@ module WIKK
|
|
172
186
|
end
|
173
187
|
end
|
174
188
|
end
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
metadata
CHANGED
@@ -1,33 +1,22 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wikk_password
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Burrowes
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: unix-crypt
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 1.3.0
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 1.3.0
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: wikk_aes_256
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
30
16
|
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.1'
|
31
20
|
- - ">="
|
32
21
|
- !ruby/object:Gem::Version
|
33
22
|
version: 0.1.4
|
@@ -35,6 +24,9 @@ dependencies:
|
|
35
24
|
prerelease: false
|
36
25
|
version_requirements: !ruby/object:Gem::Requirement
|
37
26
|
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.1'
|
38
30
|
- - ">="
|
39
31
|
- !ruby/object:Gem::Version
|
40
32
|
version: 0.1.4
|
@@ -44,28 +36,28 @@ dependencies:
|
|
44
36
|
requirements:
|
45
37
|
- - ">="
|
46
38
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.1.
|
39
|
+
version: 0.1.3
|
48
40
|
type: :development
|
49
41
|
prerelease: false
|
50
42
|
version_requirements: !ruby/object:Gem::Requirement
|
51
43
|
requirements:
|
52
44
|
- - ">="
|
53
45
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.1.
|
46
|
+
version: 0.1.3
|
55
47
|
- !ruby/object:Gem::Dependency
|
56
48
|
name: hoe
|
57
49
|
requirement: !ruby/object:Gem::Requirement
|
58
50
|
requirements:
|
59
51
|
- - "~>"
|
60
52
|
- !ruby/object:Gem::Version
|
61
|
-
version: '3.
|
53
|
+
version: '3.23'
|
62
54
|
type: :development
|
63
55
|
prerelease: false
|
64
56
|
version_requirements: !ruby/object:Gem::Requirement
|
65
57
|
requirements:
|
66
58
|
- - "~>"
|
67
59
|
- !ruby/object:Gem::Version
|
68
|
-
version: '3.
|
60
|
+
version: '3.23'
|
69
61
|
description: |-
|
70
62
|
Reads/writes password file entries of format user:password and provides tests for password validity.
|
71
63
|
Works with standard unix password types, clear text for testing, and decryptable AES_256_CBC.
|
@@ -82,12 +74,17 @@ files:
|
|
82
74
|
- Manifest.txt
|
83
75
|
- README.md
|
84
76
|
- Rakefile
|
77
|
+
- lib/unix-crypt/lib/base.rb
|
78
|
+
- lib/unix-crypt/lib/des.rb
|
79
|
+
- lib/unix-crypt/lib/md5.rb
|
80
|
+
- lib/unix-crypt/lib/sha.rb
|
81
|
+
- lib/unix-crypt/unix_crypt.rb
|
85
82
|
- lib/wikk_password.rb
|
86
|
-
homepage:
|
83
|
+
homepage: https://wikarekare.github.io/wikk_password/
|
87
84
|
licenses:
|
88
85
|
- MIT
|
89
86
|
metadata: {}
|
90
|
-
post_install_message:
|
87
|
+
post_install_message:
|
91
88
|
rdoc_options:
|
92
89
|
- "--markup"
|
93
90
|
- markdown
|
@@ -108,9 +105,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
105
|
- !ruby/object:Gem::Version
|
109
106
|
version: '0'
|
110
107
|
requirements: []
|
111
|
-
|
112
|
-
|
113
|
-
signing_key:
|
108
|
+
rubygems_version: 3.2.22
|
109
|
+
signing_key:
|
114
110
|
specification_version: 4
|
115
111
|
summary: Reads/writes password file entries of format user:password and provides tests
|
116
112
|
for password validity
|