rubyntlm 0.4.0 → 0.5.0

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -3
  3. data/README.md +9 -2
  4. data/Rakefile +8 -0
  5. data/examples/http.rb +1 -1
  6. data/lib/net/ntlm.rb +10 -9
  7. data/lib/net/ntlm/blob.rb +17 -6
  8. data/lib/net/ntlm/client.rb +61 -0
  9. data/lib/net/ntlm/client/session.rb +223 -0
  10. data/lib/net/ntlm/encode_util.rb +2 -2
  11. data/lib/net/ntlm/field_set.rb +9 -5
  12. data/lib/net/ntlm/message.rb +23 -9
  13. data/lib/net/ntlm/message/type1.rb +1 -26
  14. data/lib/net/ntlm/message/type2.rb +11 -37
  15. data/lib/net/ntlm/message/type3.rb +77 -14
  16. data/lib/net/ntlm/version.rb +1 -1
  17. data/rubyntlm.gemspec +3 -2
  18. data/spec/lib/net/ntlm/blob_spec.rb +1 -1
  19. data/spec/lib/net/ntlm/client/session_spec.rb +68 -0
  20. data/spec/lib/net/ntlm/client_spec.rb +64 -0
  21. data/spec/lib/net/ntlm/encode_util_spec.rb +3 -3
  22. data/spec/lib/net/ntlm/field_set_spec.rb +4 -4
  23. data/spec/lib/net/ntlm/field_spec.rb +5 -5
  24. data/spec/lib/net/ntlm/message/type1_spec.rb +99 -10
  25. data/spec/lib/net/ntlm/message/type2_spec.rb +68 -24
  26. data/spec/lib/net/ntlm/message/type3_spec.rb +207 -2
  27. data/spec/lib/net/ntlm/security_buffer_spec.rb +8 -8
  28. data/spec/lib/net/ntlm/string_spec.rb +14 -14
  29. data/spec/lib/net/ntlm/version_spec.rb +7 -6
  30. data/spec/lib/net/ntlm_spec.rb +20 -22
  31. data/spec/spec_helper.rb +1 -2
  32. data/spec/support/shared/examples/net/ntlm/field_shared.rb +3 -3
  33. data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +34 -34
  34. data/spec/support/shared/examples/net/ntlm/int_shared.rb +8 -8
  35. data/spec/support/shared/examples/net/ntlm/message_shared.rb +3 -3
  36. metadata +36 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 92fe9fbd41088f3bb6dd585c16c9b105a91820bc
4
- data.tar.gz: 7cb29dfd82e71ff8075f143889da01ca406d41d1
3
+ metadata.gz: 33cb0ea71a4b1d8149a2a8bf39c5f24bd2752d5b
4
+ data.tar.gz: 032e16ead1a05ddcf0268d79c4ffff2ff631bfcf
5
5
  SHA512:
6
- metadata.gz: 6fd7a286d8a55f4ff6aeb7b0bc24d8dd1c37b4daaaa73e6f1ac747de520690085ea026eeec4e3d0c7b1c3225189c5b8591de0361c5e12b32ff54a30ec9da569a
7
- data.tar.gz: d4ab713acacb9e2450d66e8bcdb814a9cdadabcd65b29b6743889434cfec3e77aa30ef475ec4871c34b5b2de81488503433b716c316954a30e1b8df2da69cc75
6
+ metadata.gz: c276677b10f3f2b7fa35817fce82e1cfd5012298c752fbfc4576f0b4d45d339a5e6589fb547c16aa6609cb4174f2e9ac396c0f49ad7e7267f5b68b20f6e1fa47
7
+ data.tar.gz: 40e3423ad351782e571611b32704b583c0b75a06a2b3acedc62dc9c6e916b0bc3356fe51d1060dd3cda0ef330b175ad108623625b3f7a6f6eb7728f66d03216e
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
- --format nested
2
- --colour
3
- --drb
1
+ --format documentation
2
+ --color
data/README.md CHANGED
@@ -6,19 +6,26 @@ Ruby/NTLM provides message creator and parser for the NTLM authentication.
6
6
 
7
7
  __100% Ruby__
8
8
 
