rubyntlm 0.5.3 → 0.6.0

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 +3 -3
  3. data/.rspec +2 -2
  4. data/.travis.yml +10 -11
  5. data/CHANGELOG.md +5 -5
  6. data/Gemfile +3 -3
  7. data/LICENSE +19 -19
  8. data/Rakefile +22 -22
  9. data/lib/net/ntlm.rb +266 -263
  10. data/lib/net/ntlm/blob.rb +28 -28
  11. data/lib/net/ntlm/channel_binding.rb +65 -0
  12. data/lib/net/ntlm/client.rb +65 -65
  13. data/lib/net/ntlm/client/session.rb +237 -223
  14. data/lib/net/ntlm/encode_util.rb +49 -49
  15. data/lib/net/ntlm/exceptions.rb +14 -0
  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 -0
  29. data/lib/net/ntlm/version.rb +11 -11
  30. data/rubyntlm.gemspec +28 -28
  31. data/spec/lib/net/ntlm/blob_spec.rb +16 -16
  32. data/spec/lib/net/ntlm/channel_binding_spec.rb +17 -0
  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 -0
  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 -0
  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 +12 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 044c8c3cccfa69d9397e63b89263b2ae5abef45d
4
- data.tar.gz: d9b2964d47468ea490d127ba88dd2579a817cb5b
3
+ metadata.gz: 96ed497ebb6619b61911e39777bf3d193909bc45
4
+ data.tar.gz: 4396365cd33577c1b52db0ae516d8c1353293877
5
5
  SHA512:
