rubyntlm 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
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