rubyntlm 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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,17 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ BLOB_SIGN = 0x00000101
5
+
6
+ class Blob < FieldSet
7
+ int32LE :blob_signature, {:value => BLOB_SIGN}
8
+ int32LE :reserved, {:value => 0}
9
+ int64LE :timestamp, {:value => 0}
10
+ string :challenge, {:value => "", :size => 8}
11
+ int32LE :unknown1, {:value => 0}
12
+ string :target_info, {:value => "", :size => 0}
13
+ int32LE :unknown2, {:value => 0}
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ class EncodeUtil
5
+ if RUBY_VERSION == "1.8.7"
6
+ require "kconv"
7
+
8
+ # Decode a UTF16 string to a ASCII string
9
+ # @param [String] str The string to convert
10
+ def self.decode_utf16le(str)
11
+ Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16)
12
+ end
13
+
14
+ # Encodes a ASCII string to a UTF16 string
15
+ # @param [String] str The string to convert
16
+ def self.encode_utf16le(str)
17
+ swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
18
+ end
19
+
20
+ # Taggle the strings endianness between big/little and little/big
21
+ # @param [String] str The string to swap the endianness on
22
+ def self.swap16(str)
23
+ str.unpack("v*").pack("n*")
24
+ end
25
+ else # Use native 1.9 string encoding functions
26
+
27
+ # Decode a UTF16 string to a ASCII string
28
+ # @param [String] str The string to convert
29
+ def self.decode_utf16le(str)
30
+ str.force_encoding(Encoding::UTF_16LE)
31
+ str.encode(Encoding::UTF_8, Encoding::UTF_16LE).force_encoding('UTF-8')
32
+ end
33
+
34
+ # Encodes a ASCII string to a UTF16 string
35
+ # @param [String] str The string to convert
36
+ # @note This implementation may seem stupid but the problem is that UTF16-LE and UTF-8 are incompatiable
37
+ # encodings. This library uses string contatination to build the packet bytes. The end result is that
38
+ # you can either marshal the encodings elsewhere of simply know that each time you call encode_utf16le
39
+ # the function will convert the string bytes to UTF-16LE and note the encoding as UTF-8 so that byte
40
+ # concatination works seamlessly.
41
+ def self.encode_utf16le(str)
42
+ str = str.force_encoding('UTF-8') if [::Encoding::ASCII_8BIT,::Encoding::US_ASCII].include?(str.encoding)
43
+ str.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('UTF-8')
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,35 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ # base classes for primitives
5
+ # @private
6
+ class Field
7
+ attr_accessor :active, :value
8
+
9
+ def initialize(opts)
10
+ @value = opts[:value]
11
+ @active = opts[:active].nil? ? true : opts[:active]
12
+ @size = opts[:size].nil? ? 0 : opts[:size]
13
+ end
14
+
15
+ def size
16
+ @active ? @size : 0
17
+ end
18
+
19
+ # Serializer function for field data
20
+ # Exists in this class to be overridden by child classes
21
+ def serialize
22
+ raise NotImplementedError
23
+ end
24
+
25
+ # Parser function for field data
26
+ # Exists in this class to be overridden by child classes
27
+ def parse(str, offset=0)
28
+ raise NotImplementedError
29
+ end
30
+
31
+ end
32
+
33
+
34
+ end
35
+ end
@@ -0,0 +1,125 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ # base class of data structure
5
+ class FieldSet
6
+ class << FieldSet
7
+
8
+ # @macro string_security_buffer
9
+ # @method $1
10
+ # @method $1=
11
+ # @return [String]
12
+ def string(name, opts)
13
+ add_field(name, Net::NTLM::String, opts)
14
+ end
15
+
16
+ # @macro int16le_security_buffer
17
+ # @method $1
18
+ # @method $1=
19
+ # @return [Int16LE]
20
+ def int16LE(name, opts)
21
+ add_field(name, Net::NTLM::Int16LE, opts)
22
+ end
23
+
24
+ # @macro int32le_security_buffer
25
+ # @method $1
26
+ # @method $1=
27
+ # @return [Int32LE]
28
+ def int32LE(name, opts)
29
+ add_field(name, Net::NTLM::Int32LE, opts)
30
+ end
31
+
32
+ # @macro int64le_security_buffer
33
+ # @method $1
34
+ # @method $1=
35
+ # @return [Int64]
36
+ def int64LE(name, opts)
37
+ add_field(name, Net::NTLM::Int64LE, opts)
38
+ end
39
+
40
+ # @macro security_buffer
41
+ # @method $1
42
+ # @method $1=
43
+ # @return [SecurityBuffer]
44
+ def security_buffer(name, opts)
45
+ add_field(name, Net::NTLM::SecurityBuffer, opts)
46
+ end
47
+
48
+ def prototypes
49
+ @proto
50
+ end
51
+
52
+ def names
53
+ return [] if @proto.nil?
54
+ @proto.map{|n, t, o| n}
55
+ end
56
+
57
+ def types
58
+ return [] if @proto.nil?
59
+ @proto.map{|n, t, o| t}
60
+ end
61
+
62
+ def opts
63
+ return [] if @proto.nil?
64
+ @proto.map{|n, t, o| o}
65
+ end
66
+
67
+ private
68
+
69
+ def add_field(name, type, opts)
70
+ (@proto ||= []).push [name, type, opts]
71
+ define_accessor name
72
+ end
73
+
74
+ def define_accessor(name)
75
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
76
+ def #{name}
77
+ self['#{name}'].value
78
+ end
79
+
80
+ def #{name}=(val)
81
+ self['#{name}'].value = val
82
+ end
83
+ End
84
+ end
85
+ end
86
+
87
+ def initialize
88
+ @alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
89
+ end
90
+
91
+ def serialize
92
+ @alist.map{|n, f| f.serialize }.join
93
+ end
94
+
95
+ def parse(str, offset=0)
96
+ @alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
97
+ end
98
+
99
+ def size
100
+ @alist.inject(0){|sum, a| sum += a[1].size}
101
+ end
102
+
103
+ def [](name)
104
+ a = @alist.assoc(name.to_s.intern)
105
+ raise ArgumentError, "no such field: #{name}" unless a
106
+ a[1]
107
+ end
108
+
109
+ def []=(name, val)
110
+ a = @alist.assoc(name.to_s.intern)
111
+ raise ArgumentError, "no such field: #{name}" unless a
112
+ a[1] = val
113
+ end
114
+
115
+ def enable(name)
116
+ self[name].active = true
117
+ end
118
+
119
+ def disable(name)
120
+ self[name].active = false
121
+ end
122
+ end
123
+
124
+ end
125
+ end
@@ -0,0 +1,26 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ class Int16LE < Field
5
+
6
+ def initialize(opt)
7
+ super(opt)
8
+ @size = 2
9
+ end
10
+
11
+ def parse(str, offset=0)
12
+ if @active and str.size >= offset + @size
13
+ @value = str[offset, @size].unpack("v")[0]
14
+ @size
15
+ else
16
+ 0
17
+ end
18
+ end
19
+
20
+ def serialize
21
+ [@value].pack("v")
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ class Int32LE < Field
5
+ def initialize(opt)
6
+ super(opt)
7
+ @size = 4
8
+ end
9
+
10
+ def parse(str, offset=0)
11
+ if @active and str.size >= offset + @size
12
+ @value = str.slice(offset, @size).unpack("V")[0]
13
+ @size
14
+ else
15
+ 0
16
+ end
17
+ end
18
+
19
+ def serialize
20
+ [@value].pack("V") if @active
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ class Int64LE < Field
5
+ def initialize(opt)
6
+ super(opt)
7
+ @size = 8
8
+ end
9
+
10
+ def parse(str, offset=0)
11
+ if @active and str.size >= offset + @size
12
+ d, u = str.slice(offset, @size).unpack("V2")
13
+ @value = (u * 0x100000000 + d)
14
+ @size
15
+ else
16
+ 0
17
+ end
18
+ end
19
+
20
+ def serialize
21
+ [@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,115 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ SSP_SIGN = "NTLMSSP\0"
5
+
6
+ FLAGS = {
7
+ :UNICODE => 0x00000001,
8
+ :OEM => 0x00000002,
9
+ :REQUEST_TARGET => 0x00000004,
10
+ :MBZ9 => 0x00000008,
11
+ :SIGN => 0x00000010,
12
+ :SEAL => 0x00000020,
13
+ :NEG_DATAGRAM => 0x00000040,
14
+ :NETWARE => 0x00000100,
15
+ :NTLM => 0x00000200,
16
+ :NEG_NT_ONLY => 0x00000400,
17
+ :MBZ7 => 0x00000800,
18
+ :DOMAIN_SUPPLIED => 0x00001000,
19
+ :WORKSTATION_SUPPLIED => 0x00002000,
20
+ :LOCAL_CALL => 0x00004000,
21
+ :ALWAYS_SIGN => 0x00008000,
22
+ :TARGET_TYPE_DOMAIN => 0x00010000,
23
+ :TARGET_INFO => 0x00800000,
24
+ :NTLM2_KEY => 0x00080000,
25
+ :KEY128 => 0x20000000,
26
+ :KEY56 => 0x80000000
27
+ }.freeze
28
+
29
+ FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
30
+
31
+ DEFAULT_FLAGS = {
32
+ :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
33
+ :TYPE2 => FLAGS[:UNICODE],
34
+ :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
35
+ }
36
+
37
+
38
+ # @private false
39
+ class Message < FieldSet
40
+ class << Message
41
+ def parse(str)
42
+ m = Type0.new
43
+ m.parse(str)
44
+ case m.type
45
+ when 1
46
+ t = Type1.parse(str)
47
+ when 2
48
+ t = Type2.parse(str)
49
+ when 3
50
+ t = Type3.parse(str)
51
+ else
52
+ raise ArgumentError, "unknown type: #{m.type}"
53
+ end
54
+ t
55
+ end
56
+
57
+ def decode64(str)
58
+ parse(Base64.decode64(str))
59
+ end
60
+ end
61
+
62
+ def has_flag?(flag)
63
+ (self[:flag].value & FLAGS[flag]) == FLAGS[flag]
64
+ end
65
+
66
+ def set_flag(flag)
67
+ self[:flag].value |= FLAGS[flag]
68
+ end
69
+
70
+ def dump_flags
71
+ FLAG_KEYS.each{ |k| print(k, "=", has_flag?(k), "\n") }
72
+ end
73
+
74
+ def serialize
75
+ deflag
76
+ super + security_buffers.map{|n, f| f.value}.join
77
+ end
78
+
79
+ def encode64
80
+ Base64.encode64(serialize).gsub(/\n/, '')
81
+ end
82
+
83
+ def decode64(str)
84
+ parse(Base64.decode64(str))
85
+ end
86
+
87
+ alias head_size size
88
+
89
+ def data_size
90
+ security_buffers.inject(0){|sum, a| sum += a[1].data_size}
91
+ end
92
+
93
+ def size
94
+ head_size + data_size
95
+ end
96
+
97
+
98
+ def security_buffers
99
+ @alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
100
+ end
101
+
102
+ def deflag
103
+ security_buffers.inject(head_size){|cur, a|
104
+ a[1].offset = cur
105
+ cur += a[1].data_size
106
+ }
107
+ end
108
+
109
+ def data_edge
110
+ security_buffers.map{ |n, f| f.active ? f.offset : size}.min
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,16 @@
1
+ module Net
2
+ module NTLM
3
+ class Message
4
+
5
+ # sub class definitions
6
+ class Type0 < Message
7
+ string :sign, {:size => 8, :value => SSP_SIGN}
8
+ int32LE :type, {:value => 0}
9
+ end
10
+
11
+
12
+ end
13
+ end
14
+ end
15
+
16
+
@@ -0,0 +1,43 @@
1
+ module Net
2
+ module NTLM
3
+ class Message
4
+
5
+ # @private false
6
+ class Type1 < Message
7
+
8
+ string :sign, {:size => 8, :value => SSP_SIGN}
9
+ int32LE :type, {:value => 1}
10
+ int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
11
+ security_buffer :domain, {:value => ""}
12
+ security_buffer :workstation, {:value => Socket.gethostname }
13
+ string :padding, {:size => 0, :value => "", :active => false }
14
+
15
+ class << Type1
16
+ # Parses a Type 1 Message
17
+ # @param [String] str A string containing Type 1 data
18
+ # @return [Type1] The parsed Type 1 message
19
+ def parse(str)
20
+ t = new
21
+ t.parse(str)
22
+ t
23
+ end
24
+ end
25
+
26
+ # @!visibility private
27
+ def parse(str)
28
+ super(str)
29
+ enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
30
+ enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
31
+ super(str)
32
+ if ( (len = data_edge - head_size) > 0)
33
+ self.padding = "\0" * len
34
+ super(str)
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+
43
+