rubyntlm 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -3
  3. data/.rspec +2 -2
  4. data/.travis.yml +10 -11
  5. data/CHANGELOG.md +5 -5
  6. data/Gemfile +3 -3
  7. data/LICENSE +19 -19
  8. data/Rakefile +22 -22
  9. data/lib/net/ntlm.rb +266 -263
  10. data/lib/net/ntlm/blob.rb +28 -28
  11. data/lib/net/ntlm/channel_binding.rb +65 -0
  12. data/lib/net/ntlm/client.rb +65 -65
  13. data/lib/net/ntlm/client/session.rb +237 -223
  14. data/lib/net/ntlm/encode_util.rb +49 -49
  15. data/lib/net/ntlm/exceptions.rb +14 -0
  16. data/lib/net/ntlm/field.rb +34 -34
  17. data/lib/net/ntlm/field_set.rb +129 -129
  18. data/lib/net/ntlm/int16_le.rb +25 -25
  19. data/lib/net/ntlm/int32_le.rb +24 -24
  20. data/lib/net/ntlm/int64_le.rb +25 -25
  21. data/lib/net/ntlm/message.rb +129 -129
  22. data/lib/net/ntlm/message/type0.rb +16 -16
  23. data/lib/net/ntlm/message/type1.rb +18 -18
  24. data/lib/net/ntlm/message/type2.rb +102 -102
  25. data/lib/net/ntlm/message/type3.rb +131 -131
  26. data/lib/net/ntlm/security_buffer.rb +47 -47
  27. data/lib/net/ntlm/string.rb +34 -34
  28. data/lib/net/ntlm/target_info.rb +89 -0
  29. data/lib/net/ntlm/version.rb +11 -11
  30. data/rubyntlm.gemspec +28 -28
  31. data/spec/lib/net/ntlm/blob_spec.rb +16 -16
  32. data/spec/lib/net/ntlm/channel_binding_spec.rb +17 -0
  33. data/spec/lib/net/ntlm/client/session_spec.rb +68 -68
  34. data/spec/lib/net/ntlm/client_spec.rb +64 -64
  35. data/spec/lib/net/ntlm/encode_util_spec.rb +16 -16
  36. data/spec/lib/net/ntlm/field_set_spec.rb +33 -33
  37. data/spec/lib/net/ntlm/field_spec.rb +34 -34
  38. data/spec/lib/net/ntlm/int16_le_spec.rb +17 -17
  39. data/spec/lib/net/ntlm/int32_le_spec.rb +18 -18
  40. data/spec/lib/net/ntlm/int64_le_spec.rb +18 -18
  41. data/spec/lib/net/ntlm/message/type0_spec.rb +20 -20
  42. data/spec/lib/net/ntlm/message/type1_spec.rb +131 -131
  43. data/spec/lib/net/ntlm/message/type2_spec.rb +132 -132
  44. data/spec/lib/net/ntlm/message/type3_spec.rb +225 -225
  45. data/spec/lib/net/ntlm/message_spec.rb +16 -16
  46. data/spec/lib/net/ntlm/security_buffer_spec.rb +64 -64
  47. data/spec/lib/net/ntlm/string_spec.rb +72 -72
  48. data/spec/lib/net/ntlm/target_info_spec.rb +76 -0
  49. data/spec/lib/net/ntlm/version_spec.rb +27 -27
  50. data/spec/lib/net/ntlm_spec.rb +127 -127
  51. data/spec/spec_helper.rb +22 -22
  52. data/spec/support/certificates/sha_256_hash.pem +19 -0
  53. data/spec/support/shared/examples/net/ntlm/field_shared.rb +25 -25
  54. data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +239 -239
  55. data/spec/support/shared/examples/net/ntlm/int_shared.rb +43 -43
  56. data/spec/support/shared/examples/net/ntlm/message_shared.rb +35 -35
  57. metadata +12 -3
