rubyntlm 0.6.6 → 0.6.8
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 +7 -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 -145
- data/lib/net/ntlm/client/session_crypto.rb +131 -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 +6 -45
- 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: b4756f78ef7cc152c4b05490fed4854c0650f85ac0bacab33ed4fdc7b888f35f
|
|
4
|
+
data.tar.gz: be12c6a59dae4bb73fe783fd2590e3486af5e682b14ba4f7cd1fed58945f2ae4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1a14372acf83aeef63f0656757ae5c7ce7f263c1a4c17b9d8b83a2dcb3bcb1296d898075d5c69968e572cb71847d1d9bf4d8e0a7c077c55273192d17a7a27e2b
|
|
7
|
+
data.tar.gz: 9bdfccee2d76d806b93306600f94d5a6e4304568a47c186f11b6e81360f33f07d9c59508538264131673ea6fd538f97f11746c934a201fe0923a5dd464e69ec4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 0.6.8 (2026-09-14)
|
|
4
|
+
|
|
5
|
+
* Fix `NameError` on sign, seal, unseal, and verify: the 0.6.7 refactor moved the crypto methods into `SessionCrypto` but left their constants behind in `Session`, breaking message protection for encrypted winrm sessions ([#81](https://github.com/WinRb/rubyntlm/issues/81), [#80](https://github.com/WinRb/rubyntlm/pull/80))
|
|
6
|
+
## 0.6.7 (2026-09-14)
|
|
7
|
+
|
|
8
|
+
* 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))
|
|
9
|
+
|
|
3
10
|
## 0.6.6 (2026-09-09)
|
|
4
11
|
|
|
5
12
|
* 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,14 +1,16 @@
|
|
|
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
|
-
CLIENT_TO_SERVER_SIGNING = "session key to client-to-server signing key magic constant\0"
|
|
9
|
-
SERVER_TO_CLIENT_SIGNING = "session key to server-to-client signing key magic constant\0"
|
|
10
|
-
CLIENT_TO_SERVER_SEALING = "session key to client-to-server sealing key magic constant\0"
|
|
11
|
-
SERVER_TO_CLIENT_SEALING = "session key to server-to-client sealing key magic constant\0"
|
|
12
14
|
|
|
13
15
|
attr_reader :client, :challenge_message, :channel_binding
|
|
14
16
|
|
|
@@ -25,50 +27,22 @@ module Net
|
|
|
25
27
|
# @return [Net::NTLM::Message::Type3]
|
|
26
28
|
def authenticate!
|
|
27
29
|
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
|
|
30
|
+
t3 = Message::Type3.create(type3_options)
|
|
31
|
+
exchange_session_key(t3) if negotiate_key_exchange?
|
|
43
32
|
t3
|
|
44
33
|
end
|
|
45
34
|
|
|
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
35
|
def sign_message(message)
|
|
58
36
|
seq = sequence
|
|
59
|
-
sig = OpenSSL::HMAC.digest(OpenSSL::Digest
|
|
60
|
-
if negotiate_key_exchange?
|
|
61
|
-
sig = client_cipher.encrypt sig
|
|
62
|
-
end
|
|
37
|
+
sig = OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), client_sign_key, "#{seq}#{message}")[0..7]
|
|
38
|
+
sig = client_cipher.encrypt sig if negotiate_key_exchange?
|
|
63
39
|
"#{VERSION_MAGIC}#{sig}#{seq}"
|
|
64
40
|
end
|
|
65
41
|
|
|
66
42
|
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
|
|
43
|
+
seq = signature[-4..]
|
|
44
|
+
sig = OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), server_sign_key, "#{seq}#{message}")[0..7]
|
|
45
|
+
sig = server_cipher.encrypt sig if negotiate_key_exchange?
|
|
72
46
|
"#{VERSION_MAGIC}#{sig}#{seq}" == signature
|
|
73
47
|
end
|
|
74
48
|
|
|
@@ -80,74 +54,38 @@ module Net
|
|
|
80
54
|
server_cipher.encrypt(emessage)
|
|
81
55
|
end
|
|
82
56
|
|
|
83
|
-
def
|
|
57
|
+
def anonymous?
|
|
84
58
|
username == '' && password == ''
|
|
85
59
|
end
|
|
86
60
|
|
|
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
|
|
61
|
+
alias is_anonymous? anonymous?
|
|
125
62
|
|
|
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
|
|
63
|
+
private
|
|
133
64
|
|
|
134
|
-
def
|
|
135
|
-
|
|
65
|
+
def type3_options
|
|
66
|
+
{
|
|
67
|
+
lm_response: anonymous? ? "\x00".b : lmv2_resp,
|
|
68
|
+
ntlm_response: anonymous? ? '' : ntlmv2_resp,
|
|
69
|
+
domain: domain,
|
|
70
|
+
user: username,
|
|
71
|
+
workstation: workstation,
|
|
72
|
+
flag: (challenge_message.flag & client.flags)
|
|
73
|
+
}
|
|
136
74
|
end
|
|
137
75
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
76
|
+
def exchange_session_key(type3)
|
|
77
|
+
type3.enable(:session_key)
|
|
78
|
+
rc4 = Net::NTLM::Rc4.new(user_session_key)
|
|
79
|
+
type3.session_key = rc4.encrypt(exported_session_key)
|
|
142
80
|
end
|
|
143
81
|
|
|
144
82
|
def use_oem_strings?
|
|
145
83
|
# @see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832
|
|
146
|
-
!challenge_message.
|
|
84
|
+
!challenge_message.flag?(:UNICODE) && challenge_message.flag?(:OEM)
|
|
147
85
|
end
|
|
148
86
|
|
|
149
87
|
def negotiate_key_exchange?
|
|
150
|
-
challenge_message.
|
|
88
|
+
challenge_message.flag? :KEY_EXCHANGE
|
|
151
89
|
end
|
|
152
90
|
|
|
153
91
|
def username
|
|
@@ -159,11 +97,11 @@ module Net
|
|
|
159
97
|
end
|
|
160
98
|
|
|
161
99
|
def workstation
|
|
162
|
-
(client.workstation ? oem_or_unicode_str(client.workstation) :
|
|
100
|
+
(client.workstation ? oem_or_unicode_str(client.workstation) : '')
|
|
163
101
|
end
|
|
164
102
|
|
|
165
103
|
def domain
|
|
166
|
-
(client.domain ? oem_or_unicode_str(client.domain) :
|
|
104
|
+
(client.domain ? oem_or_unicode_str(client.domain) : '')
|
|
167
105
|
end
|
|
168
106
|
|
|
169
107
|
def oem_or_unicode_str(str)
|
|
@@ -173,55 +111,6 @@ module Net
|
|
|
173
111
|
NTLM::EncodeUtil.encode_utf16le str
|
|
174
112
|
end
|
|
175
113
|
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
114
|
end
|
|
226
115
|
end
|
|
227
116
|
end
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
CLIENT_TO_SERVER_SIGNING = "session key to client-to-server signing key magic constant\0"
|
|
10
|
+
SERVER_TO_CLIENT_SIGNING = "session key to server-to-client signing key magic constant\0"
|
|
11
|
+
CLIENT_TO_SERVER_SEALING = "session key to client-to-server sealing key magic constant\0"
|
|
12
|
+
SERVER_TO_CLIENT_SEALING = "session key to server-to-client sealing key magic constant\0"
|
|
13
|
+
|
|
14
|
+
# @return [String] session key exchanged with (or derived for) the server
|
|
15
|
+
def exported_session_key
|
|
16
|
+
@exported_session_key ||=
|
|
17
|
+
if negotiate_key_exchange?
|
|
18
|
+
OpenSSL::Random.random_bytes(16)
|
|
19
|
+
else
|
|
20
|
+
user_session_key
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def user_session_key
|
|
27
|
+
@user_session_key ||= nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def calculate_user_session_key!
|
|
31
|
+
@user_session_key = if anonymous?
|
|
32
|
+
# see MS-NLMP section 3.4
|
|
33
|
+
"\x00".b * 16
|
|
34
|
+
else
|
|
35
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmv2_hash, nt_proof_str)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def ntlmv2_hash
|
|
40
|
+
@ntlmv2_hash ||= NTLM.ntlmv2_hash(username, password, domain,
|
|
41
|
+
{ client_challenge: client_challenge, unicode: !use_oem_strings? })
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def lmv2_resp
|
|
45
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmv2_hash,
|
|
46
|
+
server_challenge + client_challenge) + client_challenge
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def ntlmv2_resp
|
|
50
|
+
nt_proof_str + blob
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def nt_proof_str
|
|
54
|
+
@nt_proof_str ||= OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmv2_hash, server_challenge + blob)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def blob
|
|
58
|
+
@blob ||=
|
|
59
|
+
begin
|
|
60
|
+
b = Blob.new
|
|
61
|
+
b.timestamp = timestamp
|
|
62
|
+
b.challenge = client_challenge
|
|
63
|
+
b.target_info = target_info
|
|
64
|
+
b.serialize
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def target_info
|
|
69
|
+
@target_info ||= if channel_binding
|
|
70
|
+
t = Net::NTLM::TargetInfo.new(challenge_message.target_info)
|
|
71
|
+
av_id = Net::NTLM::TargetInfo::MSV_AV_CHANNEL_BINDINGS
|
|
72
|
+
t.av_pairs[av_id] = channel_binding.channel_binding_token
|
|
73
|
+
t.to_s
|
|
74
|
+
else
|
|
75
|
+
challenge_message.target_info
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def client_sign_key
|
|
80
|
+
@client_sign_key ||= OpenSSL::Digest.digest('MD5', "#{exported_session_key}#{CLIENT_TO_SERVER_SIGNING}")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def server_sign_key
|
|
84
|
+
@server_sign_key ||= OpenSSL::Digest.digest('MD5', "#{exported_session_key}#{SERVER_TO_CLIENT_SIGNING}")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def client_seal_key
|
|
88
|
+
@client_seal_key ||= OpenSSL::Digest.digest('MD5', "#{exported_session_key}#{CLIENT_TO_SERVER_SEALING}")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def server_seal_key
|
|
92
|
+
@server_seal_key ||= OpenSSL::Digest.digest('MD5', "#{exported_session_key}#{SERVER_TO_CLIENT_SEALING}")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def client_cipher
|
|
96
|
+
@client_cipher ||= Net::NTLM::Rc4.new(client_seal_key)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def server_cipher
|
|
100
|
+
@server_cipher ||= Net::NTLM::Rc4.new(server_seal_key)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def client_challenge
|
|
104
|
+
@client_challenge ||= NTLM.pack_int64le(rand(MAX64))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def server_challenge
|
|
108
|
+
@server_challenge ||= challenge_message[:challenge].serialize
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# epoch -> milsec from Jan 1, 1601
|
|
112
|
+
# @see http://support.microsoft.com/kb/188768
|
|
113
|
+
def timestamp
|
|
114
|
+
@timestamp ||= 10_000_000 * (Time.now.to_i + TIME_OFFSET)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def sequence
|
|
118
|
+
[raw_sequence].pack('V*')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def raw_sequence
|
|
122
|
+
if defined? @raw_sequence
|
|
123
|
+
@raw_sequence += 1
|
|
124
|
+
else
|
|
125
|
+
@raw_sequence = 0
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
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'
|