guilleiguaran-rubyntlm 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/README +25 -0
  2. data/Rakefile +66 -0
  3. data/lib/net/ntlm.rb +853 -0
  4. data/test/function_test.rb +111 -0
  5. metadata +66 -0
data/README ADDED
@@ -0,0 +1,25 @@
1
+ = Ruby/NTLM -- NTLM Authentication Library for Ruby
2
+
3
+ Ruby/NTLM provides message creator and parser for the NTLM authentication.
4
+
5
+ Some features:
6
+ * Independent from non-standard Ruby libraries.
7
+ * Supports NTLM and NTLMv2 reponses.
8
+
9
+ == Simple Example
10
+
11
+ * Creating NTLM Type 1 message
12
+
13
+ t1 = NTLM::Message::Type1.new()
14
+
15
+ * Parsing NTLM Type 2 message from server
16
+
17
+ t2 = NTLM::Message.parse(message_from_server)
18
+
19
+ * Creating NTLM Type 3 message
20
+
21
+ t3 = t2.response({:user => 'user', :password => 'passwd'})
22
+
23
+ == Support
24
+
25
+ You can find Ruby/NTLM RubyForge page at http://rubyforge.org/projects/rubyntlm.
@@ -0,0 +1,66 @@
1
+ # Rakefile for rubyntlm -*- ruby -*-
2
+ # $Id: Rakefile,v 1.2 2006/10/05 01:36:52 koheik Exp $
3
+
4
+ require 'rake/rdoctask'
5
+ require 'rake/testtask'
6
+ require 'rake/packagetask'
7
+ require 'rake/gempackagetask'
8
+ require File.join(File.dirname(__FILE__), 'lib', 'net', 'ntlm')
9
+
10
+ PKG_NAME = 'rubyntlm'
11
+ PKG_VERSION = Net::NTLM::VERSION::STRING
12
+
13
+ task :default => [:test]
14
+
15
+ Rake::TestTask.new(:test) do |t|
16
+ t.test_files = FileList[ "test/*.rb" ]
17
+ t.warning = true
18
+ t.verbose = true
19
+ end
20
+
21
+ # Rake::PackageTask.new(PKG_NAME, PKG_VERSION) do |p|
22
+ # p.need_tar_gz = true
23
+ # p.package_dir = 'build'
24
+ # p.package_files.include("README", "Rakefile")
25
+ # p.package_files.include("lib/net/**/*.rb", "test/**/*.rb", "examples/**/*.rb")
26
+ # end
27
+
28
+ Rake::RDocTask.new do |rd|
29
+ rd.rdoc_dir = 'doc'
30
+ rd.title = 'Ruby/NTLM library'
31
+ rd.main = "README"
32
+ rd.rdoc_files.include("README", "lib/**/*.rb")
33
+ end
34
+
35
+ dist_dirs = ["lib", "test", "examples"]
36
+ spec = Gem::Specification.new do |s|
37
+ s.name = PKG_NAME
38
+ s.version = PKG_VERSION
39
+ s.summary = %q{Ruby/NTLM library.}
40
+ s.description = %q{Ruby/NTLM provides message creator and parser for the NTLM authentication.}
41
+ s.authors = ["Kohei Kajimoto"]
42
+ s.email = %q{koheik@gmail.com}
43
+ s.homepage = %q{http://rubyforge.org/projects/rubyntlm}
44
+ s.rubyforge_project = %q{rubyntlm}
45
+
46
+ s.files = ["Rakefile", "README"]
47
+ dist_dirs.each do |dir|
48
+ s.files = s.files + Dir.glob("#{dir}/**/*.rb")
49
+ end
50
+
51
+ s.has_rdoc = true
52
+ s.extra_rdoc_files = %w( README )
53
+ s.rdoc_options.concat ['--main', 'README']
54
+
55
+ s.autorequire = 'net/ntlm'
56
+ end
57
+
58
+ Rake::GemPackageTask.new(spec) do |p|
59
+ p.gem_spec = spec
60
+ p.need_tar = true
61
+ p.need_zip = true
62
+ p.package_dir = 'build'
63
+ end
64
+
65
+
66
+
@@ -0,0 +1,853 @@
1
+ #
2
+ # = net/ntlm.rb
3
+ #
4
+ # An NTLM Authentication Library for Ruby
5
+ #
6
+ # This code is a derivative of "dbf2.rb" written by yrock
7
+ # and Minero Aoki. You can find original code here:
8
+ # http://jp.rubyist.net/magazine/?0013-CodeReview
9
+ # -------------------------------------------------------------
10
+ # Copyright (c) 2005,2006 yrock
11
+ #
12
+ # This program is free software.
13
+ # You can distribute/modify this program under the terms of the
14
+ # Ruby License.
15
+ #
16
+ # 2006-02-11 refactored by Minero Aoki
17
+ # -------------------------------------------------------------
18
+ #
19
+ # All protocol information used to write this code stems from
20
+ # "The NTLM Authentication Protocol" by Eric Glass. The author
21
+ # would thank to him for this tremendous work and making it
22
+ # available on the net.
23
+ # http://davenport.sourceforge.net/ntlm.html
24
+ # -------------------------------------------------------------
25
+ # Copyright (c) 2003 Eric Glass
26
+ #
27
+ # Permission to use, copy, modify, and distribute this document
28
+ # for any purpose and without any fee is hereby granted,
29
+ # provided that the above copyright notice and this list of
30
+ # conditions appear in all copies.
31
+ # -------------------------------------------------------------
32
+ #
33
+ # The author also looked Mozilla-Firefox-1.0.7 source code,
34
+ # namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
35
+ # Jonathan Bastien-Filiatrault's libntlm-ruby.
36
+ # "http://x2a.org/websvn/filedetails.php?
37
+ # repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
38
+ # The latter has a minor bug in its separate_keys function.
39
+ # The third key has to begin from the 14th character of the
40
+ # input string instead of 13th:)
41
+ #--
42
+ # $Id: ntlm.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $
43
+ #++
44
+
45
+ require 'base64'
46
+ require 'openssl'
47
+ require 'openssl/digest'
48
+
49
+ module Net #:nodoc:
50
+ module NTLM
51
+
52
+ module VERSION #:nodoc:
53
+ MAJOR = 0
54
+ MINOR = 1
55
+ TINY = 1
56
+ STRING = [MAJOR, MINOR, TINY].join('.')
57
+ end
58
+
59
+ SSP_SIGN = "NTLMSSP\0"
60
+ BLOB_SIGN = 0x00000101
61
+ LM_MAGIC = "KGS!@\#$%"
62
+ TIME_OFFSET = 11644473600
63
+ MAX64 = 0xffffffffffffffff
64
+
65
+ FLAGS = {
66
+ :UNICODE => 0x00000001,
67
+ :OEM => 0x00000002,
68
+ :REQUEST_TARGET => 0x00000004,
69
+ # :UNKNOWN => 0x00000008,
70
+ :SIGN => 0x00000010,
71
+ :SEAL => 0x00000020,
72
+ # :UNKNOWN => 0x00000040,
73
+ :NETWARE => 0x00000100,
74
+ :NTLM => 0x00000200,
75
+ # :UNKNOWN => 0x00000400,
76
+ # :UNKNOWN => 0x00000800,
77
+ :DOMAIN_SUPPLIED => 0x00001000,
78
+ :WORKSTATION_SUPPLIED => 0x00002000,
79
+ :LOCAL_CALL => 0x00004000,
80
+ :ALWAYS_SIGN => 0x00008000,
81
+ :TARGET_TYPE_DOMAIN => 0x00010000,
82
+ :TARGET_INFO => 0x00800000,
83
+ :NTLM2_KEY => 0x00080000,
84
+ :KEY128 => 0x20000000,
85
+ :KEY56 => 0x80000000
86
+ }
87
+
88
+ FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
89
+
90
+ DEFAULT_FLAGS = {
91
+ :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
92
+ :TYPE2 => FLAGS[:UNICODE],
93
+ :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
94
+ }
95
+
96
+ # module functions
97
+ class << self
98
+ def decode_utf16le(str)
99
+ Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16)
100
+ end
101
+
102
+ def encode_utf16le(str)
103
+ swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
104
+ end
105
+
106
+ def pack_int64le(val)
107
+ [val & 0x00000000ffffffff, val >> 32].pack("V2")
108
+ end
109
+
110
+ def swap16(str)
111
+ str.unpack("v*").pack("n*")
112
+ end
113
+
114
+ def split7(str)
115
+ s = str.dup
116
+ until s.empty?
117
+ (ret ||= []).push s.slice!(0, 7)
118
+ end
119
+ ret
120
+ end
121
+
122
+ def gen_keys(str)
123
+ split7(str).map{ |str7|
124
+ bits = split7(str7.unpack("B*")[0]).inject('')\
125
+ {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
126
+ [bits].pack("B*")
127
+ }
128
+ end
129
+
130
+ def apply_des(plain, keys)
131
+ dec = OpenSSL::Cipher::DES.new
132
+ keys.map {|k|
133
+ dec.key = k
134
+ dec.encrypt.update(plain)
135
+ }
136
+ end
137
+
138
+ def lm_hash(password)
139
+ keys = gen_keys password.upcase.ljust(14, "\0")
140
+ apply_des(LM_MAGIC, keys).join
141
+ end
142
+
143
+ def ntlm_hash(password, opt = {})
144
+ pwd = password.dup
145
+ unless opt[:unicode]
146
+ pwd = encode_utf16le(pwd)
147
+ end
148
+ OpenSSL::Digest::MD4.digest pwd
149
+ end
150
+
151
+ def ntlmv2_hash(user, password, target, opt={})
152
+ ntlmhash = ntlm_hash(password, opt)
153
+ userdomain = (user + target).upcase
154
+ unless opt[:unicode]
155
+ userdomain = encode_utf16le(userdomain)
156
+ end
157
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
158
+ end
159
+
160
+ # responses
161
+ def lm_response(arg)
162
+ begin
163
+ hash = arg[:lm_hash]
164
+ chal = arg[:challenge]
165
+ rescue
166
+ raise ArgumentError
167
+ end
168
+ chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
169
+ keys = gen_keys hash.ljust(21, "\0")
170
+ apply_des(chal, keys).join
171
+ end
172
+
173
+ def ntlm_response(arg)
174
+ hash = arg[:ntlm_hash]
175
+ chal = arg[:challenge]
176
+ chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
177
+ keys = gen_keys hash.ljust(21, "\0")
178
+ apply_des(chal, keys).join
179
+ end
180
+
181
+ def ntlmv2_response(arg, opt = {})
182
+ begin
183
+ key = arg[:ntlmv2_hash]
184
+ chal = arg[:challenge]
185
+ ti = arg[:target_info]
186
+ rescue
187
+ raise ArgumentError
188
+ end
189
+ chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
190
+
191
+ if opt[:client_challenge]
192
+ cc = opt[:client_challenge]
193
+ else
194
+ cc = rand(MAX64)
195
+ end
196
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
197
+
198
+ if opt[:timestamp]
199
+ ts = opt[:timestamp]
200
+ else
201
+ ts = Time.now.to_i
202
+ end
203
+ # epoch -> milsec from Jan 1, 1601
204
+ ts = 10000000 * (ts + TIME_OFFSET)
205
+
206
+ blob = Blob.new
207
+ blob.timestamp = ts
208
+ blob.challenge = cc
209
+ blob.target_info = ti
210
+
211
+ bb = blob.serialize
212
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
213
+ end
214
+
215
+ def lmv2_response(arg, opt = {})
216
+ key = arg[:ntlmv2_hash]
217
+ chal = arg[:challenge]
218
+
219
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
220
+
221
+ if opt[:client_challenge]
222
+ cc = opt[:client_challenge]
223
+ else
224
+ cc = rand(MAX64)
225
+ end
226
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
227
+
228
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
229
+ end
230
+
231
+ def ntlm2_session(arg, opt = {})
232
+ begin
233
+ passwd_hash = arg[:ntlm_hash]
234
+ chal = arg[:challenge]
235
+ rescue
236
+ raise ArgumentError
237
+ end
238
+
239
+ if opt[:client_challenge]
240
+ cc = opt[:client_challenge]
241
+ else
242
+ cc = rand(MAX64)
243
+ end
244
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
245
+
246
+ keys = gen_keys passwd_hash.ljust(21, "\0")
247
+ session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
248
+ response = apply_des(session_hash, keys).join
249
+ [cc.ljust(24, "\0"), response]
250
+ end
251
+ end
252
+
253
+
254
+ # base classes for primitives
255
+ class Field
256
+ attr_accessor :active, :value
257
+
258
+ def initialize(opts)
259
+ @value = opts[:value]
260
+ @active = opts[:active].nil? ? true : opts[:active]
261
+ end
262
+
263
+ def size
264
+ @active ? @size : 0
265
+ end
266
+ end
267
+
268
+ class String < Field
269
+ def initialize(opts)
270
+ super(opts)
271
+ @size = opts[:size]
272
+ end
273
+
274
+ def parse(str, offset=0)
275
+ if @active and str.size >= offset + @size
276
+ @value = str[offset, @size]
277
+ @size
278
+ else
279
+ 0
280
+ end
281
+ end
282
+
283
+ def serialize
284
+ if @active
285
+ @value
286
+ else
287
+ ""
288
+ end
289
+ end
290
+
291
+ def value=(val)
292
+ @value = val
293
+ @size = @value.nil? ? 0 : @value.size
294
+ @active = (@size > 0)
295
+ end
296
+ end
297
+
298
+
299
+ class Int16LE < Field
300
+ def initialize(opt)
301
+ super(opt)
302
+ @size = 2
303
+ end
304
+ def parse(str, offset=0)
305
+ if @active and str.size >= offset + @size
306
+ @value = str[offset, @size].unpack("v")[0]
307
+ @size
308
+ else
309
+ 0
310
+ end
311
+ end
312
+
313
+ def serialize
314
+ [@value].pack("v")
315
+ end
316
+ end
317
+
318
+ class Int32LE < Field
319
+ def initialize(opt)
320
+ super(opt)
321
+ @size = 4
322
+ end
323
+
324
+ def parse(str, offset=0)
325
+ if @active and str.size >= offset + @size
326
+ @value = str.slice(offset, @size).unpack("V")[0]
327
+ @size
328
+ else
329
+ 0
330
+ end
331
+ end
332
+
333
+ def serialize
334
+ [@value].pack("V") if @active
335
+ end
336
+ end
337
+
338
+ class Int64LE < Field
339
+ def initialize(opt)
340
+ super(opt)
341
+ @size = 8
342
+ end
343
+
344
+ def parse(str, offset=0)
345
+ if @active and str.size >= offset + @size
346
+ d, u = str.slice(offset, @size).unpack("V2")
347
+ @value = (u * 0x100000000 + d)
348
+ @size
349
+ else
350
+ 0
351
+ end
352
+ end
353
+
354
+ def serialize
355
+ [@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
356
+ end
357
+ end
358
+
359
+ # base class of data structure
360
+ class FieldSet
361
+ class << FieldSet
362
+ def define(&block)
363
+ c = Class.new(self)
364
+ def c.inherited(subclass)
365
+ proto = @proto
366
+ subclass.instance_eval {
367
+ @proto = proto
368
+ }
369
+ end
370
+ c.module_eval(&block)
371
+ c
372
+ end
373
+
374
+ def string(name, opts)
375
+ add_field(name, String, opts)
376
+ end
377
+
378
+ def int16LE(name, opts)
379
+ add_field(name, Int16LE, opts)
380
+ end
381
+
382
+ def int32LE(name, opts)
383
+ add_field(name, Int32LE, opts)
384
+ end
385
+
386
+ def int64LE(name, opts)
387
+ add_field(name, Int64LE, opts)
388
+ end
389
+
390
+ def security_buffer(name, opts)
391
+ add_field(name, SecurityBuffer, opts)
392
+ end
393
+
394
+ def prototypes
395
+ @proto
396
+ end
397
+
398
+ def names
399
+ @proto.map{|n, t, o| n}
400
+ end
401
+
402
+ def types
403
+ @proto.map{|n, t, o| t}
404
+ end
405
+
406
+ def opts
407
+ @proto.map{|n, t, o| o}
408
+ end
409
+
410
+ private
411
+
412
+ def add_field(name, type, opts)
413
+ (@proto ||= []).push [name, type, opts]
414
+ define_accessor name
415
+ end
416
+
417
+ def define_accessor(name)
418
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
419
+ def #{name}
420
+ self['#{name}'].value
421
+ end
422
+
423
+ def #{name}=(val)
424
+ self['#{name}'].value = val
425
+ end
426
+ End
427
+ end
428
+ end
429
+
430
+ def initialize
431
+ @alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
432
+ end
433
+
434
+ def serialize
435
+ @alist.map{|n, f| f.serialize }.join
436
+ end
437
+
438
+ def parse(str, offset=0)
439
+ @alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
440
+ end
441
+
442
+ def size
443
+ @alist.inject(0){|sum, a| sum += a[1].size}
444
+ end
445
+
446
+ def [](name)
447
+ a = @alist.assoc(name.to_s.intern)
448
+ raise ArgumentError, "no such field: #{name}" unless a
449
+ a[1]
450
+ end
451
+
452
+ def []=(name, val)
453
+ a = @alist.assoc(name.to_s.intern)
454
+ raise ArgumentError, "no such field: #{name}" unless a
455
+ a[1] = val
456
+ end
457
+
458
+ def enable(name)
459
+ self[name].active = true
460
+ end
461
+
462
+ def disable(name)
463
+ self[name].active = false
464
+ end
465
+ end
466
+
467
+
468
+ Blob = FieldSet.define {
469
+ int32LE :blob_signature, {:value => BLOB_SIGN}
470
+ int32LE :reserved, {:value => 0}
471
+ int64LE :timestamp, {:value => 0}
472
+ string :challenge, {:value => "", :size => 8}
473
+ int32LE :unknown1, {:value => 0}
474
+ string :target_info, {:value => "", :size => 0}
475
+ int32LE :unknown2, {:value => 0}
476
+ }
477
+
478
+ SecurityBuffer = FieldSet.define {
479
+ int16LE :length, {:value => 0}
480
+ int16LE :allocated, {:value => 0}
481
+ int32LE :offset, {:value => 0}
482
+ }
483
+
484
+ class SecurityBuffer
485
+ attr_accessor :active
486
+ def initialize(opts)
487
+ super()
488
+ @value = opts[:value]
489
+ @active = opts[:active].nil? ? true : opts[:active]
490
+ @size = 8
491
+ end
492
+
493
+ def parse(str, offset=0)
494
+ if @active and str.size >= offset + @size
495
+ super(str, offset)
496
+ @value = str[self.offset, self.length]
497
+ @size
498
+ else
499
+ 0
500
+ end
501
+ end
502
+
503
+ def serialize
504
+ super if @active
505
+ end
506
+
507
+ def value
508
+ @value
509
+ end
510
+
511
+ def value=(val)
512
+ @value = val
513
+ self.length = self.allocated = val.size
514
+ end
515
+
516
+ def data_size
517
+ @active ? @value.size : 0
518
+ end
519
+ end
520
+
521
+ class Message < FieldSet
522
+ class << Message
523
+ def parse(str)
524
+ m = Type0.new
525
+ m.parse(str)
526
+ case m.type
527
+ when 1
528
+ t = Type1.parse(str)
529
+ when 2
530
+ t = Type2.parse(str)
531
+ when 3
532
+ t = Type3.parse(str)
533
+ else
534
+ raise ArgumentError, "unknown type: #{m.type}"
535
+ end
536
+ t
537
+ end
538
+
539
+ def decode64(str)
540
+ parse(Base64.decode64(str))
541
+ end
542
+ end
543
+
544
+ def has_flag?(flag)
545
+ (self[:flag].value & FLAGS[flag]) == FLAGS[flag]
546
+ end
547
+
548
+ def set_flag(flag)
549
+ self[:flag].value |= FLAGS[flag]
550
+ end
551
+
552
+ def dump_flags
553
+ FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }
554
+ end
555
+
556
+ def serialize
557
+ deflag
558
+ super + security_buffers.map{|n, f| f.value}.join
559
+ end
560
+
561
+ def encode64
562
+ Base64.encode64(serialize).gsub(/\n/, '')
563
+ end
564
+
565
+ def decode64(str)
566
+ parse(Base64.decode64(str))
567
+ end
568
+
569
+ alias head_size size
570
+
571
+ def data_size
572
+ security_buffers.inject(0){|sum, a| sum += a[1].data_size}
573
+ end
574
+
575
+ def size
576
+ head_size + data_size
577
+ end
578
+
579
+
580
+ private
581
+
582
+ def security_buffers
583
+ @alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
584
+ end
585
+
586
+ def deflag
587
+ security_buffers.inject(head_size){|cur, a|
588
+ a[1].offset = cur
589
+ cur += a[1].data_size
590
+ }
591
+ end
592
+
593
+ def data_edge
594
+ security_buffers.map{ |n, f| f.active ? f.offset : size}.min
595
+ end
596
+
597
+ # sub class definitions
598
+
599
+ Type0 = Message.define {
600
+ string :sign, {:size => 8, :value => SSP_SIGN}
601
+ int32LE :type, {:value => 0}
602
+ }
603
+
604
+ Type1 = Message.define {
605
+ string :sign, {:size => 8, :value => SSP_SIGN}
606
+ int32LE :type, {:value => 1}
607
+ int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
608
+ security_buffer :domain, {:value => "", :active => false}
609
+ security_buffer :workstation, {:value => "", :active => false}
610
+ string :padding, {:size => 0, :value => "", :active => false }
611
+ }
612
+
613
+ class Type1
614
+ class << Type1
615
+ def parse(str)
616
+ t = new
617
+ t.parse(str)
618
+ t
619
+ end
620
+ end
621
+
622
+ def parse(str)
623
+ super(str)
624
+ enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
625
+ enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
626
+ super(str)
627
+ if ( (len = data_edge - head_size) > 0)
628
+ self.padding = "\0" * len
629
+ super(str)
630
+ end
631
+ end
632
+ end
633
+
634
+ Type2 = Message.define{
635
+ string :sign, {:size => 8, :value => SSP_SIGN}
636
+ int32LE :type, {:value => 2}
637
+ security_buffer :target_name, {:size => 0, :value => ""}
638
+ int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE2]}
639
+ int64LE :challenge, {:value => 0}
640
+ int64LE :context, {:value => 0, :active => false}
641
+ security_buffer :target_info, {:value => "", :active => false}
642
+ string :padding, {:size => 0, :value => "", :active => false }
643
+ }
644
+
645
+ class Type2
646
+ class << Type2
647
+ def parse(str)
648
+ t = new
649
+ t.parse(str)
650
+ t
651
+ end
652
+ end
653
+
654
+ def parse(str)
655
+ super(str)
656
+ if has_flag?(:TARGET_INFO)
657
+ enable(:context)
658
+ enable(:target_info)
659
+ super(str)
660
+ end
661
+ if ( (len = data_edge - head_size) > 0)
662
+ self.padding = "\0" * len
663
+ super(str)
664
+ end
665
+ end
666
+
667
+ def response(arg, opt = {})
668
+ usr = arg[:user]
669
+ pwd = arg[:password]
670
+ if usr.nil? or pwd.nil?
671
+ raise ArgumentError, "user and password have to be supplied"
672
+ end
673
+
674
+ if opt[:workstation]
675
+ ws = opt[:workstation]
676
+ else
677
+ ws = ""
678
+ end
679
+
680
+ if opt[:client_challenge]
681
+ cc = opt[:client_challenge]
682
+ else
683
+ cc = rand(MAX64)
684
+ end
685
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
686
+ opt[:client_challenge] = cc
687
+
688
+ if has_flag?(:OEM) and opt[:unicode]
689
+ usr = NTLM::decode_utf16le(usr)
690
+ pwd = NTLM::decode_utf16le(pwd)
691
+ ws = NTLM::decode_utf16le(ws)
692
+ opt[:unicode] = false
693
+ end
694
+
695
+ if has_flag?(:UNICODE) and !opt[:unicode]
696
+ usr = NTLM::encode_utf16le(usr)
697
+ pwd = NTLM::encode_utf16le(pwd)
698
+ ws = NTLM::encode_utf16le(ws)
699
+ opt[:unicode] = true
700
+ end
701
+
702
+ tgt = self.target_name
703
+ ti = self.target_info
704
+
705
+ chal = self[:challenge].serialize
706
+
707
+ if opt[:ntlmv2]
708
+ ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, tgt, opt), :challenge => chal, :target_info => ti}
709
+ lm_res = NTLM::lmv2_response(ar, opt)
710
+ ntlm_res = NTLM::ntlmv2_response(ar, opt)
711
+ elsif has_flag?(:NTLM2_KEY)
712
+ ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
713
+ lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
714
+ else
715
+ lm_res = NTLM::lm_response(pwd, chal)
716
+ ntlm_res = NTLM::ntlm_response(pwd, chal)
717
+ end
718
+
719
+ Type3.create({
720
+ :lm_response => lm_res,
721
+ :ntlm_response => ntlm_res,
722
+ :domain => tgt,
723
+ :user => usr,
724
+ :workstation => ws,
725
+ :flag => self.flag
726
+ })
727
+ end
728
+ end
729
+
730
+
731
+ Type3 = Message.define{
732
+ string :sign, {:size => 8, :value => SSP_SIGN}
733
+ int32LE :type, {:value => 3}
734
+ security_buffer :lm_response, {:value => ""}
735
+ security_buffer :ntlm_response, {:value => ""}
736
+ security_buffer :domain, {:value => ""}
737
+ security_buffer :user, {:value => ""}
738
+ security_buffer :workstation, {:value => ""}
739
+ security_buffer :session_key, {:value => "", :active => false }
740
+ int64LE :flag, {:value => 0, :active => false }
741
+ }
742
+
743
+ class Type3
744
+ class << Type3
745
+ def parse(str)
746
+ t = new
747
+ t.parse(str)
748
+ t
749
+ end
750
+
751
+ def create(arg, opt ={})
752
+ t = new
753
+ t.lm_response = arg[:lm_response]
754
+ t.ntlm_response = arg[:ntlm_response]
755
+ t.domain = arg[:domain]
756
+ t.user = arg[:user]
757
+ t.workstation = arg[:workstation]
758
+
759
+ if arg[:session_key]
760
+ t.enable(:session_key)
761
+ t.session_key = arg[session_key]
762
+ end
763
+ if arg[:flag]
764
+ t.enable(:session_key)
765
+ t.enable(:flag)
766
+ t.flag = arg[:flag]
767
+ end
768
+ t
769
+ end
770
+ end
771
+ end
772
+ end
773
+ end
774
+
775
+ # extra stuff to make nltm auth usage as easy as basic for Net::HTTP
776
+ # classes
777
+
778
+ require 'net/http'
779
+
780
+ module HTTPHeader
781
+ # could also try an automatic authentication. sends as basic first, then
782
+ # resends if required, or whatever.
783
+ # seems kind of messy exposing this stuff here.
784
+
785
+ def auth_data
786
+ @auth_data
787
+ end
788
+
789
+ # can set wait - don't authenticate unless challenged. useful when reusing
790
+ # the connection (otherwise you handshake for each request). wait should
791
+ # probably become the default, allowing the type of authentication to be
792
+ # driven by a server challenge.
793
+ def ntlm_auth user, password, wait=false
794
+ @auth_data = [:ntlm, user, password]
795
+ self['Authorization'] = 'NTLM ' + Net::NTLM::Message::Type1.new.encode64 unless wait
796
+ end
797
+ end
798
+
799
+ # here we override the default Net::HTTP#request method, in order to hide the
800
+ # necessary handshaking. maybe a more generic scheme for hooking into this
801
+ # could be useful, for other auth types
802
+
803
+ # because of the handshaking i have to rewind body stream. maybe body stream
804
+ # shouldn't be sent when authenticating??
805
+ class HTTPRequest
806
+ def reuse
807
+ if body_stream
808
+ begin body_stream.rewind
809
+ rescue; raise "error rewinding body stream for authentication"
810
+ end
811
+ end
812
+ end
813
+ end
814
+
815
+ class HTTP
816
+ alias old_request :request
817
+ private :old_request
818
+
819
+ def request req, body=nil, &block
820
+ resp = data = auth_data = nil
821
+ old_request req, body do |resp|
822
+ unless Net::HTTPUnauthorized === resp and auth_data = req.auth_data and
823
+ auth_data[0] == :ntlm and resp['www-authenticate'] == 'NTLM' ||
824
+ data = resp['www-authenticate'][/^NTLM (.*)/, 1]
825
+ data = false
826
+ yield resp if block_given?
827
+ end
828
+ end
829
+ return resp if data == false
830
+ # not really sure if i'm supposed to just rewrite the request like this?
831
+ # and the body? what about redirects? the resp.content is just the text error message
832
+ # what about post data?
833
+ req.reuse
834
+ unless data
835
+ # first stage handshake. respond to challenge
836
+ # puts "* authenticating (0) ..."
837
+ # this time wait is true.
838
+ req.ntlm_auth(*auth_data[1..2])
839
+ request req, body, &block
840
+ else
841
+ # puts "* authenticating (1) ..."
842
+ challenge = Net::NTLM::Message.decode64 data
843
+ # challenge.target_name could be provided back as a prompt.
844
+ # maybe if password is unspecified, a callback can be used to provide
845
+ # a user prompt.
846
+ resp = challenge.response({:user => auth_data[1], :password => auth_data[2]}, {:ntlmv2 => true})
847
+ req['Authorization'] = 'NTLM ' + resp.encode64
848
+ old_request(req, body) { |resp| yield resp if block_given? }
849
+ resp
850
+ end
851
+ end
852
+ end
853
+ end
@@ -0,0 +1,111 @@
1
+ # $Id$
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
3
+ require 'test/unit'
4
+ require 'net/ntlm'
5
+
6
+ class FunctionTest < Test::Unit::TestCase #:nodoc:
7
+ def setup
8
+ @passwd = "SecREt01"
9
+ @user = "user"
10
+ @domain = "domain"
11
+ @challenge = ["0123456789abcdef"].pack("H*")
12
+ @client_ch = ["ffffff0011223344"].pack("H*")
13
+ @timestamp = 1055844000
14
+ @trgt_info = [
15
+ "02000c0044004f004d00410049004e00" +
16
+ "01000c00530045005200560045005200" +
17
+ "0400140064006f006d00610069006e00" +
18
+ "2e0063006f006d000300220073006500" +
19
+ "72007600650072002e0064006f006d00" +
20
+ "610069006e002e0063006f006d000000" +
21
+ "0000"
22
+ ].pack("H*")
23
+ end
24
+
25
+ def test_lm_hash
26
+ ahash = ["ff3750bcc2b22412c2265b23734e0dac"].pack("H*")
27
+ assert_equal ahash, Net::NTLM::lm_hash(@passwd)
28
+ end
29
+
30
+ def test_ntlm_hash
31
+ ahash = ["cd06ca7c7e10c99b1d33b7485a2ed808"].pack("H*")
32
+ assert_equal ahash, Net::NTLM::ntlm_hash(@passwd)
33
+ end
34
+
35
+ def test_ntlmv2_hash
36
+ ahash = ["04b8e0ba74289cc540826bab1dee63ae"].pack("H*")
37
+ assert_equal ahash, Net::NTLM::ntlmv2_hash(@user, @passwd, @domain)
38
+ end
39
+
40
+ def test_lm_response
41
+ ares = ["c337cd5cbd44fc9782a667af6d427c6de67c20c2d3e77c56"].pack("H*")
42
+ assert_equal ares, Net::NTLM::lm_response(
43
+ {
44
+ :lm_hash => Net::NTLM::lm_hash(@passwd),
45
+ :challenge => @challenge
46
+ }
47
+ )
48
+ end
49
+
50
+ def test_ntlm_response
51
+ ares = ["25a98c1c31e81847466b29b2df4680f39958fb8c213a9cc6"].pack("H*")
52
+ ntlm_hash = Net::NTLM::ntlm_hash(@passwd)
53
+ assert_equal ares, Net::NTLM::ntlm_response(
54
+ {
55
+ :ntlm_hash => ntlm_hash,
56
+ :challenge => @challenge
57
+ }
58
+ )
59
+ end
60
+
61
+ def test_lmv2_response
62
+ ares = ["d6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344"].pack("H*")
63
+ assert_equal ares, Net::NTLM::lmv2_response(
64
+ {
65
+ :ntlmv2_hash => Net::NTLM::ntlmv2_hash(@user, @passwd, @domain),
66
+ :challenge => @challenge
67
+ },
68
+ { :client_challenge => @client_ch }
69
+ )
70
+ end
71
+
72
+ def test_ntlmv2_response
73
+ ares = [
74
+ "cbabbca713eb795d04c97abc01ee4983" +
75
+ "01010000000000000090d336b734c301" +
76
+ "ffffff00112233440000000002000c00" +
77
+ "44004f004d00410049004e0001000c00" +
78
+ "53004500520056004500520004001400" +
79
+ "64006f006d00610069006e002e006300" +
80
+ "6f006d00030022007300650072007600" +
81
+ "650072002e0064006f006d0061006900" +
82
+ "6e002e0063006f006d00000000000000" +
83
+ "0000"
84
+ ].pack("H*")
85
+ assert_equal ares, Net::NTLM::ntlmv2_response(
86
+ {
87
+ :ntlmv2_hash => Net::NTLM::ntlmv2_hash(@user, @passwd, @domain),
88
+ :challenge => @challenge,
89
+ :target_info => @trgt_info
90
+ },
91
+ {
92
+ :timestamp => @timestamp,
93
+ :client_challenge => @client_ch
94
+ }
95
+ )
96
+ end
97
+
98
+ def test_ntlm2_session
99
+ acha = ["ffffff001122334400000000000000000000000000000000"].pack("H*")
100
+ ares = ["10d550832d12b2ccb79d5ad1f4eed3df82aca4c3681dd455"].pack("H*")
101
+ session = Net::NTLM::ntlm2_session(
102
+ {
103
+ :ntlm_hash => Net::NTLM::ntlm_hash(@passwd),
104
+ :challenge => @challenge
105
+ },
106
+ { :client_challenge => @client_ch }
107
+ )
108
+ assert_equal acha, session[0]
109
+ assert_equal ares, session[1]
110
+ end
111
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: guilleiguaran-rubyntlm
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 2
9
+ version: 0.1.2
10
+ platform: ruby
11
+ authors:
12
+ - Kohei Kajimoto
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-02-25 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Ruby/NTLM provides message creator and parser for the NTLM authentication.
22
+ email: koheik@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README
29
+ files:
30
+ - Rakefile
31
+ - README
32
+ - lib/net/ntlm.rb
33
+ - test/function_test.rb
34
+ has_rdoc: true
35
+ homepage: http://rubyforge.org/projects/rubyntlm
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options:
40
+ - --main
41
+ - README
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ segments:
49
+ - 0
50
+ version: "0"
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ requirements: []
59
+
60
+ rubyforge_project: rubyntlm
61
+ rubygems_version: 1.3.6
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Ruby/NTLM library.
65
+ test_files:
66
+ - test/function_test.rb