@@ -1,131 +1,131 @@
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
- int32LE :flag, {:value => 0, :active => false }
17
- string :os_version, {:size => 8, :active => false }
18
-
19
- class << Type3
20
- # Builds a Type 3 packet
21
- # @note All options must be properly encoded with either unicode or oem encoding
22
- # @return [Type3]
23
- # @option arg [String] :lm_response The LM hash
24
- # @option arg [String] :ntlm_response The NTLM hash
25
- # @option arg [String] :domain The domain to authenticate to
26
- # @option arg [String] :workstation The name of the calling workstation
27
- # @option arg [String] :session_key The session key
28
- # @option arg [Integer] :flag Flags for the packet
29
- def create(arg, opt ={})
30
- t = new
31
- t.lm_response = arg[:lm_response]
32
- t.ntlm_response = arg[:ntlm_response]
33
- t.domain = arg[:domain]
34
- t.user = arg[:user]
35
-
36
- if arg[:workstation]
37
- t.workstation = arg[:workstation]
38
- end
39
-
40
- if arg[:session_key]
41
- t.enable(:session_key)
42
- t.session_key = arg[:session_key]
43
- end
44
-
45
- if arg[:flag]
46
- t.enable(:session_key)
47
- t.enable(:flag)
48
- t.flag = arg[:flag]
49
- end
50
- t
51
- end
52
- end
53
-
54
- # @param server_challenge (see #password?)
55
- def blank_password?(server_challenge)
56
- password?('', server_challenge)
57
- end
58
-
59
- # @param password [String]
60
- # @param server_challenge [String] The server's {Type2#challenge challenge} from the
61
- # {Type2} message for which this object is a response.
62
- # @return [true] if +password+ was the password used to generate this
63
- # {Type3} message
64
- # @return [false] otherwise
65
- def password?(password, server_challenge)
66
- case ntlm_version
67
- when :ntlm2_session
68
- ntlm2_session_password?(password, server_challenge)
69
- when :ntlmv2
70
- ntlmv2_password?(password, server_challenge)
71
- else
72
- raise
73
- end
74
- end
75
-
76
- # @return [Symbol]
77
- def ntlm_version
78
- if ntlm_response.size == 24 && lm_response[0,8] != "\x00"*8 && lm_response[8,16] == "\x00"*16
79
- :ntlm2_session
80
- elsif ntlm_response.size == 24
81
- :ntlmv1
82
- elsif ntlm_response.size > 24
83
- :ntlmv2
84
- end
85
- end
86
-
87
- private
88
-
89
- def ntlm2_session_password?(password, server_challenge)
90
- hash = ntlm_response
91
- _lm, empty_hash = NTLM.ntlm2_session(
92
- {
93
- :ntlm_hash => NTLM.ntlm_hash(password),
94
- :challenge => server_challenge,
95
- },
96
- {
97
- :client_challenge => lm_response[0,8]
98
- }
99
- )
100
- hash == empty_hash
101
- end
102
-
103
- def ntlmv2_password?(password, server_challenge)
104
-
105
- # The first 16 bytes of the ntlm_response are the HMAC of the blob
106
- # that follows it.
107
- blob = Blob.new
108
- blob.parse(ntlm_response[16..-1])
109
-
110
- empty_hash = NTLM.ntlmv2_response(
111
- {
112
- # user and domain came from the serialized data here, so
113
- # they're already unicode
114
- :ntlmv2_hash => NTLM.ntlmv2_hash(user, '', domain, :unicode => true),
115
- :challenge => server_challenge,
116
- :target_info => blob.target_info
117
- },
118
- {
119
- :client_challenge => blob.challenge,
120
- # The blob's timestamp is already in milliseconds since 1601,
121
- # so convert it back to epoch time first
122
- :timestamp => (blob.timestamp / 10_000_000) - NTLM::TIME_OFFSET,
123
- }
124
- )
125
-
126
- empty_hash == ntlm_response
127
- end
128
- end
129
- end
130
- end
131
- end
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
+ int32LE :flag, {:value => 0, :active => false }
17
+ string :os_version, {:size => 8, :active => false }
18
+
19
+ class << Type3
20
+ # Builds a Type 3 packet
21
+ # @note All options must be properly encoded with either unicode or oem encoding
22
+ # @return [Type3]
23
+ # @option arg [String] :lm_response The LM hash
24
+ # @option arg [String] :ntlm_response The NTLM hash
25
+ # @option arg [String] :domain The domain to authenticate to
26
+ # @option arg [String] :workstation The name of the calling workstation
27
+ # @option arg [String] :session_key The session key
28
+ # @option arg [Integer] :flag Flags for the packet
29
+ def create(arg, opt ={})
30
+ t = new
31
+ t.lm_response = arg[:lm_response]
32
+ t.ntlm_response = arg[:ntlm_response]
33
+ t.domain = arg[:domain]
34
+ t.user = arg[:user]
35
+
36
+ if arg[:workstation]
37
+ t.workstation = arg[:workstation]
38
+ end
39
+
40
+ if arg[:session_key]
41
+ t.enable(:session_key)
42
+ t.session_key = arg[:session_key]
43
+ end
44
+
45
+ if arg[:flag]
46
+ t.enable(:session_key)
47
+ t.enable(:flag)
48
+ t.flag = arg[:flag]
49
+ end
50
+ t
51
+ end
52
+ end
53
+
54
+ # @param server_challenge (see #password?)
55
+ def blank_password?(server_challenge)
56
+ password?('', server_challenge)
57
+ end
58
+
59
+ # @param password [String]
60
+ # @param server_challenge [String] The server's {Type2#challenge challenge} from the
61
+ # {Type2} message for which this object is a response.
62
+ # @return [true] if +password+ was the password used to generate this
63
+ # {Type3} message
64
+ # @return [false] otherwise
65
+ def password?(password, server_challenge)
66
+ case ntlm_version
67
+ when :ntlm2_session
68
+ ntlm2_session_password?(password, server_challenge)
69
+ when :ntlmv2
70
+ ntlmv2_password?(password, server_challenge)
71
+ else
72
+ raise
73
+ end
74
+ end
75
+
76
+ # @return [Symbol]
77
+ def ntlm_version
78
+ if ntlm_response.size == 24 && lm_response[0,8] != "\x00"*8 && lm_response[8,16] == "\x00"*16
79
+ :ntlm2_session
80
+ elsif ntlm_response.size == 24
81
+ :ntlmv1
82
+ elsif ntlm_response.size > 24
83
+ :ntlmv2
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def ntlm2_session_password?(password, server_challenge)
90
+ hash = ntlm_response
91
+ _lm, empty_hash = NTLM.ntlm2_session(
92
+ {
93
+ :ntlm_hash => NTLM.ntlm_hash(password),
94
+ :challenge => server_challenge,
95
+ },
96
+ {
97
+ :client_challenge => lm_response[0,8]
98
+ }
99
+ )
100
+ hash == empty_hash
101
+ end
102
+
103
+ def ntlmv2_password?(password, server_challenge)
104
+
105
+ # The first 16 bytes of the ntlm_response are the HMAC of the blob
106
+ # that follows it.
107
+ blob = Blob.new
108
+ blob.parse(ntlm_response[16..-1])
109
+
110
+ empty_hash = NTLM.ntlmv2_response(
111
+ {
112
+ # user and domain came from the serialized data here, so
113
+ # they're already unicode
114
+ :ntlmv2_hash => NTLM.ntlmv2_hash(user, '', domain, :unicode => true),
115
+ :challenge => server_challenge,
116
+ :target_info => blob.target_info
117
+ },
118
+ {
119
+ :client_challenge => blob.challenge,
120
+ # The blob's timestamp is already in milliseconds since 1601,
121
+ # so convert it back to epoch time first
122
+ :timestamp => (blob.timestamp / 10_000_000) - NTLM::TIME_OFFSET,
123
+ }
124
+ )
125
+
126
+ empty_hash == ntlm_response
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -1,48 +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
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
48
  end
