rubyntlm 0.3.4 → 0.4.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -0
  3. data/.travis.yml +0 -1
  4. data/LICENSE +20 -0
  5. data/Rakefile +8 -15
  6. data/lib/net/ntlm.rb +22 -654
  7. data/lib/net/ntlm/blob.rb +17 -0
  8. data/lib/net/ntlm/encode_util.rb +49 -0
  9. data/lib/net/ntlm/field.rb +35 -0
  10. data/lib/net/ntlm/field_set.rb +125 -0
  11. data/lib/net/ntlm/int16_le.rb +26 -0
  12. data/lib/net/ntlm/int32_le.rb +25 -0
  13. data/lib/net/ntlm/int64_le.rb +26 -0
  14. data/lib/net/ntlm/message.rb +115 -0
  15. data/lib/net/ntlm/message/type0.rb +16 -0
  16. data/lib/net/ntlm/message/type1.rb +43 -0
  17. data/lib/net/ntlm/message/type2.rb +126 -0
  18. data/lib/net/ntlm/message/type3.rb +68 -0
  19. data/lib/net/ntlm/security_buffer.rb +48 -0
  20. data/lib/net/ntlm/string.rb +35 -0
  21. data/lib/net/ntlm/version.rb +11 -0
  22. data/rubyntlm.gemspec +4 -1
  23. data/spec/lib/net/ntlm/blob_spec.rb +16 -0
  24. data/spec/lib/net/ntlm/encode_util_spec.rb +16 -0
  25. data/spec/lib/net/ntlm/field_set_spec.rb +33 -0
  26. data/spec/lib/net/ntlm/field_spec.rb +34 -0
  27. data/spec/lib/net/ntlm/int16_le_spec.rb +18 -0
  28. data/spec/lib/net/ntlm/int32_le_spec.rb +19 -0
  29. data/spec/lib/net/ntlm/int64_le_spec.rb +19 -0
  30. data/spec/lib/net/ntlm/message/type0_spec.rb +21 -0
  31. data/spec/lib/net/ntlm/message/type1_spec.rb +42 -0
  32. data/spec/lib/net/ntlm/message/type2_spec.rb +88 -0
  33. data/spec/lib/net/ntlm/message/type3_spec.rb +20 -0
  34. data/spec/lib/net/ntlm/message_spec.rb +17 -0
  35. data/spec/lib/net/ntlm/security_buffer_spec.rb +64 -0
  36. data/spec/lib/net/ntlm/string_spec.rb +72 -0
  37. data/spec/lib/net/ntlm/version_spec.rb +26 -0
  38. data/spec/lib/net/ntlm_spec.rb +121 -0
  39. data/spec/spec_helper.rb +21 -0
  40. data/spec/support/shared/examples/net/ntlm/field_shared.rb +25 -0
  41. data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +239 -0
  42. data/spec/support/shared/examples/net/ntlm/int_shared.rb +43 -0
  43. data/spec/support/shared/examples/net/ntlm/message_shared.rb +35 -0
  44. metadata +77 -5
  45. data/spec/unit/ntlm_spec.rb +0 -183
