rubyntlm 0.6.1 → 0.6.2

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -3
  3. data/.rspec +2 -2
  4. data/.travis.yml +13 -12
  5. data/CHANGELOG.md +118 -6
  6. data/Gemfile +3 -3
  7. data/LICENSE +19 -19
  8. data/Rakefile +25 -22
  9. data/lib/net/ntlm.rb +266 -266
  10. data/lib/net/ntlm/blob.rb +28 -28
  11. data/lib/net/ntlm/channel_binding.rb +65 -65
  12. data/lib/net/ntlm/client.rb +65 -65
  13. data/lib/net/ntlm/client/session.rb +237 -237
  14. data/lib/net/ntlm/encode_util.rb +48 -48
  15. data/lib/net/ntlm/exceptions.rb +14 -14
  16. data/lib/net/ntlm/field.rb +34 -34
  17. data/lib/net/ntlm/field_set.rb +129 -129
  18. data/lib/net/ntlm/int16_le.rb +25 -25
  19. data/lib/net/ntlm/int32_le.rb +24 -24
  20. data/lib/net/ntlm/int64_le.rb +25 -25
  21. data/lib/net/ntlm/message.rb +129 -129
  22. data/lib/net/ntlm/message/type0.rb +16 -16
  23. data/lib/net/ntlm/message/type1.rb +18 -18
  24. data/lib/net/ntlm/message/type2.rb +102 -102
  25. data/lib/net/ntlm/message/type3.rb +131 -131
  26. data/lib/net/ntlm/security_buffer.rb +47 -47
  27. data/lib/net/ntlm/string.rb +34 -34
  28. data/lib/net/ntlm/target_info.rb +89 -89
  29. data/lib/net/ntlm/version.rb +11 -11
  30. data/rubyntlm.gemspec +29 -28
  31. data/spec/lib/net/ntlm/blob_spec.rb +16 -16
  32. data/spec/lib/net/ntlm/channel_binding_spec.rb +17 -17
  33. data/spec/lib/net/ntlm/client/session_spec.rb +68 -68
  34. data/spec/lib/net/ntlm/client_spec.rb +64 -64
  35. data/spec/lib/net/ntlm/encode_util_spec.rb +16 -16
  36. data/spec/lib/net/ntlm/field_set_spec.rb +33 -33
  37. data/spec/lib/net/ntlm/field_spec.rb +34 -34
  38. data/spec/lib/net/ntlm/int16_le_spec.rb +17 -17
  39. data/spec/lib/net/ntlm/int32_le_spec.rb +18 -18
  40. data/spec/lib/net/ntlm/int64_le_spec.rb +18 -18
  41. data/spec/lib/net/ntlm/message/type0_spec.rb +20 -20
  42. data/spec/lib/net/ntlm/message/type1_spec.rb +131 -131
  43. data/spec/lib/net/ntlm/message/type2_spec.rb +132 -132
  44. data/spec/lib/net/ntlm/message/type3_spec.rb +225 -225
  45. data/spec/lib/net/ntlm/message_spec.rb +16 -16
  46. data/spec/lib/net/ntlm/security_buffer_spec.rb +64 -64
  47. data/spec/lib/net/ntlm/string_spec.rb +72 -72
  48. data/spec/lib/net/ntlm/target_info_spec.rb +76 -76
  49. data/spec/lib/net/ntlm/version_spec.rb +27 -27
  50. data/spec/lib/net/ntlm_spec.rb +127 -127
  51. data/spec/spec_helper.rb +22 -22
  52. data/spec/support/certificates/sha_256_hash.pem +19 -19
  53. data/spec/support/shared/examples/net/ntlm/field_shared.rb +25 -25
  54. data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +239 -239
  55. data/spec/support/shared/examples/net/ntlm/int_shared.rb +43 -43
  56. data/spec/support/shared/examples/net/ntlm/message_shared.rb +35 -35
  57. metadata +17 -3
