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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +48 -48
  4. data/lib/net/ntlm/blob.rb +11 -10
  5. data/lib/net/ntlm/channel_binding.rb +13 -9
  6. data/lib/net/ntlm/client/session.rb +34 -145
  7. data/lib/net/ntlm/client/session_crypto.rb +131 -0
  8. data/lib/net/ntlm/client.rb +8 -9
  9. data/lib/net/ntlm/encode_util.rb +39 -38
  10. data/lib/net/ntlm/exceptions.rb +3 -0
  11. data/lib/net/ntlm/field.rb +29 -31
  12. data/lib/net/ntlm/field_set.rb +39 -18
  13. data/lib/net/ntlm/int16_le.rb +8 -8
  14. data/lib/net/ntlm/int32_le.rb +8 -7
  15. data/lib/net/ntlm/int64_le.rb +8 -7
  16. data/lib/net/ntlm/md4.rb +102 -64
  17. data/lib/net/ntlm/message/type0.rb +4 -7
  18. data/lib/net/ntlm/message/type1.rb +8 -9
  19. data/lib/net/ntlm/message/type2.rb +87 -67
  20. data/lib/net/ntlm/message/type3.rb +43 -41
  21. data/lib/net/ntlm/message.rb +116 -111
  22. data/lib/net/ntlm/rc4.rb +51 -44
  23. data/lib/net/ntlm/response.rb +112 -0
  24. data/lib/net/ntlm/security_buffer.rb +13 -15
  25. data/lib/net/ntlm/string.rb +8 -7
  26. data/lib/net/ntlm/target_info.rb +52 -44
  27. data/lib/net/ntlm/version.rb +3 -1
  28. data/lib/net/ntlm.rb +37 -125
  29. data/lib/rubyntlm.rb +3 -1
  30. metadata +6 -45
  31. data/.devcontainer/devcontainer.json +0 -22
  32. data/.github/dependabot.yml +0 -10
  33. data/.github/workflows/build.yml +0 -55
  34. data/.gitignore +0 -11
  35. data/.rspec +0 -3
  36. data/Gemfile +0 -20
  37. data/Rakefile +0 -37
  38. data/examples/http.rb +0 -86
  39. data/examples/imap.rb +0 -73
  40. data/examples/smtp.rb +0 -94
  41. data/rubyntlm.gemspec +0 -28
  42. data/spec/lib/net/ntlm/blob_spec.rb +0 -14
  43. data/spec/lib/net/ntlm/channel_binding_spec.rb +0 -15
  44. data/spec/lib/net/ntlm/client/session_spec.rb +0 -85
  45. data/spec/lib/net/ntlm/client_spec.rb +0 -62
  46. data/spec/lib/net/ntlm/encode_util_spec.rb +0 -14
  47. data/spec/lib/net/ntlm/field_set_spec.rb +0 -31
  48. data/spec/lib/net/ntlm/field_spec.rb +0 -32
  49. data/spec/lib/net/ntlm/int16_le_spec.rb +0 -16
  50. data/spec/lib/net/ntlm/int32_le_spec.rb +0 -17
  51. data/spec/lib/net/ntlm/int64_le_spec.rb +0 -17
  52. data/spec/lib/net/ntlm/md4_spec.rb +0 -16
  53. data/spec/lib/net/ntlm/message/type0_spec.rb +0 -19
  54. data/spec/lib/net/ntlm/message/type1_spec.rb +0 -129
  55. data/spec/lib/net/ntlm/message/type2_spec.rb +0 -131
  56. data/spec/lib/net/ntlm/message/type3_spec.rb +0 -336
  57. data/spec/lib/net/ntlm/message_spec.rb +0 -15
  58. data/spec/lib/net/ntlm/security_buffer_spec.rb +0 -62
  59. data/spec/lib/net/ntlm/string_spec.rb +0 -70
  60. data/spec/lib/net/ntlm/target_info_spec.rb +0 -74
  61. data/spec/lib/net/ntlm/version_spec.rb +0 -26
  62. data/spec/lib/net/ntlm_spec.rb +0 -133
  63. data/spec/spec_helper.rb +0 -51
  64. data/spec/support/certificates/sha_256_hash.pem +0 -19
  65. data/spec/support/shared/examples/net/ntlm/field_shared.rb +0 -25
  66. data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +0 -239
  67. data/spec/support/shared/examples/net/ntlm/int_shared.rb +0 -43
  68. 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: f618c1c4edae0dd1fc9e9a20d690f3b56d7280ba4e07dc097bfe86e8720ab043
