rubyntlm 0.6.3 → 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.devcontainer/devcontainer.json +22 -0
- data/.github/dependabot.yml +10 -0
- data/.github/workflows/build.yml +24 -0
- data/CHANGELOG.md +14 -1
- data/README.md +1 -1
- data/lib/net/ntlm/client/session.rb +23 -33
- data/lib/net/ntlm/encode_util.rb +1 -1
- data/lib/net/ntlm/md4.rb +80 -0
- data/lib/net/ntlm/message.rb +16 -8
- data/lib/net/ntlm/rc4.rb +59 -0
- data/lib/net/ntlm/version.rb +1 -1
- data/lib/net/ntlm.rb +27 -11
- data/rubyntlm.gemspec +4 -2
- data/spec/lib/net/ntlm/client/session_spec.rb +19 -0
- data/spec/lib/net/ntlm/message/type3_spec.rb +42 -0
- data/spec/lib/net/ntlm_spec.rb +8 -0
- metadata +25 -6
- data/.travis.yml +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12b18439c86b30b978043850938e3ec611230d2e0783828d6db337dfe4ea97ad
|
4
|
+
data.tar.gz: 872a21844c21c9f64815abd312156c3fcddb8ebedc5185ec128106b6714b8521
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03df0639b70648b2db81684060ce732d46f21a392590f4995ddb4c97399036ffc8d9a47fd1460b64441a34bebdbabfbd1f3d625bf1246a9e178a0901770f0ee5
|
7
|
+
data.tar.gz: e3ff9341eb1738c501fe90d9456a8203e5771af2c459bb53f0168e24cbdd549540a0369d9a83f2864dce4fac86595cca4c7207b88f11a1b843d9fdbf52b59b20
|
@@ -0,0 +1,22 @@
|
|
1
|
+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
2
|
+
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby
|
3
|
+
{
|
4
|
+
"name": "Ruby",
|
5
|
+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
6
|
+
"image": "mcr.microsoft.com/devcontainers/ruby:1-3.3-bullseye"
|
7
|
+
|
8
|
+
// Features to add to the dev container. More info: https://containers.dev/features.
|
9
|
+
// "features": {},
|
10
|
+
|
11
|
+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
12
|
+
// "forwardPorts": [],
|
13
|
+
|
14
|
+
// Use 'postCreateCommand' to run commands after the container is created.
|
15
|
+
// "postCreateCommand": "ruby --version",
|
16
|
+
|
17
|
+
// Configure tool-specific properties.
|
18
|
+
// "customizations": {},
|
19
|
+
|
20
|
+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
21
|
+
// "remoteUser": "root"
|
22
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
---
|
2
|
+
name: build
|
3
|
+
|
4
|
+
"on":
|
5
|
+
pull_request:
|
6
|
+
push:
|
7
|
+
branches:
|
8
|
+
- master
|
9
|
+
|
10
|
+
jobs:
|
11
|
+
unit:
|
12
|
+
strategy:
|
13
|
+
fail-fast: false
|
14
|
+
matrix:
|
15
|
+
os: [ubuntu-latest, windows-2019]
|
16
|
+
ruby: ['2.6', '2.7', '3.0', '3.1', '3.2', '3.3']
|
17
|
+
runs-on: ${{ matrix.os }}
|
18
|
+
steps:
|
19
|
+
- uses: actions/checkout@v4
|
20
|
+
- uses: ruby/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
ruby-version: ${{ matrix.ruby }}
|
23
|
+
bundler-cache: true
|
24
|
+
- run: bundle exec rake
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.6.5 (2024-06-11)
|
4
|
+
|
5
|
+
* Update available NegotiateFlags during authentication
|
6
|
+
* Fix NTLMv2 hash when username contains non-ASCII characters by @cdelafuente-r7 in https://github.com/WinRb/rubyntlm/pull/56
|
7
|
+
|
8
|
+
## 0.6.4 (2024-06-06)
|
9
|
+
|
10
|
+
* Fix applying DES-CBC when using OpenSSL 3 by @paulvt in https://github.com/WinRb/rubyntlm/pull/51
|
11
|
+
* Add dependency to `base64` gem by @yahonda in https://github.com/WinRb/rubyntlm/pull/62
|
12
|
+
* Avoid usage of legacy algorithms on libssl-3.0+ by @larskanis in https://github.com/WinRb/rubyntlm/pull/53
|
13
|
+
* Add anonymous authentication support by @zeroSteiner in https://github.com/WinRb/rubyntlm/pull/45
|
14
|
+
* Update minimum supported ruby to 2.6. Add support for ruby 3.2 and 3.3
|
15
|
+
|
3
16
|
## [0.6.3](https://github.com/WinRb/rubyntlm/tree/0.6.3) (2021-01-26)
|
4
17
|
[Full Changelog](https://github.com/WinRb/rubyntlm/compare/v0.6.2...0.6.3)
|
5
18
|
|
@@ -124,4 +137,4 @@
|
|
124
137
|
## [v0.2.0](https://github.com/WinRb/rubyntlm/tree/v0.2.0) (2013-03-22)
|
125
138
|
|
126
139
|
|
127
|
-
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
|
140
|
+
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Ruby/NTLM -- NTLM Authentication Library for Ruby
|
2
2
|
|
3
|
-
[![
|
3
|
+
[![CI status](https://github.com/WinRb/rubyntlm/actions/workflows/build.yml/badge.svg)](https://github.com/WinRb/rubyntlm/actions/workflows/build.yml)
|
4
4
|
|
5
5
|
Ruby/NTLM provides message creator and parser for the NTLM authentication.
|
6
6
|
|
@@ -26,8 +26,8 @@ module Net
|
|
26
26
|
def authenticate!
|
27
27
|
calculate_user_session_key!
|
28
28
|
type3_opts = {
|
29
|
-
:lm_response => lmv2_resp,
|
30
|
-
:ntlm_response => ntlmv2_resp,
|
29
|
+
:lm_response => is_anonymous? ? "\x00".b : lmv2_resp,
|
30
|
+
:ntlm_response => is_anonymous? ? '' : ntlmv2_resp,
|
31
31
|
:domain => domain,
|
32
32
|
:user => username,
|
33
33
|
:workstation => workstation,
|
@@ -36,11 +36,8 @@ module Net
|
|
36
36
|
t3 = Message::Type3.create type3_opts
|
37
37
|
if negotiate_key_exchange?
|
38
38
|
t3.enable(:session_key)
|
39
|
-
rc4 =
|
40
|
-
rc4.encrypt
|
41
|
-
rc4.key = user_session_key
|
42
|
-
sk = rc4.update exported_session_key
|
43
|
-
sk << rc4.final
|
39
|
+
rc4 = Net::NTLM::Rc4.new(user_session_key)
|
40
|
+
sk = rc4.encrypt exported_session_key
|
44
41
|
t3.session_key = sk
|
45
42
|
end
|
46
43
|
t3
|
@@ -50,7 +47,7 @@ module Net
|
|
50
47
|
@exported_session_key ||=
|
51
48
|
begin
|
52
49
|
if negotiate_key_exchange?
|
53
|
-
OpenSSL::
|
50
|
+
OpenSSL::Random.random_bytes(16)
|
54
51
|
else
|
55
52
|
user_session_key
|
56
53
|
end
|
@@ -61,8 +58,7 @@ module Net
|
|
61
58
|
seq = sequence
|
62
59
|
sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, client_sign_key, "#{seq}#{message}")[0..7]
|
63
60
|
if negotiate_key_exchange?
|
64
|
-
sig = client_cipher.
|
65
|
-
sig << client_cipher.final
|
61
|
+
sig = client_cipher.encrypt sig
|
66
62
|
end
|
67
63
|
"#{VERSION_MAGIC}#{sig}#{seq}"
|
68
64
|
end
|
@@ -71,20 +67,21 @@ module Net
|
|
71
67
|
seq = signature[-4..-1]
|
72
68
|
sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, server_sign_key, "#{seq}#{message}")[0..7]
|
73
69
|
if negotiate_key_exchange?
|
74
|
-
sig = server_cipher.
|
75
|
-
sig << server_cipher.final
|
70
|
+
sig = server_cipher.encrypt sig
|
76
71
|
end
|
77
72
|
"#{VERSION_MAGIC}#{sig}#{seq}" == signature
|
78
73
|
end
|
79
74
|
|
80
75
|
def seal_message(message)
|
81
|
-
|
82
|
-
emessage + client_cipher.final
|
76
|
+
client_cipher.encrypt(message)
|
83
77
|
end
|
84
78
|
|
85
79
|
def unseal_message(emessage)
|
86
|
-
|
87
|
-
|
80
|
+
server_cipher.encrypt(emessage)
|
81
|
+
end
|
82
|
+
|
83
|
+
def is_anonymous?
|
84
|
+
username == '' && password == ''
|
88
85
|
end
|
89
86
|
|
90
87
|
private
|
@@ -123,23 +120,11 @@ module Net
|
|
123
120
|
end
|
124
121
|
|
125
122
|
def client_cipher
|
126
|
-
@client_cipher ||=
|
127
|
-
begin
|
128
|
-
rc4 = OpenSSL::Cipher.new("rc4")
|
129
|
-
rc4.encrypt
|
130
|
-
rc4.key = client_seal_key
|
131
|
-
rc4
|
132
|
-
end
|
123
|
+
@client_cipher ||= Net::NTLM::Rc4.new(client_seal_key)
|
133
124
|
end
|
134
125
|
|
135
126
|
def server_cipher
|
136
|
-
@server_cipher ||=
|
137
|
-
begin
|
138
|
-
rc4 = OpenSSL::Cipher.new("rc4")
|
139
|
-
rc4.decrypt
|
140
|
-
rc4.key = server_seal_key
|
141
|
-
rc4
|
142
|
-
end
|
127
|
+
@server_cipher ||= Net::NTLM::Rc4.new(server_seal_key)
|
143
128
|
end
|
144
129
|
|
145
130
|
def client_challenge
|
@@ -157,7 +142,8 @@ module Net
|
|
157
142
|
end
|
158
143
|
|
159
144
|
def use_oem_strings?
|
160
|
-
|
145
|
+
# @see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832
|
146
|
+
!challenge_message.has_flag?(:UNICODE) && challenge_message.has_flag?(:OEM)
|
161
147
|
end
|
162
148
|
|
163
149
|
def negotiate_key_exchange?
|
@@ -193,7 +179,12 @@ module Net
|
|
193
179
|
end
|
194
180
|
|
195
181
|
def calculate_user_session_key!
|
196
|
-
|
182
|
+
if is_anonymous?
|
183
|
+
# see MS-NLMP section 3.4
|
184
|
+
@user_session_key = "\x00".b * 16
|
185
|
+
else
|
186
|
+
@user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
|
187
|
+
end
|
197
188
|
end
|
198
189
|
|
199
190
|
def lmv2_resp
|
@@ -231,7 +222,6 @@ module Net
|
|
231
222
|
end
|
232
223
|
end
|
233
224
|
end
|
234
|
-
|
235
225
|
end
|
236
226
|
end
|
237
227
|
end
|
data/lib/net/ntlm/encode_util.rb
CHANGED
@@ -39,7 +39,7 @@ module NTLM
|
|
39
39
|
# the function will convert the string bytes to UTF-16LE and note the encoding as UTF-8 so that byte
|
40
40
|
# concatination works seamlessly.
|
41
41
|
def self.encode_utf16le(str)
|
42
|
-
str.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('
|
42
|
+
str.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('ASCII-8BIT')
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
data/lib/net/ntlm/md4.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Net
|
4
|
+
module NTLM
|
5
|
+
|
6
|
+
class Md4
|
7
|
+
|
8
|
+
begin
|
9
|
+
OpenSSL::Digest::MD4.digest("")
|
10
|
+
rescue
|
11
|
+
# libssl-3.0+ doesn't support legacy MD4 -> use our own implementation
|
12
|
+
|
13
|
+
require 'stringio'
|
14
|
+
|
15
|
+
def self.digest(string)
|
16
|
+
# functions
|
17
|
+
mask = (1 << 32) - 1
|
18
|
+
f = proc {|x, y, z| x & y | x.^(mask) & z}
|
19
|
+
g = proc {|x, y, z| x & y | x & z | y & z}
|
20
|
+
h = proc {|x, y, z| x ^ y ^ z}
|
21
|
+
r = proc {|v, s| (v << s).&(mask) | (v.&(mask) >> (32 - s))}
|
22
|
+
|
23
|
+
# initial hash
|
24
|
+
a, b, c, d = 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476
|
25
|
+
|
26
|
+
bit_len = string.size << 3
|
27
|
+
string += "\x80"
|
28
|
+
while (string.size % 64) != 56
|
29
|
+
string += "\0"
|
30
|
+
end
|
31
|
+
string = string.force_encoding('ascii-8bit') + [bit_len & mask, bit_len >> 32].pack("V2")
|
32
|
+
|
33
|
+
if string.size % 64 != 0
|
34
|
+
fail "failed to pad to correct length"
|
35
|
+
end
|
36
|
+
|
37
|
+
io = StringIO.new(string)
|
38
|
+
block = ""
|
39
|
+
|
40
|
+
while io.read(64, block)
|
41
|
+
x = block.unpack("V16")
|
42
|
+
|
43
|
+
# Process this block.
|
44
|
+
aa, bb, cc, dd = a, b, c, d
|
45
|
+
[0, 4, 8, 12].each {|i|
|
46
|
+
a = r[a + f[b, c, d] + x[i], 3]; i += 1
|
47
|
+
d = r[d + f[a, b, c] + x[i], 7]; i += 1
|
48
|
+
c = r[c + f[d, a, b] + x[i], 11]; i += 1
|
49
|
+
b = r[b + f[c, d, a] + x[i], 19]
|
50
|
+
}
|
51
|
+
[0, 1, 2, 3].each {|i|
|
52
|
+
a = r[a + g[b, c, d] + x[i] + 0x5a827999, 3]; i += 4
|
53
|
+
d = r[d + g[a, b, c] + x[i] + 0x5a827999, 5]; i += 4
|
54
|
+
c = r[c + g[d, a, b] + x[i] + 0x5a827999, 9]; i += 4
|
55
|
+
b = r[b + g[c, d, a] + x[i] + 0x5a827999, 13]
|
56
|
+
}
|
57
|
+
[0, 2, 1, 3].each {|i|
|
58
|
+
a = r[a + h[b, c, d] + x[i] + 0x6ed9eba1, 3]; i += 8
|
59
|
+
d = r[d + h[a, b, c] + x[i] + 0x6ed9eba1, 9]; i -= 4
|
60
|
+
c = r[c + h[d, a, b] + x[i] + 0x6ed9eba1, 11]; i += 8
|
61
|
+
b = r[b + h[c, d, a] + x[i] + 0x6ed9eba1, 15]
|
62
|
+
}
|
63
|
+
a = (a + aa) & mask
|
64
|
+
b = (b + bb) & mask
|
65
|
+
c = (c + cc) & mask
|
66
|
+
d = (d + dd) & mask
|
67
|
+
end
|
68
|
+
|
69
|
+
[a, b, c, d].pack("V4")
|
70
|
+
end
|
71
|
+
|
72
|
+
else
|
73
|
+
# Openssl/libssl provides MD4, so we can use it.
|
74
|
+
def self.digest(string)
|
75
|
+
OpenSSL::Digest::MD4.digest(string)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/net/ntlm/message.rb
CHANGED
@@ -3,28 +3,36 @@ module NTLM
|
|
3
3
|
|
4
4
|
SSP_SIGN = "NTLMSSP\0"
|
5
5
|
|
6
|
+
# See [2.2.2.5 NEGOTIATE](https://msdn.microsoft.com/en-us/library/cc236650.aspx)
|
6
7
|
FLAGS = {
|
7
8
|
:UNICODE => 0x00000001,
|
8
9
|
:OEM => 0x00000002,
|
9
10
|
:REQUEST_TARGET => 0x00000004,
|
10
|
-
:MBZ9 => 0x00000008,
|
11
11
|
:SIGN => 0x00000010,
|
12
12
|
:SEAL => 0x00000020,
|
13
13
|
:NEG_DATAGRAM => 0x00000040,
|
14
|
-
:
|
14
|
+
:NEG_LM_KEY => 0x00000080,
|
15
15
|
:NTLM => 0x00000200,
|
16
|
-
:
|
17
|
-
:MBZ7 => 0x00000800,
|
16
|
+
:NEG_ANONYMOUS => 0x00000800,
|
18
17
|
:DOMAIN_SUPPLIED => 0x00001000,
|
19
18
|
:WORKSTATION_SUPPLIED => 0x00002000,
|
20
|
-
:LOCAL_CALL => 0x00004000,
|
21
19
|
:ALWAYS_SIGN => 0x00008000,
|
22
20
|
:TARGET_TYPE_DOMAIN => 0x00010000,
|
21
|
+
:TARGET_TYPE_SERVER => 0x00020000,
|
23
22
|
:NTLM2_KEY => 0x00080000,
|
23
|
+
:NEG_IDENTIFY => 0x00100000,
|
24
|
+
:NON_NT_SESSION_KEY => 0x00400000,
|
24
25
|
:TARGET_INFO => 0x00800000,
|
26
|
+
:NEG_VERSION => 0x02000000,
|
25
27
|
:KEY128 => 0x20000000,
|
26
28
|
:KEY_EXCHANGE => 0x40000000,
|
27
|
-
:KEY56 => 0x80000000
|
29
|
+
:KEY56 => 0x80000000,
|
30
|
+
# Undocumented flags:
|
31
|
+
:MBZ9 => 0x00000008,
|
32
|
+
:NETWARE => 0x00000100,
|
33
|
+
:NEG_NT_ONLY => 0x00000400,
|
34
|
+
:MBZ7 => 0x00000800, # alias for :NEG_ANONYMOUS
|
35
|
+
:LOCAL_CALL => 0x00004000,
|
28
36
|
}.freeze
|
29
37
|
|
30
38
|
FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
|
@@ -35,7 +43,6 @@ module NTLM
|
|
35
43
|
:TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
|
36
44
|
}
|
37
45
|
|
38
|
-
|
39
46
|
# @private false
|
40
47
|
class Message < FieldSet
|
41
48
|
class << Message
|
@@ -87,7 +94,7 @@ module NTLM
|
|
87
94
|
|
88
95
|
def serialize
|
89
96
|
deflag
|
90
|
-
super + security_buffers.map{|n, f| f.value}.join
|
97
|
+
super + security_buffers.map{|n, f| f.value + (has_flag?(:UNICODE) ? "\x00".b * (f.value.length % 2) : '')}.join
|
91
98
|
end
|
92
99
|
|
93
100
|
def encode64
|
@@ -117,6 +124,7 @@ module NTLM
|
|
117
124
|
security_buffers.inject(head_size){|cur, a|
|
118
125
|
a[1].offset = cur
|
119
126
|
cur += a[1].data_size
|
127
|
+
has_flag?(:UNICODE) ? cur + cur % 2 : cur
|
120
128
|
}
|
121
129
|
end
|
122
130
|
|
data/lib/net/ntlm/rc4.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Net
|
4
|
+
module NTLM
|
5
|
+
|
6
|
+
begin
|
7
|
+
OpenSSL::Cipher.new("rc4")
|
8
|
+
rescue
|
9
|
+
# libssl-3.0+ doesn't support legacy Rc4 -> use our own implementation
|
10
|
+
|
11
|
+
class Rc4
|
12
|
+
def initialize(str)
|
13
|
+
raise ArgumentError, "RC4: Key supplied is blank" if str.eql?('')
|
14
|
+
initialize_state(str)
|
15
|
+
@q1, @q2 = 0, 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def encrypt(text)
|
19
|
+
text.each_byte.map do |b|
|
20
|
+
@q1 = (@q1 + 1) % 256
|
21
|
+
@q2 = (@q2 + @state[@q1]) % 256
|
22
|
+
@state[@q1], @state[@q2] = @state[@q2], @state[@q1]
|
23
|
+
b ^ @state[(@state[@q1] + @state[@q2]) % 256]
|
24
|
+
end.pack("C*")
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# The initial state which is then modified by the key-scheduling algorithm
|
30
|
+
INITIAL_STATE = (0..255).to_a
|
31
|
+
|
32
|
+
# Performs the key-scheduling algorithm to initialize the state.
|
33
|
+
def initialize_state(key)
|
34
|
+
i = j = 0
|
35
|
+
@state = INITIAL_STATE.dup
|
36
|
+
key_length = key.length
|
37
|
+
while i < 256
|
38
|
+
j = (j + @state[i] + key.getbyte(i % key_length)) % 256
|
39
|
+
@state[i], @state[j] = @state[j], @state[i]
|
40
|
+
i += 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
else
|
46
|
+
# Openssl/libssl provides RC4, so we can use it.
|
47
|
+
class Rc4
|
48
|
+
def initialize(str)
|
49
|
+
@ci = OpenSSL::Cipher.new("rc4")
|
50
|
+
@ci.key = str
|
51
|
+
end
|
52
|
+
|
53
|
+
def encrypt(text)
|
54
|
+
@ci.update(text) + @ci.final
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/net/ntlm/version.rb
CHANGED
data/lib/net/ntlm.rb
CHANGED
@@ -59,6 +59,8 @@ require 'net/ntlm/message/type2'
|
|
59
59
|
require 'net/ntlm/message/type3'
|
60
60
|
|
61
61
|
require 'net/ntlm/encode_util'
|
62
|
+
require 'net/ntlm/md4'
|
63
|
+
require 'net/ntlm/rc4'
|
62
64
|
|
63
65
|
require 'net/ntlm/client'
|
64
66
|
require 'net/ntlm/channel_binding'
|
@@ -94,10 +96,10 @@ module Net
|
|
94
96
|
end
|
95
97
|
end
|
96
98
|
|
97
|
-
#
|
99
|
+
# Convert the value to a 64-bit little-endian integer
|
98
100
|
# @param [String] val The string to convert
|
99
101
|
def pack_int64le(val)
|
100
|
-
|
102
|
+
[val & 0x00000000ffffffff, val >> 32].pack("V2")
|
101
103
|
end
|
102
104
|
|
103
105
|
# Builds an array of strings that are 7 characters long
|
@@ -111,7 +113,8 @@ module Net
|
|
111
113
|
ret
|
112
114
|
end
|
113
115
|
|
114
|
-
#
|
116
|
+
# Each byte of a DES key contains seven bits of key material and one odd-parity bit.
|
117
|
+
# The parity bit should be set so that there are an odd number of 1 bits in each byte.
|
115
118
|
# @param [String] str String to generate keys for
|
116
119
|
# @api private
|
117
120
|
def gen_keys(str)
|
@@ -123,22 +126,24 @@ module Net
|
|
123
126
|
end
|
124
127
|
|
125
128
|
def apply_des(plain, keys)
|
126
|
-
dec = OpenSSL::Cipher.new("des-cbc").encrypt
|
127
|
-
dec.padding = 0
|
128
129
|
keys.map {|k|
|
129
|
-
|
130
|
+
# Spec requires des-cbc, but openssl 3 does not support single des
|
131
|
+
# by default, so just do triple DES (EDE) with the same key
|
132
|
+
dec = OpenSSL::Cipher.new("des-ede-cbc").encrypt
|
133
|
+
dec.padding = 0
|
134
|
+
dec.key = k + k
|
130
135
|
dec.update(plain) + dec.final
|
131
136
|
}
|
132
137
|
end
|
133
138
|
|
134
|
-
# Generates a
|
139
|
+
# Generates a {https://en.wikipedia.org/wiki/LAN_Manager LAN Manager Hash}
|
135
140
|
# @param [String] password The password to base the hash on
|
136
141
|
def lm_hash(password)
|
137
142
|
keys = gen_keys password.upcase.ljust(14, "\0")
|
138
143
|
apply_des(LM_MAGIC, keys).join
|
139
144
|
end
|
140
145
|
|
141
|
-
# Generate
|
146
|
+
# Generate an NTLM Hash
|
142
147
|
# @param [String] password The password to base the hash on
|
143
148
|
# @option opt :unicode (false) Unicode encode the password
|
144
149
|
def ntlm_hash(password, opt = {})
|
@@ -146,14 +151,14 @@ module Net
|
|
146
151
|
unless opt[:unicode]
|
147
152
|
pwd = EncodeUtil.encode_utf16le(pwd)
|
148
153
|
end
|
149
|
-
|
154
|
+
Net::NTLM::Md4.digest pwd
|
150
155
|
end
|
151
156
|
|
152
157
|
# Generate a NTLMv2 Hash
|
153
158
|
# @param [String] user The username
|
154
159
|
# @param [String] password The password
|
155
160
|
# @param [String] target The domain or workstation to authenticate to
|
156
|
-
# @option opt :unicode (false) Unicode encode the domain
|
161
|
+
# @option [Boolean] opt :unicode (false) Unicode encode the domain.
|
157
162
|
def ntlmv2_hash(user, password, target, opt={})
|
158
163
|
if is_ntlm_hash? password
|
159
164
|
decoded_password = EncodeUtil.decode_utf16le(password)
|
@@ -161,7 +166,18 @@ module Net
|
|
161
166
|
else
|
162
167
|
ntlmhash = ntlm_hash(password, opt)
|
163
168
|
end
|
164
|
-
|
169
|
+
|
170
|
+
if opt[:unicode]
|
171
|
+
# Uppercase operation on username containing non-ASCI characters
|
172
|
+
# after behing unicode encoded with `EncodeUtil.encode_utf16le`
|
173
|
+
# doesn't play well. Upcase should be done before encoding.
|
174
|
+
user_upcase = EncodeUtil.decode_utf16le(user).upcase
|
175
|
+
user_upcase = EncodeUtil.encode_utf16le(user_upcase)
|
176
|
+
else
|
177
|
+
user_upcase = user.upcase
|
178
|
+
end
|
179
|
+
userdomain = user_upcase + target
|
180
|
+
|
165
181
|
unless opt[:unicode]
|
166
182
|
userdomain = EncodeUtil.encode_utf16le(userdomain)
|
167
183
|
end
|
data/rubyntlm.gemspec
CHANGED
@@ -13,11 +13,10 @@ Gem::Specification.new do |s|
|
|
13
13
|
|
14
14
|
|
15
15
|
s.files = `git ls-files`.split($/)
|
16
|
-
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
16
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
17
|
s.require_paths = ["lib"]
|
19
18
|
|
20
|
-
s.required_ruby_version = '>=
|
19
|
+
s.required_ruby_version = '>= 2.6.0'
|
21
20
|
|
22
21
|
s.license = 'MIT'
|
23
22
|
|
@@ -26,4 +25,7 @@ Gem::Specification.new do |s|
|
|
26
25
|
s.add_development_dependency "rake"
|
27
26
|
s.add_development_dependency "rspec", ">= 2.11"
|
28
27
|
s.add_development_dependency "simplecov"
|
28
|
+
s.add_dependency "base64"
|
29
|
+
|
30
|
+
s.metadata["rubygems_mfa_required"] = "true"
|
29
31
|
end
|
@@ -65,4 +65,23 @@ describe Net::NTLM::Client::Session do
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
+
context 'when authenticating anonymously' do
|
69
|
+
let(:inst) { Net::NTLM::Client::Session.new(Net::NTLM::Client.new('', ''), t2_challenge) }
|
70
|
+
|
71
|
+
describe "#authenticate!" do
|
72
|
+
it "should set the response fields correctly" do
|
73
|
+
t3 = inst.authenticate!
|
74
|
+
expect(t3).to be_a(Net::NTLM::Message::Type3)
|
75
|
+
expect(t3.lm_response).to eq("\x00".b)
|
76
|
+
expect(t3.ntlm_response).to eq('')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#is_anonymous?" do
|
81
|
+
it "should be true" do
|
82
|
+
expect(inst.is_anonymous?).to be_truthy
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
68
87
|
end
|
@@ -222,4 +222,46 @@ describe Net::NTLM::Message::Type3 do
|
|
222
222
|
|
223
223
|
end
|
224
224
|
|
225
|
+
describe '#serialize' do
|
226
|
+
context 'when the username contains non-ASCI characters' do
|
227
|
+
let(:t3) {
|
228
|
+
t2 = Net::NTLM::Message::Type2.new
|
229
|
+
t2.response(
|
230
|
+
{
|
231
|
+
:user => 'Hélène',
|
232
|
+
:password => '123456',
|
233
|
+
:domain => ''
|
234
|
+
},
|
235
|
+
{
|
236
|
+
:ntlmv2 => true,
|
237
|
+
:workstation => 'testlab.local'
|
238
|
+
}
|
239
|
+
)
|
240
|
+
}
|
241
|
+
|
242
|
+
it 'serializes without error' do
|
243
|
+
expect { t3.serialize }.not_to raise_error
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
subject(:message) { described_class.create(opts) }
|
248
|
+
context 'with the UNICODE flag set' do
|
249
|
+
let(:opts) { {lm_response: "\x00".b, ntlm_response: '', domain: '', workstation: '', user: '', flag: Net::NTLM::DEFAULT_FLAGS[:TYPE3] | Net::NTLM::FLAGS[:UNICODE] } }
|
250
|
+
|
251
|
+
it 'should pad the domain field to a multiple of 2' do
|
252
|
+
message.serialize
|
253
|
+
expect(message[:domain][:offset].value % 2).to eq 0
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'should pad the user field to a multiple of 2' do
|
257
|
+
message.serialize
|
258
|
+
expect(message[:user][:offset].value % 2).to eq 0
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'should pad the workstation field to a multiple of 2' do
|
262
|
+
message.serialize
|
263
|
+
expect(message[:workstation][:offset].value % 2).to eq 0
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
225
267
|
end
|
data/spec/lib/net/ntlm_spec.rb
CHANGED
@@ -59,6 +59,14 @@ describe Net::NTLM do
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
+
context 'when the username contains non-ASCI characters' do
|
63
|
+
let(:user) { 'юзер' }
|
64
|
+
|
65
|
+
it 'should return the correct ntlmv2 hash' do
|
66
|
+
expect(Net::NTLM::ntlmv2_hash(user, passwd, domain, { unicode: true })).to eq(["a0f4b914a37faeaee884b6b04a20faf0"].pack("H*"))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
62
70
|
it 'should generate an lm_response' do
|
63
71
|
expect(Net::NTLM::lm_response(
|
64
72
|
{
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyntlm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kohei Kajimoto
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-06-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: github_changelog_generator
|
@@ -81,6 +81,20 @@ dependencies:
|
|
81
81
|
- - ">="
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: base64
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
84
98
|
description: Ruby/NTLM provides message creator and parser for the NTLM authentication.
|
85
99
|
email:
|
86
100
|
- koheik@gmail.com
|
@@ -89,9 +103,11 @@ executables: []
|
|
89
103
|
extensions: []
|
90
104
|
extra_rdoc_files: []
|
91
105
|
files:
|
106
|
+
- ".devcontainer/devcontainer.json"
|
107
|
+
- ".github/dependabot.yml"
|
108
|
+
- ".github/workflows/build.yml"
|
92
109
|
- ".gitignore"
|
93
110
|
- ".rspec"
|
94
|
-
- ".travis.yml"
|
95
111
|
- CHANGELOG.md
|
96
112
|
- Gemfile
|
97
113
|
- LICENSE
|
@@ -112,11 +128,13 @@ files:
|
|
112
128
|
- lib/net/ntlm/int16_le.rb
|
113
129
|
- lib/net/ntlm/int32_le.rb
|
114
130
|
- lib/net/ntlm/int64_le.rb
|
131
|
+
- lib/net/ntlm/md4.rb
|
115
132
|
- lib/net/ntlm/message.rb
|
116
133
|
- lib/net/ntlm/message/type0.rb
|
117
134
|
- lib/net/ntlm/message/type1.rb
|
118
135
|
- lib/net/ntlm/message/type2.rb
|
119
136
|
- lib/net/ntlm/message/type3.rb
|
137
|
+
- lib/net/ntlm/rc4.rb
|
120
138
|
- lib/net/ntlm/security_buffer.rb
|
121
139
|
- lib/net/ntlm/string.rb
|
122
140
|
- lib/net/ntlm/target_info.rb
|
@@ -152,7 +170,8 @@ files:
|
|
152
170
|
homepage: https://github.com/winrb/rubyntlm
|
153
171
|
licenses:
|
154
172
|
- MIT
|
155
|
-
metadata:
|
173
|
+
metadata:
|
174
|
+
rubygems_mfa_required: 'true'
|
156
175
|
post_install_message:
|
157
176
|
rdoc_options: []
|
158
177
|
require_paths:
|
@@ -161,14 +180,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
161
180
|
requirements:
|
162
181
|
- - ">="
|
163
182
|
- !ruby/object:Gem::Version
|
164
|
-
version:
|
183
|
+
version: 2.6.0
|
165
184
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
166
185
|
requirements:
|
167
186
|
- - ">="
|
168
187
|
- !ruby/object:Gem::Version
|
169
188
|
version: '0'
|
170
189
|
requirements: []
|
171
|
-
rubygems_version: 3.
|
190
|
+
rubygems_version: 3.5.3
|
172
191
|
signing_key:
|
173
192
|
specification_version: 4
|
174
193
|
summary: Ruby/NTLM library.
|