rubyntlm 0.6.6 → 0.6.7
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/CHANGELOG.md +4 -0
- data/README.md +48 -48
- data/lib/net/ntlm/blob.rb +11 -10
- data/lib/net/ntlm/channel_binding.rb +13 -9
- data/lib/net/ntlm/client/session.rb +34 -141
- data/lib/net/ntlm/client/session_crypto.rb +126 -0
- data/lib/net/ntlm/client.rb +8 -9
- data/lib/net/ntlm/encode_util.rb +39 -38
- data/lib/net/ntlm/exceptions.rb +3 -0
- data/lib/net/ntlm/field.rb +29 -31
- data/lib/net/ntlm/field_set.rb +39 -18
- data/lib/net/ntlm/int16_le.rb +8 -8
- data/lib/net/ntlm/int32_le.rb +8 -7
- data/lib/net/ntlm/int64_le.rb +8 -7
- data/lib/net/ntlm/md4.rb +102 -64
- data/lib/net/ntlm/message/type0.rb +4 -7
- data/lib/net/ntlm/message/type1.rb +8 -9
- data/lib/net/ntlm/message/type2.rb +87 -67
- data/lib/net/ntlm/message/type3.rb +43 -41
- data/lib/net/ntlm/message.rb +116 -111
- data/lib/net/ntlm/rc4.rb +51 -44
- data/lib/net/ntlm/response.rb +112 -0
- data/lib/net/ntlm/security_buffer.rb +13 -15
- data/lib/net/ntlm/string.rb +8 -7
- data/lib/net/ntlm/target_info.rb +52 -44
- data/lib/net/ntlm/version.rb +3 -1
- data/lib/net/ntlm.rb +37 -125
- data/lib/rubyntlm.rb +3 -1
- metadata +5 -44
- data/.devcontainer/devcontainer.json +0 -22
- data/.github/dependabot.yml +0 -10
- data/.github/workflows/build.yml +0 -55
- data/.gitignore +0 -11
- data/.rspec +0 -3
- data/Gemfile +0 -20
- data/Rakefile +0 -37
- data/examples/http.rb +0 -86
- data/examples/imap.rb +0 -73
- data/examples/smtp.rb +0 -94
- data/rubyntlm.gemspec +0 -28
- data/spec/lib/net/ntlm/blob_spec.rb +0 -14
- data/spec/lib/net/ntlm/channel_binding_spec.rb +0 -15
- data/spec/lib/net/ntlm/client/session_spec.rb +0 -85
- data/spec/lib/net/ntlm/client_spec.rb +0 -62
- data/spec/lib/net/ntlm/encode_util_spec.rb +0 -14
- data/spec/lib/net/ntlm/field_set_spec.rb +0 -31
- data/spec/lib/net/ntlm/field_spec.rb +0 -32
- data/spec/lib/net/ntlm/int16_le_spec.rb +0 -16
- data/spec/lib/net/ntlm/int32_le_spec.rb +0 -17
- data/spec/lib/net/ntlm/int64_le_spec.rb +0 -17
- data/spec/lib/net/ntlm/md4_spec.rb +0 -16
- data/spec/lib/net/ntlm/message/type0_spec.rb +0 -19
- data/spec/lib/net/ntlm/message/type1_spec.rb +0 -129
- data/spec/lib/net/ntlm/message/type2_spec.rb +0 -131
- data/spec/lib/net/ntlm/message/type3_spec.rb +0 -336
- data/spec/lib/net/ntlm/message_spec.rb +0 -15
- data/spec/lib/net/ntlm/security_buffer_spec.rb +0 -62
- data/spec/lib/net/ntlm/string_spec.rb +0 -70
- data/spec/lib/net/ntlm/target_info_spec.rb +0 -74
- data/spec/lib/net/ntlm/version_spec.rb +0 -26
- data/spec/lib/net/ntlm_spec.rb +0 -133
- data/spec/spec_helper.rb +0 -51
- data/spec/support/certificates/sha_256_hash.pem +0 -19
- data/spec/support/shared/examples/net/ntlm/field_shared.rb +0 -25
- data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +0 -239
- data/spec/support/shared/examples/net/ntlm/int_shared.rb +0 -43
- data/spec/support/shared/examples/net/ntlm/message_shared.rb +0 -35
data/lib/net/ntlm/rc4.rb
CHANGED
|
@@ -1,59 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'openssl'
|
|
2
4
|
|
|
3
5
|
module Net
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Rc4
|
|
12
|
-
def initialize(str)
|
|
13
|
-
raise ArgumentError, "RC4: Key supplied is blank" if str.eql?('')
|
|
14
|
-
initialize_state(str)
|
|
15
|
-
@q1, @q2 = 0, 0
|
|
16
|
-
end
|
|
6
|
+
# NTLM cryptographic primitives.
|
|
7
|
+
module NTLM
|
|
8
|
+
begin
|
|
9
|
+
OpenSSL::Cipher.new('rc4')
|
|
10
|
+
rescue StandardError
|
|
11
|
+
# libssl-3.0+ doesn't support legacy Rc4 -> use our own implementation
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
# Pure-Ruby RC4 implementation, used when OpenSSL does not provide RC4.
|
|
14
|
+
class Rc4
|
|
15
|
+
def initialize(str)
|
|
16
|
+
raise ArgumentError, 'RC4: Key supplied is blank' if str.eql?('')
|
|
17
|
+
|
|
18
|
+
initialize_state(str)
|
|
19
|
+
@q1 = 0
|
|
20
|
+
@q2 = 0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def encrypt(text)
|
|
24
|
+
text.each_byte.map { |byte| keystream_byte ^ byte }.pack('C*')
|
|
25
|
+
end
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
private
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
# The initial state which is then modified by the key-scheduling algorithm
|
|
30
|
+
INITIAL_STATE = (0..255).to_a
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
j = (j + @state[i] + key.getbyte(i % key_length)) % 256
|
|
39
|
-
@state[i], @state[j] = @state[j], @state[i]
|
|
40
|
-
i += 1
|
|
32
|
+
# Advances the cipher state and returns the next keystream byte.
|
|
33
|
+
def keystream_byte
|
|
34
|
+
@q1 = (@q1 + 1) % 256
|
|
35
|
+
@q2 = (@q2 + @state[@q1]) % 256
|
|
36
|
+
@state[@q1], @state[@q2] = @state[@q2], @state[@q1]
|
|
37
|
+
@state[(@state[@q1] + @state[@q2]) % 256]
|
|
41
38
|
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
# Performs the key-scheduling algorithm to initialize the state.
|
|
41
|
+
def initialize_state(key)
|
|
42
|
+
i = j = 0
|
|
43
|
+
@state = INITIAL_STATE.dup
|
|
44
|
+
key_length = key.length
|
|
45
|
+
while i < 256
|
|
46
|
+
j = (j + @state[i] + key.getbyte(i % key_length)) % 256
|
|
47
|
+
@state[i], @state[j] = @state[j], @state[i]
|
|
48
|
+
i += 1
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
51
|
end
|
|
52
|
+
else
|
|
53
|
+
# Openssl/libssl provides RC4, so we can use it.
|
|
54
|
+
class Rc4
|
|
55
|
+
def initialize(str)
|
|
56
|
+
@ci = OpenSSL::Cipher.new('rc4')
|
|
57
|
+
@ci.key = str
|
|
58
|
+
end
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
def encrypt(text)
|
|
61
|
+
@ci.update(text) + @ci.final
|
|
62
|
+
end
|
|
55
63
|
end
|
|
56
64
|
end
|
|
57
65
|
end
|
|
58
66
|
end
|
|
59
|
-
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Net
|
|
4
|
+
# NTLM response builders: LM, NTLM, NTLMv2, LMv2 and NTLM2 session responses.
|
|
5
|
+
module NTLM
|
|
6
|
+
class << self
|
|
7
|
+
# Builds an LM response for the given challenge
|
|
8
|
+
# @param [Hash] arg :lm_hash, :challenge
|
|
9
|
+
def lm_response(arg)
|
|
10
|
+
hash, chal = lm_args(arg)
|
|
11
|
+
keys = gen_keys hash.ljust(21, "\0")
|
|
12
|
+
apply_des(pack_challenge(chal), keys).join
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Builds an NTLM response for the given challenge
|
|
16
|
+
# @param [Hash] arg :ntlm_hash, :challenge
|
|
17
|
+
def ntlm_response(arg)
|
|
18
|
+
hash = arg[:ntlm_hash]
|
|
19
|
+
chal = arg[:challenge]
|
|
20
|
+
chal = NTLM.pack_int64le(chal) if chal.is_a?(Integer)
|
|
21
|
+
keys = gen_keys hash.ljust(21, "\0")
|
|
22
|
+
apply_des(chal, keys).join
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Builds an NTLMv2 response for the given challenge
|
|
26
|
+
# @param [Hash] arg :ntlmv2_hash, :challenge, :target_info
|
|
27
|
+
# @option opt [String] :client_challenge 8-byte client challenge
|
|
28
|
+
# @option opt [Integer] :timestamp Time of the response
|
|
29
|
+
def ntlmv2_response(arg, opt = {})
|
|
30
|
+
key, chal, ti = ntlmv2_args(arg)
|
|
31
|
+
cc = client_challenge(opt)
|
|
32
|
+
bb = ntlmv2_blob(ti, opt[:timestamp], cc).serialize
|
|
33
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), key, pack_challenge(chal) + bb) + bb
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Builds an LMv2 response for the given challenge
|
|
37
|
+
# @param [Hash] arg :ntlmv2_hash, :challenge
|
|
38
|
+
# @option opt [String] :client_challenge 8-byte client challenge
|
|
39
|
+
def lmv2_response(arg, opt = {})
|
|
40
|
+
key = arg[:ntlmv2_hash]
|
|
41
|
+
chal = arg[:challenge]
|
|
42
|
+
|
|
43
|
+
chal = NTLM.pack_int64le(chal) if chal.is_a?(Integer)
|
|
44
|
+
|
|
45
|
+
cc = opt[:client_challenge] || rand(MAX64)
|
|
46
|
+
cc = NTLM.pack_int64le(cc) if cc.is_a?(Integer)
|
|
47
|
+
|
|
48
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), key, chal + cc) + cc
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Builds an NTLM2 session response for the given challenge
|
|
52
|
+
# @param [Hash] arg :ntlm_hash, :challenge
|
|
53
|
+
# @option opt [String] :client_challenge 8-byte client challenge
|
|
54
|
+
def ntlm2_session(arg, opt = {})
|
|
55
|
+
passwd_hash, chal = ntlm2_session_args(arg)
|
|
56
|
+
cc = client_challenge(opt)
|
|
57
|
+
keys = gen_keys(passwd_hash.ljust(21, "\0"))
|
|
58
|
+
session_hash = OpenSSL::Digest::MD5.digest(pack_challenge(chal) + cc).slice(0, 8)
|
|
59
|
+
response = apply_des(session_hash, keys).join
|
|
60
|
+
[cc.ljust(24, "\0"), response]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# @api private
|
|
66
|
+
def lm_args(arg)
|
|
67
|
+
[arg[:lm_hash], arg[:challenge]]
|
|
68
|
+
rescue StandardError
|
|
69
|
+
raise ArgumentError
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @api private
|
|
73
|
+
def ntlmv2_args(arg)
|
|
74
|
+
[arg[:ntlmv2_hash], arg[:challenge], arg[:target_info]]
|
|
75
|
+
rescue StandardError
|
|
76
|
+
raise ArgumentError
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @api private
|
|
80
|
+
def ntlm2_session_args(arg)
|
|
81
|
+
[arg[:ntlm_hash], arg[:challenge]]
|
|
82
|
+
rescue StandardError
|
|
83
|
+
raise ArgumentError
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @api private
|
|
87
|
+
def client_challenge(opt)
|
|
88
|
+
cc = opt[:client_challenge] || rand(MAX64)
|
|
89
|
+
cc = NTLM.pack_int64le(cc) if cc.is_a?(Integer)
|
|
90
|
+
cc
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @api private
|
|
94
|
+
def pack_challenge(chal)
|
|
95
|
+
chal.is_a?(Integer) ? NTLM.pack_int64le(chal) : chal
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @api private
|
|
99
|
+
def ntlmv2_blob(target_info, timestamp, challenge)
|
|
100
|
+
ts = timestamp || Time.now.to_i
|
|
101
|
+
# epoch -> milsec from Jan 1, 1601
|
|
102
|
+
ts = 10_000_000 * (ts + TIME_OFFSET)
|
|
103
|
+
|
|
104
|
+
blob = Blob.new
|
|
105
|
+
blob.timestamp = ts
|
|
106
|
+
blob.challenge = challenge
|
|
107
|
+
blob.target_info = target_info
|
|
108
|
+
blob
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -1,25 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
3
|
module Net
|
|
3
4
|
module NTLM
|
|
4
|
-
|
|
5
|
+
# NTLMSSP security buffer: length/allocated/offset triplet plus payload.
|
|
5
6
|
class SecurityBuffer < FieldSet
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
int32LE :offset, {:value => 0}
|
|
7
|
+
int16_le :length, { value: 0 }
|
|
8
|
+
int16_le :allocated, { value: 0 }
|
|
9
|
+
int32_le :offset, { value: 0 }
|
|
10
10
|
|
|
11
11
|
attr_accessor :active
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
def initialize(opts = {})
|
|
13
14
|
super()
|
|
14
15
|
@value = opts[:value]
|
|
15
|
-
@active = opts[:active].nil?
|
|
16
|
+
@active = opts[:active].nil? || opts[:active]
|
|
16
17
|
@size = 8
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
def parse(str, offset=0)
|
|
20
|
-
if @active
|
|
20
|
+
def parse(str, offset = 0)
|
|
21
|
+
if @active && str.size >= offset + @size
|
|
21
22
|
super(str, offset)
|
|
22
|
-
@value = str[self.offset,
|
|
23
|
+
@value = str[self.offset, length]
|
|
23
24
|
@size
|
|
24
25
|
else
|
|
25
26
|
0
|
|
@@ -30,9 +31,7 @@ module Net
|
|
|
30
31
|
super if @active
|
|
31
32
|
end
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
@value
|
|
35
|
-
end
|
|
34
|
+
attr_reader :value
|
|
36
35
|
|
|
37
36
|
def value=(val)
|
|
38
37
|
@value = val
|
|
@@ -43,6 +42,5 @@ module Net
|
|
|
43
42
|
@active ? @value.size : 0
|
|
44
43
|
end
|
|
45
44
|
end
|
|
46
|
-
|
|
47
45
|
end
|
|
48
|
-
end
|
|
46
|
+
end
|
data/lib/net/ntlm/string.rb
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Net
|
|
2
4
|
module NTLM
|
|
3
|
-
|
|
5
|
+
# Fixed-size string field.
|
|
4
6
|
class String < Field
|
|
5
7
|
def initialize(opts)
|
|
6
8
|
super(opts)
|
|
7
9
|
@size = opts[:size]
|
|
8
10
|
end
|
|
9
11
|
|
|
10
|
-
def parse(str, offset=0)
|
|
11
|
-
if @active
|
|
12
|
+
def parse(str, offset = 0)
|
|
13
|
+
if @active && str.size >= offset + @size
|
|
12
14
|
@value = str[offset, @size]
|
|
13
15
|
@size
|
|
14
16
|
else
|
|
@@ -20,16 +22,15 @@ module Net
|
|
|
20
22
|
if @active
|
|
21
23
|
@value.to_s
|
|
22
24
|
else
|
|
23
|
-
|
|
25
|
+
''
|
|
24
26
|
end
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def value=(val)
|
|
28
30
|
@value = val
|
|
29
31
|
@size = @value.nil? ? 0 : @value.size
|
|
30
|
-
@active =
|
|
32
|
+
@active = @size.positive?
|
|
31
33
|
end
|
|
32
34
|
end
|
|
33
|
-
|
|
34
35
|
end
|
|
35
|
-
end
|
|
36
|
+
end
|
data/lib/net/ntlm/target_info.rb
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Net
|
|
2
4
|
module NTLM
|
|
3
|
-
|
|
4
5
|
# Represents a list of AV_PAIR structures
|
|
5
6
|
# @see https://msdn.microsoft.com/en-us/library/cc236646.aspx
|
|
6
7
|
class TargetInfo
|
|
7
|
-
|
|
8
8
|
# Allowed AvId values for an AV_PAIR
|
|
9
|
-
MSV_AV_EOL = "\x00\x00"
|
|
10
|
-
MSV_AV_NB_COMPUTER_NAME = "\x01\x00"
|
|
11
|
-
MSV_AV_NB_DOMAIN_NAME = "\x02\x00"
|
|
12
|
-
MSV_AV_DNS_COMPUTER_NAME = "\x03\x00"
|
|
13
|
-
MSV_AV_DNS_DOMAIN_NAME = "\x04\x00"
|
|
14
|
-
MSV_AV_DNS_TREE_NAME = "\x05\x00"
|
|
15
|
-
MSV_AV_FLAGS = "\x06\x00"
|
|
16
|
-
MSV_AV_TIMESTAMP = "\x07\x00"
|
|
17
|
-
MSV_AV_SINGLE_HOST = "\x08\x00"
|
|
18
|
-
MSV_AV_TARGET_NAME = "\x09\x00"
|
|
19
|
-
MSV_AV_CHANNEL_BINDINGS = "\x0A\x00"
|
|
9
|
+
MSV_AV_EOL = "\x00\x00"
|
|
10
|
+
MSV_AV_NB_COMPUTER_NAME = "\x01\x00"
|
|
11
|
+
MSV_AV_NB_DOMAIN_NAME = "\x02\x00"
|
|
12
|
+
MSV_AV_DNS_COMPUTER_NAME = "\x03\x00"
|
|
13
|
+
MSV_AV_DNS_DOMAIN_NAME = "\x04\x00"
|
|
14
|
+
MSV_AV_DNS_TREE_NAME = "\x05\x00"
|
|
15
|
+
MSV_AV_FLAGS = "\x06\x00"
|
|
16
|
+
MSV_AV_TIMESTAMP = "\x07\x00"
|
|
17
|
+
MSV_AV_SINGLE_HOST = "\x08\x00"
|
|
18
|
+
MSV_AV_TARGET_NAME = "\x09\x00"
|
|
19
|
+
MSV_AV_CHANNEL_BINDINGS = "\x0A\x00"
|
|
20
20
|
|
|
21
21
|
# @param av_pair_sequence [String] AV_PAIR list from challenge message
|
|
22
22
|
def initialize(av_pair_sequence)
|
|
@@ -26,8 +26,8 @@ module Net
|
|
|
26
26
|
attr_reader :av_pairs
|
|
27
27
|
|
|
28
28
|
def to_s
|
|
29
|
-
result = ''
|
|
30
|
-
av_pairs.each do |k,v|
|
|
29
|
+
result = +''
|
|
30
|
+
av_pairs.each do |k, v|
|
|
31
31
|
result << k
|
|
32
32
|
result << [v.length].pack('S')
|
|
33
33
|
result << v
|
|
@@ -40,49 +40,57 @@ module Net
|
|
|
40
40
|
private
|
|
41
41
|
|
|
42
42
|
VALID_PAIR_ID = [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
54
|
].freeze
|
|
55
55
|
|
|
56
56
|
def read_pairs(av_pair_sequence)
|
|
57
|
-
offset = 0
|
|
58
57
|
result = {}
|
|
59
58
|
return result if av_pair_sequence.nil?
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
id = av_pair_sequence[offset..offset+1]
|
|
60
|
+
read_all_pairs(av_pair_sequence, result)
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
62
|
+
result
|
|
63
|
+
end
|
|
76
64
|
|
|
77
|
-
|
|
65
|
+
def read_all_pairs(sequence, result)
|
|
66
|
+
offset = 0
|
|
67
|
+
loop do
|
|
68
|
+
offset = read_pair(sequence, offset, result)
|
|
69
|
+
break if offset >= sequence.length
|
|
78
70
|
end
|
|
71
|
+
end
|
|
79
72
|
|
|
80
|
-
|
|
73
|
+
def read_pair(sequence, offset, result)
|
|
74
|
+
id = sequence[offset..offset + 1]
|
|
75
|
+
validate_pair_id(id, sequence)
|
|
76
|
+
length = sequence[offset + 2..offset + 3].unpack1('S').to_i
|
|
77
|
+
result[id] = sequence[offset + 4, length] if length.positive?
|
|
78
|
+
offset + 4 + length
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def validate_pair_id(id, sequence)
|
|
82
|
+
return if VALID_PAIR_ID.include?(id)
|
|
83
|
+
|
|
84
|
+
raise Net::NTLM::InvalidTargetDataError.new(
|
|
85
|
+
"Invalid AvId #{to_hex(id)} in AV_PAIR structure",
|
|
86
|
+
sequence
|
|
87
|
+
)
|
|
81
88
|
end
|
|
82
89
|
|
|
83
90
|
def to_hex(str)
|
|
84
91
|
return nil if str.nil?
|
|
85
|
-
|
|
92
|
+
|
|
93
|
+
str.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0').upcase}" }.join('-')
|
|
86
94
|
end
|
|
87
95
|
end
|
|
88
96
|
end
|