@@ -0,0 +1,126 @@
1
+ module Net
2
+ module NTLM
3
+ class Message
4
+
5
+ # @private false
6
+ class Type2 < Message
7
+
8
+ string :sign, {:size => 8, :value => SSP_SIGN}
9
+ int32LE :type, {:value => 2}
10
+ security_buffer :target_name, {:size => 0, :value => ""}
11
+ int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE2]}
12
+ int64LE :challenge, {:value => 0}
13
+ int64LE :context, {:value => 0, :active => false}
14
+ security_buffer :target_info, {:value => "", :active => false}
15
+ string :padding, {:size => 0, :value => "", :active => false }
16
+
17
+ class << Type2
18
+ # Parse a Type 2 packet
19
+ # @param [String] str A string containing Type 2 data
20
+ # @return [Type2]
21
+ def parse(str)
22
+ t = new
23
+ t.parse(str)
24
+ t
25
+ end
26
+ end
27
+
28
+ # @!visibility private
29
+ def parse(str)
30
+ super(str)
31
+ if has_flag?(:TARGET_INFO)
32
+ enable(:context)
33
+ enable(:target_info)
34
+ super(str)
35
+ end
36
+ if ( (len = data_edge - head_size) > 0)
37
+ self.padding = "\0" * len
38
+ super(str)
39
+ end
40
+ end
41
+
42
+ # Generates a Type 3 response based on the Type 2 Information
43
+ # @return [Type3]
44
+ # @option arg [String] :username The username to authenticate with
45
+ # @option arg [String] :password The user's password
46
+ # @option arg [String] :domain ('') The domain to authenticate to
47
+ # @option opt [String] :workstation (Socket.gethostname) The name of the calling workstation
48
+ # @option opt [Boolean] :use_default_target (False) Use the domain supplied by the server in the Type 2 packet
49
+ # @note An empty :domain option authenticates to the local machine.
50
+ # @note The :use_default_target has presidence over the :domain option
51
+ def response(arg, opt = {})
52
+ usr = arg[:user]
53
+ pwd = arg[:password]
54
+ domain = arg[:domain] ? arg[:domain].upcase : ""
55
+ if usr.nil? or pwd.nil?
56
+ raise ArgumentError, "user and password have to be supplied"
57
+ end
58
+
59
+ if opt[:workstation]
60
+ ws = opt[:workstation]
61
+ else
62
+ ws = Socket.gethostname
63
+ end
64
+
65
+ if opt[:client_challenge]
66
+ cc = opt[:client_challenge]
67
+ else
68
+ cc = rand(MAX64)
69
+ end
70
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
71
+ opt[:client_challenge] = cc
72
+
73
+ if has_flag?(:OEM) and opt[:unicode]
74
+ usr = NTLM::EncodeUtil.decode_utf16le(usr)
75
+ pwd = NTLM::EncodeUtil.decode_utf16le(pwd)
76
+ ws = NTLM::EncodeUtil.decode_utf16le(ws)
77
+ domain = NTLM::EncodeUtil.decode_utf16le(domain)
78
+ opt[:unicode] = false
79
+ end
80
+
81
+ if has_flag?(:UNICODE) and !opt[:unicode]
82
+ usr = NTLM::EncodeUtil.encode_utf16le(usr)
83
+ pwd = NTLM::EncodeUtil.encode_utf16le(pwd)
84
+ ws = NTLM::EncodeUtil.encode_utf16le(ws)
85
+ domain = NTLM::EncodeUtil.encode_utf16le(domain)
86
+ opt[:unicode] = true
87
+ end
88
+
89
+ if opt[:use_default_target]
90
+ domain = self.target_name
91
+ end
92
+
93
+ ti = self.target_info
94
+
95
+ chal = self[:challenge].serialize
96
+
97
+ if opt[:ntlmv2]
98
+ ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, domain, opt), :challenge => chal, :target_info => ti}
99
+ lm_res = NTLM::lmv2_response(ar, opt)
100
+ ntlm_res = NTLM::ntlmv2_response(ar, opt)
101
+ elsif has_flag?(:NTLM2_KEY)
102
+ ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
103
+ lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
104
+ else
105
+ lm_res = NTLM::lm_response(pwd, chal)
106
+ ntlm_res = NTLM::ntlm_response(pwd, chal)
107
+ end
108
+
109
+ Type3.create({
110
+ :lm_response => lm_res,
111
+ :ntlm_response => ntlm_res,
112
+ :domain => domain,
113
+ :user => usr,
114
+ :workstation => ws,
115
+ :flag => self.flag
116
+ })
117
+ end
118
+ end
119
+
120
+
121
+
122
+ end
123
+ end
124
+ end
125
+
126
+
@@ -0,0 +1,68 @@
1
+ module Net
2
+ module NTLM
3
+ class Message
4
+
5
+ # @private false
6
+ class Type3 < Message
7
+
8
+ string :sign, {:size => 8, :value => SSP_SIGN}
9
+ int32LE :type, {:value => 3}
10
+ security_buffer :lm_response, {:value => ""}
11
+ security_buffer :ntlm_response, {:value => ""}
12
+ security_buffer :domain, {:value => ""}
13
+ security_buffer :user, {:value => ""}
14
+ security_buffer :workstation, {:value => ""}
15
+ security_buffer :session_key, {:value => "", :active => false }
16
+ int64LE :flag, {:value => 0, :active => false }
17
+
18
+ class << Type3
19
+ # Parse a Type 3 packet
20
+ # @param [String] str A string containing Type 3 data
21
+ # @return [Type2]
22
+ def parse(str)
23
+ t = new
24
+ t.parse(str)
25
+ t
26
+ end
27
+
28
+ # Builds a Type 3 packet
29
+ # @note All options must be properly encoded with either unicode or oem encoding
30
+ # @return [Type3]
31
+ # @option arg [String] :lm_response The LM hash
32
+ # @option arg [String] :ntlm_response The NTLM hash
33
+ # @option arg [String] :domain The domain to authenticate to
34
+ # @option arg [String] :workstation The name of the calling workstation
35
+ # @option arg [String] :session_key The session key
36
+ # @option arg [Integer] :flag Flags for the packet
37
+ def create(arg, opt ={})
38
+ t = new
39
+ t.lm_response = arg[:lm_response]
40
+ t.ntlm_response = arg[:ntlm_response]
41
+ t.domain = arg[:domain]
42
+ t.user = arg[:user]
43
+
44
+ if arg[:workstation]
45
+ t.workstation = arg[:workstation]
46
+ end
47
+
48
+ if arg[:session_key]
49
+ t.enable(:session_key)
50
+ t.session_key = arg[session_key]
51
+ end
52
+
53
+ if arg[:flag]
54
+ t.enable(:session_key)
55
+ t.enable(:flag)
56
+ t.flag = arg[:flag]
57
+ end
58
+ t
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+ end
65
+ end
66
+ end
67
+
68
+
@@ -0,0 +1,48 @@
1
+
2
+ module Net
3
+ module NTLM
4
+
5
+ class SecurityBuffer < FieldSet
6
+
7
+ int16LE :length, {:value => 0}
8
+ int16LE :allocated, {:value => 0}
9
+ int32LE :offset, {:value => 0}
10
+
11
+ attr_accessor :active
12
+ def initialize(opts={})
13
+ super()
14
+ @value = opts[:value]
15
+ @active = opts[:active].nil? ? true : opts[:active]
16
+ @size = 8
17
+ end
18
+
19
+ def parse(str, offset=0)
20
+ if @active and str.size >= offset + @size
21
+ super(str, offset)
22
+ @value = str[self.offset, self.length]
23
+ @size
24
+ else
25
+ 0
26
+ end
27
+ end
28
+
29
+ def serialize
30
+ super if @active
31
+ end
32
+
33
+ def value
34
+ @value
35
+ end
36
+
37
+ def value=(val)
38
+ @value = val
39
+ self.length = self.allocated = val.size
40
+ end
41
+
42
+ def data_size
43
+ @active ? @value.size : 0
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,35 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ class String < Field
5
+ def initialize(opts)
6
+ super(opts)
7
+ @size = opts[:size]
8
+ end
9
+
10
+ def parse(str, offset=0)
11
+ if @active and str.size >= offset + @size
12
+ @value = str[offset, @size]
13
+ @size
14
+ else
15
+ 0
16
+ end
17
+ end
18
+
19
+ def serialize
20
+ if @active
21
+ @value.to_s
22
+ else
23
+ ""
24
+ end
25
+ end
26
+
27
+ def value=(val)
28
+ @value = val
29
+ @size = @value.nil? ? 0 : @value.size
30
+ @active = (@size > 0)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ module Net
2
+ module NTLM
3
+ # @private
4
+ module VERSION
5
+ MAJOR = 0
6
+ MINOR = 4
7
+ TINY = 0
8
+ STRING = [MAJOR, MINOR, TINY].join('.')
9
+ end
10
+ end
11
+ end
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), 'lib', 'net', 'ntlm')
1
+ require File.join(File.dirname(__FILE__), 'lib', 'net', 'ntlm', 'version')
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.platform = Gem::Platform::RUBY
@@ -18,7 +18,10 @@ Gem::Specification.new do |s|
18
18
  s.require_paths = ["lib"]