@@ -1,28 +1,28 @@
1
- module Net
2
- module NTLM
3
-
4
- BLOB_SIGN = 0x00000101
5
-
6
- 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}
14
-
15
- def parse(str, offset=0)
16
- # 28 is the length of all fields before the variable-length
17
- # target_info field.
18
- if str.size > 28
19
- enable(:target_info)
20
- # Grab everything except the last 4 bytes (which will be :unknown2)
21
- self[:target_info].value = str[28..-5]
22
- end
23
- super
24
- end
25
- end
26
-
27
- end
28
- end
1
+ module Net
2
+ module NTLM
3
+
4
+ BLOB_SIGN = 0x00000101
5
+
6
+ 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}
14
+
15
+ def parse(str, offset=0)
16
+ # 28 is the length of all fields before the variable-length
17
+ # target_info field.
18
+ if str.size > 28
19
+ enable(:target_info)
20
+ # Grab everything except the last 4 bytes (which will be :unknown2)
21
+ self[:target_info].value = str[28..-5]
22
+ end
23
+ super
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -1,65 +1,65 @@
1
- module Net
2
- module NTLM
3
- class ChannelBinding
4
-
5
- # Creates a ChannelBinding used for Extended Protection Authentication
6
- # @see http://blogs.msdn.com/b/openspecification/archive/2013/03/26/ntlm-and-channel-binding-hash-aka-exteneded-protection-for-authentication.aspx
7
- #
8
- # @param outer_channel [OpenSSL::X509::Certificate] Server certificate securing
9
- # the outer TLS channel
10
- # @return [NTLM::ChannelBinding] A ChannelBinding holding a token that can be
11
- # embedded in a {Type3} message
12
- def self.create(outer_channel)
13
- new(outer_channel)
14
- end
15
-
16
- # @param outer_channel [OpenSSL::X509::Certificate] Server certificate securing
17
- # the outer TLS channel
18
- def initialize(outer_channel)
19
- @channel = outer_channel
20
- @unique_prefix = 'tls-server-end-point'
21
- @initiator_addtype = 0
22
- @initiator_address_length = 0
23
- @acceptor_addrtype = 0
24
- @acceptor_address_length = 0
25
- end
26
-
27
- attr_reader :channel, :unique_prefix, :initiator_addtype
28
- attr_reader :initiator_address_length, :acceptor_addrtype
29
- attr_reader :acceptor_address_length
30
-
31
- # Returns a channel binding hash acceptable for use as a AV_PAIR MsvAvChannelBindings
32
- # field value as specified in the NTLM protocol
33
- #
34
- # @return [String] MD5 hash of gss_channel_bindings_struct
35
- def channel_binding_token
36
- @channel_binding_token ||= OpenSSL::Digest::MD5.new(gss_channel_bindings_struct).digest
37
- end
38
-
39
- def gss_channel_bindings_struct
40
- @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')
45
- token << [application_data.length].pack('I')
46
- token << application_data
47
- token
48
- end
49
- end
50
-
51
- def channel_hash
52
- @channel_hash ||= OpenSSL::Digest::SHA256.new(channel.to_der)
53
- end
54
-
55
- def application_data
56
- @application_data ||= begin
57
- data = unique_prefix
58
- data << ':'
59
- data << channel_hash.digest
60
- data
61
- end
62
- end
63
- end
64
- end
65
- end
1
+ module Net
2
+ module NTLM
3
+ class ChannelBinding
4
+
5
+ # Creates a ChannelBinding used for Extended Protection Authentication
6
+ # @see http://blogs.msdn.com/b/openspecification/archive/2013/03/26/ntlm-and-channel-binding-hash-aka-exteneded-protection-for-authentication.aspx
7
+ #
8
+ # @param outer_channel [OpenSSL::X509::Certificate] Server certificate securing
9
+ # the outer TLS channel
10
+ # @return [NTLM::ChannelBinding] A ChannelBinding holding a token that can be
11
+ # embedded in a {Type3} message
12
+ def self.create(outer_channel)
13
+ new(outer_channel)
14
+ end
15
+
16
+ # @param outer_channel [OpenSSL::X509::Certificate] Server certificate securing
17
+ # the outer TLS channel
18
+ def initialize(outer_channel)
19
+ @channel = outer_channel
20
+ @unique_prefix = 'tls-server-end-point'
21
+ @initiator_addtype = 0
22
+ @initiator_address_length = 0
23
+ @acceptor_addrtype = 0
24
+ @acceptor_address_length = 0
25
+ end
26
+
27
+ attr_reader :channel, :unique_prefix, :initiator_addtype
28
+ attr_reader :initiator_address_length, :acceptor_addrtype
29
+ attr_reader :acceptor_address_length
30
+
31
+ # Returns a channel binding hash acceptable for use as a AV_PAIR MsvAvChannelBindings
32
+ # field value as specified in the NTLM protocol
33
+ #
34
+ # @return [String] MD5 hash of gss_channel_bindings_struct
35
+ def channel_binding_token
36
+ @channel_binding_token ||= OpenSSL::Digest::MD5.new(gss_channel_bindings_struct).digest
37
+ end
38
+
39
+ def gss_channel_bindings_struct
40
+ @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')
45
+ token << [application_data.length].pack('I')
46
+ token << application_data
47
+ token
48
+ end
49
+ end
50
+
51
+ def channel_hash
52
+ @channel_hash ||= OpenSSL::Digest::SHA256.new(channel.to_der)
53
+ end
54
+
55
+ def application_data
56
+ @application_data ||= begin
57
+ data = unique_prefix
58
+ data << ':'
59
+ data << channel_hash.digest
60
+ data
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,65 +1,65 @@
1
- module Net
2
- module NTLM
3
- class Client
4
-
5
- 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]
9
-
10
- attr_reader :username, :password, :domain, :workstation, :flags
11
-
12
- # @note All string parameters should be encoded in UTF-8. The proper
13
- # final encoding for placing in the various {Message messages} will be
14
- # chosen based on negotiation with the server.
15
- #
16
- # @param username [String]
17
- # @param password [String]
18
- # @option opts [String] :domain where we're authenticating to
19
- # @option opts [String] :workstation local workstation name
20
- # @option opts [Fixnum] :flags (DEFAULT_FLAGS) see Net::NTLM::Message::Type1.flag
21
- def initialize(username, password, opts = {})
22
- @username = username
23
- @password = password
24
- @domain = opts[:domain] || nil
25
- @workstation = opts[:workstation] || nil
26
- @flags = opts[:flags] || DEFAULT_FLAGS
27
- end
28
-
29
- # @return [NTLM::Message]
30
- def init_context(resp = nil, channel_binding = nil)
31
- if resp.nil?
32
- @session = nil
33
- type1_message
34
- else
35
- @session = Client::Session.new(self, Net::NTLM::Message.decode64(resp), channel_binding)
36
- @session.authenticate!
37
- end
38
- end
39
-
40
- # @return [Client::Session]
41
- def session
42
- @session
43
- end
44
-
45
- def session_key
46
- @session.exported_session_key
47
- end
48
-
49
- private
50
-
51
- # @return [Message::Type1]
52
- def type1_message
53
- type1 = Message::Type1.new
54
- type1[:flag].value = flags
55
- type1.domain = domain if domain
56
- type1.workstation = workstation if workstation
57
-
58
- type1
59
- end
60
-
61
- end
62
- end
63
- end
64
-
65
- require "net/ntlm/client/session"
1
+ module Net
2
+ module NTLM
3
+ class Client
4
+
5
+ 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]
9
+
10
+ attr_reader :username, :password, :domain, :workstation, :flags
11
+
12
+ # @note All string parameters should be encoded in UTF-8. The proper
13
+ # final encoding for placing in the various {Message messages} will be
14
+ # chosen based on negotiation with the server.
15
+ #
16
+ # @param username [String]
17
+ # @param password [String]
18
+ # @option opts [String] :domain where we're authenticating to
19
+ # @option opts [String] :workstation local workstation name
20
+ # @option opts [Fixnum] :flags (DEFAULT_FLAGS) see Net::NTLM::Message::Type1.flag
21
+ def initialize(username, password, opts = {})
22
+ @username = username
23
+ @password = password
24
+ @domain = opts[:domain] || nil
25
+ @workstation = opts[:workstation] || nil
26
+ @flags = opts[:flags] || DEFAULT_FLAGS
27
+ end
28
+
29
+ # @return [NTLM::Message]
30
+ def init_context(resp = nil, channel_binding = nil)
31
+ if resp.nil?
32
+ @session = nil
33
+ type1_message
34
+ else
35
+ @session = Client::Session.new(self, Net::NTLM::Message.decode64(resp), channel_binding)
36
+ @session.authenticate!
37
+ end
38
+ end
39
+
40
+ # @return [Client::Session]
41
+ def session
42
+ @session
43
+ end
44
+
45
+ def session_key
46
+ @session.exported_session_key
47
+ end
48
+
49
+ private
50
+
51
+ # @return [Message::Type1]
52
+ def type1_message
53
+ type1 = Message::Type1.new
54
+ type1[:flag].value = flags
55
+ type1.domain = domain if domain
56
+ type1.workstation = workstation if workstation
57
+
58
+ type1
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+
65
+ require "net/ntlm/client/session"
@@ -1,237 +1,237 @@
1
- module Net
2
- module NTLM
3
- class Client::Session
4
-
5
- VERSION_MAGIC = "\x01\x00\x00\x00"
6
- TIME_OFFSET = 11644473600
7
- 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
-
13
- attr_reader :client, :challenge_message, :channel_binding
14
-
15
- # @param client [Net::NTLM::Client] the client instance
16
- # @param challenge_message [Net::NTLM::Message::Type2] server message
17
- def initialize(client, challenge_message, channel_binding = nil)
18
- @client = client
19
- @challenge_message = challenge_message
20
- @channel_binding = channel_binding
21
- end
22
-
23
- # Generate an NTLMv2 AUTHENTICATE_MESSAGE
24
- # @see http://msdn.microsoft.com/en-us/library/cc236643.aspx
25
- # @return [Net::NTLM::Message::Type3]
26
- def authenticate!
27
- calculate_user_session_key!
28
- type3_opts = {
29
- :lm_response => lmv2_resp,
30
- :ntlm_response => 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 = OpenSSL::Cipher::Cipher.new("rc4")
40
- rc4.encrypt
41
- rc4.key = user_session_key
42
- sk = rc4.update exported_session_key
43
- sk << rc4.final
44
- t3.session_key = sk
45
- end
46
- t3
47
- end
48
-
49
- def exported_session_key
50
- @exported_session_key ||=
51
- begin
52
- if negotiate_key_exchange?
53
- OpenSSL::Cipher.new("rc4").random_key
54
- else
55
- user_session_key
56
- end
57
- end
58
- end
59
-
60
- def sign_message(message)
61
- seq = sequence
62
- sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, client_sign_key, "#{seq}#{message}")[0..7]
63
- if negotiate_key_exchange?
64
- sig = client_cipher.update sig
65
- sig << client_cipher.final
66
- end
67
- "#{VERSION_MAGIC}#{sig}#{seq}"
68
- end
69
-
70
- def verify_signature(signature, message)
71
- seq = signature[-4..-1]
72
- sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, server_sign_key, "#{seq}#{message}")[0..7]
73
- if negotiate_key_exchange?
74
- sig = server_cipher.update sig
75
- sig << server_cipher.final
76
- end
77
- "#{VERSION_MAGIC}#{sig}#{seq}" == signature
78
- end
79
-
80
- def seal_message(message)
81
- emessage = client_cipher.update(message)
82
- emessage + client_cipher.final
83
- end
84
-
85
- def unseal_message(emessage)
86
- message = server_cipher.update(emessage)
87
- message + server_cipher.final
88
- end
89
-
90
- private
91
-
92
-
93
- def user_session_key
94
- @user_session_key ||= nil
95
- end
96
-
97
- def sequence
98
- [raw_sequence].pack("V*")
99
- end
100
-
101
- def raw_sequence
102
- if defined? @raw_sequence
103
- @raw_sequence += 1
104
- else
105
- @raw_sequence = 0
106
- end
107
- end
108
-
109
- def client_sign_key
110
- @client_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SIGNING}"
111
- end
112
-
113
- def server_sign_key
114
- @server_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SIGNING}"
115
- end
116
-
117
- def client_seal_key
118
- @client_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SEALING}"
119
- end
120
-
121
- def server_seal_key
122
- @server_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SEALING}"
123
- end
124
-
125
- def client_cipher
126
- @client_cipher ||=
127
- begin
128
- rc4 = OpenSSL::Cipher::Cipher.new("rc4")
129
- rc4.encrypt
130
- rc4.key = client_seal_key
131
- rc4
132
- end
133
- end
134
-
135
- def server_cipher
136
- @server_cipher ||=
137
- begin
138
- rc4 = OpenSSL::Cipher::Cipher.new("rc4")
139
- rc4.decrypt
140
- rc4.key = server_seal_key
141
- rc4
142
- end
143
- end
144
-
145
- def client_challenge
146
- @client_challenge ||= NTLM.pack_int64le(rand(MAX64))
147
- end
148
-
149
- def server_challenge
150
- @server_challenge ||= challenge_message[:challenge].serialize
151
- end
152
-
153
- # epoch -> milsec from Jan 1, 1601
154
- # @see http://support.microsoft.com/kb/188768
155
- def timestamp
156
- @timestamp ||= 10_000_000 * (Time.now.to_i + TIME_OFFSET)
157
- end
158
-
159
- def use_oem_strings?
160
- challenge_message.has_flag? :OEM
161
- end
162
-
163
- def negotiate_key_exchange?
164
- challenge_message.has_flag? :KEY_EXCHANGE
165
- end
166
-
167
- def username
168
- oem_or_unicode_str client.username
169
- end
170
-
171
- def password
172
- oem_or_unicode_str client.password
173
- end
174
-
175
- def workstation
176
- (client.workstation ? oem_or_unicode_str(client.workstation) : "")
177
- end
178
-
179
- def domain
180
- (client.domain ? oem_or_unicode_str(client.domain) : "")
181
- end
182
-
183
- def oem_or_unicode_str(str)
184
- if use_oem_strings?
185
- NTLM::EncodeUtil.decode_utf16le str
186
- else
187
- NTLM::EncodeUtil.encode_utf16le str
188
- end
189
- end
190
-
191
- def ntlmv2_hash
192
- @ntlmv2_hash ||= NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
193
- end
194
-
195
- def calculate_user_session_key!
196
- @user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
197
- end
198
-
199
- def lmv2_resp
200
- OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + client_challenge) + client_challenge
201
- end
202
-
203
- def ntlmv2_resp
204
- nt_proof_str + blob
205
- end
206
-
207
- def nt_proof_str
208
- @nt_proof_str ||= OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + blob)
209
- end
210
-
211
- def blob
212
- @blob ||=
213
- begin
214
- b = Blob.new
215
- b.timestamp = timestamp
216
- b.challenge = client_challenge
217
- b.target_info = target_info
218
- b.serialize
219
- end
220
- end
221
-
222
- def target_info
223
- @target_info ||= begin
224
- if channel_binding
225
- t = Net::NTLM::TargetInfo.new(challenge_message.target_info)
226
- av_id = Net::NTLM::TargetInfo::MSV_AV_CHANNEL_BINDINGS
227
- t.av_pairs[av_id] = channel_binding.channel_binding_token
228
- t.to_s
229
- else
230
- challenge_message.target_info
231
- end
232
- end
233
- end
234
-
235
- end
236
- end
237
- end
1
+ module Net
2
+ module NTLM
3
+ class Client::Session
4
+
5
+ VERSION_MAGIC = "\x01\x00\x00\x00"
6
+ TIME_OFFSET = 11644473600
7
+ 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
+
13
+ attr_reader :client, :challenge_message, :channel_binding
14
+
15
+ # @param client [Net::NTLM::Client] the client instance
16
+ # @param challenge_message [Net::NTLM::Message::Type2] server message
17
+ def initialize(client, challenge_message, channel_binding = nil)
18
+ @client = client
19
+ @challenge_message = challenge_message
20
+ @channel_binding = channel_binding
21
+ end
22
+
23
+ # Generate an NTLMv2 AUTHENTICATE_MESSAGE
24
+ # @see http://msdn.microsoft.com/en-us/library/cc236643.aspx
25
+ # @return [Net::NTLM::Message::Type3]
26
+ def authenticate!
27
+ calculate_user_session_key!
28
+ type3_opts = {
29
+ :lm_response => lmv2_resp,
30
+ :ntlm_response => 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 = OpenSSL::Cipher.new("rc4")
40
+ rc4.encrypt
41
+ rc4.key = user_session_key
42
+ sk = rc4.update exported_session_key
43
+ sk << rc4.final
44
+ t3.session_key = sk
45
+ end
46
+ t3
47
+ end
48
+
49
+ def exported_session_key
50
+ @exported_session_key ||=
51
+ begin
52
+ if negotiate_key_exchange?
53
+ OpenSSL::Cipher.new("rc4").random_key
54
+ else
55
+ user_session_key
56
+ end
57
+ end
58
+ end
59
+
60
+ def sign_message(message)
61
+ seq = sequence
62
+ sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, client_sign_key, "#{seq}#{message}")[0..7]
63
+ if negotiate_key_exchange?
64
+ sig = client_cipher.update sig
65
+ sig << client_cipher.final
66
+ end
67
+ "#{VERSION_MAGIC}#{sig}#{seq}"
68
+ end
69
+
70
+ def verify_signature(signature, message)
71
+ seq = signature[-4..-1]
72
+ sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, server_sign_key, "#{seq}#{message}")[0..7]
73
+ if negotiate_key_exchange?
74
+ sig = server_cipher.update sig
75
+ sig << server_cipher.final
76
+ end
77
+ "#{VERSION_MAGIC}#{sig}#{seq}" == signature
78
+ end
79
+
80
+ def seal_message(message)
81
+ emessage = client_cipher.update(message)
82
+ emessage + client_cipher.final
83
+ end
84
+
85
+ def unseal_message(emessage)
86
+ message = server_cipher.update(emessage)
87
+ message + server_cipher.final
88
+ end
89
+
90
+ private
91
+
92
+
93
+ def user_session_key
94
+ @user_session_key ||= nil
95
+ end
96
+
97
+ def sequence
98
+ [raw_sequence].pack("V*")
99
+ end
100
+
101
+ def raw_sequence
102
+ if defined? @raw_sequence
103
+ @raw_sequence += 1
104
+ else
105
+ @raw_sequence = 0
106
+ end
107
+ end
108
+
109
+ def client_sign_key
110
+ @client_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SIGNING}"
111
+ end
112
+
113
+ def server_sign_key
114
+ @server_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SIGNING}"
115
+ end
116
+
117
+ def client_seal_key
118
+ @client_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SEALING}"
119
+ end
120
+
121
+ def server_seal_key
122
+ @server_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SEALING}"
123
+ end
124
+
125
+ 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
133
+ end
134
+
135
+ 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
143
+ end
144
+
145
+ def client_challenge
146
+ @client_challenge ||= NTLM.pack_int64le(rand(MAX64))
147
+ end
148
+
149
+ def server_challenge
150
+ @server_challenge ||= challenge_message[:challenge].serialize
151
+ end
152
+
153
+ # epoch -> milsec from Jan 1, 1601
154
+ # @see http://support.microsoft.com/kb/188768
155
+ def timestamp
156
+ @timestamp ||= 10_000_000 * (Time.now.to_i + TIME_OFFSET)
157
+ end
158
+
159
+ def use_oem_strings?
160
+ challenge_message.has_flag? :OEM
161
+ end
162
+
163
+ def negotiate_key_exchange?
164
+ challenge_message.has_flag? :KEY_EXCHANGE
165
+ end
166
+
167
+ def username
168
+ oem_or_unicode_str client.username
169
+ end
170
+
171
+ def password
172
+ oem_or_unicode_str client.password
173
+ end
174
+
175
+ def workstation
176
+ (client.workstation ? oem_or_unicode_str(client.workstation) : "")
177
+ end
178
+
179
+ def domain
180
+ (client.domain ? oem_or_unicode_str(client.domain) : "")
181
+ end
182
+
183
+ def oem_or_unicode_str(str)
184
+ if use_oem_strings?
185
+ NTLM::EncodeUtil.decode_utf16le str
186
+ else
187
+ NTLM::EncodeUtil.encode_utf16le str
188
+ end
189
+ end
190
+
191
+ def ntlmv2_hash
192
+ @ntlmv2_hash ||= NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
193
+ end
194
+
195
+ def calculate_user_session_key!
196
+ @user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
197
+ end
198
+
199
+ def lmv2_resp
200
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + client_challenge) + client_challenge
201
+ end
202
+
203
+ def ntlmv2_resp
204
+ nt_proof_str + blob
205
+ end
206
+
207
+ def nt_proof_str
208
+ @nt_proof_str ||= OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + blob)
209
+ end
210
+
211
+ def blob
212
+ @blob ||=
213
+ begin
214
+ b = Blob.new
215
+ b.timestamp = timestamp
216
+ b.challenge = client_challenge
217
+ b.target_info = target_info
218
+ b.serialize
219
+ end
220
+ end
221
+
222
+ def target_info
223
+ @target_info ||= begin
224
+ if channel_binding
225
+ t = Net::NTLM::TargetInfo.new(challenge_message.target_info)
226
+ av_id = Net::NTLM::TargetInfo::MSV_AV_CHANNEL_BINDINGS
227
+ t.av_pairs[av_id] = channel_binding.channel_binding_token
228
+ t.to_s
229
+ else
230
+ challenge_message.target_info
231
+ end
232
+ end
233
+ end
234
+
235
+ end
236
+ end
237
+ end