6
- metadata.gz: 6ffeaeb30b32bddab3468fe985657bf508a04e27f4327c26767a58d170bb8293c683e4d55cfdeeb78609deb26e7b3392c46312298adbe5867bffe5b50d86b3b5
7
- data.tar.gz: 599b495aa2de031410ec8f630809e1017b2c79f912a5178aa53ee30d0a65f98ac1b965b108dd6a2068009f6b0cf10774cd345352313c97696bb2089352a96b60
6
+ metadata.gz: 5959b911914ac4b71dc44296ee98345f874db4786da7630db08d016856bfa5555103e94310dd1bb0252211646c45ff87a3f94a9399be939e35ab0c8d32e067be
7
+ data.tar.gz: 1ab87702641e2e9946527ee504020fad142445ce6ac78206102b3fa378550a9c44ebb51795089f081e72b56329234dd772c8da7281f44badfe976d28dbc47c73
data/.gitignore CHANGED
@@ -1,3 +1,3 @@
1
- .yardoc
2
- /doc
3
- Gemfile.lock
1
+ .yardoc
2
+ /doc
3
+ Gemfile.lock
data/.rspec CHANGED
@@ -1,2 +1,2 @@
1
- --format documentation
2
- --color
1
+ --format documentation
2
+ --color
@@ -1,11 +1,10 @@
1
- language: ruby
2
- rvm:
3
- - 1.9.3
4
- - 1.9.2
5
- - 1.8.7
6
- - 2.0.0
7
- - rbx-19mode
8
- - rbx-18mode
9
- - jruby-19mode
10
- before_install:
11
- - gem update bundler
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - 2.0.0
6
+ - rbx-19mode
7
+ - rbx-18mode
8
+ - jruby-19mode
9
+ before_install:
10
+ - gem update bundler
@@ -1,6 +1,6 @@
1
- 0.2.0 - Major changes to behavior!!!!
2
- - Bug - Type 1 packets do not include a domain and workstation by defauly. Packet capture software will see this type of packet as malformed. All packets now include this information
3
- - Bug - Type 3 packets do not include the calling workstation. This should be setup by default.
4
-
5
- 0.1.2
1
+ 0.2.0 - Major changes to behavior!!!!
2
+ - Bug - Type 1 packets do not include a domain and workstation by defauly. Packet capture software will see this type of packet as malformed. All packets now include this information
3
+ - Bug - Type 3 packets do not include the calling workstation. This should be setup by default.
4
+
5
+ 0.1.2
6
6
  - Feature user can specify the target domain
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2013 Paul Morton, Matt Zukowski, Kohei Kajimoto
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of
6
- this software and associated documentation files (the "Software"), to deal in
7
- the Software without restriction, including without limitation the rights to
8
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
- the Software, and to permit persons to whom the Software is furnished to do so,
10
- subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Paul Morton, Matt Zukowski, Kohei Kajimoto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
20
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -1,22 +1,22 @@
1
- require "bundler/gem_tasks"
2
-
3
-
4
- require 'rspec/core/rake_task'
5
- RSpec::Core::RakeTask.new(:spec)
6
-
7
- task :default => :spec
8
-
9
- desc "Generate code coverage"
10
- task :coverage do
11
- ENV['COVERAGE'] = 'true'
12
- Rake::Task["spec"].execute
13
- end
14
-
15
- desc "Open a Pry console for this library"
16
- task :console do
17
- require 'pry'
18
- require 'net/ntlm'
19
- ARGV.clear
20
- Pry.start
21
- end
22
-
1
+ require "bundler/gem_tasks"
2
+
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
9
+ desc "Generate code coverage"
10
+ task :coverage do
11
+ ENV['COVERAGE'] = 'true'
12
+ Rake::Task["spec"].execute
13
+ end
14
+
15
+ desc "Open a Pry console for this library"
16
+ task :console do
17
+ require 'pry'
18
+ require 'net/ntlm'
19
+ ARGV.clear
20
+ Pry.start
21
+ end
22
+
@@ -1,263 +1,266 @@
1
- # encoding: UTF-8
2
- #
3
- # = net/ntlm.rb
4
- #
5
- # An NTLM Authentication Library for Ruby
6
- #
7
- # This code is a derivative of "dbf2.rb" written by yrock
8
- # and Minero Aoki. You can find original code here:
9
- # http://jp.rubyist.net/magazine/?0013-CodeReview
10
- # -------------------------------------------------------------
11
- # Copyright (c) 2005,2006 yrock
12
- #
13
- #
14
- # 2006-02-11 refactored by Minero Aoki
15
- # -------------------------------------------------------------
16
- #
17
- # All protocol information used to write this code stems from
18
- # "The NTLM Authentication Protocol" by Eric Glass. The author
19
- # would thank to him for this tremendous work and making it
20
- # available on the net.
21
- # http://davenport.sourceforge.net/ntlm.html
22
- # -------------------------------------------------------------
23
- # Copyright (c) 2003 Eric Glass
24
- #
25
- # -------------------------------------------------------------
26
- #
27
- # The author also looked Mozilla-Firefox-1.0.7 source code,
28
- # namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
29
- # Jonathan Bastien-Filiatrault's libntlm-ruby.
30
- # "http://x2a.org/websvn/filedetails.php?
31
- # repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
32
- # The latter has a minor bug in its separate_keys function.
33
- # The third key has to begin from the 14th character of the
34
- # input string instead of 13th:)
35
- #--
36
- # $Id: ntlm.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $
37
- #++
38
-
39
- require 'base64'
40
- require 'openssl'
41
- require 'openssl/digest'
42
- require 'socket'
43
-
44
- # Load Order is important here
45
- require 'net/ntlm/field'
46
- require 'net/ntlm/int16_le'
47
- require 'net/ntlm/int32_le'
48
- require 'net/ntlm/int64_le'
49
- require 'net/ntlm/string'
50
-
51
- require 'net/ntlm/field_set'
52
- require 'net/ntlm/blob'
53
- require 'net/ntlm/security_buffer'
54
- require 'net/ntlm/message'
55
- require 'net/ntlm/message/type0'
56
- require 'net/ntlm/message/type1'
57
- require 'net/ntlm/message/type2'
58
- require 'net/ntlm/message/type3'
59
-
60
- require 'net/ntlm/encode_util'
61
-
62
- require 'net/ntlm/client'
63
-
64
- module Net
65
- module NTLM
66
-
67
- LM_MAGIC = "KGS!@\#$%"
68
- TIME_OFFSET = 11644473600
69
- MAX64 = 0xffffffffffffffff
70
-
71
-
72
- class << self
73
-
74
- # Valid format for LAN Manager hex digest portion: 32 hexadecimal characters.
75
- LAN_MANAGER_HEX_DIGEST_REGEXP = /[0-9a-f]{32}/i
76
- # Valid format for NT LAN Manager hex digest portion: 32 hexadecimal characters.
77
- NT_LAN_MANAGER_HEX_DIGEST_REGEXP = /[0-9a-f]{32}/i
78
- # Valid format for an NTLM hash composed of `'<LAN Manager hex digest>:<NT LAN Manager hex digest>'`.
79
- DATA_REGEXP = /\A#{LAN_MANAGER_HEX_DIGEST_REGEXP}:#{NT_LAN_MANAGER_HEX_DIGEST_REGEXP}\z/
80
-
81
- # Takes a string and determines whether it is a valid NTLM Hash
82
- # @param [String] the string to validate
83
- # @return [Boolean] whether or not the string is a valid NTLM hash
84
- def is_ntlm_hash?(data)
85
- decoded_data = data.dup
86
- decoded_data = EncodeUtil.decode_utf16le(decoded_data)
87
- if DATA_REGEXP.match(decoded_data)
88
- true
89
- else
90
- false
91
- end
92
- end
93
-
94
- # Conver the value to a 64-Bit Little Endian Int
95
- # @param [String] val The string to convert
96
- def pack_int64le(val)
97
- [val & 0x00000000ffffffff, val >> 32].pack("V2")
98
- end
99
-
100
- # Builds an array of strings that are 7 characters long
101
- # @param [String] str The string to split
102
- # @api private
103
- def split7(str)
104
- s = str.dup
105
- until s.empty?
106
- (ret ||= []).push s.slice!(0, 7)
107
- end
108
- ret
109
- end
110
-
111
- # Not sure what this is doing
112
- # @param [String] str String to generate keys for
113
- # @api private
114
- def gen_keys(str)
115
- split7(str).map{ |str7|
116
- bits = split7(str7.unpack("B*")[0]).inject('')\
117
- {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
118
- [bits].pack("B*")
119
- }
120
- end
121
-
122
- def apply_des(plain, keys)
123
- dec = OpenSSL::Cipher::Cipher.new("des-cbc")
124
- dec.padding = 0
125
- keys.map {|k|
126
- dec.key = k
127
- dec.encrypt.update(plain) + dec.final
128
- }
129
- end
130
-
131
- # Generates a Lan Manager Hash
132
- # @param [String] password The password to base the hash on
133
- def lm_hash(password)
134
- keys = gen_keys password.upcase.ljust(14, "\0")
135
- apply_des(LM_MAGIC, keys).join
136
- end
137
-
138
- # Generate a NTLM Hash
139
- # @param [String] password The password to base the hash on
140
- # @option opt :unicode (false) Unicode encode the password
141
- def ntlm_hash(password, opt = {})
142
- pwd = password.dup
143
- unless opt[:unicode]
144
- pwd = EncodeUtil.encode_utf16le(pwd)
145
- end
146
- OpenSSL::Digest::MD4.digest pwd
147
- end
148
-
149
- # Generate a NTLMv2 Hash
150
- # @param [String] user The username
151
- # @param [String] password The password
152
- # @param [String] target The domain or workstation to authenticate to
153
- # @option opt :unicode (false) Unicode encode the domain
154
- def ntlmv2_hash(user, password, target, opt={})
155
- if is_ntlm_hash? password
156
- decoded_password = EncodeUtil.decode_utf16le(password)
157
- ntlmhash = [decoded_password.upcase[33,65]].pack('H32')
158
- else
159
- ntlmhash = ntlm_hash(password, opt)
160
- end
161
- userdomain = user.upcase + target
162
- unless opt[:unicode]
163
- userdomain = EncodeUtil.encode_utf16le(userdomain)
164
- end
165
- OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
166
- end
167
-
168
- def lm_response(arg)
169
- begin
170
- hash = arg[:lm_hash]
171
- chal = arg[:challenge]
172
- rescue
173
- raise ArgumentError
174
- end
175
- chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
176
- keys = gen_keys hash.ljust(21, "\0")
177
- apply_des(chal, keys).join
178
- end
179
-
180
- def ntlm_response(arg)
181
- hash = arg[:ntlm_hash]
182
- chal = arg[:challenge]
183
- chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
184
- keys = gen_keys hash.ljust(21, "\0")
185
- apply_des(chal, keys).join
186
- end
187
-
188
- def ntlmv2_response(arg, opt = {})
189
- begin
190
- key = arg[:ntlmv2_hash]
191
- chal = arg[:challenge]
192
- ti = arg[:target_info]
193
- rescue
194
- raise ArgumentError
195
- end
196
- chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
197
-
198
- if opt[:client_challenge]
199
- cc = opt[:client_challenge]
200
- else
201
- cc = rand(MAX64)
202
- end
203
- cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
204
-
205
- if opt[:timestamp]
206
- ts = opt[:timestamp]
207
- else
208
- ts = Time.now.to_i
209
- end
210
- # epoch -> milsec from Jan 1, 1601
211
- ts = 10_000_000 * (ts + TIME_OFFSET)
212
-
213
- blob = Blob.new
214
- blob.timestamp = ts
215
- blob.challenge = cc
216
- blob.target_info = ti
217
-
218
- bb = blob.serialize
219
-
220
- OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
221
- end
222
-
223
- def lmv2_response(arg, opt = {})
224
- key = arg[:ntlmv2_hash]
225
- chal = arg[:challenge]
226
-
227
- chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
228
-
229
- if opt[:client_challenge]
230
- cc = opt[:client_challenge]
231
- else
232
- cc = rand(MAX64)
233
- end
234
- cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
235
-
236
- OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
237
- end
238
-
239
- def ntlm2_session(arg, opt = {})
240
- begin
241
- passwd_hash = arg[:ntlm_hash]
242
- chal = arg[:challenge]
243
- rescue
244
- raise ArgumentError
245
- end
246
- chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
247
-
248
- if opt[:client_challenge]
249
- cc = opt[:client_challenge]
250
- else
251
- cc = rand(MAX64)
252
- end
253
- cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
254
-
255
- keys = gen_keys(passwd_hash.ljust(21, "\0"))
256
- session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
257
- response = apply_des(session_hash, keys).join
258
- [cc.ljust(24, "\0"), response]
259
- end
260
- end
261
-
262
- end
263
- end
1
+ # encoding: UTF-8
2
+ #
3
+ # = net/ntlm.rb
4
+ #
5
+ # An NTLM Authentication Library for Ruby
6
+ #
7
+ # This code is a derivative of "dbf2.rb" written by yrock
8
+ # and Minero Aoki. You can find original code here:
9
+ # http://jp.rubyist.net/magazine/?0013-CodeReview
10
+ # -------------------------------------------------------------
11
+ # Copyright (c) 2005,2006 yrock
12
+ #
13
+ #
14
+ # 2006-02-11 refactored by Minero Aoki
15
+ # -------------------------------------------------------------
16
+ #
17
+ # All protocol information used to write this code stems from
18
+ # "The NTLM Authentication Protocol" by Eric Glass. The author
19
+ # would thank to him for this tremendous work and making it
20
+ # available on the net.
21
+ # http://davenport.sourceforge.net/ntlm.html
22
+ # -------------------------------------------------------------
23
+ # Copyright (c) 2003 Eric Glass
24
+ #
25
+ # -------------------------------------------------------------
26
+ #
27
+ # The author also looked Mozilla-Firefox-1.0.7 source code,
28
+ # namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
29
+ # Jonathan Bastien-Filiatrault's libntlm-ruby.
30
+ # "http://x2a.org/websvn/filedetails.php?
31
+ # repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
32
+ # The latter has a minor bug in its separate_keys function.
33
+ # The third key has to begin from the 14th character of the
34
+ # input string instead of 13th:)
35
+ #--
36
+ # $Id: ntlm.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $
37
+ #++
38
+
39
+ require 'base64'
40
+ require 'openssl'
41
+ require 'openssl/digest'
42
+ require 'socket'
43
+
44
+ # Load Order is important here
45
+ require 'net/ntlm/exceptions'
46
+ require 'net/ntlm/field'
47
+ require 'net/ntlm/int16_le'
48
+ require 'net/ntlm/int32_le'
49
+ require 'net/ntlm/int64_le'
50
+ require 'net/ntlm/string'
51
+
52
+ require 'net/ntlm/field_set'
53
+ require 'net/ntlm/blob'
54
+ require 'net/ntlm/security_buffer'
55
+ require 'net/ntlm/message'
56
+ require 'net/ntlm/message/type0'
57
+ require 'net/ntlm/message/type1'
58
+ require 'net/ntlm/message/type2'
59
+ require 'net/ntlm/message/type3'
60
+
61
+ require 'net/ntlm/encode_util'
62
+
63
+ require 'net/ntlm/client'
64
+ require 'net/ntlm/channel_binding'
65
+ require 'net/ntlm/target_info'
66
+
67
+ module Net
68
+ module NTLM
69
+
70
+ LM_MAGIC = "KGS!@\#$%"
71
+ TIME_OFFSET = 11644473600
72
+ MAX64 = 0xffffffffffffffff
73
+
74
+
75
+ class << self
76
+
77
+ # Valid format for LAN Manager hex digest portion: 32 hexadecimal characters.
78
+ LAN_MANAGER_HEX_DIGEST_REGEXP = /[0-9a-f]{32}/i
79
+ # Valid format for NT LAN Manager hex digest portion: 32 hexadecimal characters.
80
+ NT_LAN_MANAGER_HEX_DIGEST_REGEXP = /[0-9a-f]{32}/i
81
+ # Valid format for an NTLM hash composed of `'<LAN Manager hex digest>:<NT LAN Manager hex digest>'`.
82
+ DATA_REGEXP = /\A#{LAN_MANAGER_HEX_DIGEST_REGEXP}:#{NT_LAN_MANAGER_HEX_DIGEST_REGEXP}\z/
83
+
84
+ # Takes a string and determines whether it is a valid NTLM Hash
85
+ # @param [String] the string to validate
86
+ # @return [Boolean] whether or not the string is a valid NTLM hash
87
+ def is_ntlm_hash?(data)
88
+ decoded_data = data.dup
89
+ decoded_data = EncodeUtil.decode_utf16le(decoded_data)
90
+ if DATA_REGEXP.match(decoded_data)
91
+ true
92
+ else
93
+ false
94
+ end
95
+ end
96
+
97
+ # Conver the value to a 64-Bit Little Endian Int
98
+ # @param [String] val The string to convert
99
+ def pack_int64le(val)
100
+ [val & 0x00000000ffffffff, val >> 32].pack("V2")
101
+ end
102
+
103
+ # Builds an array of strings that are 7 characters long
104
+ # @param [String] str The string to split
105
+ # @api private
106
+ def split7(str)
107
+ s = str.dup
108
+ until s.empty?
109
+ (ret ||= []).push s.slice!(0, 7)
110
+ end
111
+ ret
112
+ end
113
+
114
+ # Not sure what this is doing
115
+ # @param [String] str String to generate keys for
116
+ # @api private
117
+ def gen_keys(str)
118
+ split7(str).map{ |str7|
119
+ bits = split7(str7.unpack("B*")[0]).inject('')\
120
+ {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
121
+ [bits].pack("B*")
122
+ }
123
+ end
124
+
125
+ def apply_des(plain, keys)
126
+ dec = OpenSSL::Cipher::Cipher.new("des-cbc")
127
+ dec.padding = 0
128
+ keys.map {|k|
129
+ dec.key = k
130
+ dec.encrypt.update(plain) + dec.final
131
+ }
132
+ end
133
+
134
+ # Generates a Lan Manager Hash
135
+ # @param [String] password The password to base the hash on
136
+ def lm_hash(password)
137
+ keys = gen_keys password.upcase.ljust(14, "\0")
138
+ apply_des(LM_MAGIC, keys).join
139
+ end
140
+
141
+ # Generate a NTLM Hash
142
+ # @param [String] password The password to base the hash on
143
+ # @option opt :unicode (false) Unicode encode the password
144
+ def ntlm_hash(password, opt = {})
145
+ pwd = password.dup
146
+ unless opt[:unicode]
147
+ pwd = EncodeUtil.encode_utf16le(pwd)
148
+ end
149
+ OpenSSL::Digest::MD4.digest pwd
150
+ end
151
+
152
+ # Generate a NTLMv2 Hash
153
+ # @param [String] user The username
154
+ # @param [String] password The password
155
+ # @param [String] target The domain or workstation to authenticate to
156
+ # @option opt :unicode (false) Unicode encode the domain
157
+ def ntlmv2_hash(user, password, target, opt={})
158
+ if is_ntlm_hash? password
159
+ decoded_password = EncodeUtil.decode_utf16le(password)
160
+ ntlmhash = [decoded_password.upcase[33,65]].pack('H32')
161
+ else
162
+ ntlmhash = ntlm_hash(password, opt)
163
+ end
164
+ userdomain = user.upcase + target
165
+ unless opt[:unicode]
166
+ userdomain = EncodeUtil.encode_utf16le(userdomain)
167
+ end
168
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
169
+ end
170
+
171
+ def lm_response(arg)
172
+ begin
173
+ hash = arg[:lm_hash]
174
+ chal = arg[:challenge]
175
+ rescue
176
+ raise ArgumentError
177
+ end
178
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
179
+ keys = gen_keys hash.ljust(21, "\0")
180
+ apply_des(chal, keys).join
181
+ end
182
+
183
+ def ntlm_response(arg)
184
+ hash = arg[:ntlm_hash]
185
+ chal = arg[:challenge]
186
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
187
+ keys = gen_keys hash.ljust(21, "\0")
188
+ apply_des(chal, keys).join
189
+ end
190
+
191
+ def ntlmv2_response(arg, opt = {})
192
+ begin
193
+ key = arg[:ntlmv2_hash]
194
+ chal = arg[:challenge]
195
+ ti = arg[:target_info]
196
+ rescue
197
+ raise ArgumentError
198
+ end
199
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
200
+
201
+ if opt[:client_challenge]
202
+ cc = opt[:client_challenge]
203
+ else
204
+ cc = rand(MAX64)
205
+ end
206
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
207
+
208
+ if opt[:timestamp]
209
+ ts = opt[:timestamp]
210
+ else
211
+ ts = Time.now.to_i
212
+ end
213
+ # epoch -> milsec from Jan 1, 1601
214
+ ts = 10_000_000 * (ts + TIME_OFFSET)
215
+
216
+ blob = Blob.new
217
+ blob.timestamp = ts
218
+ blob.challenge = cc
219
+ blob.target_info = ti
220
+
221
+ bb = blob.serialize
222
+
223
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
224
+ end
225
+
226
+ def lmv2_response(arg, opt = {})
227
+ key = arg[:ntlmv2_hash]
228
+ chal = arg[:challenge]
229
+
230
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
231
+
232
+ if opt[:client_challenge]
233
+ cc = opt[:client_challenge]
234
+ else
235
+ cc = rand(MAX64)
236
+ end
237
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
238
+
239
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
240
+ end
241
+
242
+ def ntlm2_session(arg, opt = {})
243
+ begin
244
+ passwd_hash = arg[:ntlm_hash]
245
+ chal = arg[:challenge]
246
+ rescue
247
+ raise ArgumentError
248
+ end
249
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
250
+
251
+ if opt[:client_challenge]
252
+ cc = opt[:client_challenge]
253
+ else
254
+ cc = rand(MAX64)
255
+ end
256
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
257
+
258
+ keys = gen_keys(passwd_hash.ljust(21, "\0"))
259
+ session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
260
+ response = apply_des(session_hash, keys).join
261
+ [cc.ljust(24, "\0"), response]
262
+ end
263
+ end
264
+
265
+ end
266
+ end