9
+ How to install
10
+ --------------
11
+
12
+ ```ruby
13
+ require 'rubyntlm'
14
+ ```
15
+
9
16
  Simple Example
10
17
  --------------
11
18
 
12
19
  ### Creating NTLM Type 1 message
13
20
 
14
21
  ```ruby
15
- t1 = NTLM::Message::Type1.new()
22
+ t1 = Net::NTLM::Message::Type1.new()
16
23
  ```
17
24
 
18
25
  ### Parsing NTLM Type 2 message from server
19
26
 
20
27
  ```ruby
21
- t2 = NTLM::Message.parse(message_from_server)
28
+ t2 = Net::NTLM::Message.parse(message_from_server)
22
29
  ```
23
30
 
24
31
  ### Creating NTLM Type 3 message
data/Rakefile CHANGED
@@ -12,3 +12,11 @@ task :coverage do
12
12
  Rake::Task["spec"].execute
13
13
  end
14
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
+
data/examples/http.rb CHANGED
@@ -48,7 +48,7 @@ def main
48
48
 
49
49
  unless $user and $passwd
50
50
  target = t2.target_name
51
- target = Net::NTLM::decode_utf16le(target) if t2.has_flag?(:UNICODE)
51
+ target = Net::NTLM::EncodeUtil.decode_utf16le(target) if t2.has_flag?(:UNICODE)
52
52
  puts "Target: #{target}"
53
53
  print "User name: "
54
54
  ($user = $stdin.readline).chomp!
data/lib/net/ntlm.rb CHANGED
@@ -57,11 +57,9 @@ require 'net/ntlm/message/type1'
57
57
  require 'net/ntlm/message/type2'
58
58
  require 'net/ntlm/message/type3'
59
59
 
60
-
61
60
  require 'net/ntlm/encode_util'
62
61
 
63
-
64
-
62
+ require 'net/ntlm/client'
65
63
 
66
64
  module Net
67
65
  module NTLM
@@ -102,10 +100,11 @@ module Net
102
100
  end
103
101
 
104
102
  def apply_des(plain, keys)
105
- dec = OpenSSL::Cipher::DES.new
103
+ dec = OpenSSL::Cipher::Cipher.new("des-cbc")
104
+ dec.padding = 0
106
105
  keys.map {|k|
107
106
  dec.key = k
108
- dec.encrypt.update(plain)
107
+ dec.encrypt.update(plain) + dec.final
109
108
  }
110
109
  end
111
110
 
@@ -134,7 +133,7 @@ module Net
134
133
  # @option opt :unicode (false) Unicode encode the domain
135
134
  def ntlmv2_hash(user, password, target, opt={})
136
135
  ntlmhash = ntlm_hash(password, opt)
137
- userdomain = (user + target).upcase
136
+ userdomain = user.upcase + target
138
137
  unless opt[:unicode]
139
138
  userdomain = EncodeUtil.encode_utf16le(userdomain)
140
139
  end
@@ -184,7 +183,7 @@ module Net
184
183
  ts = Time.now.to_i
185
184
  end
186
185
  # epoch -> milsec from Jan 1, 1601
187
- ts = 10000000 * (ts + TIME_OFFSET)
186
+ ts = 10_000_000 * (ts + TIME_OFFSET)
188
187
 
189
188
  blob = Blob.new
190
189
  blob.timestamp = ts
@@ -192,6 +191,7 @@ module Net
192
191
  blob.target_info = ti
193
192
 
194
193
  bb = blob.serialize
194
+
195
195
  OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
196
196
  end
197
197
 
@@ -218,15 +218,16 @@ module Net
218
218
  rescue
219
219
  raise ArgumentError
220
220
  end
221
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
221
222
 
222
223
  if opt[:client_challenge]
223
- cc = opt[:client_challenge]
224
+ cc = opt[:client_challenge]
224
225
  else
225
226
  cc = rand(MAX64)
226
227
  end
227
228
  cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
228
229
 
229
- keys = gen_keys passwd_hash.ljust(21, "\0")
230
+ keys = gen_keys(passwd_hash.ljust(21, "\0"))
230
231
  session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
231
232
  response = apply_des(session_hash, keys).join
232
233
  [cc.ljust(24, "\0"), response]
