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.
- checksums.yaml +4 -4
- data/.rspec +3 -0
- data/.travis.yml +0 -1
- data/LICENSE +20 -0
- data/Rakefile +8 -15
- data/lib/net/ntlm.rb +22 -654
- data/lib/net/ntlm/blob.rb +17 -0
- data/lib/net/ntlm/encode_util.rb +49 -0
- data/lib/net/ntlm/field.rb +35 -0
- data/lib/net/ntlm/field_set.rb +125 -0
- data/lib/net/ntlm/int16_le.rb +26 -0
- data/lib/net/ntlm/int32_le.rb +25 -0
- data/lib/net/ntlm/int64_le.rb +26 -0
- data/lib/net/ntlm/message.rb +115 -0
- data/lib/net/ntlm/message/type0.rb +16 -0
- data/lib/net/ntlm/message/type1.rb +43 -0
- data/lib/net/ntlm/message/type2.rb +126 -0
- data/lib/net/ntlm/message/type3.rb +68 -0
- data/lib/net/ntlm/security_buffer.rb +48 -0
- data/lib/net/ntlm/string.rb +35 -0
- data/lib/net/ntlm/version.rb +11 -0
- data/rubyntlm.gemspec +4 -1
- data/spec/lib/net/ntlm/blob_spec.rb +16 -0
- data/spec/lib/net/ntlm/encode_util_spec.rb +16 -0
- data/spec/lib/net/ntlm/field_set_spec.rb +33 -0
- data/spec/lib/net/ntlm/field_spec.rb +34 -0
- data/spec/lib/net/ntlm/int16_le_spec.rb +18 -0
- data/spec/lib/net/ntlm/int32_le_spec.rb +19 -0
- data/spec/lib/net/ntlm/int64_le_spec.rb +19 -0
- data/spec/lib/net/ntlm/message/type0_spec.rb +21 -0
- data/spec/lib/net/ntlm/message/type1_spec.rb +42 -0
- data/spec/lib/net/ntlm/message/type2_spec.rb +88 -0
- data/spec/lib/net/ntlm/message/type3_spec.rb +20 -0
- data/spec/lib/net/ntlm/message_spec.rb +17 -0
- data/spec/lib/net/ntlm/security_buffer_spec.rb +64 -0
- data/spec/lib/net/ntlm/string_spec.rb +72 -0
- data/spec/lib/net/ntlm/version_spec.rb +26 -0
- data/spec/lib/net/ntlm_spec.rb +121 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/shared/examples/net/ntlm/field_shared.rb +25 -0
- data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +239 -0
- data/spec/support/shared/examples/net/ntlm/int_shared.rb +43 -0
- data/spec/support/shared/examples/net/ntlm/message_shared.rb +35 -0
- metadata +77 -5
- 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
|
data/rubyntlm.gemspec
CHANGED
@@ -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
|