rubyntlm 0.5.3 → 0.6.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 (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