rubyntlm 0.4.0 → 0.5.0

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