data/lib/net/ntlm/blob.rb CHANGED
@@ -4,14 +4,25 @@ module Net
4
4
  BLOB_SIGN = 0x00000101
5
5
 
6
6
  class Blob < FieldSet
7
- int32LE :blob_signature, {:value => BLOB_SIGN}
8
- int32LE :reserved, {:value => 0}
7
+ int32LE :blob_signature, {:value => BLOB_SIGN}
8
+ int32LE :reserved, {:value => 0}
9
9
  int64LE :timestamp, {:value => 0}
10
10
  string :challenge, {:value => "", :size => 8}
11
- int32LE :unknown1, {:value => 0}
12
- string :target_info, {:value => "", :size => 0}
13
- int32LE :unknown2, {:value => 0}
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
14
25
  end
15
26
 
16
27
  end
17
- end
28
+ end
@@ -0,0 +1,61 @@
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)
31
+ if resp.nil?
32
+ @session = nil
33
+ type1_message
34
+ else
35
+ @session = Client::Session.new(self, Net::NTLM::Message.decode64(resp))
36
+ @session.authenticate!
37
+ end
38
+ end
39
+
40
+ # @return [Client::Session]
41
+ def session
42
+ @session
43
+ end
44
+
45
+ private
46
+
47
+ # @return [Message::Type1]
48
+ def type1_message
49
+ type1 = Message::Type1.new
50
+ type1[:flag].value = flags
51
+ type1.domain = domain if domain
52
+ type1.workstation = workstation if workstation
53
+
54
+ type1
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+
61
+ require "net/ntlm/client/session"
@@ -0,0 +1,223 @@
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
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)
18
+ @client = client
19
+ @challenge_message = challenge_message
20
+ end
21
+
22
+ # Generate an NTLMv2 AUTHENTICATE_MESSAGE
23
+ # @see http://msdn.microsoft.com/en-us/library/cc236643.aspx
24
+ # @return [Net::NTLM::Message::Type3]
25
+ def authenticate!
26
+ calculate_user_session_key!
27
+ type3_opts = {
28
+ :lm_response => lmv2_resp,
29
+ :ntlm_response => ntlmv2_resp,
30
+ :domain => domain,
31
+ :user => username,
32
+ :workstation => workstation,
33
+ :flag => (challenge_message.flag & client.flags)
34
+ }
35
+ t3 = Message::Type3.create type3_opts
36
+ if negotiate_key_exchange?
37
+ t3.enable(:session_key)
38
+ rc4 = OpenSSL::Cipher::Cipher.new("rc4")
39
+ rc4.encrypt
40
+ rc4.key = user_session_key
41
+ sk = rc4.update master_key
42
+ sk << rc4.final
43
+ t3.session_key = sk
44
+ end
45
+ t3
46
+ end
47
+
48
+ def sign_message(message)
49
+ seq = sequence
50
+ sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, client_sign_key, "#{seq}#{message}")[0..7]
51
+ if negotiate_key_exchange?
52
+ sig = client_cipher.update sig
53
+ sig << client_cipher.final
54
+ end
55
+ "#{VERSION_MAGIC}#{sig}#{seq}"
56
+ end
57
+
58
+ def verify_signature(signature, message)
59
+ seq = signature[-4..-1]
60
+ sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, server_sign_key, "#{seq}#{message}")[0..7]
61
+ if negotiate_key_exchange?
62
+ sig = server_cipher.update sig
63
+ sig << server_cipher.final
64
+ end
65
+ "#{VERSION_MAGIC}#{sig}#{seq}" == signature
66
+ end
67
+
68
+ def seal_message(message)
69
+ emessage = client_cipher.update(message)
70
+ emessage + client_cipher.final
71
+ end
72
+
73
+ def unseal_message(emessage)
74
+ message = server_cipher.update(emessage)
75
+ message + server_cipher.final
76
+ end
77
+
78
+
79
+ private
80
+
81
+
82
+ def user_session_key
83
+ @user_session_key ||= nil
84
+ end
85
+
86
+ def master_key
87
+ @master_key ||= begin
88
+ if negotiate_key_exchange?
89
+ OpenSSL::Cipher.new("rc4").random_key
90
+ else
91
+ user_session_key
92
+ end
93
+ end
94
+ end
95
+
96
+ def sequence
97
+ [raw_sequence].pack("V*")
98
+ end
99
+
100
+ def raw_sequence
101
+ if defined? @raw_sequence
102
+ @raw_sequence += 1
103
+ else
104
+ @raw_sequence = 0
105
+ end
106
+ end
107
+
108
+ def client_sign_key
109
+ @client_sign_key ||= OpenSSL::Digest::MD5.digest "#{master_key}#{CLIENT_TO_SERVER_SIGNING}"
110
+ end
111
+
112
+ def server_sign_key
113
+ @server_sign_key ||= OpenSSL::Digest::MD5.digest "#{master_key}#{SERVER_TO_CLIENT_SIGNING}"
114
+ end
115
+
116
+ def client_seal_key
117
+ @client_seal_key ||= OpenSSL::Digest::MD5.digest "#{master_key}#{CLIENT_TO_SERVER_SEALING}"
118
+ end
119
+
120
+ def server_seal_key
121
+ @server_seal_key ||= OpenSSL::Digest::MD5.digest "#{master_key}#{SERVER_TO_CLIENT_SEALING}"
122
+ end
123
+
124
+ def client_cipher
125
+ @client_cipher ||=
126
+ begin
127
+ rc4 = OpenSSL::Cipher::Cipher.new("rc4")
128
+ rc4.encrypt
129
+ rc4.key = client_seal_key
130
+ rc4
131
+ end
132
+ end
133
+
134
+ def server_cipher
135
+ @server_cipher ||=
136
+ begin
137
+ rc4 = OpenSSL::Cipher::Cipher.new("rc4")
138
+ rc4.decrypt
139
+ rc4.key = server_seal_key
140
+ rc4
141
+ end
142
+ end
143
+
144
+ def client_challenge
145
+ @client_challenge ||= NTLM.pack_int64le(rand(MAX64))
146
+ end
147
+
148
+ def server_challenge
149
+ @server_challenge ||= challenge_message[:challenge].serialize
150
+ end
151
+
152
+ # epoch -> milsec from Jan 1, 1601
153
+ # @see http://support.microsoft.com/kb/188768
154
+ def timestamp
155
+ @timestamp ||= 10_000_000 * (Time.now.to_i + TIME_OFFSET)
156
+ end
157
+
158
+ def use_oem_strings?
159
+ challenge_message.has_flag? :OEM
160
+ end
161
+
162
+ def negotiate_key_exchange?
163
+ challenge_message.has_flag? :KEY_EXCHANGE
164
+ end
165
+
166
+ def username
167
+ oem_or_unicode_str client.username
168
+ end
169
+
170
+ def password
171
+ oem_or_unicode_str client.password
172
+ end
173
+
174
+ def workstation
175
+ (client.domain ? oem_or_unicode_str(client.workstation) : "")
176
+ end
177
+
178
+ def domain
179
+ (client.domain ? oem_or_unicode_str(client.domain) : "")
180
+ end
181
+
182
+ def oem_or_unicode_str(str)
183
+ if use_oem_strings?
184
+ NTLM::EncodeUtil.decode_utf16le str
185
+ else
186
+ NTLM::EncodeUtil.encode_utf16le str
187
+ end
188
+ end
189
+
190
+ def ntlmv2_hash
191
+ @ntlmv2_hash ||= NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
192
+ end
193
+
194
+ def calculate_user_session_key!
195
+ @user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
196
+ end
197
+
198
+ def lmv2_resp
199
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + client_challenge) + client_challenge
200
+ end
201
+
202
+ def ntlmv2_resp
203
+ nt_proof_str + blob
204
+ end
205
+
206
+ def nt_proof_str
207
+ @nt_proof_str ||= OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + blob)
208
+ end
209
+
210
+ def blob
211
+ @blob ||=
212
+ begin
213
+ b = Blob.new
214
+ b.timestamp = timestamp
215
+ b.challenge = client_challenge
216
+ b.target_info = challenge_message.target_info
217
+ b.serialize
218
+ end
219
+ end
220
+
221
+ end
222
+ end
223
+ end