4
- data.tar.gz: 8b1516a2ae5919c22fd2788fbdb1c3cb78492826a629536fcb597afa6d2ecd69
3
+ metadata.gz: b4756f78ef7cc152c4b05490fed4854c0650f85ac0bacab33ed4fdc7b888f35f
4
+ data.tar.gz: be12c6a59dae4bb73fe783fd2590e3486af5e682b14ba4f7cd1fed58945f2ae4
5
5
  SHA512:
6
- metadata.gz: 0c5a6e37d4945d1abef840ceaf4b78b9149aba4c3cfc05f9b3bbeaa8b0e488b87c551d9652e0e9bda69983b788430cde3e8b968978dae0046f5d5f1dbf99cee2
7
- data.tar.gz: a8a0fa4b29d5fa6b9d39bbaf6c3e1a9c342eb2c16e6cd43d969be33dac159c4b19babab2210810b4d14e1a1a0a2c62308cc04b05d6d6b84fe70dc232f5410ade
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
- [![build status](https://github.com/WinRb/rubyntlm/actions/workflows/build.yml/badge.svg?branch=master&event=push)](https://github.com/WinRb/rubyntlm/actions/workflows/build.yml?query=branch%3Amaster+event%3Apush)
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 requst from your branch into master (Please be sure to provide enough detail for us to cipher what this change is doing)
1
+ # Ruby/NTLM -- NTLM Authentication Library for Ruby
2
+
3
+ [![build status](https://github.com/WinRb/rubyntlm/actions/workflows/build.yml/badge.svg)](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
- int32LE :blob_signature, {:value => BLOB_SIGN}
8
- int32LE :reserved, {:value => 0}
9
- int64LE :timestamp, {:value => 0}
10
- string :challenge, {:value => "", :size => 8}
11
- int32LE :unknown1, {:value => 0}
12
- string :target_info, {:value => "", :size => 0}
13
- int32LE :unknown2, {:value => 0}
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
- attr_reader :initiator_address_length, :acceptor_addrtype
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 = [initiator_addtype].pack('I')
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 = 11644473600
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
- type3_opts = {
29
- :lm_response => is_anonymous? ? "\x00".b : lmv2_resp,
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::MD5.new, client_sign_key, "#{seq}#{message}")[0..7]
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..-1]
68
- sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, server_sign_key, "#{seq}#{message}")[0..7]
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 is_anonymous?
57
+ def anonymous?
84
58
  username == '' && password == ''
85
59
  end
86
60
 
87
- private
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
- def server_cipher
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 server_challenge
135
- @server_challenge ||= challenge_message[:challenge].serialize
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
- # epoch -> milsec from Jan 1, 1601
139
- # @see http://support.microsoft.com/kb/188768
140
- def timestamp
141
- @timestamp ||= 10_000_000 * (Time.now.to_i + TIME_OFFSET)
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.has_flag?(:UNICODE) && challenge_message.has_flag?(:OEM)
84
+ !challenge_message.flag?(:UNICODE) && challenge_message.flag?(:OEM)
147
85
  end
148
86
 
149
87
  def negotiate_key_exchange?
150
- challenge_message.has_flag? :KEY_EXCHANGE
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
@@ -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
- NTLM::FLAGS[:SIGN] | NTLM::FLAGS[:SEAL] | NTLM::FLAGS[:REQUEST_TARGET] |
7
- NTLM::FLAGS[:NTLM] | NTLM::FLAGS[:ALWAYS_SIGN] | NTLM::FLAGS[:NTLM2_KEY] |
8
- NTLM::FLAGS[:KEY128] | NTLM::FLAGS[:KEY_EXCHANGE] | NTLM::FLAGS[:KEY56]
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
- def session
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 "net/ntlm/client/session"
64
+ require 'net/ntlm/client/session'