19
19
 
20
20
  s.required_ruby_version = '>= 1.8.7'
21
+
22
+ s.license = 'MIT'
21
23
 
22
24
  s.add_development_dependency "rake"
23
25
  s.add_development_dependency "rspec"
26
+ s.add_development_dependency "simplecov"
24
27
  end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Net::NTLM::Blob do
4
+
5
+ fields = [
6
+ { :name => :blob_signature, :class => Net::NTLM::Int32LE, :value => 257, :active => true },
7
+ { :name => :reserved, :class => Net::NTLM::Int32LE, :value => 0, :active => true },
8
+ { :name => :timestamp, :class => Net::NTLM::Int64LE, :value => 0, :active => true },
9
+ { :name => :challenge, :class => Net::NTLM::String, :value => '', :active => true },
10
+ { :name => :unknown1, :class => Net::NTLM::Int32LE, :value => 0, :active => true },
11
+ { :name => :target_info, :class => Net::NTLM::String, :value => '', :active => true },
12
+ { :name => :unknown2, :class => Net::NTLM::Int32LE, :value => 0, :active => true },
13
+ ]
14
+
15
+ it_behaves_like 'a fieldset', fields
16
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Net::NTLM::EncodeUtil do
4
+
5
+ context '#encode_utf16le' do
6
+ it 'should convert an ASCII string to UTF' do
7
+ Net::NTLM::EncodeUtil.encode_utf16le('Test').should == "T\x00e\x00s\x00t\x00"
8
+ end
9
+ end
10
+
11
+ context '#decode_utf16le' do
12
+ it 'should convert a UTF string to ASCII' do
13
+ Net::NTLM::EncodeUtil.decode_utf16le("T\x00e\x00s\x00t\x00").should == 'Test'
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Net::NTLM::FieldSet do
4
+
5
+ fields = []
6
+
7
+ it_behaves_like 'a fieldset', fields
8
+
9
+ subject(:fieldset_class) do
10
+ Class.new(Net::NTLM::FieldSet)
11
+ end
12
+
13
+ context 'an instance' do
14
+ subject(:fieldset_object) do
15
+ fieldset_class.string(:test_string, { :value => 'Test', :active => true, :size => 4})
16
+ fieldset_class.string(:test_string2, { :value => 'Foo', :active => true, :size => 3})
17
+ fieldset_class.new
18
+ end
19
+
20
+ it 'should serialize all the fields' do
21
+ fieldset_object.serialize.should == 'TestFoo'
22
+ end
23
+
24
+ it 'should parse a string across the fields' do
25
+ fieldset_object.parse('FooBarBaz')
26
+ fieldset_object.serialize.should == 'FooBarB'
27
+ end
28
+
29
+ it 'should return an aggregate size of all the fields' do
30
+ fieldset_object.size.should == 7
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Net::NTLM::Field do
4
+
5
+ it_behaves_like 'a field', 'Foo', false
6
+
7
+ context 'with no size specified' do
8
+ let (:field_without_size) { Net::NTLM::Field.new({ :value => 'Foo', :active => true }) }
9
+ it 'should set size to 0 if not active' do
10
+ field_without_size.size.should == 0
11
+ end
12
+
13
+ it 'should return 0 if active but no size specified' do
14
+ field_without_size.active = true
15
+ field_without_size.size.should == 0
16
+ end
17
+ end
18
+
19
+ context 'with a size specified' do
20
+ let (:field_with_size) { Net::NTLM::Field.new({ :value => 'Foo', :active => true, :size => 100 }) }
21
+
22
+ it 'should return the size provided in the initialize options if active' do
23
+ field_with_size.size.should == 100
24
+ end
25
+
26
+ it 'should still return 0 if not active' do
27
+ field_with_size.active = false
28
+ field_with_size.size.should == 0
29
+ end
30
+ end
31
+
32
+
33
+
34
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Net::NTLM::Int16LE do
4
+
5
+ int_values = {
6
+ :default => 15,
7
+ :default_hex => "\x0F\x00",
8
+ :alt => 14,
9
+ :alt_hex => "\x0E\x00",
10
+ :small => "\x0F",
11
+ :size => 2,
12
+ :bits => 16
13
+ }
14
+
15
+ it_behaves_like 'a field', 15, false
16
+ it_behaves_like 'an integer field', int_values
17
+
18
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Net::NTLM::Int32LE do
4
+
5
+ int_values = {
6
+ :default => 252716124,
7
+ :default_hex => "\x5C\x24\x10\x0f",
8
+ :alt => 235938908,
9
+ :alt_hex => "\x5C\x24\x10\x0e",
10
+ :small => "\x0F\x00",
11
+ :size => 4,
12
+ :bits => 32
13
+ }
14
+
15
+
16
+ it_behaves_like 'a field', 252716124, false
17
+ it_behaves_like 'an integer field', int_values
18
+
19
+ end