rubyntlm 0.6.6 → 0.6.7
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/CHANGELOG.md +4 -0
- data/README.md +48 -48
- data/lib/net/ntlm/blob.rb +11 -10
- data/lib/net/ntlm/channel_binding.rb +13 -9
- data/lib/net/ntlm/client/session.rb +34 -141
- data/lib/net/ntlm/client/session_crypto.rb +126 -0
- data/lib/net/ntlm/client.rb +8 -9
- data/lib/net/ntlm/encode_util.rb +39 -38
- data/lib/net/ntlm/exceptions.rb +3 -0
- data/lib/net/ntlm/field.rb +29 -31
- data/lib/net/ntlm/field_set.rb +39 -18
- data/lib/net/ntlm/int16_le.rb +8 -8
- data/lib/net/ntlm/int32_le.rb +8 -7
- data/lib/net/ntlm/int64_le.rb +8 -7
- data/lib/net/ntlm/md4.rb +102 -64
- data/lib/net/ntlm/message/type0.rb +4 -7
- data/lib/net/ntlm/message/type1.rb +8 -9
- data/lib/net/ntlm/message/type2.rb +87 -67
- data/lib/net/ntlm/message/type3.rb +43 -41
- data/lib/net/ntlm/message.rb +116 -111
- data/lib/net/ntlm/rc4.rb +51 -44
- data/lib/net/ntlm/response.rb +112 -0
- data/lib/net/ntlm/security_buffer.rb +13 -15
- data/lib/net/ntlm/string.rb +8 -7
- data/lib/net/ntlm/target_info.rb +52 -44
- data/lib/net/ntlm/version.rb +3 -1
- data/lib/net/ntlm.rb +37 -125
- data/lib/rubyntlm.rb +3 -1
- metadata +5 -44
- data/.devcontainer/devcontainer.json +0 -22
- data/.github/dependabot.yml +0 -10
- data/.github/workflows/build.yml +0 -55
- data/.gitignore +0 -11
- data/.rspec +0 -3
- data/Gemfile +0 -20
- data/Rakefile +0 -37
- data/examples/http.rb +0 -86
- data/examples/imap.rb +0 -73
- data/examples/smtp.rb +0 -94
- data/rubyntlm.gemspec +0 -28
- data/spec/lib/net/ntlm/blob_spec.rb +0 -14
- data/spec/lib/net/ntlm/channel_binding_spec.rb +0 -15
- data/spec/lib/net/ntlm/client/session_spec.rb +0 -85
- data/spec/lib/net/ntlm/client_spec.rb +0 -62
- data/spec/lib/net/ntlm/encode_util_spec.rb +0 -14
- data/spec/lib/net/ntlm/field_set_spec.rb +0 -31
- data/spec/lib/net/ntlm/field_spec.rb +0 -32
- data/spec/lib/net/ntlm/int16_le_spec.rb +0 -16
- data/spec/lib/net/ntlm/int32_le_spec.rb +0 -17
- data/spec/lib/net/ntlm/int64_le_spec.rb +0 -17
- data/spec/lib/net/ntlm/md4_spec.rb +0 -16
- data/spec/lib/net/ntlm/message/type0_spec.rb +0 -19
- data/spec/lib/net/ntlm/message/type1_spec.rb +0 -129
- data/spec/lib/net/ntlm/message/type2_spec.rb +0 -131
- data/spec/lib/net/ntlm/message/type3_spec.rb +0 -336
- data/spec/lib/net/ntlm/message_spec.rb +0 -15
- data/spec/lib/net/ntlm/security_buffer_spec.rb +0 -62
- data/spec/lib/net/ntlm/string_spec.rb +0 -70
- data/spec/lib/net/ntlm/target_info_spec.rb +0 -74
- data/spec/lib/net/ntlm/version_spec.rb +0 -26
- data/spec/lib/net/ntlm_spec.rb +0 -133
- data/spec/spec_helper.rb +0 -51
- data/spec/support/certificates/sha_256_hash.pem +0 -19
- data/spec/support/shared/examples/net/ntlm/field_shared.rb +0 -25
- data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +0 -239
- data/spec/support/shared/examples/net/ntlm/int_shared.rb +0 -43
- data/spec/support/shared/examples/net/ntlm/message_shared.rb +0 -35
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6c6428b007c4a1fde172235fd6ef82d25e15c578363d6bdcdfa53a69477c41bb
|
|
4
|
+
data.tar.gz: d5c6ad650e686e2b71b745e9446d3ab75a249447dd7982db3500291bdfc66d4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b162d138b966c7b15c3bd7ac926a3113be2f3e50f91ebabddd32bff9559c374d9e25c6263b6c96d81692526a203c6303d41ba8921a81089856dc05ee56fe06c5
|
|
7
|
+
data.tar.gz: de9f079fb994624d5c62effba8fcc5c08fa4b879b0a8e76a0e1d4de10fd409aa5940e07a2c15b185f68d54f28750c7fb0119f85c40f0c3be44d89629ebc36302
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 0.6.7 (2026-09-14)
|
|
4
|
+
|
|
5
|
+
* Fix gem packaging: 0.6.6 shipped every file with mode 0660 (no world-read), so `require` failed for non-owner users. 0.6.7 is identical code, repackaged with the standard 0644 modes ([#77](https://github.com/WinRb/rubyntlm/issues/77))
|
|
6
|
+
|
|
3
7
|
## 0.6.6 (2026-09-09)
|
|
4
8
|
|
|
5
9
|
* Security: fix NTLMv2 authentication bypass in `Type3#password?`, which verified NTLMv2 responses against an empty password instead of the supplied one
|
data/README.md
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
# Ruby/NTLM -- NTLM Authentication Library for Ruby
|
|
2
|
-
|
|
3
|
-
[
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### Parsing NTLM Type 2 message from server
|
|
26
|
-
|
|
27
|
-
```ruby
|
|
28
|
-
t2 = Net::NTLM::Message.parse(message_from_server)
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Creating NTLM Type 3 message
|
|
32
|
-
|
|
33
|
-
```ruby
|
|
34
|
-
t3 = t2.response({:user => 'user', :password => 'passwd'})
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Support
|
|
38
|
-
-------
|
|
39
|
-
|
|
40
|
-
https://groups.google.com/forum/?fromgroups#!forum/rubyntlm
|
|
41
|
-
|
|
42
|
-
Contributing
|
|
43
|
-
------------
|
|
44
|
-
1. Fork it.
|
|
45
|
-
2. Create a branch (git checkout -b my_feature_branch)
|
|
46
|
-
3. Commit your changes (git commit -am "Added a sweet feature")
|
|
47
|
-
4. Push to the branch (git push origin my_feature_branch)
|
|
48
|
-
5. Create a pull
|
|
1
|
+
# Ruby/NTLM -- NTLM Authentication Library for Ruby
|
|
2
|
+
|
|
3
|
+
[](https://github.com/WinRb/rubyntlm/actions/workflows/build.yml)
|
|
4
|
+
|
|
5
|
+
Ruby/NTLM provides message creator and parser for the NTLM authentication.
|
|
6
|
+
|
|
7
|
+
__100% Ruby__
|
|
8
|
+
|
|
9
|
+
How to install
|
|
10
|
+
--------------
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
require 'rubyntlm'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Simple Example
|
|
17
|
+
--------------
|
|
18
|
+
|
|
19
|
+
### Creating NTLM Type 1 message
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
t1 = Net::NTLM::Message::Type1.new()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Parsing NTLM Type 2 message from server
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
t2 = Net::NTLM::Message.parse(message_from_server)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Creating NTLM Type 3 message
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
t3 = t2.response({:user => 'user', :password => 'passwd'})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Support
|
|
38
|
+
-------
|
|
39
|
+
|
|
40
|
+
https://groups.google.com/forum/?fromgroups#!forum/rubyntlm
|
|
41
|
+
|
|
42
|
+
Contributing
|
|
43
|
+
------------
|
|
44
|
+
1. Fork it.
|
|
45
|
+
2. Create a branch (git checkout -b my_feature_branch)
|
|
46
|
+
3. Commit your changes (git commit -am "Added a sweet feature")
|
|
47
|
+
4. Push to the branch (git push origin my_feature_branch)
|
|
48
|
+
5. Create a pull request from your branch into main (Please be sure to provide enough detail for us to cipher what this change is doing)
|
data/lib/net/ntlm/blob.rb
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Net
|
|
2
4
|
module NTLM
|
|
3
|
-
|
|
4
5
|
BLOB_SIGN = 0x00000101
|
|
5
6
|
|
|
7
|
+
# NTLMv2 client challenge blob structure.
|
|
6
8
|
class Blob < FieldSet
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
string
|
|
11
|
-
|
|
12
|
-
string
|
|
13
|
-
|
|
9
|
+
int32_le :blob_signature, { value: BLOB_SIGN }
|
|
10
|
+
int32_le :reserved, { value: 0 }
|
|
11
|
+
int64_le :timestamp, { value: 0 }
|
|
12
|
+
string :challenge, { value: '', size: 8 }
|
|
13
|
+
int32_le :unknown1, { value: 0 }
|
|
14
|
+
string :target_info, { value: '', size: 0 }
|
|
15
|
+
int32_le :unknown2, { value: 0 }
|
|
14
16
|
|
|
15
|
-
def parse(str, offset=0)
|
|
17
|
+
def parse(str, offset = 0)
|
|
16
18
|
# 28 is the length of all fields before the variable-length
|
|
17
19
|
# target_info field.
|
|
18
20
|
if str.size > 28
|
|
@@ -23,6 +25,5 @@ module Net
|
|
|
23
25
|
super
|
|
24
26
|
end
|
|
25
27
|
end
|
|
26
|
-
|
|
27
28
|
end
|
|
28
29
|
end
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Net
|
|
2
4
|
module NTLM
|
|
5
|
+
# Channel binding data used for the MsvAvChannelBindings AV_PAIR.
|
|
3
6
|
class ChannelBinding
|
|
4
|
-
|
|
5
7
|
# Creates a ChannelBinding used for Extended Protection Authentication
|
|
6
8
|
# @see http://blogs.msdn.com/b/openspecification/archive/2013/03/26/ntlm-and-channel-binding-hash-aka-exteneded-protection-for-authentication.aspx
|
|
7
9
|
#
|
|
@@ -24,9 +26,8 @@ module Net
|
|
|
24
26
|
@acceptor_address_length = 0
|
|
25
27
|
end
|
|
26
28
|
|
|
27
|
-
attr_reader :channel, :unique_prefix, :initiator_addtype
|
|
28
|
-
|
|
29
|
-
attr_reader :acceptor_address_length
|
|
29
|
+
attr_reader :channel, :unique_prefix, :initiator_addtype, :initiator_address_length, :acceptor_addrtype,
|
|
30
|
+
:acceptor_address_length
|
|
30
31
|
|
|
31
32
|
# Returns a channel binding hash acceptable for use as a AV_PAIR MsvAvChannelBindings
|
|
32
33
|
# field value as specified in the NTLM protocol
|
|
@@ -38,10 +39,7 @@ module Net
|
|
|
38
39
|
|
|
39
40
|
def gss_channel_bindings_struct
|
|
40
41
|
@gss_channel_bindings_struct ||= begin
|
|
41
|
-
token =
|
|
42
|
-
token << [initiator_address_length].pack('I')
|
|
43
|
-
token << [acceptor_addrtype].pack('I')
|
|
44
|
-
token << [acceptor_address_length].pack('I')
|
|
42
|
+
token = pack_address_header
|
|
45
43
|
token << [application_data.length].pack('I')
|
|
46
44
|
token << application_data
|
|
47
45
|
token
|
|
@@ -54,12 +52,18 @@ module Net
|
|
|
54
52
|
|
|
55
53
|
def application_data
|
|
56
54
|
@application_data ||= begin
|
|
57
|
-
data = unique_prefix
|
|
55
|
+
data = +unique_prefix
|
|
58
56
|
data << ':'
|
|
59
57
|
data << channel_hash.digest
|
|
60
58
|
data
|
|
61
59
|
end
|
|
62
60
|
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def pack_address_header
|
|
65
|
+
[initiator_addtype, initiator_address_length, acceptor_addrtype, acceptor_address_length].pack('I4')
|
|
66
|
+
end
|
|
63
67
|
end
|
|
64
68
|
end
|
|
65
69
|
end
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/ntlm/client/session_crypto'
|
|
4
|
+
|
|
1
5
|
module Net
|
|
2
6
|
module NTLM
|
|
7
|
+
# An authenticated NTLM session: message signing, sealing and key exchange.
|
|
3
8
|
class Client::Session
|
|
9
|
+
include Client::SessionCrypto
|
|
4
10
|
|
|
5
11
|
VERSION_MAGIC = "\x01\x00\x00\x00"
|
|
6
|
-
TIME_OFFSET =
|
|
12
|
+
TIME_OFFSET = 11_644_473_600
|
|
7
13
|
MAX64 = 0xffffffffffffffff
|
|
8
14
|
CLIENT_TO_SERVER_SIGNING = "session key to client-to-server signing key magic constant\0"
|
|
9
15
|
SERVER_TO_CLIENT_SIGNING = "session key to server-to-client signing key magic constant\0"
|
|
@@ -25,50 +31,22 @@ module Net
|
|
|
25
31
|
# @return [Net::NTLM::Message::Type3]
|
|
26
32
|
def authenticate!
|
|
27
33
|
calculate_user_session_key!
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
:ntlm_response => is_anonymous? ? '' : ntlmv2_resp,
|
|
31
|
-
:domain => domain,
|
|
32
|
-
:user => username,
|
|
33
|
-
:workstation => workstation,
|
|
34
|
-
:flag => (challenge_message.flag & client.flags)
|
|
35
|
-
}
|
|
36
|
-
t3 = Message::Type3.create type3_opts
|
|
37
|
-
if negotiate_key_exchange?
|
|
38
|
-
t3.enable(:session_key)
|
|
39
|
-
rc4 = Net::NTLM::Rc4.new(user_session_key)
|
|
40
|
-
sk = rc4.encrypt exported_session_key
|
|
41
|
-
t3.session_key = sk
|
|
42
|
-
end
|
|
34
|
+
t3 = Message::Type3.create(type3_options)
|
|
35
|
+
exchange_session_key(t3) if negotiate_key_exchange?
|
|
43
36
|
t3
|
|
44
37
|
end
|
|
45
38
|
|
|
46
|
-
def exported_session_key
|
|
47
|
-
@exported_session_key ||=
|
|
48
|
-
begin
|
|
49
|
-
if negotiate_key_exchange?
|
|
50
|
-
OpenSSL::Random.random_bytes(16)
|
|
51
|
-
else
|
|
52
|
-
user_session_key
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
39
|
def sign_message(message)
|
|
58
40
|
seq = sequence
|
|
59
|
-
sig = OpenSSL::HMAC.digest(OpenSSL::Digest
|
|
60
|
-
if negotiate_key_exchange?
|
|
61
|
-
sig = client_cipher.encrypt sig
|
|
62
|
-
end
|
|
41
|
+
sig = OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), client_sign_key, "#{seq}#{message}")[0..7]
|
|
42
|
+
sig = client_cipher.encrypt sig if negotiate_key_exchange?
|
|
63
43
|
"#{VERSION_MAGIC}#{sig}#{seq}"
|
|
64
44
|
end
|
|
65
45
|
|
|
66
46
|
def verify_signature(signature, message)
|
|
67
|
-
seq = signature[-4
|
|
68
|
-
sig = OpenSSL::HMAC.digest(OpenSSL::Digest
|
|
69
|
-
if negotiate_key_exchange?
|
|
70
|
-
sig = server_cipher.encrypt sig
|
|
71
|
-
end
|
|
47
|
+
seq = signature[-4..]
|
|
48
|
+
sig = OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), server_sign_key, "#{seq}#{message}")[0..7]
|
|
49
|
+
sig = server_cipher.encrypt sig if negotiate_key_exchange?
|
|
72
50
|
"#{VERSION_MAGIC}#{sig}#{seq}" == signature
|
|
73
51
|
end
|
|
74
52
|
|
|
@@ -80,74 +58,38 @@ module Net
|
|
|
80
58
|
server_cipher.encrypt(emessage)
|
|
81
59
|
end
|
|
82
60
|
|
|
83
|
-
def
|
|
61
|
+
def anonymous?
|
|
84
62
|
username == '' && password == ''
|
|
85
63
|
end
|
|
86
64
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def user_session_key
|
|
91
|
-
@user_session_key ||= nil
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def sequence
|
|
95
|
-
[raw_sequence].pack("V*")
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def raw_sequence
|
|
99
|
-
if defined? @raw_sequence
|
|
100
|
-
@raw_sequence += 1
|
|
101
|
-
else
|
|
102
|
-
@raw_sequence = 0
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def client_sign_key
|
|
107
|
-
@client_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SIGNING}"
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def server_sign_key
|
|
111
|
-
@server_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SIGNING}"
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def client_seal_key
|
|
115
|
-
@client_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SEALING}"
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def server_seal_key
|
|
119
|
-
@server_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SEALING}"
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def client_cipher
|
|
123
|
-
@client_cipher ||= Net::NTLM::Rc4.new(client_seal_key)
|
|
124
|
-
end
|
|
65
|
+
alias is_anonymous? anonymous?
|
|
125
66
|
|
|
126
|
-
|
|
127
|
-
@server_cipher ||= Net::NTLM::Rc4.new(server_seal_key)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def client_challenge
|
|
131
|
-
@client_challenge ||= NTLM.pack_int64le(rand(MAX64))
|
|
132
|
-
end
|
|
67
|
+
private
|
|
133
68
|
|
|
134
|
-
def
|
|
135
|
-
|
|
69
|
+
def type3_options
|
|
70
|
+
{
|
|
71
|
+
lm_response: anonymous? ? "\x00".b : lmv2_resp,
|
|
72
|
+
ntlm_response: anonymous? ? '' : ntlmv2_resp,
|
|
73
|
+
domain: domain,
|
|
74
|
+
user: username,
|
|
75
|
+
workstation: workstation,
|
|
76
|
+
flag: (challenge_message.flag & client.flags)
|
|
77
|
+
}
|
|
136
78
|
end
|
|
137
79
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
80
|
+
def exchange_session_key(type3)
|
|
81
|
+
type3.enable(:session_key)
|
|
82
|
+
rc4 = Net::NTLM::Rc4.new(user_session_key)
|
|
83
|
+
type3.session_key = rc4.encrypt(exported_session_key)
|
|
142
84
|
end
|
|
143
85
|
|
|
144
86
|
def use_oem_strings?
|
|
145
87
|
# @see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832
|
|
146
|
-
!challenge_message.
|
|
88
|
+
!challenge_message.flag?(:UNICODE) && challenge_message.flag?(:OEM)
|
|
147
89
|
end
|
|
148
90
|
|
|
149
91
|
def negotiate_key_exchange?
|
|
150
|
-
challenge_message.
|
|
92
|
+
challenge_message.flag? :KEY_EXCHANGE
|
|
151
93
|
end
|
|
152
94
|
|
|
153
95
|
def username
|
|
@@ -159,11 +101,11 @@ module Net
|
|
|
159
101
|
end
|
|
160
102
|
|
|
161
103
|
def workstation
|
|
162
|
-
(client.workstation ? oem_or_unicode_str(client.workstation) :
|
|
104
|
+
(client.workstation ? oem_or_unicode_str(client.workstation) : '')
|
|
163
105
|
end
|
|
164
106
|
|
|
165
107
|
def domain
|
|
166
|
-
(client.domain ? oem_or_unicode_str(client.domain) :
|
|
108
|
+
(client.domain ? oem_or_unicode_str(client.domain) : '')
|
|
167
109
|
end
|
|
168
110
|
|
|
169
111
|
def oem_or_unicode_str(str)
|
|
@@ -173,55 +115,6 @@ module Net
|
|
|
173
115
|
NTLM::EncodeUtil.encode_utf16le str
|
|
174
116
|
end
|
|
175
117
|
end
|
|
176
|
-
|
|
177
|
-
def ntlmv2_hash
|
|
178
|
-
@ntlmv2_hash ||= NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def calculate_user_session_key!
|
|
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
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def lmv2_resp
|
|
191
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + client_challenge) + client_challenge
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def ntlmv2_resp
|
|
195
|
-
nt_proof_str + blob
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def nt_proof_str
|
|
199
|
-
@nt_proof_str ||= OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + blob)
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def blob
|
|
203
|
-
@blob ||=
|
|
204
|
-
begin
|
|
205
|
-
b = Blob.new
|
|
206
|
-
b.timestamp = timestamp
|
|
207
|
-
b.challenge = client_challenge
|
|
208
|
-
b.target_info = target_info
|
|
209
|
-
b.serialize
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def target_info
|
|
214
|
-
@target_info ||= begin
|
|
215
|
-
if channel_binding
|
|
216
|
-
t = Net::NTLM::TargetInfo.new(challenge_message.target_info)
|
|
217
|
-
av_id = Net::NTLM::TargetInfo::MSV_AV_CHANNEL_BINDINGS
|
|
218
|
-
t.av_pairs[av_id] = channel_binding.channel_binding_token
|
|
219
|
-
t.to_s
|
|
220
|
-
else
|
|
221
|
-
challenge_message.target_info
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
118
|
end
|
|
226
119
|
end
|
|
227
120
|
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Net
|
|
4
|
+
module NTLM
|
|
5
|
+
class Client
|
|
6
|
+
# Session key derivation and NTLMv2 response building for
|
|
7
|
+
# {Net::NTLM::Client::Session}. Included into Session; not used directly.
|
|
8
|
+
module SessionCrypto
|
|
9
|
+
# @return [String] session key exchanged with (or derived for) the server
|
|
10
|
+
def exported_session_key
|
|
11
|
+
@exported_session_key ||=
|
|
12
|
+
if negotiate_key_exchange?
|
|
13
|
+
OpenSSL::Random.random_bytes(16)
|
|
14
|
+
else
|
|
15
|
+
user_session_key
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def user_session_key
|
|
22
|
+
@user_session_key ||= nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def calculate_user_session_key!
|
|
26
|
+
@user_session_key = if anonymous?
|
|
27
|
+
# see MS-NLMP section 3.4
|
|
28
|
+
"\x00".b * 16
|
|
29
|
+
else
|
|
30
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmv2_hash, nt_proof_str)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def ntlmv2_hash
|
|
35
|
+
@ntlmv2_hash ||= NTLM.ntlmv2_hash(username, password, domain,
|
|
36
|
+
{ client_challenge: client_challenge, unicode: !use_oem_strings? })
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def lmv2_resp
|
|
40
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmv2_hash,
|
|
41
|
+
server_challenge + client_challenge) + client_challenge
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ntlmv2_resp
|
|
45
|
+
nt_proof_str + blob
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def nt_proof_str
|
|
49
|
+
@nt_proof_str ||= OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmv2_hash, server_challenge + blob)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def blob
|
|
53
|
+
@blob ||=
|
|
54
|
+
begin
|
|
55
|
+
b = Blob.new
|
|
56
|
+
b.timestamp = timestamp
|
|
57
|
+
b.challenge = client_challenge
|
|
58
|
+
b.target_info = target_info
|
|
59
|
+
b.serialize
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def target_info
|
|
64
|
+
@target_info ||= if channel_binding
|
|
65
|
+
t = Net::NTLM::TargetInfo.new(challenge_message.target_info)
|
|
66
|
+
av_id = Net::NTLM::TargetInfo::MSV_AV_CHANNEL_BINDINGS
|
|
67
|
+
t.av_pairs[av_id] = channel_binding.channel_binding_token
|
|
68
|
+
t.to_s
|
|
69
|
+
else
|
|
70
|
+
challenge_message.target_info
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def client_sign_key
|
|
75
|
+
@client_sign_key ||= OpenSSL::Digest.digest('MD5', "#{exported_session_key}#{CLIENT_TO_SERVER_SIGNING}")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def server_sign_key
|
|
79
|
+
@server_sign_key ||= OpenSSL::Digest.digest('MD5', "#{exported_session_key}#{SERVER_TO_CLIENT_SIGNING}")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def client_seal_key
|
|
83
|
+
@client_seal_key ||= OpenSSL::Digest.digest('MD5', "#{exported_session_key}#{CLIENT_TO_SERVER_SEALING}")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def server_seal_key
|
|
87
|
+
@server_seal_key ||= OpenSSL::Digest.digest('MD5', "#{exported_session_key}#{SERVER_TO_CLIENT_SEALING}")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def client_cipher
|
|
91
|
+
@client_cipher ||= Net::NTLM::Rc4.new(client_seal_key)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def server_cipher
|
|
95
|
+
@server_cipher ||= Net::NTLM::Rc4.new(server_seal_key)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def client_challenge
|
|
99
|
+
@client_challenge ||= NTLM.pack_int64le(rand(MAX64))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def server_challenge
|
|
103
|
+
@server_challenge ||= challenge_message[:challenge].serialize
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# epoch -> milsec from Jan 1, 1601
|
|
107
|
+
# @see http://support.microsoft.com/kb/188768
|
|
108
|
+
def timestamp
|
|
109
|
+
@timestamp ||= 10_000_000 * (Time.now.to_i + TIME_OFFSET)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def sequence
|
|
113
|
+
[raw_sequence].pack('V*')
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def raw_sequence
|
|
117
|
+
if defined? @raw_sequence
|
|
118
|
+
@raw_sequence += 1
|
|
119
|
+
else
|
|
120
|
+
@raw_sequence = 0
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
data/lib/net/ntlm/client.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Net
|
|
2
4
|
module NTLM
|
|
5
|
+
# High-level NTLM client: builds type1 and type3 messages for a session.
|
|
3
6
|
class Client
|
|
4
|
-
|
|
5
7
|
DEFAULT_FLAGS = NTLM::FLAGS[:UNICODE] | NTLM::FLAGS[:OEM] |
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
NTLM::FLAGS[:SIGN] | NTLM::FLAGS[:SEAL] | NTLM::FLAGS[:REQUEST_TARGET] |
|
|
9
|
+
NTLM::FLAGS[:NTLM] | NTLM::FLAGS[:ALWAYS_SIGN] | NTLM::FLAGS[:NTLM2_KEY] |
|
|
10
|
+
NTLM::FLAGS[:KEY128] | NTLM::FLAGS[:KEY_EXCHANGE] | NTLM::FLAGS[:KEY56]
|
|
9
11
|
|
|
10
12
|
attr_reader :username, :password, :domain, :workstation, :flags
|
|
11
13
|
|
|
@@ -38,9 +40,7 @@ module Net
|
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
# @return [Client::Session]
|
|
41
|
-
|
|
42
|
-
@session
|
|
43
|
-
end
|
|
43
|
+
attr_reader :session
|
|
44
44
|
|
|
45
45
|
def session_key
|
|
46
46
|
@session.exported_session_key
|
|
@@ -57,9 +57,8 @@ module Net
|
|
|
57
57
|
|
|
58
58
|
type1
|
|
59
59
|
end
|
|
60
|
-
|
|
61
60
|
end
|
|
62
61
|
end
|
|
63
62
|
end
|
|
64
63
|
|
|
65
|
-
require
|
|
64
|
+
require 'net/ntlm/client/session'
|