@@ -1,35 +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
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
35
  end
@@ -0,0 +1,89 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ # Represents a list of AV_PAIR structures
5
+ # @see https://msdn.microsoft.com/en-us/library/cc236646.aspx
6
+ class TargetInfo
7
+
8
+ # Allowed AvId values for an AV_PAIR
9
+ MSV_AV_EOL = "\x00\x00".freeze
10
+ MSV_AV_NB_COMPUTER_NAME = "\x01\x00".freeze
11
+ MSV_AV_NB_DOMAIN_NAME = "\x02\x00".freeze
12
+ MSV_AV_DNS_COMPUTER_NAME = "\x03\x00".freeze
13
+ MSV_AV_DNS_DOMAIN_NAME = "\x04\x00".freeze
14
+ MSV_AV_DNS_TREE_NAME = "\x05\x00".freeze
15
+ MSV_AV_FLAGS = "\x06\x00".freeze
16
+ MSV_AV_TIMESTAMP = "\x07\x00".freeze
17
+ MSV_AV_SINGLE_HOST = "\x08\x00".freeze
18
+ MSV_AV_TARGET_NAME = "\x09\x00".freeze
19
+ MSV_AV_CHANNEL_BINDINGS = "\x0A\x00".freeze
20
+
21
+ # @param av_pair_sequence [String] AV_PAIR list from challenge message
22
+ def initialize(av_pair_sequence)
23
+ @av_pairs = read_pairs(av_pair_sequence)
24
+ end
25
+
26
+ attr_reader :av_pairs
27
+
28
+ def to_s
29
+ result = ''
30
+ av_pairs.each do |k,v|
31
+ result << k
32
+ result << [v.length].pack('S')
33
+ result << v
34
+ end
35
+ result << Net::NTLM::TargetInfo::MSV_AV_EOL
36
+ result << [0].pack('S')
37
+ result.force_encoding(Encoding::ASCII_8BIT)
38
+ end
39
+
40
+ private
41
+
42
+ VALID_PAIR_ID = [
43
+ MSV_AV_EOL,
44
+ MSV_AV_NB_COMPUTER_NAME,
45
+ MSV_AV_NB_DOMAIN_NAME,
46
+ MSV_AV_DNS_COMPUTER_NAME,
47
+ MSV_AV_DNS_DOMAIN_NAME,
48
+ MSV_AV_DNS_TREE_NAME,
49
+ MSV_AV_FLAGS,
50
+ MSV_AV_TIMESTAMP,
51
+ MSV_AV_SINGLE_HOST,
52
+ MSV_AV_TARGET_NAME,
53
+ MSV_AV_CHANNEL_BINDINGS
54
+ ].freeze
55
+
56
+ def read_pairs(av_pair_sequence)
57
+ offset = 0
58
+ result = {}
59
+ return result if av_pair_sequence.nil?
60
+
61
+ until offset >= av_pair_sequence.length
62
+ id = av_pair_sequence[offset..offset+1]
63
+
64
+ unless VALID_PAIR_ID.include?(id)
65
+ raise Net::NTLM::InvalidTargetDataError.new(
66
+ "Invalid AvId #{to_hex(id)} in AV_PAIR structure",
67
+ av_pair_sequence
68
+ )
69
+ end
70
+
71
+ length = av_pair_sequence[offset+2..offset+3].unpack('S')[0].to_i
72
+ if length > 0
73
+ value = av_pair_sequence[offset+4..offset+4+length-1]
74
+ result[id] = value
75
+ end
76
+
77
+ offset += 4 + length
78
+ end
79
+
80
+ result
81
+ end
82
+
83
+ def to_hex(str)
84
+ return nil if str.nil?
85
+ str.bytes.map {|b| '0x' + b.to_s(16).rjust(2,'0').upcase}.join('-')
86
+ end
87
+ end
88
+ end
89
+ end